Commit 024b2bab authored by Benjamin Eberlei's avatar Benjamin Eberlei

DDC-496 Finished first versions of platform and schema manager for DB2,...

DDC-496 Finished first versions of platform and schema manager for DB2, DDC-528 Added support for PDO_IBM driver, passing all but 3 tests that are related to CLOB fields
parent 5fd6e687
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
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 <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
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
......@@ -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. */
......
......@@ -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.
*
......
......@@ -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
......@@ -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);
}
......
......@@ -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);
}
......
......@@ -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);
}
......
......@@ -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;
......
......@@ -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;
}
......
......@@ -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;
}
......
......@@ -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;
......
......@@ -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');
......
......@@ -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');
......
......@@ -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;
}
......
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