diff --git a/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php b/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php new file mode 100644 index 000000000..3f05f5c75 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php @@ -0,0 +1,126 @@ +. +*/ + +namespace Doctrine\DBAL\Driver\PDOIbm; + +use Doctrine\DBAL\Connection; + +/** + * Driver for the PDO IBM extension + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @version $Revision$ + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * Attempts to establish a connection with the underlying driver. + * + * @param array $params + * @param string $username + * @param string $password + * @param array $driverOptions + * @return Doctrine\DBAL\Driver\Connection + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + $conn = new \Doctrine\DBAL\Driver\PDOConnection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + return $conn; + } + + /** + * Constructs the MySql PDO DSN. + * + * @return string The DSN. + */ + private function _constructPdoDsn(array $params) + { + $dsn = 'ibm:'; + if (isset($params['host'])) { + $dsn .= 'HOSTNAME=' . $params['host'] . ';'; + } + if (isset($params['port'])) { + $dsn .= 'PORT=' . $params['port'] . ';'; + } + $dsn .= 'PROTOCOL=TCPIP;'; + if (isset($params['dbname'])) { + $dsn .= 'DATABASE=' . $params['dbname'] . ';'; + } + + return $dsn; + } + + /** + * Gets the DatabasePlatform instance that provides all the metadata about + * the platform this driver connects to. + * + * @return Doctrine\DBAL\Platforms\AbstractPlatform The database platform. + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\Db2Platform; + } + + /** + * Gets the SchemaManager that can be used to inspect and change the underlying + * database schema of the platform this driver connects to. + * + * @param Doctrine\DBAL\Connection $conn + * @return Doctrine\DBAL\SchemaManager + */ + public function getSchemaManager(Connection $conn) + { + return new \Doctrine\DBAL\Schema\Db2SchemaManager($conn); + } + + /** + * Gets the name of the driver. + * + * @return string The name of the driver. + */ + public function getName() + { + return 'pdo_ibm'; + } + + /** + * Get the name of the database connected to for this driver. + * + * @param Doctrine\DBAL\Connection $conn + * @return string $database + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + return $params['dbname']; + } +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/DriverManager.php b/lib/Doctrine/DBAL/DriverManager.php index 4ed2ac1f3..21a5c09d4 100644 --- a/lib/Doctrine/DBAL/DriverManager.php +++ b/lib/Doctrine/DBAL/DriverManager.php @@ -45,6 +45,7 @@ final class DriverManager 'pdo_mssql' => 'Doctrine\DBAL\Driver\PDOMsSql\Driver', 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver', 'ibm_db2' => 'Doctrine\DBAL\Driver\IbmDb2\Db2Driver', + 'pdo_ibm' => 'Doctrine\DBAL\Driver\PDOIbm\Driver', ); /** Private constructor. This class cannot be instantiated. */ diff --git a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php index d991c4b1e..702e6a8dc 100644 --- a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -1177,6 +1177,17 @@ abstract class AbstractPlatform return 'TEMPORARY'; } + /** + * Some vendors require temporary table names to be qualified specially. + * + * @param string $tableName + * @return string + */ + public function getTemporaryTableName($tableName) + { + return $tableName; + } + /** * Get sql query to show a list of database. * diff --git a/lib/Doctrine/DBAL/Platforms/Db2Platform.php b/lib/Doctrine/DBAL/Platforms/Db2Platform.php index 955440f43..b02d2eade 100644 --- a/lib/Doctrine/DBAL/Platforms/Db2Platform.php +++ b/lib/Doctrine/DBAL/Platforms/Db2Platform.php @@ -90,7 +90,7 @@ class Db2Platform extends AbstractPlatform */ public function getIntegerTypeDeclarationSQL(array $columnDef) { - return 'INTEGER'; + return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); } /** @@ -101,7 +101,7 @@ class Db2Platform extends AbstractPlatform */ public function getBigIntTypeDeclarationSQL(array $columnDef) { - return 'BIGINT'; + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); } /** @@ -112,7 +112,7 @@ class Db2Platform extends AbstractPlatform */ public function getSmallIntTypeDeclarationSQL(array $columnDef) { - return 'SMALLINT'; + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); } /** @@ -123,7 +123,11 @@ class Db2Platform extends AbstractPlatform */ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) { - + $autoinc = ''; + if ( ! empty($columnDef['autoincrement'])) { + $autoinc = ' GENERATED BY DEFAULT AS IDENTITY'; + } + return $autoinc; } /** @@ -135,6 +139,10 @@ class Db2Platform extends AbstractPlatform */ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) { + if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) { + return "TIMESTAMP(0) WITH DEFAULT"; + } + return 'TIMESTAMP(0)'; } @@ -292,10 +300,10 @@ class Db2Platform extends AbstractPlatform * * @return string */ - public function getCurrentTimestampSQL() + /*public function getCurrentTimestampSQL() { return 'current timestamp'; - } + }*/ /** * Obtain DBMS specific SQL code portion needed to set an index @@ -380,14 +388,113 @@ class Db2Platform extends AbstractPlatform 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'] = ''; } } + unset($field['default']); // @todo this needs fixing + if (isset($field['version']) && $field['version']) { + if ((string)$field['type'] != "DateTime") { + $field['default'] = "1"; + } + } + return parent::getDefaultValueDeclarationSQL($field); } + /** + * Get the insert sql for an empty insert statement + * + * @param string $tableName + * @param string $identifierColumnName + * @return string $sql + */ + public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName) + { + return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)'; + } + + public function getCreateTemporaryTableSnippetSQL() + { + return "DECLARE GLOBAL TEMPORARY TABLE"; + } + + /** + * DB2 automatically moves temporary tables into the SESSION. schema. + * + * @param string $tableName + * @return string + */ + public function getTemporaryTableName($tableName) + { + return "SESSION." . $tableName; + } + + public function getCurrentTimestampSQL() + { + return "VALUES CURRENT TIMESTAMP"; + } + + public function modifyLimitQuery($query, $limit, $offset = null) + { + if ($limit === null && $offset === null) { + return $query; + } + + $limit = (int)$limit; + $offset = (int)(($offset)?:0); + + // Todo OVER() needs ORDER BY data! + $sql = 'SELECT db22.* FROM (SELECT ROW_NUMBER() OVER() AS DC_ROWNUM, db21.* '. + 'FROM (' . $query . ') db21) db22 WHERE db22.DC_ROWNUM BETWEEN ' . ($offset+1) .' AND ' . ($offset+$limit); + return $sql; + } + + /** + * returns the position of the first occurrence of substring $substr in string $str + * + * @param string $substr literal string to find + * @param string $str literal string + * @param int $pos position to start at, beginning of string by default + * @return integer + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'LOCATE(' . $substr . ', ' . $str . ')'; + } else { + return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')'; + } + } + + /** + * return string to call a function to get a substring inside an SQL statement + * + * Note: Not SQL92, but common functionality. + * + * SQLite only supports the 2 parameter variant of this function + * + * @param string $value an sql string literal or column name/alias + * @param integer $from where to start the substring portion + * @param integer $len the substring portion length + * @return string + */ + public function getSubstringExpression($value, $from, $len = null) + { + if ($len === null) + return 'SUBSTR(' . $value . ', ' . $from . ')'; + else { + return 'SUBSTR(' . $value . ', ' . $from . ', ' . $len . ')'; + } + } + public function supportsIdentityColumns() { return true; @@ -397,4 +504,17 @@ class Db2Platform extends AbstractPlatform { return true; } + + /** + * Gets the character casing of a column in an SQL result set of this platform. + * + * DB2 returns all column names in SQL result sets in uppercase. + * + * @param string $column The column name for which to get the correct character casing. + * @return string The column name in the character casing used in SQL result sets. + */ + public function getSQLResultCasing($column) + { + return strtoupper($column); + } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php b/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php index b3d802bac..656c9c00b 100644 --- a/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php @@ -187,8 +187,14 @@ class Db2SchemaManager extends AbstractSchemaManager protected function _getPortableViewDefinition($view) { $view = array_change_key_case($view, \CASE_LOWER); - $pos = strpos($view['text'], ' AS '); - $sql = substr($view['text'], $pos+4); + // sadly this still segfaults on PDO_IBM, see http://pecl.php.net/bugs/bug.php?id=17199 + //$view['text'] = (is_resource($view['text']) ? stream_get_contents($view['text']) : $view['text']); + if (!is_resource($view['text'])) { + $pos = strpos($view['text'], ' AS '); + $sql = substr($view['text'], $pos+4); + } else { + $sql = ''; + } return new View($view['name'], $sql); } diff --git a/lib/Doctrine/DBAL/Types/ArrayType.php b/lib/Doctrine/DBAL/Types/ArrayType.php index 4eb79af24..672710365 100644 --- a/lib/Doctrine/DBAL/Types/ArrayType.php +++ b/lib/Doctrine/DBAL/Types/ArrayType.php @@ -40,6 +40,7 @@ class ArrayType extends Type public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { + $value = (is_resource($value)) ? stream_get_contents($value) : $value; return unserialize($value); } diff --git a/lib/Doctrine/DBAL/Types/ObjectType.php b/lib/Doctrine/DBAL/Types/ObjectType.php index 6b59f5757..668746f36 100644 --- a/lib/Doctrine/DBAL/Types/ObjectType.php +++ b/lib/Doctrine/DBAL/Types/ObjectType.php @@ -21,6 +21,7 @@ class ObjectType extends Type public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { + $value = (is_resource($value)) ? stream_get_contents($value) : $value; return unserialize($value); } diff --git a/lib/Doctrine/DBAL/Types/TextType.php b/lib/Doctrine/DBAL/Types/TextType.php index 1ef008e04..4fcd87270 100644 --- a/lib/Doctrine/DBAL/Types/TextType.php +++ b/lib/Doctrine/DBAL/Types/TextType.php @@ -36,6 +36,19 @@ class TextType extends Type return $platform->getClobTypeDeclarationSQL($fieldDeclaration); } + /** + * Converts a value from its database representation to its PHP representation + * of this type. + * + * @param mixed $value The value to convert. + * @param AbstractPlatform $platform The currently used database platform. + * @return mixed The PHP representation of the value. + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (is_resource($value)) ? stream_get_contents($value) : $value; + } + public function getName() { return Type::TEXT; diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php index 26453b2b9..8ddb78fff 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php @@ -58,7 +58,7 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor $primaryDqlAlias = $AST->deleteClause->aliasIdentificationVariable; $rootClass = $em->getClassMetadata($primaryClass->rootEntityName); - $tempTable = $rootClass->getTemporaryIdTableName(); + $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); @@ -95,8 +95,7 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor ); } $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' - . $platform->getColumnDeclarationListSQL($columnDefinitions) - . ', PRIMARY KEY(' . $idColumnList . '))'; + . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; $this->_dropTempTableSql = 'DROP TABLE ' . $tempTable; } diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php index add39dc83..902cb0251 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php @@ -64,7 +64,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor $updateItems = $updateClause->updateItems; - $tempTable = $rootClass->getTemporaryIdTableName(); + $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); @@ -131,8 +131,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor ); } $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' - . $platform->getColumnDeclarationListSQL($columnDefinitions) - . ', PRIMARY KEY(' . $idColumnList . '))'; + . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; $this->_dropTempTableSql = 'DROP TABLE ' . $tempTable; } diff --git a/tests/Doctrine/Tests/DBAL/Functional/AllTests.php b/tests/Doctrine/Tests/DBAL/Functional/AllTests.php index 31719886b..2132fe5b4 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/AllTests.php +++ b/tests/Doctrine/Tests/DBAL/Functional/AllTests.php @@ -25,6 +25,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\MySqlSchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\PostgreSqlSchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\OracleSchemaManagerTest'); + $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\Db2SchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\ConnectionTest'); return $suite; diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php index 167c9290b..9b8d21bc9 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -284,8 +284,6 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $this->markTestSkipped('Alter Table is not supported by this platform.'); } - $this->_conn->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); - $this->createTestTable('alter_table'); $this->createTestTable('alter_table_foreign'); diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index 07aa2edf8..23dfa3a73 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -16,13 +16,12 @@ require_once __DIR__ . '/../../TestInit.php'; */ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase { + private $platform = null; + protected function setUp() { $this->useModelSet('cms'); parent::setUp(); - - if ($this->_em->getConnection()->getDatabasePlatform()->getName() == 'oracle') { - $this->markTestSkipped('The ' . __CLASS__ .' does not work with Oracle due to character casing.'); - } + $this->platform = $this->_em->getConnection()->getDatabasePlatform(); } public function testBasicNativeQuery() @@ -38,8 +37,8 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('name'), 'name'); $query = $this->_em->createNativeQuery('SELECT id, name FROM cms_users WHERE username = ?', $rsm); $query->setParameter(1, 'romanb'); @@ -70,11 +69,11 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); - $rsm->addFieldResult('u', 'status', 'status'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('name'), 'name'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('status'), 'status'); $rsm->addJoinedEntityResult('Doctrine\Tests\Models\CMS\CmsPhonenumber', 'p', 'u', 'phonenumbers'); - $rsm->addFieldResult('p', 'phonenumber', 'phonenumber'); + $rsm->addFieldResult('p', $this->platform->getSQLResultCasing('phonenumber'), 'phonenumber'); $query = $this->_em->createNativeQuery('SELECT id, name, status, phonenumber FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username = ?', $rsm); $query->setParameter(1, 'romanb'); @@ -115,14 +114,14 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); - $rsm->addFieldResult('u', 'status', 'status'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('name'), 'name'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('status'), 'status'); $rsm->addJoinedEntityResult('Doctrine\Tests\Models\CMS\CmsAddress', 'a', 'u', 'address'); - $rsm->addFieldResult('a', 'a_id', 'id'); - $rsm->addFieldResult('a', 'country', 'country'); - $rsm->addFieldResult('a', 'zip', 'zip'); - $rsm->addFieldResult('a', 'city', 'city'); + $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('a_id'), 'id'); + $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('country'), 'country'); + $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('zip'), 'zip'); + $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('city'), 'city'); $query = $this->_em->createNativeQuery('SELECT u.id, u.name, u.status, a.id AS a_id, a.country, a.zip, a.city FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ?', $rsm); $query->setParameter(1, 'romanb'); diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 9e02cb285..2d1f370b0 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -251,7 +251,7 @@ abstract class OrmFunctionalTestCase extends OrmTestCase $queries = ""; for($i = count($this->_sqlLoggerStack->queries)-1; $i > max(count($this->_sqlLoggerStack->queries)-25, 0); $i--) { $query = $this->_sqlLoggerStack->queries[$i]; - $params = array_map(function($p) { return "'".$p."'"; }, $query['params'] ?: array()); + $params = array_map(function($p) { if (is_object($p)) return get_class($p); else return "'".$p."'"; }, $query['params'] ?: array()); $queries .= ($i+1).". SQL: '".$query['sql']."' Params: ".implode(", ", $params).PHP_EOL; }