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
......
......@@ -30,7 +30,8 @@ class DB2Platform extends AbstractPlatform
*/
public function getBlobTypeDeclarationSQL(array $field)
{
throw DBALException::notSupported(__METHOD__);
// todo blob(n) with $field['length'];
return 'BLOB(1M)';
}
/**
......@@ -47,6 +48,7 @@ class DB2Platform extends AbstractPlatform
'varchar' => 'string',
'character' => 'string',
'clob' => 'text',
'blob' => 'blob',
'decimal' => 'decimal',
'double' => 'float',
'real' => 'float',
......@@ -126,55 +128,111 @@ class DB2Platform extends AbstractPlatform
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
public function getBitAndComparisonExpression($value1, $value2)
{
if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) {
return "TIMESTAMP(0) WITH DEFAULT";
}
return 'BITAND(' . $value1 . ', ' . $value2 . ')';
}
return 'TIMESTAMP(0)';
/**
* {@inheritdoc}
*/
public function getBitOrComparisonExpression($value1, $value2)
{
return 'BITOR(' . $value1 . ', ' . $value2 . ')';
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public function getDateTypeDeclarationSQL(array $fieldDeclaration)
public function getDateAddDaysExpression($date, $days)
{
return 'DATE';
return $date . ' + ' . $days . ' days';
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
public function getDateAddHourExpression($date, $hours)
{
return 'TIME';
return $date . ' + ' . $hours . ' hours';
}
/**
* {@inheritdoc}
*/
public function getDateAddMonthExpression($date, $months)
{
return $date . ' + ' . $months . ' months';
}
/**
* {@inheritdoc}
*/
public function getDateDiffExpression($date1, $date2)
{
return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')';
}
/**
* {@inheritdoc}
*/
public function getDateSubDaysExpression($date, $days)
{
return $date . ' - ' . $days . ' days';
}
/**
* {@inheritdoc}
*/
public function getDateSubHourExpression($date, $hours)
{
return $date . ' - ' . $hours . ' hours';
}
/**
* {@inheritdoc}
*/
public function getDateSubMonthExpression($date, $months)
{
return $date . ' - ' . $months . ' months';
}
/**
* {@inheritDoc}
*/
public function getListDatabasesSQL()
public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
{
throw DBALException::notSupported(__METHOD__);
if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) {
return "TIMESTAMP(0) WITH DEFAULT";
}
return 'TIMESTAMP(0)';
}
/**
* {@inheritDoc}
*/
public function getListSequencesSQL($database)
public function getDateTypeDeclarationSQL(array $fieldDeclaration)
{
throw DBALException::notSupported(__METHOD__);
return 'DATE';
}
/**
* {@inheritDoc}
*/
public function getListTableConstraintsSQL($table)
public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
{
return 'TIME';
}
/**
* {@inheritdoc}
*/
public function getTruncateTableSQL($tableName, $cascade = false)
{
throw DBALException::notSupported(__METHOD__);
return 'TRUNCATE ' . $tableName . ' IMMEDIATE';
}
/**
......@@ -191,7 +249,11 @@ class DB2Platform extends AbstractPlatform
{
return "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno,
c.typename, c.default, c.nulls, c.length, c.scale,
c.identity, tc.type AS tabconsttype, k.colseq
c.identity, tc.type AS tabconsttype, k.colseq,
CASE
WHEN c.generated = 'D' THEN 1
ELSE 0
END AS autoincrement
FROM syscat.columns c
LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc
ON (k.tabschema = tc.tabschema
......@@ -211,14 +273,6 @@ class DB2Platform extends AbstractPlatform
return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'";
}
/**
* {@inheritDoc}
*/
public function getListUsersSQL()
{
throw DBALException::notSupported(__METHOD__);
}
/**
* {@inheritDoc}
*/
......@@ -232,7 +286,21 @@ class DB2Platform extends AbstractPlatform
*/
public function getListTableIndexesSQL($table, $currentDatabase = null)
{
return "SELECT NAME, COLNAMES, UNIQUERULE FROM SYSIBM.SYSINDEXES WHERE TBNAME = UPPER('" . $table . "')";
return "SELECT idx.INDNAME AS key_name,
idxcol.COLNAME AS column_name,
CASE
WHEN idx.UNIQUERULE = 'P' THEN 1
ELSE 0
END AS primary,
CASE
WHEN idx.UNIQUERULE = 'D' THEN 1
ELSE 0
END AS non_unique
FROM SYSCAT.INDEXES AS idx
JOIN SYSCAT.INDEXCOLUSE AS idxcol
ON idx.INDSCHEMA = idxcol.INDSCHEMA AND idx.INDNAME = idxcol.INDNAME
WHERE idx.TABNAME = UPPER('" . $table . "')
ORDER BY idxcol.COLSEQ ASC";
}
/**
......@@ -240,8 +308,31 @@ class DB2Platform extends AbstractPlatform
*/
public function getListTableForeignKeysSQL($table)
{
return "SELECT TBNAME, RELNAME, REFTBNAME, DELETERULE, UPDATERULE, FKCOLNAMES, PKCOLNAMES ".
"FROM SYSIBM.SYSRELS WHERE TBNAME = UPPER('".$table."')";
return "SELECT fkcol.COLNAME AS local_column,
fk.REFTABNAME AS foreign_table,
pkcol.COLNAME AS foreign_column,
fk.CONSTNAME AS index_name,
CASE
WHEN fk.UPDATERULE = 'R' THEN 'RESTRICT'
ELSE NULL
END AS on_update,
CASE
WHEN fk.DELETERULE = 'C' THEN 'CASCADE'
WHEN fk.DELETERULE = 'N' THEN 'SET NULL'
WHEN fk.DELETERULE = 'R' THEN 'RESTRICT'
ELSE NULL
END AS on_delete
FROM SYSCAT.REFERENCES AS fk
JOIN SYSCAT.KEYCOLUSE AS fkcol
ON fk.CONSTNAME = fkcol.CONSTNAME
AND fk.TABSCHEMA = fkcol.TABSCHEMA
AND fk.TABNAME = fkcol.TABNAME
JOIN SYSCAT.KEYCOLUSE AS pkcol
ON fk.REFKEYNAME = pkcol.CONSTNAME
AND fk.REFTABSCHEMA = pkcol.TABSCHEMA
AND fk.REFTABNAME = pkcol.TABNAME
WHERE fk.TABNAME = UPPER('" . $table . "')
ORDER BY fkcol.COLSEQ ASC";
}
/**
......@@ -260,22 +351,6 @@ class DB2Platform extends AbstractPlatform
return "DROP VIEW ".$name;
}
/**
* {@inheritDoc}
*/
public function getDropSequenceSQL($sequence)
{
throw DBALException::notSupported(__METHOD__);
}
/**
* {@inheritDoc}
*/
public function getSequenceNextValSQL($sequenceName)
{
throw DBALException::notSupported(__METHOD__);
}
/**
* {@inheritDoc}
*/
......@@ -289,7 +364,7 @@ class DB2Platform extends AbstractPlatform
*/
public function getDropDatabaseSQL($database)
{
return "DROP DATABASE ".$database.";";
return "DROP DATABASE " . $database;
}
/**
......@@ -313,7 +388,7 @@ class DB2Platform extends AbstractPlatform
*/
public function getCurrentDateSQL()
{
return 'VALUES CURRENT DATE';
return 'CURRENT DATE';
}
/**
......@@ -321,7 +396,7 @@ class DB2Platform extends AbstractPlatform
*/
public function getCurrentTimeSQL()
{
return 'VALUES CURRENT TIME';
return 'CURRENT TIME';
}
/**
......@@ -329,7 +404,7 @@ class DB2Platform extends AbstractPlatform
*/
public function getCurrentTimestampSQL()
{
return "VALUES CURRENT TIMESTAMP";
return "CURRENT TIMESTAMP";
}
/**
......@@ -373,7 +448,18 @@ class DB2Platform extends AbstractPlatform
continue;
}
$queryParts[] = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
$columnDef = $column->toArray();
$queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef);
// Adding non-nullable columns to a table requires a default value to be specified.
if ( ! empty($columnDef['notnull']) &&
! isset($columnDef['default']) &&
empty($columnDef['autoincrement'])
) {
$queryPart .= ' WITH DEFAULT';
}
$queryParts[] = $queryPart;
}
foreach ($diff->removedColumns as $column) {
......@@ -400,7 +486,7 @@ class DB2Platform extends AbstractPlatform
continue;
}
$queryParts[] = 'RENAME ' . $oldColumnName . ' TO ' . $column->getQuotedName($this);
$queryParts[] = 'RENAME COLUMN ' . $oldColumnName . ' TO ' . $column->getQuotedName($this);
}
$tableSql = array();
......@@ -410,10 +496,19 @@ class DB2Platform extends AbstractPlatform
$sql[] = 'ALTER TABLE ' . $diff->name . ' ' . implode(" ", $queryParts);
}
$sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff));
// Some table alteration operations require a table reorganization.
if ( ! empty($diff->removedColumns) || ! empty($diff->changedColumns)) {
$sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $diff->name . "')";
}
$sql = array_merge(
$this->getPreAlterTableIndexForeignKeySQL($diff),
$sql,
$this->getPostAlterTableIndexForeignKeySQL($diff)
);
if ($diff->newName !== false) {
$sql[] = 'RENAME TABLE TO ' . $diff->newName;
$sql[] = 'RENAME TABLE ' . $diff->name . ' TO ' . $diff->newName;
}
}
......@@ -423,23 +518,46 @@ class DB2Platform extends AbstractPlatform
/**
* {@inheritDoc}
*/
public function getDefaultValueDeclarationSQL($field)
protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
{
if (isset($field['notnull']) && $field['notnull'] && !isset($field['default'])) {
if (in_array((string)$field['type'], array("Integer", "BigInteger", "SmallInteger"))) {
$field['default'] = 0;
} else if((string)$field['type'] == "DateTime") {
$field['default'] = "00-00-00 00:00:00";
} else if ((string)$field['type'] == "Date") {
$field['default'] = "00-00-00";
} else if((string)$field['type'] == "Time") {
$field['default'] = "00:00:00";
} else {
$field['default'] = '';
$sql = array();
$table = $diff->name;
foreach ($diff->removedIndexes as $remKey => $remIndex) {
foreach ($diff->addedIndexes as $addKey => $addIndex) {
if ($remIndex->getColumns() == $addIndex->getColumns()) {
if ($remIndex->isPrimary()) {
$sql[] = 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY';
} elseif ($remIndex->isUnique()) {
$sql[] = 'ALTER TABLE ' . $table . ' DROP UNIQUE ' . $remIndex->getQuotedName($this);
} else {
$sql[] = $this->getDropIndexSQL($remIndex, $table);
}
$sql[] = $this->getCreateIndexSQL($addIndex, $table);
unset($diff->removedIndexes[$remKey]);
unset($diff->addedIndexes[$addKey]);
break;
}
}
}
unset($field['default']); // @todo this needs fixing
$sql = array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff));
return $sql;
}
/**
* {@inheritDoc}
*/
public function getDefaultValueDeclarationSQL($field)
{
if ( ! empty($field['autoincrement'])) {
return '';
}
if (isset($field['version']) && $field['version']) {
if ((string)$field['type'] != "DateTime") {
$field['default'] = "1";
......
......@@ -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());
......
<?php
namespace Doctrine\Tests\DBAL\Platforms;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Table;
class DB2PlatformTest extends AbstractPlatformTestCase
{
/**
* @var \Doctrine\DBAL\Platforms\DB2Platform
*/
protected $_platform;
public function createPlatform()
{
return new DB2Platform();
}
public function getGenerateAlterTableSql()
{
return array(
"ALTER TABLE mytable ADD COLUMN quota INTEGER DEFAULT NULL DROP COLUMN foo ALTER bar baz VARCHAR(255) DEFAULT 'def' NOT NULL ALTER bloo bloo SMALLINT DEFAULT '0' NOT NULL",
"CALL SYSPROC.ADMIN_CMD ('REORG TABLE mytable')",
'RENAME TABLE mytable TO userlist',
);
}
public function getGenerateForeignKeySql()
{
return 'ALTER TABLE test ADD FOREIGN KEY (fk_name_id) REFERENCES other_table (id)';
}
public function getGenerateIndexSql()
{
return 'CREATE INDEX my_idx ON mytable (user_name, last_login)';
}
public function getGenerateTableSql()
{
return 'CREATE TABLE test (id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL, test VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))';
}
public function getGenerateTableWithMultiColumnUniqueIndexSql()
{
return array(
'CREATE TABLE test (foo VARCHAR(255) DEFAULT NULL, bar VARCHAR(255) DEFAULT NULL)',
'CREATE UNIQUE INDEX UNIQ_D87F7E0C8C73652176FF8CAA ON test (foo, bar)'
);
}
public function getGenerateUniqueIndexSql()
{
return 'CREATE UNIQUE INDEX index_name ON test (test, test2)';
}
protected function getQuotedColumnInForeignKeySQL()
{
return array(
'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, foo VARCHAR(255) NOT NULL, "bar" VARCHAR(255) NOT NULL)',
'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES "foreign" ("create", bar, "foo-bar")',
'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_NON_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES foo ("create", bar, "foo-bar")',
'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_INTENDED_QUOTATION FOREIGN KEY ("create", foo, "bar") REFERENCES "foo-bar" ("create", bar, "foo-bar")',
);
}
protected function getQuotedColumnInIndexSQL()
{
return array(
'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL)',
'CREATE INDEX IDX_22660D028FD6E0FB ON "quoted" ("create")'
);
}
protected function getQuotedColumnInPrimaryKeySQL()
{
return array(
'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, PRIMARY KEY("create"))'
);
}
protected function getBitAndComparisonExpressionSql($value1, $value2)
{
return 'BITAND(' . $value1 . ', ' . $value2 . ')';
}
protected function getBitOrComparisonExpressionSql($value1, $value2)
{
return 'BITOR(' . $value1 . ', ' . $value2 . ')';
}
public function getCreateTableColumnCommentsSQL()
{
return array(
"CREATE TABLE test (id INTEGER NOT NULL, PRIMARY KEY(id))",
);
}
public function getAlterTableColumnCommentsSQL()
{
return array(
"ALTER TABLE mytable ADD COLUMN quota INTEGER NOT NULL WITH DEFAULT ALTER foo foo VARCHAR(255) NOT NULL ALTER bar baz VARCHAR(255) NOT NULL",
"CALL SYSPROC.ADMIN_CMD ('REORG TABLE mytable')"
);
}
public function getCreateTableColumnTypeCommentsSQL()
{
return array(
'CREATE TABLE test (id INTEGER NOT NULL, "data" CLOB(1M) NOT NULL, PRIMARY KEY(id))',
);
}
public function testHasCorrectPlatformName()
{
$this->assertEquals('db2', $this->_platform->getName());
}
public function testGeneratesCreateTableSQLWithCommonIndexes()
{
$table = new Table('test');
$table->addColumn('id', 'integer');
$table->addColumn('name', 'string', array('length' => 50));
$table->setPrimaryKey(array('id'));
$table->addIndex(array('name'));
$table->addIndex(array('id', 'name'), 'composite_idx');
$this->assertEquals(
array(
'CREATE TABLE test (id INTEGER NOT NULL, name VARCHAR(50) NOT NULL, PRIMARY KEY(id))',
'CREATE INDEX IDX_D87F7E0C5E237E06 ON test (name)',
'CREATE INDEX composite_idx ON test (id, name)'
),
$this->_platform->getCreateTableSQL($table)
);
}
public function testGeneratesCreateTableSQLWithForeignKeyConstraints()
{
$table = new Table('test');
$table->addColumn('id', 'integer');
$table->addColumn('fk_1', 'integer');
$table->addColumn('fk_2', 'integer');
$table->setPrimaryKey(array('id'));
$table->addForeignKeyConstraint('foreign_table', array('fk_1', 'fk_2'), array('pk_1', 'pk_2'));
$table->addForeignKeyConstraint(
'foreign_table2',
array('fk_1', 'fk_2'),
array('pk_1', 'pk_2'),
array(),
'named_fk'
);
$this->assertEquals(
array(
'CREATE TABLE test (id INTEGER NOT NULL, fk_1 INTEGER NOT NULL, fk_2 INTEGER NOT NULL)',
'ALTER TABLE test ADD CONSTRAINT FK_D87F7E0C177612A38E7F4319 FOREIGN KEY (fk_1, fk_2) REFERENCES foreign_table (pk_1, pk_2)',
'ALTER TABLE test ADD CONSTRAINT named_fk FOREIGN KEY (fk_1, fk_2) REFERENCES foreign_table2 (pk_1, pk_2)',
),
$this->_platform->getCreateTableSQL($table, AbstractPlatform::CREATE_FOREIGNKEYS)
);
}
public function testGeneratesCreateTableSQLWithCheckConstraints()
{
$table = new Table('test');
$table->addColumn('id', 'integer');
$table->addColumn('check_max', 'integer', array('platformOptions' => array('max' => 10)));
$table->addColumn('check_min', 'integer', array('platformOptions' => array('min' => 10)));
$table->setPrimaryKey(array('id'));
$this->assertEquals(
array(
'CREATE TABLE test (id INTEGER NOT NULL, check_max INTEGER NOT NULL, check_min INTEGER NOT NULL, PRIMARY KEY(id), CHECK (check_max <= 10), CHECK (check_min >= 10))'
),
$this->_platform->getCreateTableSQL($table)
);
}
public function testGeneratesColumnTypesDeclarationSQL()
{
$fullColumnDef = array(
'length' => 10,
'fixed' => true,
'unsigned' => true,
'autoincrement' => true
);
$this->assertEquals('VARCHAR(255)', $this->_platform->getVarcharTypeDeclarationSQL(array()));
$this->assertEquals('VARCHAR(10)', $this->_platform->getVarcharTypeDeclarationSQL(array('length' => 10)));
$this->assertEquals('CHAR(255)', $this->_platform->getVarcharTypeDeclarationSQL(array('fixed' => true)));
$this->assertEquals('CHAR(10)', $this->_platform->getVarcharTypeDeclarationSQL($fullColumnDef));
$this->assertEquals('SMALLINT', $this->_platform->getSmallIntTypeDeclarationSQL(array()));
$this->assertEquals('SMALLINT', $this->_platform->getSmallIntTypeDeclarationSQL(array(
'unsigned' => true
)));
$this->assertEquals('SMALLINT GENERATED BY DEFAULT AS IDENTITY', $this->_platform->getSmallIntTypeDeclarationSQL($fullColumnDef));
$this->assertEquals('INTEGER', $this->_platform->getIntegerTypeDeclarationSQL(array()));
$this->assertEquals('INTEGER', $this->_platform->getIntegerTypeDeclarationSQL(array(
'unsigned' => true
)));
$this->assertEquals('INTEGER GENERATED BY DEFAULT AS IDENTITY', $this->_platform->getIntegerTypeDeclarationSQL($fullColumnDef));
$this->assertEquals('BIGINT', $this->_platform->getBigIntTypeDeclarationSQL(array()));
$this->assertEquals('BIGINT', $this->_platform->getBigIntTypeDeclarationSQL(array(
'unsigned' => true
)));
$this->assertEquals('BIGINT GENERATED BY DEFAULT AS IDENTITY', $this->_platform->getBigIntTypeDeclarationSQL($fullColumnDef));
$this->assertEquals('BLOB(1M)', $this->_platform->getBlobTypeDeclarationSQL($fullColumnDef));
$this->assertEquals('SMALLINT', $this->_platform->getBooleanTypeDeclarationSQL($fullColumnDef));
$this->assertEquals('CLOB(1M)', $this->_platform->getClobTypeDeclarationSQL($fullColumnDef));
$this->assertEquals('DATE', $this->_platform->getDateTypeDeclarationSQL($fullColumnDef));
$this->assertEquals('TIMESTAMP(0) WITH DEFAULT', $this->_platform->getDateTimeTypeDeclarationSQL(array('version' => true)));
$this->assertEquals('TIMESTAMP(0)', $this->_platform->getDateTimeTypeDeclarationSQL($fullColumnDef));
$this->assertEquals('TIME', $this->_platform->getTimeTypeDeclarationSQL($fullColumnDef));
}
public function testInitializesDoctrineTypeMappings()
{
$this->_platform->initializeDoctrineTypeMappings();
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('smallint'));
$this->assertSame('smallint', $this->_platform->getDoctrineTypeMapping('smallint'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('bigint'));
$this->assertSame('bigint', $this->_platform->getDoctrineTypeMapping('bigint'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('integer'));
$this->assertSame('integer', $this->_platform->getDoctrineTypeMapping('integer'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('time'));
$this->assertSame('time', $this->_platform->getDoctrineTypeMapping('time'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('date'));
$this->assertSame('date', $this->_platform->getDoctrineTypeMapping('date'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('varchar'));
$this->assertSame('string', $this->_platform->getDoctrineTypeMapping('varchar'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('character'));
$this->assertSame('string', $this->_platform->getDoctrineTypeMapping('character'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('clob'));
$this->assertSame('text', $this->_platform->getDoctrineTypeMapping('clob'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('blob'));
$this->assertSame('blob', $this->_platform->getDoctrineTypeMapping('blob'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('decimal'));
$this->assertSame('decimal', $this->_platform->getDoctrineTypeMapping('decimal'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('double'));
$this->assertSame('float', $this->_platform->getDoctrineTypeMapping('double'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('real'));
$this->assertSame('float', $this->_platform->getDoctrineTypeMapping('real'));
$this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('timestamp'));
$this->assertSame('datetime', $this->_platform->getDoctrineTypeMapping('timestamp'));
}
public function testGeneratesDDLSnippets()
{
$this->assertEquals("CREATE DATABASE foobar", $this->_platform->getCreateDatabaseSQL('foobar'));
$this->assertEquals("DROP DATABASE foobar", $this->_platform->getDropDatabaseSQL('foobar'));
$this->assertEquals('DECLARE GLOBAL TEMPORARY TABLE', $this->_platform->getCreateTemporaryTableSnippetSQL());
$this->assertEquals('TRUNCATE foobar IMMEDIATE', $this->_platform->getTruncateTableSQL('foobar'));
$this->assertEquals('TRUNCATE foobar IMMEDIATE', $this->_platform->getTruncateTableSQL('foobar'), true);
$viewSql = 'SELECT * FROM footable';
$this->assertEquals('CREATE VIEW fooview AS ' . $viewSql, $this->_platform->getCreateViewSQL('fooview', $viewSql));
$this->assertEquals('DROP VIEW fooview', $this->_platform->getDropViewSQL('fooview'));
}
public function testGeneratesCreateUnnamedPrimaryKeySQL()
{
$this->assertEquals(
'ALTER TABLE foo ADD PRIMARY KEY (a, b)',
$this->_platform->getCreatePrimaryKeySQL(
new Index('any_pk_name', array('a', 'b'), true, true),
'foo'
)
);
}
public function testGeneratesSQLSnippets()
{
$this->assertEquals('CURRENT DATE', $this->_platform->getCurrentDateSQL());
$this->assertEquals('CURRENT TIME', $this->_platform->getCurrentTimeSQL());
$this->assertEquals('CURRENT TIMESTAMP', $this->_platform->getCurrentTimestampSQL());
$this->assertEquals("'1987/05/02' + 4 days", $this->_platform->getDateAddDaysExpression("'1987/05/02'", 4));
$this->assertEquals("'1987/05/02' + 12 hours", $this->_platform->getDateAddHourExpression("'1987/05/02'", 12));
$this->assertEquals("'1987/05/02' + 102 months", $this->_platform->getDateAddMonthExpression("'1987/05/02'", 102));
$this->assertEquals("DAYS('1987/05/02') - DAYS('1987/04/01')", $this->_platform->getDateDiffExpression("'1987/05/02'", "'1987/04/01'"));
$this->assertEquals("'1987/05/02' - 4 days", $this->_platform->getDateSubDaysExpression("'1987/05/02'", 4));
$this->assertEquals("'1987/05/02' - 12 hours", $this->_platform->getDateSubHourExpression("'1987/05/02'", 12));
$this->assertEquals("'1987/05/02' - 102 months", $this->_platform->getDateSubMonthExpression("'1987/05/02'", 102));
$this->assertEquals(' WITH RR USE AND KEEP UPDATE LOCKS', $this->_platform->getForUpdateSQL());
$this->assertEquals('LOCATE(substring_column, string_column)', $this->_platform->getLocateExpression('string_column', 'substring_column'));
$this->assertEquals('LOCATE(substring_column, string_column)', $this->_platform->getLocateExpression('string_column', 'substring_column'));
$this->assertEquals('LOCATE(substring_column, string_column, 1)', $this->_platform->getLocateExpression('string_column', 'substring_column', 1));
$this->assertEquals('SUBSTR(column, 5)', $this->_platform->getSubstringExpression('column', 5));
$this->assertEquals('SUBSTR(column, 5, 2)', $this->_platform->getSubstringExpression('column', 5, 2));
}
public function testModifiesLimitQuery()
{
$this->assertEquals(
'SELECT * FROM user',
$this->_platform->modifyLimitQuery('SELECT * FROM user', null, null)
);
$this->assertEquals(
'SELECT db22.* FROM (SELECT ROW_NUMBER() OVER() AS DC_ROWNUM, db21.* FROM (SELECT * FROM user) db21) db22 WHERE db22.DC_ROWNUM BETWEEN 1 AND 10',
$this->_platform->modifyLimitQuery('SELECT * FROM user', 10, 0)
);
$this->assertEquals(
'SELECT db22.* FROM (SELECT ROW_NUMBER() OVER() AS DC_ROWNUM, db21.* FROM (SELECT * FROM user) db21) db22 WHERE db22.DC_ROWNUM BETWEEN 1 AND 10',
$this->_platform->modifyLimitQuery('SELECT * FROM user', 10)
);
$this->assertEquals(
'SELECT db22.* FROM (SELECT ROW_NUMBER() OVER() AS DC_ROWNUM, db21.* FROM (SELECT * FROM user) db21) db22 WHERE db22.DC_ROWNUM BETWEEN 6 AND 15',
$this->_platform->modifyLimitQuery('SELECT * FROM user', 10, 5)
);
$this->assertEquals(
'SELECT db22.* FROM (SELECT ROW_NUMBER() OVER() AS DC_ROWNUM, db21.* FROM (SELECT * FROM user) db21) db22 WHERE db22.DC_ROWNUM BETWEEN 6 AND 5',
$this->_platform->modifyLimitQuery('SELECT * FROM user', 0, 5)
);
}
public function testPrefersIdentityColumns()
{
$this->assertTrue($this->_platform->prefersIdentityColumns());
}
public function testSupportsIdentityColumns()
{
$this->assertTrue($this->_platform->supportsIdentityColumns());
}
public function testDoesNotSupportSavePoints()
{
$this->assertFalse($this->_platform->supportsSavepoints());
}
public function testDoesNotSupportReleasePoints()
{
$this->assertFalse($this->_platform->supportsReleaseSavepoints());
}
public function testDoesNotSupportCreateDropDatabase()
{
$this->assertFalse($this->_platform->supportsCreateDropDatabase());
}
public function testReturnsSQLResultCasing()
{
$this->assertSame('COL', $this->_platform->getSQLResultCasing('cOl'));
}
}
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