Commit 752b5692 authored by Benjamin Eberlei's avatar Benjamin Eberlei

Merge pull request #447 from deeky666/fix-db2

Fix IBM DB2 implementation / ibm_db2 driver
parents f8b1a95d db2e0f37
# Upgrade to 2.5
No BC breaks yet.
## Support for pdo_ibm driver removed
The ``pdo_ibm`` driver is buggy and does not work well with Doctrine. Therefore it will no
longer be supported and has been removed from the ``Doctrine\DBAL\DriverManager`` drivers
map. It is highly encouraged to to use `ibm_db2` driver instead if you want to connect
to an IBM DB2 database as it is much more stable and secure.
If for some reason you have to utilize the ``pdo_ibm`` driver you can still use the `driverClass`
connection parameter to explicitly specify the ``Doctrine\DBAL\Driver\PDOIbm\Driver`` class.
However be aware that you are doing this at your own risk and it will not be guaranteed that
Doctrine will work as expected.
# Upgrade to 2.4
......
......@@ -33,6 +33,16 @@ class DB2Statement implements \IteratorAggregate, Statement
*/
private $_bindParam = array();
/**
* @var string Name of the default class to instantiate when fetch mode is \PDO::FETCH_CLASS.
*/
private $defaultFetchClass = '\stdClass';
/**
* @var string Constructor arguments for the default class to instantiate when fetch mode is \PDO::FETCH_CLASS.
*/
private $defaultFetchClassCtorArgs = array();
/**
* @var integer
*/
......@@ -165,7 +175,9 @@ class DB2Statement implements \IteratorAggregate, Statement
*/
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
{
$this->_defaultFetchMode = $fetchMode;
$this->_defaultFetchMode = $fetchMode;
$this->defaultFetchClass = $arg2 ? $arg2 : $this->defaultFetchClass;
$this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
return true;
}
......@@ -191,8 +203,27 @@ class DB2Statement implements \IteratorAggregate, Statement
return db2_fetch_both($this->_stmt);
case \PDO::FETCH_ASSOC:
return db2_fetch_assoc($this->_stmt);
case \PDO::FETCH_CLASS:
$className = $this->defaultFetchClass;
$ctorArgs = $this->defaultFetchClassCtorArgs;
if (func_num_args() >= 2) {
$args = func_get_args();
$className = $args[1];
$ctorArgs = isset($args[2]) ? $args[2] : array();
}
$result = db2_fetch_object($this->_stmt);
if ($result instanceof \stdClass) {
$result = $this->castObject($result, $className, $ctorArgs);
}
return $result;
case \PDO::FETCH_NUM:
return db2_fetch_array($this->_stmt);
case PDO::FETCH_OBJ:
return db2_fetch_object($this->_stmt);
default:
throw new DB2Exception("Given Fetch-Style " . $fetchMode . " is not supported.");
}
......@@ -204,8 +235,22 @@ class DB2Statement implements \IteratorAggregate, Statement
public function fetchAll($fetchMode = null)
{
$rows = array();
while ($row = $this->fetch($fetchMode)) {
$rows[] = $row;
switch ($fetchMode) {
case \PDO::FETCH_CLASS:
while ($row = call_user_func_array(array($this, 'fetch'), func_get_args())) {
$rows[] = $row;
}
break;
case \PDO::FETCH_COLUMN:
while ($row = $this->fetchColumn()) {
$rows[] = $row;
}
break;
default:
while ($row = $this->fetch($fetchMode)) {
$rows[] = $row;
}
}
return $rows;
......@@ -231,4 +276,68 @@ class DB2Statement implements \IteratorAggregate, Statement
{
return (@db2_num_rows($this->_stmt))?:0;
}
/**
* Casts a stdClass object to the given class name mapping its' properties.
*
* @param \stdClass $sourceObject Object to cast from.
* @param string|object $destinationClass Name of the class or class instance to cast to.
* @param array $ctorArgs Arguments to use for constructing the destination class instance.
*
* @return object
*
* @throws DB2Exception
*/
private function castObject(\stdClass $sourceObject, $destinationClass, array $ctorArgs = array())
{
if ( ! is_string($destinationClass)) {
if ( ! is_object($destinationClass)) {
throw new DB2Exception(sprintf(
'Destination class has to be of type string or object, %s given.', gettype($destinationClass)
));
}
} else {
$destinationClass = new \ReflectionClass($destinationClass);
$destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
}
$sourceReflection = new \ReflectionObject($sourceObject);
$destinationClassReflection = new \ReflectionObject($destinationClass);
/** @var \ReflectionProperty[] $destinationProperties */
$destinationProperties = array_change_key_case($destinationClassReflection->getProperties(), \CASE_LOWER);
foreach ($sourceReflection->getProperties() as $sourceProperty) {
$sourceProperty->setAccessible(true);
$name = $sourceProperty->getName();
$value = $sourceProperty->getValue($sourceObject);
// Try to find a case-matching property.
if ($destinationClassReflection->hasProperty($name)) {
$destinationProperty = $destinationClassReflection->getProperty($name);
$destinationProperty->setAccessible(true);
$destinationProperty->setValue($destinationClass, $value);
continue;
}
$name = strtolower($name);
// Try to find a property without matching case.
// Fallback for the driver returning either all uppercase or all lowercase column names.
if (isset($destinationProperties[$name])) {
$destinationProperty = $destinationProperties[$name];
$destinationProperty->setAccessible(true);
$destinationProperty->setValue($destinationClass, $value);
continue;
}
$destinationClass->$name = $value;
}
return $destinationClass;
}
}
......@@ -44,7 +44,6 @@ final class DriverManager
'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver',
'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver',
'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver',
'pdo_ibm' => 'Doctrine\DBAL\Driver\PDOIbm\Driver',
'pdo_sqlsrv' => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver',
'mysqli' => 'Doctrine\DBAL\Driver\Mysqli\Driver',
'drizzle_pdo_mysql' => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver',
......@@ -73,7 +72,6 @@ final class DriverManager
* pdo_pgsql
* pdo_oci (unstable)
* pdo_sqlsrv
* pdo_ibm (unstable)
* pdo_sqlsrv
* mysqli
* sqlanywhere
......
This diff is collapsed.
......@@ -37,6 +37,7 @@ class Connection extends \Doctrine\DBAL\Connection
const PORTABILITY_EMPTY_TO_NULL = 4;
const PORTABILITY_FIX_CASE = 8;
const PORTABILITY_DB2 = 13;
const PORTABILITY_ORACLE = 9;
const PORTABILITY_POSTGRESQL = 13;
const PORTABILITY_SQLITE = 13;
......@@ -76,6 +77,8 @@ class Connection extends \Doctrine\DBAL\Connection
$params['portability'] = self::PORTABILITY_SQLANYWHERE;
} else if ($this->_platform->getName() === 'sqlsrv') {
$params['portability'] = $params['portabililty'] & self::PORTABILITY_SQLSRV;
} elseif ($this->_platform->getName() === 'db2') {
$params['portability'] = self::PORTABILITY_DB2;
} else {
$params['portability'] = $params['portability'] & self::PORTABILITY_OTHERVENDORS;
}
......
......@@ -19,9 +19,6 @@
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
use Doctrine\DBAL\Events;
/**
* IBM Db2 Schema Manager.
*
......@@ -60,6 +57,12 @@ class DB2SchemaManager extends AbstractSchemaManager
$scale = false;
$precision = false;
$default = null;
if (null !== $tableColumn['default'] && 'NULL' != $tableColumn['default']) {
$default = trim($tableColumn['default'], "'");
}
$type = $this->_platform->getDoctrineTypeMapping($tableColumn['typename']);
switch (strtolower($tableColumn['typename'])) {
......@@ -86,7 +89,8 @@ class DB2SchemaManager extends AbstractSchemaManager
'length' => $length,
'unsigned' => (bool)$unsigned,
'fixed' => (bool)$fixed,
'default' => ($tableColumn['default'] == "NULL") ? null : $tableColumn['default'],
'default' => $default,
'autoincrement' => (boolean) $tableColumn['autoincrement'],
'notnull' => (bool) ($tableColumn['nulls'] == 'N'),
'scale' => null,
'precision' => null,
......@@ -118,46 +122,14 @@ class DB2SchemaManager extends AbstractSchemaManager
/**
* {@inheritdoc}
*/
protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
protected function _getPortableTableIndexesList($tableIndexRows, $tableName = null)
{
$eventManager = $this->_platform->getEventManager();
$indexes = array();
foreach($tableIndexes as $indexKey => $data) {
$data = array_change_key_case($data, \CASE_LOWER);
$unique = ($data['uniquerule'] == "D") ? false : true;
$primary = ($data['uniquerule'] == "P");
$indexName = strtolower($data['name']);
$data = array(
'name' => $indexName,
'columns' => explode("+", ltrim($data['colnames'], '+')),
'unique' => $unique,
'primary' => $primary
);
$index = null;
$defaultPrevented = false;
if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) {
$eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn);
$eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs);
$defaultPrevented = $eventArgs->isDefaultPrevented();
$index = $eventArgs->getIndex();
}
if ( ! $defaultPrevented) {
$index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary']);
}
if ($index) {
$indexes[$indexKey] = $index;
}
foreach ($tableIndexRows as &$tableIndexRow) {
$tableIndexRow = array_change_key_case($tableIndexRow, \CASE_LOWER);
$tableIndexRow['primary'] = (boolean) $tableIndexRow['primary'];
}
return $indexes;
return parent::_getPortableTableIndexesList($tableIndexRows, $tableName);
}
/**
......@@ -165,23 +137,45 @@ class DB2SchemaManager extends AbstractSchemaManager
*/
protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
{
$tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER);
$tableForeignKey['deleterule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['deleterule']);
$tableForeignKey['updaterule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['updaterule']);
return new ForeignKeyConstraint(
array_map('trim', (array)$tableForeignKey['fkcolnames']),
$tableForeignKey['reftbname'],
array_map('trim', (array)$tableForeignKey['pkcolnames']),
$tableForeignKey['relname'],
array(
'onUpdate' => $tableForeignKey['updaterule'],
'onDelete' => $tableForeignKey['deleterule'],
)
$tableForeignKey['local_columns'],
$tableForeignKey['foreign_table'],
$tableForeignKey['foreign_columns'],
$tableForeignKey['name'],
$tableForeignKey['options']
);
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableForeignKeysList($tableForeignKeys)
{
$foreignKeys = array();
foreach ($tableForeignKeys as $tableForeignKey) {
$tableForeignKey = array_change_key_case($tableForeignKey, \CASE_LOWER);
if (!isset($foreignKeys[$tableForeignKey['index_name']])) {
$foreignKeys[$tableForeignKey['index_name']] = array(
'local_columns' => array($tableForeignKey['local_column']),
'foreign_table' => $tableForeignKey['foreign_table'],
'foreign_columns' => array($tableForeignKey['foreign_column']),
'name' => $tableForeignKey['index_name'],
'options' => array(
'onUpdate' => $tableForeignKey['on_update'],
'onDelete' => $tableForeignKey['on_delete'],
)
);
} else {
$foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column'];
$foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column'];
}
}
return parent::_getPortableTableForeignKeysList($foreignKeys);
}
/**
* {@inheritdoc}
*/
......
......@@ -273,12 +273,12 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
$this->assertTrue($tableIndexes['primary']->isUnique());
$this->assertTrue($tableIndexes['primary']->isPrimary());
$this->assertEquals('test_index_name', $tableIndexes['test_index_name']->getName());
$this->assertEquals('test_index_name', strtolower($tableIndexes['test_index_name']->getName()));
$this->assertEquals(array('test'), array_map('strtolower', $tableIndexes['test_index_name']->getColumns()));
$this->assertTrue($tableIndexes['test_index_name']->isUnique());
$this->assertFalse($tableIndexes['test_index_name']->isPrimary());
$this->assertEquals('test_composite_idx', $tableIndexes['test_composite_idx']->getName());
$this->assertEquals('test_composite_idx', strtolower($tableIndexes['test_composite_idx']->getName()));
$this->assertEquals(array('id', 'test'), array_map('strtolower', $tableIndexes['test_composite_idx']->getColumns()));
$this->assertFalse($tableIndexes['test_composite_idx']->isUnique());
$this->assertFalse($tableIndexes['test_composite_idx']->isPrimary());
......
This diff is collapsed.
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