3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
20 namespace Doctrine\DBAL\Platforms
;
22 use Doctrine\DBAL\DBALException
;
23 use Doctrine\DBAL\Schema\Column
;
24 use Doctrine\DBAL\Schema\ColumnDiff
;
25 use Doctrine\DBAL\Schema\Identifier
;
26 use Doctrine\DBAL\Schema\Index
;
27 use Doctrine\DBAL\Schema\Table
;
28 use Doctrine\DBAL\Schema\TableDiff
;
29 use Doctrine\DBAL\Types\Type
;
31 class DB2Platform
extends AbstractPlatform
36 public function getBinaryMaxLength()
44 public function getBinaryDefaultLength()
52 public function getBlobTypeDeclarationSQL(array $field)
54 // todo blob(n) with $field['length'];
61 public function initializeDoctrineTypeMappings()
63 $this->doctrineTypeMapping
= array(
64 'smallint' => 'smallint',
66 'integer' => 'integer',
69 'varchar' => 'string',
70 'character' => 'string',
71 'varbinary' => 'binary',
75 'decimal' => 'decimal',
78 'timestamp' => 'datetime',
85 public function isCommentedDoctrineType(Type
$doctrineType)
87 if ($doctrineType->getName() === Type
::BOOLEAN
) {
88 // We require a commented boolean type in order to distinguish between boolean and smallint
89 // as both (have to) map to the same native type.
93 return parent
::isCommentedDoctrineType($doctrineType);
99 protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
101 return $fixed ?
($length ?
'CHAR(' . $length . ')' : 'CHAR(255)')
102 : ($length ?
'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
108 protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
110 return $fixed ?
'BINARY(' . ($length ?
: 255) . ')' : 'VARBINARY(' . ($length ?
: 255) . ')';
116 public function getClobTypeDeclarationSQL(array $field)
118 // todo clob(n) with $field['length'];
125 public function getName()
133 public function getBooleanTypeDeclarationSQL(array $columnDef)
141 public function getIntegerTypeDeclarationSQL(array $columnDef)
143 return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
149 public function getBigIntTypeDeclarationSQL(array $columnDef)
151 return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
157 public function getSmallIntTypeDeclarationSQL(array $columnDef)
159 return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
165 protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
168 if ( ! empty($columnDef['autoincrement'])) {
169 $autoinc = ' GENERATED BY DEFAULT AS IDENTITY';
178 public function getBitAndComparisonExpression($value1, $value2)
180 return 'BITAND(' . $value1 . ', ' . $value2 . ')';
186 public function getBitOrComparisonExpression($value1, $value2)
188 return 'BITOR(' . $value1 . ', ' . $value2 . ')';
194 protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
197 case self
::DATE_INTERVAL_UNIT_WEEK
:
199 $unit = self
::DATE_INTERVAL_UNIT_DAY
;
202 case self
::DATE_INTERVAL_UNIT_QUARTER
:
204 $unit = self
::DATE_INTERVAL_UNIT_MONTH
;
208 return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit;
214 public function getDateDiffExpression($date1, $date2)
216 return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')';
222 public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
224 if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) {
225 return "TIMESTAMP(0) WITH DEFAULT";
228 return 'TIMESTAMP(0)';
234 public function getDateTypeDeclarationSQL(array $fieldDeclaration)
242 public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
250 public function getTruncateTableSQL($tableName, $cascade = false)
252 $tableIdentifier = new Identifier($tableName);
254 return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this) . ' IMMEDIATE';
258 * This code fragment is originally from the Zend_Db_Adapter_Db2 class, but has been edited.
260 * @license New BSD License
262 * @param string $table
263 * @param string $database
267 public function getListTableColumnsSQL($table, $database = null)
269 $table = $this->quoteStringLiteral($table);
271 // We do the funky subquery and join syscat.columns.default this crazy way because
272 // as of db2 v10, the column is CLOB(64k) and the distinct operator won't allow a CLOB,
273 // it wants shorter stuff like a varchar.
289 tc.type AS tabconsttype,
290 c.remarks AS comment,
293 WHEN c.generated = 'D' THEN 1
296 FROM syscat.columns c
297 LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc
298 ON (k.tabschema = tc.tabschema
299 AND k.tabname = tc.tabname
301 ON (c.tabschema = k.tabschema
302 AND c.tabname = k.tabname
303 AND c.colname = k.colname)
304 WHERE UPPER(c.tabname) = UPPER(" . $table . ")
307 JOIN syscat.columns cols
308 ON subq.tabschema = cols.tabschema
309 AND subq.tabname = cols.tabname
310 AND subq.colno = cols.colno
318 public function getListTablesSQL()
320 return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'";
326 public function getListViewsSQL($database)
328 return "SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS";
334 public function getListTableIndexesSQL($table, $currentDatabase = null)
336 $table = $this->quoteStringLiteral($table);
338 return "SELECT idx.INDNAME AS key_name,
339 idxcol.COLNAME AS column_name,
341 WHEN idx.UNIQUERULE = 'P' THEN 1
345 WHEN idx.UNIQUERULE = 'D' THEN 1
348 FROM SYSCAT.INDEXES AS idx
349 JOIN SYSCAT.INDEXCOLUSE AS idxcol
350 ON idx.INDSCHEMA = idxcol.INDSCHEMA AND idx.INDNAME = idxcol.INDNAME
351 WHERE idx.TABNAME = UPPER(" . $table . ")
352 ORDER BY idxcol.COLSEQ ASC";
358 public function getListTableForeignKeysSQL($table)
360 $table = $this->quoteStringLiteral($table);
362 return "SELECT fkcol.COLNAME AS local_column,
363 fk.REFTABNAME AS foreign_table,
364 pkcol.COLNAME AS foreign_column,
365 fk.CONSTNAME AS index_name,
367 WHEN fk.UPDATERULE = 'R' THEN 'RESTRICT'
371 WHEN fk.DELETERULE = 'C' THEN 'CASCADE'
372 WHEN fk.DELETERULE = 'N' THEN 'SET NULL'
373 WHEN fk.DELETERULE = 'R' THEN 'RESTRICT'
376 FROM SYSCAT.REFERENCES AS fk
377 JOIN SYSCAT.KEYCOLUSE AS fkcol
378 ON fk.CONSTNAME = fkcol.CONSTNAME
379 AND fk.TABSCHEMA = fkcol.TABSCHEMA
380 AND fk.TABNAME = fkcol.TABNAME
381 JOIN SYSCAT.KEYCOLUSE AS pkcol
382 ON fk.REFKEYNAME = pkcol.CONSTNAME
383 AND fk.REFTABSCHEMA = pkcol.TABSCHEMA
384 AND fk.REFTABNAME = pkcol.TABNAME
385 WHERE fk.TABNAME = UPPER(" . $table . ")
386 ORDER BY fkcol.COLSEQ ASC";
392 public function getCreateViewSQL($name, $sql)
394 return "CREATE VIEW ".$name." AS ".$sql;
400 public function getDropViewSQL($name)
402 return "DROP VIEW ".$name;
408 public function getCreateDatabaseSQL($database)
410 return "CREATE DATABASE ".$database;
416 public function getDropDatabaseSQL($database)
418 return "DROP DATABASE " . $database;
424 public function supportsCreateDropDatabase()
432 public function supportsReleaseSavepoints()
440 public function supportsCommentOnStatement()
448 public function getCurrentDateSQL()
450 return 'CURRENT DATE';
456 public function getCurrentTimeSQL()
458 return 'CURRENT TIME';
464 public function getCurrentTimestampSQL()
466 return "CURRENT TIMESTAMP";
472 public function getIndexDeclarationSQL($name, Index
$index)
474 // Index declaration in statements like CREATE TABLE is not supported.
475 throw DBALException
::notSupported(__METHOD__
);
481 protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
484 if (isset($options['indexes'])) {
485 $indexes = $options['indexes'];
487 $options['indexes'] = array();
489 $sqls = parent
::_getCreateTableSQL($tableName, $columns, $options);
491 foreach ($indexes as $definition) {
492 $sqls[] = $this->getCreateIndexSQL($definition, $tableName);
500 public function getAlterTableSQL(TableDiff
$diff)
503 $columnSql = array();
504 $commentsSQL = array();
506 $queryParts = array();
507 foreach ($diff->addedColumns
as $column) {
508 if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
512 $columnDef = $column->toArray();
513 $queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef);
515 // Adding non-nullable columns to a table requires a default value to be specified.
516 if ( ! empty($columnDef['notnull']) &&
517 ! isset($columnDef['default']) &&
518 empty($columnDef['autoincrement'])
520 $queryPart .= ' WITH DEFAULT';
523 $queryParts[] = $queryPart;
525 $comment = $this->getColumnComment($column);
527 if (null !== $comment && '' !== $comment) {
528 $commentsSQL[] = $this->getCommentOnColumnSQL(
529 $diff->getName($this)->getQuotedName($this),
530 $column->getQuotedName($this),
536 foreach ($diff->removedColumns
as $column) {
537 if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
541 $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this);
544 foreach ($diff->changedColumns
as $columnDiff) {
545 if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
549 if ($columnDiff->hasChanged('comment')) {
550 $commentsSQL[] = $this->getCommentOnColumnSQL(
551 $diff->getName($this)->getQuotedName($this),
552 $columnDiff->column
->getQuotedName($this),
553 $this->getColumnComment($columnDiff->column
)
556 if (count($columnDiff->changedProperties
) === 1) {
561 $this->gatherAlterColumnSQL($diff->fromTable
, $columnDiff, $sql, $queryParts);
564 foreach ($diff->renamedColumns
as $oldColumnName => $column) {
565 if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
569 $oldColumnName = new Identifier($oldColumnName);
571 $queryParts[] = 'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .
572 ' TO ' . $column->getQuotedName($this);
577 if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
578 if (count($queryParts) > 0) {
579 $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(" ", $queryParts);
582 // Some table alteration operations require a table reorganization.
583 if ( ! empty($diff->removedColumns
) ||
! empty($diff->changedColumns
)) {
584 $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $diff->getName($this)->getQuotedName($this) . "')";
587 $sql = array_merge($sql, $commentsSQL);
589 if ($diff->newName
!== false) {
590 $sql[] = 'RENAME TABLE ' . $diff->getName($this)->getQuotedName($this) . ' TO ' . $diff->getNewName()->getQuotedName($this);
594 $this->getPreAlterTableIndexForeignKeySQL($diff),
596 $this->getPostAlterTableIndexForeignKeySQL($diff)
600 return array_merge($sql, $tableSql, $columnSql);
604 * Gathers the table alteration SQL for a given column diff.
606 * @param Table $table The table to gather the SQL for.
607 * @param ColumnDiff $columnDiff The column diff to evaluate.
608 * @param array $sql The sequence of table alteration statements to fill.
609 * @param array $queryParts The sequence of column alteration clauses to fill.
611 private function gatherAlterColumnSQL(Table
$table, ColumnDiff
$columnDiff, array &$sql, array &$queryParts)
613 $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff);
615 if (empty($alterColumnClauses)) {
619 // If we have a single column alteration, we can append the clause to the main query.
620 if (count($alterColumnClauses) === 1) {
621 $queryParts[] = current($alterColumnClauses);
626 // We have multiple alterations for the same column,
627 // so we need to trigger a complete ALTER TABLE statement
628 // for each ALTER COLUMN clause.
629 foreach ($alterColumnClauses as $alterColumnClause) {
630 $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ' . $alterColumnClause;
635 * Returns the ALTER COLUMN SQL clauses for altering a column described by the given column diff.
637 * @param ColumnDiff $columnDiff The column diff to evaluate.
641 private function getAlterColumnClausesSQL(ColumnDiff
$columnDiff)
643 $column = $columnDiff->column
->toArray();
645 $alterClause = 'ALTER COLUMN ' . $columnDiff->column
->getQuotedName($this);
647 if ($column['columnDefinition']) {
648 return array($alterClause . ' ' . $column['columnDefinition']);
653 if ($columnDiff->hasChanged('type') ||
654 $columnDiff->hasChanged('length') ||
655 $columnDiff->hasChanged('precision') ||
656 $columnDiff->hasChanged('scale') ||
657 $columnDiff->hasChanged('fixed')
659 $clauses[] = $alterClause . ' SET DATA TYPE ' . $column['type']->getSQLDeclaration($column, $this);
662 if ($columnDiff->hasChanged('notnull')) {
663 $clauses[] = $column['notnull'] ?
$alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL';
666 if ($columnDiff->hasChanged('default')) {
667 if (isset($column['default'])) {
668 $defaultClause = $this->getDefaultValueDeclarationSQL($column);
670 if ($defaultClause) {
671 $clauses[] = $alterClause . ' SET' . $defaultClause;
674 $clauses[] = $alterClause . ' DROP DEFAULT';
684 protected function getPreAlterTableIndexForeignKeySQL(TableDiff
$diff)
687 $table = $diff->getName($this)->getQuotedName($this);
689 foreach ($diff->removedIndexes
as $remKey => $remIndex) {
690 foreach ($diff->addedIndexes
as $addKey => $addIndex) {
691 if ($remIndex->getColumns() == $addIndex->getColumns()) {
692 if ($remIndex->isPrimary()) {
693 $sql[] = 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY';
694 } elseif ($remIndex->isUnique()) {
695 $sql[] = 'ALTER TABLE ' . $table . ' DROP UNIQUE ' . $remIndex->getQuotedName($this);
697 $sql[] = $this->getDropIndexSQL($remIndex, $table);
700 $sql[] = $this->getCreateIndexSQL($addIndex, $table);
702 unset($diff->removedIndexes
[$remKey]);
703 unset($diff->addedIndexes
[$addKey]);
710 $sql = array_merge($sql, parent
::getPreAlterTableIndexForeignKeySQL($diff));
718 protected function getRenameIndexSQL($oldIndexName, Index
$index, $tableName)
720 if (strpos($tableName, '.') !== false) {
721 list($schema) = explode('.', $tableName);
722 $oldIndexName = $schema . '.' . $oldIndexName;
725 return array('RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this));
731 public function getDefaultValueDeclarationSQL($field)
733 if ( ! empty($field['autoincrement'])) {
737 if (isset($field['version']) && $field['version']) {
738 if ((string) $field['type'] != "DateTime") {
739 $field['default'] = "1";
743 return parent
::getDefaultValueDeclarationSQL($field);
749 public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName)
751 return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)';
757 public function getCreateTemporaryTableSnippetSQL()
759 return "DECLARE GLOBAL TEMPORARY TABLE";
765 public function getTemporaryTableName($tableName)
767 return "SESSION." . $tableName;
773 protected function doModifyLimitQuery($query, $limit, $offset = null)
778 $where[] = sprintf('db22.DC_ROWNUM >= %d', $offset +
1);
781 if ($limit !== null) {
782 $where[] = sprintf('db22.DC_ROWNUM <= %d', $offset +
$limit);
789 // Todo OVER() needs ORDER BY data!
791 'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s',
793 implode(' AND ', $where)
800 public function getLocateExpression($str, $substr, $startPos = false)
802 if ($startPos == false) {
803 return 'LOCATE(' . $substr . ', ' . $str . ')';
806 return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')';
812 public function getSubstringExpression($value, $from, $length = null)
814 if ($length === null) {
815 return 'SUBSTR(' . $value . ', ' . $from . ')';
818 return 'SUBSTR(' . $value . ', ' . $from . ', ' . $length . ')';
824 public function supportsIdentityColumns()
832 public function prefersIdentityColumns()
840 * DB2 returns all column names in SQL result sets in uppercase.
842 public function getSQLResultCasing($column)
844 return strtoupper($column);
850 public function getForUpdateSQL()
852 return ' WITH RR USE AND KEEP UPDATE LOCKS';
858 public function getDummySelectSQL()
860 return 'SELECT 1 FROM sysibm.sysdummy1';
866 * DB2 supports savepoints, but they work semantically different than on other vendor platforms.
868 * TODO: We have to investigate how to get DB2 up and running with savepoints.
870 public function supportsSavepoints()
878 protected function getReservedKeywordsClass()
880 return 'Doctrine\DBAL\Platforms\Keywords\DB2Keywords';