Commit d919b50e authored by Benjamin Eberlei's avatar Benjamin Eberlei

DBAL-6 - Add support for BLOBs

parent 33395dcc
# Upgrade to 2.2
## Doctrine\DBAL\Connection#insert and Doctrine\DBAL\Connnection#update
Both methods now accept an optional last parameter $types with binding types of the values passed.
This can potentially break child classes that have overwritten one of these methods.
\ No newline at end of file
......@@ -475,9 +475,10 @@ class Connection implements DriverConnection
*
* @param string $table The name of the table to update.
* @param array $identifier The update criteria. An associative array containing column-value pairs.
* @param array $types Types of the merged $data and $identifier arrays in that order.
* @return integer The number of affected rows.
*/
public function update($tableName, array $data, array $identifier)
public function update($tableName, array $data, array $identifier, array $types = array())
{
$this->connect();
$set = array();
......@@ -491,7 +492,7 @@ class Connection implements DriverConnection
. ' WHERE ' . implode(' = ? AND ', array_keys($identifier))
. ' = ?';
return $this->executeUpdate($sql, $params);
return $this->executeUpdate($sql, $params, $types);
}
/**
......@@ -499,9 +500,10 @@ class Connection implements DriverConnection
*
* @param string $table The name of the table to insert data into.
* @param array $data An associative array containing column-value pairs.
* @param array $types Types of the inserted data.
* @return integer The number of affected rows.
*/
public function insert($tableName, array $data)
public function insert($tableName, array $data, array $types = array())
{
$this->connect();
......@@ -518,7 +520,7 @@ class Connection implements DriverConnection
. ' (' . implode(', ', $cols) . ')'
. ' VALUES (' . implode(', ', $placeholders) . ')';
return $this->executeUpdate($query, array_values($data));
return $this->executeUpdate($query, array_values($data), $types);
}
/**
......
......@@ -30,13 +30,15 @@ use \PDO;
class OCI8Statement implements \Doctrine\DBAL\Driver\Statement
{
/** Statement handle. */
protected $_dbh;
protected $_sth;
protected $_executeMode;
protected static $_PARAM = ':param';
protected static $fetchStyleMap = array(
PDO::FETCH_BOTH => OCI_BOTH,
PDO::FETCH_ASSOC => OCI_ASSOC,
PDO::FETCH_NUM => OCI_NUM
PDO::FETCH_NUM => OCI_NUM,
PDO::PARAM_LOB => OCI_B_BLOB,
);
protected $_paramMap = array();
......@@ -50,6 +52,7 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement
{
list($statement, $paramMap) = self::convertPositionalToNamedPlaceholders($statement);
$this->_sth = oci_parse($dbh, $statement);
$this->_dbh = $dbh;
$this->_paramMap = $paramMap;
$this->_executeMode = $executeMode;
}
......@@ -109,8 +112,15 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement
{
$column = isset($this->_paramMap[$column]) ? $this->_paramMap[$column] : $column;
if ($type == \PDO::PARAM_LOB) {
$lob = oci_new_descriptor($this->_dbh, OCI_D_LOB);
$lob->writeTemporary($variable, OCI_TEMP_BLOB);
return oci_bind_by_name($this->_sth, $column, $lob, -1, OCI_B_BLOB);
} else {
return oci_bind_by_name($this->_sth, $column, $variable);
}
}
/**
* Closes the cursor, enabling the statement to be executed again.
......
......@@ -174,6 +174,11 @@ abstract class AbstractPlatform
*/
abstract public function getClobTypeDeclarationSQL(array $field);
/**
* Gets the SQL Snippet used to declare a BLOB column type.
*/
abstract public function getBlobTypeDeclarationSQL(array $field);
/**
* Gets the name of the platform.
*
......
......@@ -25,6 +25,14 @@ use Doctrine\DBAL\Schema\TableDiff;
class DB2Platform extends AbstractPlatform
{
/**
* Gets the SQL Snippet used to declare a BLOB column type.
*/
public function getBlobTypeDeclarationSQL(array $field)
{
throw DBALException::notSupported(__METHOD__);
}
public function initializeDoctrineTypeMappings()
{
$this->doctrineTypeMapping = array(
......
......@@ -827,4 +827,12 @@ class MsSqlPlatform extends AbstractPlatform
{
return 'TRUNCATE TABLE '.$tableName;
}
/**
* Gets the SQL Snippet used to declare a BLOB column type.
*/
public function getBlobTypeDeclarationSQL(array $field)
{
return 'VARBINARY(MAX)';
}
}
......@@ -690,4 +690,12 @@ class MySqlPlatform extends AbstractPlatform
return 'DROP TEMPORARY TABLE ' . $table;
}
/**
* Gets the SQL Snippet used to declare a BLOB column type.
*/
public function getBlobTypeDeclarationSQL(array $field)
{
return 'LONGBLOB';
}
}
......@@ -774,4 +774,12 @@ LEFT JOIN all_cons_columns r_cols
{
return 'Doctrine\DBAL\Platforms\Keywords\OracleKeywords';
}
/**
* Gets the SQL Snippet used to declare a BLOB column type.
*/
public function getBlobTypeDeclarationSQL(array $field)
{
return 'BLOB';
}
}
......@@ -750,4 +750,12 @@ class PostgreSqlPlatform extends AbstractPlatform
{
return 'Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords';
}
/**
* Gets the SQL Snippet used to declare a BLOB column type.
*/
public function getBlobTypeDeclarationSQL(array $field)
{
return 'BYTEA';
}
}
......@@ -492,4 +492,12 @@ class SqlitePlatform extends AbstractPlatform
{
return 'Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords';
}
/**
* Gets the SQL Snippet used to declare a BLOB column type.
*/
public function getBlobTypeDeclarationSQL(array $field)
{
return 'BLOB';
}
}
......@@ -34,6 +34,16 @@ class BlobType extends Type
return $platform->getBlobTypeDeclarationSQL($fieldDeclaration);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
$hex='';
for ($i=0; $i < strlen($value); $i++) {
$hex .= dechex(ord($value[$i]));
}
return $value;
}
/**
* Converts a value from its database representation to its PHP representation
* of this type.
......@@ -44,11 +54,16 @@ class BlobType extends Type
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return (is_resource($value)) ? stream_get_contents($value) : $value;
if (is_string($value)) {
$value = fopen('data://text/plain;base64,' . base64_encode($value), 'r');
} else if ( ! is_resource($value)) {
throw ConversionException::conversionFailed($value, self::BLOB);
}
return $value;
}
public function getName()
{
return Type::TEXT;
return Type::BLOB;
}
}
\ No newline at end of file
......@@ -46,6 +46,7 @@ abstract class Type
const SMALLINT = 'smallint';
const STRING = 'string';
const TEXT = 'text';
const BLOB = 'blob';
const FLOAT = 'float';
/** Map of already instantiated type objects. One instance per type (flyweight). */
......@@ -67,6 +68,7 @@ abstract class Type
self::TIME => 'Doctrine\DBAL\Types\TimeType',
self::DECIMAL => 'Doctrine\DBAL\Types\DecimalType',
self::FLOAT => 'Doctrine\DBAL\Types\FloatType',
self::BLOB => 'Doctrine\DBAL\Types\BlobType',
);
/* Prevent instantiation and force use of the factory method. */
......
<?php
namespace Doctrine\Tests\DBAL\Functional;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Connection;
use PDO;
require_once __DIR__ . '/../../TestInit.php';
/**
* @group DBAL-6
*/
class BlobTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
public function setUp()
{
parent::setUp();
try {
/* @var $sm \Doctrine\DBAL\Schema\AbstractSchemaManager */
$table = new \Doctrine\DBAL\Schema\Table("blob_table");
$table->addColumn('id', 'integer');
$table->addColumn('clobfield', 'text');
$table->addColumn('blobfield', 'blob');
$table->setPrimaryKey(array('id'));
$sm = $this->_conn->getSchemaManager();
$sm->createTable($table);
} catch(\Exception $e) {
}
$this->_conn->exec($this->_conn->getDatabasePlatform()->getTruncateTableSQL('blob_table'));
}
public function testInsert()
{
$ret = $this->_conn->insert('blob_table',
array('id' => 1, 'clobfield' => 'test', 'blobfield' => 'test'),
array(\PDO::PARAM_INT, \PDO::PARAM_STR, \PDO::PARAM_LOB)
);
$this->assertEquals(1, $ret);
}
public function testSelect()
{
$ret = $this->_conn->insert('blob_table',
array('id' => 1, 'clobfield' => 'test', 'blobfield' => 'test'),
array(\PDO::PARAM_INT, \PDO::PARAM_STR, \PDO::PARAM_LOB)
);
$this->assertBlobContains('test');
}
public function testUpdate()
{
$ret = $this->_conn->insert('blob_table',
array('id' => 1, 'clobfield' => 'test', 'blobfield' => 'test'),
array(\PDO::PARAM_INT, \PDO::PARAM_STR, \PDO::PARAM_LOB)
);
$this->_conn->update('blob_table',
array('blobfield' => 'test2'),
array('id' => 1),
array(\PDO::PARAM_LOB, \PDO::PARAM_INT)
);
$this->assertBlobContains('test2');
}
private function assertBlobContains($text)
{
$rows = $this->_conn->fetchAll('SELECT * FROM blob_table');
$this->assertEquals(1, count($rows));
$row = array_change_key_case($rows[0], CASE_LOWER);
$blobValue = Type::getType('blob')->convertToPHPValue($row['blobfield'], $this->_conn->getDatabasePlatform());
$this->assertInternalType('resource', $blobValue);
$this->assertEquals($text, stream_get_contents($blobValue));
}
}
\ No newline at end of file
......@@ -6,6 +6,14 @@ use Doctrine\DBAL\Platforms;
class MockPlatform extends \Doctrine\DBAL\Platforms\AbstractPlatform
{
/**
* Gets the SQL Snippet used to declare a BLOB column type.
*/
public function getBlobTypeDeclarationSQL(array $field)
{
throw DBALException::notSupported(__METHOD__);
}
public function getBooleanTypeDeclarationSQL(array $columnDef) {}
public function getIntegerTypeDeclarationSQL(array $columnDef) {}
public function getBigIntTypeDeclarationSQL(array $columnDef) {}
......
......@@ -30,7 +30,7 @@ class ConnectionMock extends \Doctrine\DBAL\Connection
/**
* @override
*/
public function insert($tableName, array $data)
public function insert($tableName, array $data, array $types = array())
{
$this->_inserts[$tableName][] = $data;
}
......
......@@ -88,4 +88,11 @@ class DatabasePlatformMock extends \Doctrine\DBAL\Platforms\AbstractPlatform
{
}
/**
* Gets the SQL Snippet used to declare a BLOB column type.
*/
public function getBlobTypeDeclarationSQL(array $field)
{
throw DBALException::notSupported(__METHOD__);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment