diff --git a/docs/en/reference/platforms.rst b/docs/en/reference/platforms.rst index 3bbbfd86345..5ea986f816d 100644 --- a/docs/en/reference/platforms.rst +++ b/docs/en/reference/platforms.rst @@ -53,6 +53,7 @@ PostgreSQL ^^^^^^^^^^ - ``PostgreSqlPlatform`` for all versions. +- ``PostgreSQL91Platform`` for version 9.1 and above. - ``PostgreSQL92Platform`` for version 9.2 and above. SAP Sybase SQL Anywhere diff --git a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php index 0491e0a3841..2758a87883d 100644 --- a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -2317,7 +2317,7 @@ public function getColumnCharsetDeclarationSQL($charset) */ public function getColumnCollationDeclarationSQL($collation) { - return ''; + return $this->supportsColumnCollation() ? 'COLLATE ' . $collation : ''; } /** @@ -2948,6 +2948,16 @@ public function supportsViews() return true; } + /** + * Does this platform support column collation? + * + * @return boolean + */ + public function supportsColumnCollation() + { + return false; + } + /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored datetime value of this platform. diff --git a/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php b/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php index f7e1bad0ef1..58814ff963e 100644 --- a/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php +++ b/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php @@ -228,6 +228,116 @@ public function getDropDatabaseSQL($name) return 'DROP DATABASE ' . $name; } + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $index => $definition) { + $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); + } + } + + // add all indexes + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach($options['indexes'] as $index => $definition) { + $queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition); + } + } + + // attach all primary keys + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_unique(array_values($options['primary'])); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $query = 'CREATE '; + + if (!empty($options['temporary'])) { + $query .= 'TEMPORARY '; + } + + $query .= 'TABLE ' . $tableName . ' (' . $queryFields . ') '; + $query .= $this->buildTableOptions($options); + $query .= $this->buildPartitionOptions($options); + + $sql[] = $query; + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); + } + } + + return $sql; + } + + /** + * Build SQL for table options + * + * @param array $options + * + * @return string + */ + private function buildTableOptions(array $options) + { + if (isset($options['table_options'])) { + return $options['table_options']; + } + + $tableOptions = array(); + + // Collate + if ( ! isset($options['collate'])) { + $options['collate'] = 'utf8_unicode_ci'; + } + + $tableOptions[] = sprintf('COLLATE %s', $options['collate']); + + // Engine + if ( ! isset($options['engine'])) { + $options['engine'] = 'InnoDB'; + } + + $tableOptions[] = sprintf('ENGINE = %s', $options['engine']); + + // Auto increment + if (isset($options['auto_increment'])) { + $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']); + } + + // Comment + if (isset($options['comment'])) { + $comment = trim($options['comment'], " '"); + + $tableOptions[] = sprintf("COMMENT = '%s' ", str_replace("'", "''", $comment)); + } + + // Row format + if (isset($options['row_format'])) { + $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']); + } + + return implode(' ', $tableOptions); + } + + /** + * Build SQL for partition options. + * + * @param array $options + * + * @return string + */ + private function buildPartitionOptions(array $options) + { + return (isset($options['partition_options'])) + ? ' ' . $options['partition_options'] + : ''; + } + /** * {@inheritDoc} */ @@ -264,7 +374,7 @@ public function getListTableColumnsSQL($table, $database = null) } return "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT, IS_NULLABLE, IS_AUTO_INCREMENT, CHARACTER_MAXIMUM_LENGTH, COLUMN_DEFAULT," . - " NUMERIC_PRECISION, NUMERIC_SCALE" . + " NUMERIC_PRECISION, NUMERIC_SCALE, COLLATION_NAME" . " FROM DATA_DICTIONARY.COLUMNS" . " WHERE TABLE_SCHEMA=" . $database . " AND TABLE_NAME = '" . $table . "'"; } @@ -333,6 +443,14 @@ public function supportsViews() return false; } + /** + * {@inheritdoc} + */ + public function supportsColumnCollation() + { + return true; + } + /** * {@inheritDoc} */ diff --git a/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL91Keywords.php b/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL91Keywords.php new file mode 100644 index 00000000000..4e5c632d358 --- /dev/null +++ b/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL91Keywords.php @@ -0,0 +1,148 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * PostgreSQL 9.1 reserved keywords list. + * + * @author Martin Hasoň + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class PostgreSQL91Keywords extends PostgreSQLKeywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'PostgreSQL91'; + } + + /** + * {@inheritdoc} + * + * @link http://www.postgresql.org/docs/9.1/static/sql-keywords-appendix.html + */ + protected function getKeywords() + { + return array( + 'ALL', + 'ANALYSE', + 'ANALYZE', + 'AND', + 'ANY', + 'ARRAY', + 'AS', + 'ASC', + 'ASYMMETRIC', + 'AUTHORIZATION', + 'BINARY', + 'BOTH', + 'CASE', + 'CAST', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'CONCURRENTLY', + 'CONSTRAINT', + 'CREATE', + 'CROSS', + 'CURRENT_CATALOG', + 'CURRENT_DATE', + 'CURRENT_ROLE', + 'CURRENT_SCHEMA', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'DEFAULT', + 'DEFERRABLE', + 'DESC', + 'DISTINCT', + 'DO', + 'ELSE', + 'END', + 'EXCEPT', + 'FALSE', + 'FETCH', + 'FOR', + 'FOREIGN', + 'FREEZE', + 'FROM', + 'FULL', + 'GRANT', + 'GROUP', + 'HAVING', + 'ILIKE', + 'IN', + 'INITIALLY', + 'INNER', + 'INTERSECT', + 'INTO', + 'IS', + 'ISNULL', + 'JOIN', + 'LEADING', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'NATURAL', + 'NOT', + 'NOTNULL', + 'NULL', + 'OFFSET', + 'ON', + 'ONLY', + 'OR', + 'ORDER', + 'OUTER', + 'OVER', + 'OVERLAPS', + 'PLACING', + 'PRIMARY', + 'REFERENCES', + 'RETURNING', + 'RIGHT', + 'SELECT', + 'SESSION_USER', + 'SIMILAR', + 'SOME', + 'SYMMETRIC', + 'TABLE', + 'THEN', + 'TO', + 'TRAILING', + 'TRUE', + 'UNION', + 'UNIQUE', + 'USER', + 'USING', + 'VARIADIC', + 'VERBOSE', + 'WHEN', + 'WHERE', + 'WINDOW', + 'WITH', + ); + } +} diff --git a/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL92Keywords.php b/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL92Keywords.php index f1f2c4ad73f..7180dfd9834 100644 --- a/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL92Keywords.php +++ b/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL92Keywords.php @@ -26,7 +26,7 @@ * @link www.doctrine-project.org * @since 2.5 */ -class PostgreSQL92Keywords extends PostgreSQLKeywords +class PostgreSQL92Keywords extends PostgreSQL91Keywords { /** * {@inheritdoc} @@ -43,106 +43,8 @@ public function getName() */ protected function getKeywords() { - return array( - 'ALL', - 'ANALYSE', - 'ANALYZE', - 'AND', - 'ANY', - 'ARRAY', - 'AS', - 'ASC', - 'ASYMMETRIC', - 'AUTHORIZATION', - 'BINARY', - 'BOTH', - 'CASE', - 'CAST', - 'CHECK', - 'COLLATE', + return array_merge(parent::getKeywords(), array( 'COLLATION', - 'COLUMN', - 'CONCURRENTLY', - 'CONSTRAINT', - 'CREATE', - 'CROSS', - 'CURRENT_CATALOG', - 'CURRENT_DATE', - 'CURRENT_ROLE', - 'CURRENT_SCHEMA', - 'CURRENT_TIME', - 'CURRENT_TIMESTAMP', - 'CURRENT_USER', - 'DEFAULT', - 'DEFERRABLE', - 'DESC', - 'DISTINCT', - 'DO', - 'ELSE', - 'END', - 'EXCEPT', - 'FALSE', - 'FETCH', - 'FOR', - 'FOREIGN', - 'FREEZE', - 'FROM', - 'FULL', - 'GRANT', - 'GROUP', - 'HAVING', - 'ILIKE', - 'IN', - 'INITIALLY', - 'INNER', - 'INTERSECT', - 'INTO', - 'IS', - 'ISNULL', - 'JOIN', - 'LEADING', - 'LEFT', - 'LIKE', - 'LIMIT', - 'LOCALTIME', - 'LOCALTIMESTAMP', - 'NATURAL', - 'NOT', - 'NOTNULL', - 'NULL', - 'OFFSET', - 'ON', - 'ONLY', - 'OR', - 'ORDER', - 'OUTER', - 'OVER', - 'OVERLAPS', - 'PLACING', - 'PRIMARY', - 'REFERENCES', - 'RETURNING', - 'RIGHT', - 'SELECT', - 'SESSION_USER', - 'SIMILAR', - 'SOME', - 'SYMMETRIC', - 'TABLE', - 'THEN', - 'TO', - 'TRAILING', - 'TRUE', - 'UNION', - 'UNIQUE', - 'USER', - 'USING', - 'VARIADIC', - 'VERBOSE', - 'WHEN', - 'WHERE', - 'WINDOW', - 'WITH', - ); + )); } } diff --git a/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php b/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php index 60865c4574a..0f054832696 100644 --- a/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php @@ -334,6 +334,8 @@ public function getBooleanTypeDeclarationSQL(array $field) * Obtain DBMS specific SQL code portion needed to set the COLLATION * of a field declaration to be used in statements like CREATE TABLE. * + * @deprecated Deprecated since version 2.5, Use {@link self::getColumnCollationDeclarationSQL()} instead. + * * @param string $collation name of the collation * * @return string DBMS specific SQL code portion needed to set the COLLATION @@ -341,7 +343,7 @@ public function getBooleanTypeDeclarationSQL(array $field) */ public function getCollationFieldDeclaration($collation) { - return 'COLLATE ' . $collation; + return $this->getColumnCollationDeclarationSQL($collation); } /** @@ -376,6 +378,11 @@ public function supportsInlineColumnComments() /** * {@inheritDoc} */ + public function supportsColumnCollation() + { + return true; + } + public function getListTablesSQL() { return "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"; @@ -387,13 +394,15 @@ public function getListTablesSQL() public function getListTableColumnsSQL($table, $database = null) { if ($database) { - return "SELECT COLUMN_NAME AS Field, COLUMN_TYPE AS Type, IS_NULLABLE AS `Null`, ". - "COLUMN_KEY AS `Key`, COLUMN_DEFAULT AS `Default`, EXTRA AS Extra, COLUMN_COMMENT AS Comment, " . - "CHARACTER_SET_NAME AS CharacterSet, COLLATION_NAME AS CollationName ". - "FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '" . $database . "' AND TABLE_NAME = '" . $table . "'"; + $database = "'" . $database . "'"; + } else { + $database = 'DATABASE()'; } - return 'DESCRIBE ' . $table; + return "SELECT COLUMN_NAME AS Field, COLUMN_TYPE AS Type, IS_NULLABLE AS `Null`, ". + "COLUMN_KEY AS `Key`, COLUMN_DEFAULT AS `Default`, EXTRA AS Extra, COLUMN_COMMENT AS Comment, " . + "CHARACTER_SET_NAME AS CharacterSet, COLLATION_NAME AS Collation ". + "FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = " . $database . " AND TABLE_NAME = '" . $table . "'"; } /** diff --git a/lib/Doctrine/DBAL/Platforms/PostgreSQL91Platform.php b/lib/Doctrine/DBAL/Platforms/PostgreSQL91Platform.php new file mode 100644 index 00000000000..b14aa7301ed --- /dev/null +++ b/lib/Doctrine/DBAL/Platforms/PostgreSQL91Platform.php @@ -0,0 +1,65 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +/** + * Provides the behavior, features and SQL dialect of the PostgreSQL 9.1 database platform. + * + * @author Martin Hasoň + * @link www.doctrine-project.org + * @since 2.5 + */ +class PostgreSQL91Platform extends PostgreSqlPlatform +{ + /** + * {@inheritDoc} + */ + public function supportsColumnCollation() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\PostgreSQL91Keywords'; + } + + /** + * {@inheritDoc} + */ + public function getColumnCollationDeclarationSQL($collation) + { + return 'COLLATE ' . $this->quoteSingleIdentifier($collation); + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + $sql = parent::getListTableColumnsSQL($table, $database); + $parts = explode('AS complete_type,', $sql, 2); + + return $parts[0].'AS complete_type, (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,'.$parts[1]; + } +} diff --git a/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php b/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php index 0e69c2a7220..dcb5ebe9c7f 100644 --- a/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php +++ b/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php @@ -26,7 +26,7 @@ * @link www.doctrine-project.org * @since 2.5 */ -class PostgreSQL92Platform extends PostgreSqlPlatform +class PostgreSQL92Platform extends PostgreSQL91Platform { /** * {@inheritdoc} diff --git a/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php b/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php index eb8ba421b62..328a91e3db4 100644 --- a/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php @@ -139,6 +139,14 @@ public function getDefaultSchemaName() return 'dbo'; } + /** + * {@inheritDoc} + */ + public function supportsColumnCollation() + { + return true; + } + /** * {@inheritDoc} */ @@ -1438,14 +1446,6 @@ public function getDefaultValueDeclarationSQL($field) return " DEFAULT '" . $field['default'] . "'"; } - /** - * {@inheritdoc} - */ - public function getColumnCollationDeclarationSQL($collation) - { - return 'COLLATE ' . $collation; - } - /** * {@inheritdoc} * @@ -1456,8 +1456,8 @@ public function getColumnDeclarationSQL($name, array $field) if (isset($field['columnDefinition'])) { $columnDef = $this->getCustomTypeDeclarationSQL($field); } else { - $collation = (isset($field['collate']) && $field['collate']) ? - ' ' . $this->getColumnCollationDeclarationSQL($field['collate']) : ''; + $collation = (isset($field['collation']) && $field['collation']) ? + ' ' . $this->getColumnCollationDeclarationSQL($field['collation']) : ''; $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : ''; diff --git a/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php b/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php index 04c47f45afa..d1091a7413b 100644 --- a/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php +++ b/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php @@ -478,6 +478,14 @@ public function supportsIdentityColumns() return true; } + /** + * {@inheritDoc} + */ + public function supportsColumnCollation() + { + return true; + } + /** * {@inheritDoc} */ diff --git a/lib/Doctrine/DBAL/Schema/Comparator.php b/lib/Doctrine/DBAL/Schema/Comparator.php index 58c214428ae..8381c991dfb 100644 --- a/lib/Doctrine/DBAL/Schema/Comparator.php +++ b/lib/Doctrine/DBAL/Schema/Comparator.php @@ -19,6 +19,8 @@ namespace Doctrine\DBAL\Schema; +use Doctrine\DBAL\Types; + /** * Compares two Schemas and return an instance of SchemaDiff. * @@ -365,82 +367,72 @@ public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint */ public function diffColumn(Column $column1, Column $column2) { + $properties1 = $column1->toArray(); + $properties2 = $column2->toArray(); + $changedProperties = array(); - if ($column1->getType() != $column2->getType()) { - $changedProperties[] = 'type'; - } - if ($column1->getNotnull() != $column2->getNotnull()) { - $changedProperties[] = 'notnull'; + foreach (array('type', 'notnull', 'unsigned', 'autoincrement') as $property) { + if ($properties1[$property] != $properties2[$property]) { + $changedProperties[] = $property; + } } - - $column1Default = $column1->getDefault(); - $column2Default = $column2->getDefault(); - - if ($column1Default != $column2Default || + + if ($properties1['default'] != $properties2['default'] || // Null values need to be checked additionally as they tell whether to create or drop a default value. // null != 0, null != false, null != '' etc. This affects platform's table alteration SQL generation. - (null === $column1Default && null !== $column2Default) || - (null === $column2Default && null !== $column1Default) + (null === $properties1['default'] && null !== $properties2['default']) || + (null === $properties2['default'] && null !== $properties1['default']) ) { $changedProperties[] = 'default'; } - if ($column1->getUnsigned() != $column2->getUnsigned()) { - $changedProperties[] = 'unsigned'; - } - - $column1Type = $column1->getType(); - - if ($column1Type instanceof \Doctrine\DBAL\Types\StringType || - $column1Type instanceof \Doctrine\DBAL\Types\BinaryType - ) { + if ($properties1['type'] instanceof Types\StringType || $properties1['type'] instanceof Types\BinaryType) { // check if value of length is set at all, default value assumed otherwise. - $length1 = $column1->getLength() ?: 255; - $length2 = $column2->getLength() ?: 255; + $length1 = $properties1['length'] ?: 255; + $length2 = $properties2['length'] ?: 255; if ($length1 != $length2) { $changedProperties[] = 'length'; } - if ($column1->getFixed() != $column2->getFixed()) { + if ($properties1['fixed'] != $properties2['fixed']) { $changedProperties[] = 'fixed'; } - } - - if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) { - if (($column1->getPrecision()?:10) != ($column2->getPrecision()?:10)) { + } elseif ($properties1['type'] instanceof Types\DecimalType) { + if (($properties1['precision'] ?: 10) != ($properties2['precision'] ?: 10)) { $changedProperties[] = 'precision'; } - if ($column1->getScale() != $column2->getScale()) { + if ($properties1['scale'] != $properties2['scale']) { $changedProperties[] = 'scale'; } } - if ($column1->getAutoincrement() != $column2->getAutoincrement()) { - $changedProperties[] = 'autoincrement'; - } - // only allow to delete comment if its set to '' not to null. - if ($column1->getComment() !== null && $column1->getComment() != $column2->getComment()) { + if ($properties1['comment'] !== null && $properties1['comment'] != $properties2['comment']) { $changedProperties[] = 'comment'; } - $options1 = $column1->getCustomSchemaOptions(); - $options2 = $column2->getCustomSchemaOptions(); - - $commonKeys = array_keys(array_intersect_key($options1, $options2)); + $customOptions1 = $column1->getCustomSchemaOptions(); + $customOptions2 = $column2->getCustomSchemaOptions(); - foreach ($commonKeys as $key) { - if ($options1[$key] !== $options2[$key]) { + foreach (array_merge(array_keys($customOptions1), array_keys($customOptions2)) as $key) { + if ( ! array_key_exists($key, $properties1) || ! array_key_exists($key, $properties2)) { + $changedProperties[] = $key; + } elseif ($properties1[$key] !== $properties2[$key]) { $changedProperties[] = $key; } } - $diffKeys = array_keys(array_diff_key($options1, $options2) + array_diff_key($options2, $options1)); + $platformOptions1 = $column1->getPlatformOptions(); + $platformOptions2 = $column2->getPlatformOptions(); - $changedProperties = array_merge($changedProperties, $diffKeys); + foreach (array_keys(array_intersect_key($platformOptions1, $platformOptions2)) as $key) { + if ($properties1[$key] !== $properties2[$key]) { + $changedProperties[] = $key; + } + } - return $changedProperties; + return array_unique($changedProperties); } /** diff --git a/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php b/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php index fa978983775..1e4f3a7e888 100644 --- a/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php @@ -19,6 +19,8 @@ namespace Doctrine\DBAL\Schema; +use Doctrine\DBAL\Types\Type; + /** * Schema manager for the Drizzle RDBMS. * @@ -31,7 +33,6 @@ class DrizzleSchemaManager extends AbstractSchemaManager */ protected function _getPortableTableColumnDefinition($tableColumn) { - $tableName = $tableColumn['COLUMN_NAME']; $dbType = strtolower($tableColumn['DATA_TYPE']); $type = $this->_platform->getDoctrineTypeMapping($dbType); @@ -48,7 +49,13 @@ protected function _getPortableTableColumnDefinition($tableColumn) 'comment' => (isset($tableColumn['COLUMN_COMMENT']) ? $tableColumn['COLUMN_COMMENT'] : null), ); - return new Column($tableName, \Doctrine\DBAL\Types\Type::getType($type), $options); + $column = new Column($tableColumn['COLUMN_NAME'], Type::getType($type), $options); + + if ( ! empty($tableColumn['COLLATION_NAME'])) { + $column->setPlatformOption('collation', $tableColumn['COLLATION_NAME']); + } + + return $column; } /** diff --git a/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php b/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php index f18df6e6bc9..25dc9693bb3 100644 --- a/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php @@ -19,6 +19,8 @@ namespace Doctrine\DBAL\Schema; +use Doctrine\DBAL\Types\Type; + /** * Schema manager for the MySql RDBMS. * @@ -168,7 +170,7 @@ protected function _getPortableTableColumnDefinition($tableColumn) 'scale' => null, 'precision' => null, 'autoincrement' => (bool) (strpos($tableColumn['extra'], 'auto_increment') !== false), - 'comment' => (isset($tableColumn['comment'])) ? $tableColumn['comment'] : null + 'comment' => isset($tableColumn['comment']) ? $tableColumn['comment'] : null, ); if ($scale !== null && $precision !== null) { @@ -176,7 +178,13 @@ protected function _getPortableTableColumnDefinition($tableColumn) $options['precision'] = $precision; } - return new Column($tableColumn['field'], \Doctrine\DBAL\Types\Type::getType($type), $options); + $column = new Column($tableColumn['field'], Type::getType($type), $options); + + if (isset($tableColumn['collation'])) { + $column->setPlatformOption('collation', $tableColumn['collation']); + } + + return $column; } /** diff --git a/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php b/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php index a98ae9d4bfa..088d66b5dd0 100644 --- a/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php @@ -19,6 +19,8 @@ namespace Doctrine\DBAL\Schema; +use Doctrine\DBAL\Types\Type; + /** * PostgreSQL Schema Manager. * @@ -402,6 +404,12 @@ protected function _getPortableTableColumnDefinition($tableColumn) 'comment' => $tableColumn['comment'], ); - return new Column($tableColumn['field'], \Doctrine\DBAL\Types\Type::getType($type), $options); + $column = new Column($tableColumn['field'], Type::getType($type), $options); + + if (isset($tableColumn['collation']) && !empty($tableColumn['collation'])) { + $column->setPlatformOption('collation', $tableColumn['collation']); + } + + return $column; } } diff --git a/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php b/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php index be61cf4ed26..b227c0a9345 100644 --- a/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php @@ -99,12 +99,11 @@ protected function _getPortableTableColumnDefinition($tableColumn) 'comment' => $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, ); - $platformOptions = array( - 'collate' => $tableColumn['collation'] == 'NULL' ? null : $tableColumn['collation'] - ); - $column = new Column($tableColumn['name'], Type::getType($type), $options); - $column->setPlatformOptions($platformOptions); + + if (isset($tableColumn['collation']) && $tableColumn['collation'] !== 'NULL') { + $column->setPlatformOption('collation', $tableColumn['collation']); + } return $column; } diff --git a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php index 88f0e487d47..f33163dfee2 100644 --- a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php @@ -20,6 +20,8 @@ namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Types\StringType; +use Doctrine\DBAL\Types\TextType; /** * Sqlite SchemaManager. @@ -217,8 +219,11 @@ protected function _getPortableTableIndexDefinition($tableIndex) protected function _getPortableTableColumnList($table, $database, $tableColumns) { $list = parent::_getPortableTableColumnList($table, $database, $tableColumns); + + // find column with autoincrement $autoincrementColumn = null; $autoincrementCount = 0; + foreach ($tableColumns as $tableColumn) { if ('0' != $tableColumn['pk']) { $autoincrementCount++; @@ -236,6 +241,18 @@ protected function _getPortableTableColumnList($table, $database, $tableColumns) } } + // inspect column collation + $createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'"); + $createSql = isset($createSql[0]['sql']) ? $createSql[0]['sql'] : ''; + + foreach ($list as $columnName => $column) { + $type = $column->getType(); + + if ($type instanceof StringType || $type instanceof TextType) { + $column->setPlatformOption('collation', $this->parseColumnCollationFromSQL($columnName, $createSql) ?: 'BINARY'); + } + } + return $list; } @@ -393,4 +410,17 @@ private function getTableDiffForAlterForeignKey(ForeignKeyConstraint $foreignKey return $tableDiff; } + + private function parseColumnCollationFromSQL($column, $sql) + { + if (preg_match( + '{(?:'.preg_quote($column).'|'.preg_quote($this->_platform->quoteSingleIdentifier($column)).') + [^,(]+(?:\([^()]+\)[^,]*)? + (?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)* + COLLATE\s+["\']?([^\s,"\')]+)}isx', $sql, $match)) { + return $match[1]; + } + + return false; + } } diff --git a/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php b/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php index 7f8c8fad514..5636250dbdf 100644 --- a/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php +++ b/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php @@ -40,6 +40,7 @@ class ReservedWordsCommand extends Command 'sqlserver2012' => 'Doctrine\DBAL\Platforms\Keywords\SQLServer2012Keywords', 'sqlite' => 'Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords', 'pgsql' => 'Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords', + 'pgsql91' => 'Doctrine\DBAL\Platforms\Keywords\PostgreSQL91Keywords', 'pgsql92' => 'Doctrine\DBAL\Platforms\Keywords\PostgreSQL92Keywords', 'oracle' => 'Doctrine\DBAL\Platforms\Keywords\OracleKeywords', 'db2' => 'Doctrine\DBAL\Platforms\Keywords\DB2Keywords', diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/DrizzleSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/DrizzleSchemaManagerTest.php index 5df688de4b2..5f73bcd9703 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/DrizzleSchemaManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/DrizzleSchemaManagerTest.php @@ -2,6 +2,8 @@ namespace Doctrine\Tests\DBAL\Functional\Schema; +use Doctrine\DBAL\Schema\Table; + require_once __DIR__ . '/../../../TestInit.php'; class DrizzleSchemaManagerTest extends SchemaManagerFunctionalTestCase @@ -10,7 +12,7 @@ public function testListTableWithBinary() { $tableName = 'test_binary_table'; - $table = new \Doctrine\DBAL\Schema\Table($tableName); + $table = new Table($tableName); $table->addColumn('id', 'integer'); $table->addColumn('column_varbinary', 'binary', array()); $table->addColumn('column_binary', 'binary', array('fixed' => true)); @@ -26,4 +28,22 @@ public function testListTableWithBinary() $this->assertInstanceOf('Doctrine\DBAL\Types\BinaryType', $table->getColumn('column_binary')->getType()); $this->assertFalse($table->getColumn('column_binary')->getFixed()); } + + public function testColumnCollation() + { + $table = new Table('test_collation'); + $table->addOption('collate', $collation = 'utf8_unicode_ci'); + $table->addColumn('id', 'integer'); + $table->addColumn('text', 'text'); + $table->addColumn('foo', 'text')->setPlatformOption('collation', 'utf8_swedish_ci'); + $table->addColumn('bar', 'text')->setPlatformOption('collation', 'utf8_general_ci'); + $this->_sm->dropAndCreateTable($table); + + $columns = $this->_sm->listTableColumns('test_collation'); + + $this->assertArrayNotHasKey('collation', $columns['id']->getPlatformOptions()); + $this->assertEquals('utf8_unicode_ci', $columns['text']->getPlatformOption('collation')); + $this->assertEquals('utf8_swedish_ci', $columns['foo']->getPlatformOption('collation')); + $this->assertEquals('utf8_general_ci', $columns['bar']->getPlatformOption('collation')); + } } diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php index 4b96ac950ae..f2f9ce20cb2 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php @@ -153,4 +153,23 @@ public function testDoesNotPropagateDefaultValuesForUnsupportedColumnTypes() $this->assertNull($onlineTable->getColumn('def_blob_null')->getDefault()); $this->assertFalse($onlineTable->getColumn('def_blob_null')->getNotnull()); } + + public function testColumnCollation() + { + $table = new Table('test_collation'); + $table->addOption('collate', $collation = 'latin1_swedish_ci'); + $table->addOption('charset', 'latin1'); + $table->addColumn('id', 'integer'); + $table->addColumn('text', 'text'); + $table->addColumn('foo', 'text')->setPlatformOption('collation', 'latin1_swedish_ci'); + $table->addColumn('bar', 'text')->setPlatformOption('collation', 'utf8_general_ci'); + $this->_sm->dropAndCreateTable($table); + + $columns = $this->_sm->listTableColumns('test_collation'); + + $this->assertArrayNotHasKey('collation', $columns['id']->getPlatformOptions()); + $this->assertEquals('latin1_swedish_ci', $columns['text']->getPlatformOption('collation')); + $this->assertEquals('latin1_swedish_ci', $columns['foo']->getPlatformOption('collation')); + $this->assertEquals('utf8_general_ci', $columns['bar']->getPlatformOption('collation')); + } } diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SQLServerSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SQLServerSchemaManagerTest.php index 792dfc4736f..296b50378f7 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/SQLServerSchemaManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SQLServerSchemaManagerTest.php @@ -10,10 +10,10 @@ class SQLServerSchemaManagerTest extends SchemaManagerFunctionalTestCase { - protected function getPlatformName() - { - return "mssql"; - } + protected function getPlatformName() + { + return "mssql"; + } /** * @group DBAL-255 @@ -35,22 +35,22 @@ public function testDropColumnConstraints() $this->assertEquals(1, count($columns)); } - public function testCollationCharset() + public function testColumnCollation() { - $table = new \Doctrine\DBAL\Schema\Table($tableName = 'test_collation_charset'); + $table = new \Doctrine\DBAL\Schema\Table($tableName = 'test_collation'); $column = $table->addColumn($columnName = 'test', 'string'); $this->_sm->dropAndCreateTable($table); $columns = $this->_sm->listTableColumns($tableName); - $this->assertTrue($columns[$columnName]->hasPlatformOption('collate')); // SQL Server should report a default collation on the column + $this->assertTrue($columns[$columnName]->hasPlatformOption('collation')); // SQL Server should report a default collation on the column - $column->setPlatformOption('collate', $collation = 'Icelandic_CS_AS'); + $column->setPlatformOption('collation', $collation = 'Icelandic_CS_AS'); $this->_sm->dropAndCreateTable($table); $columns = $this->_sm->listTableColumns($tableName); - $this->assertEquals($collation, $columns[$columnName]->getPlatformOption('collate')); + $this->assertEquals($collation, $columns[$columnName]->getPlatformOption('collation')); } public function testDefaultContraints() diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php index 0cf11b24bd2..38bb4b780cc 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php @@ -2,8 +2,6 @@ namespace Doctrine\Tests\DBAL\Functional\Schema; -use Doctrine\DBAL\Schema\ForeignKeyConstraint; - use Doctrine\DBAL\Schema; require_once __DIR__ . '/../../../TestInit.php'; @@ -62,17 +60,34 @@ public function testListForeignKeysFromExistingDatabase() ); $expected = array( - new ForeignKeyConstraint(array('log'), 'log', array(null), 'FK_3', + new Schema\ForeignKeyConstraint(array('log'), 'log', array(null), 'FK_3', array('onUpdate' => 'SET NULL', 'onDelete' => 'NO ACTION', 'deferrable' => false, 'deferred' => false)), - new ForeignKeyConstraint(array('parent'), 'user', array('id'), '1', + new Schema\ForeignKeyConstraint(array('parent'), 'user', array('id'), '1', array('onUpdate' => 'NO ACTION', 'onDelete' => 'CASCADE', 'deferrable' => false, 'deferred' => false)), - new ForeignKeyConstraint(array('page'), 'page', array('key'), 'FK_1', + new Schema\ForeignKeyConstraint(array('page'), 'page', array('key'), 'FK_1', array('onUpdate' => 'NO ACTION', 'onDelete' => 'NO ACTION', 'deferrable' => true, 'deferred' => true)), ); $this->assertEquals($expected, $this->_sm->listTableForeignKeys('user')); } + public function testColumnCollation() + { + $table = new Schema\Table('test_collation'); + $table->addColumn('id', 'integer'); + $table->addColumn('text', 'text'); + $table->addColumn('foo', 'text')->setPlatformOption('collation', 'BINARY'); + $table->addColumn('bar', 'text')->setPlatformOption('collation', 'NOCASE'); + $this->_sm->dropAndCreateTable($table); + + $columns = $this->_sm->listTableColumns('test_collation'); + + $this->assertArrayNotHasKey('collation', $columns['id']->getPlatformOptions()); + $this->assertEquals('BINARY', $columns['text']->getPlatformOption('collation')); + $this->assertEquals('BINARY', $columns['foo']->getPlatformOption('collation')); + $this->assertEquals('NOCASE', $columns['bar']->getPlatformOption('collation')); + } + public function testListTableWithBinary() { $tableName = 'test_binary_table'; diff --git a/tests/Doctrine/Tests/DBAL/Platforms/PostgreSQL91PlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/PostgreSQL91PlatformTest.php new file mode 100644 index 00000000000..5075e312828 --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Platforms/PostgreSQL91PlatformTest.php @@ -0,0 +1,21 @@ +assertEquals( + 'COLLATE "en_US.UTF-8"', + $this->_platform->getColumnCollationDeclarationSQL('en_US.UTF-8') + ); + } +} diff --git a/tests/Doctrine/Tests/DBAL/Schema/ComparatorTest.php b/tests/Doctrine/Tests/DBAL/Schema/ComparatorTest.php index d5ab17c33bb..8b071b606c7 100644 --- a/tests/Doctrine/Tests/DBAL/Schema/ComparatorTest.php +++ b/tests/Doctrine/Tests/DBAL/Schema/ComparatorTest.php @@ -1001,4 +1001,38 @@ public function assertSchemaSequenceChangeCount($diff, $newSequenceCount=0, $cha $this->assertEquals($changeSequenceCount, count($diff->changedSequences), "Expected number of changed sequences is wrong."); $this->assertEquals($removeSequenceCount, count($diff->removedSequences), "Expected number of removed sequences is wrong."); } + + public function testDiffColumnPlatformOptions() + { + $column1 = new Column('foo', Type::getType('string'), array('platformOptions' => array('foo' => 'foo', 'bar' => 'bar'))); + $column2 = new Column('foo', Type::getType('string'), array('platformOptions' => array('foo' => 'foo', 'foobar' => 'foobar'))); + $column3 = new Column('foo', Type::getType('string'), array('platformOptions' => array('foo' => 'foo', 'bar' => 'rab'))); + $column4 = new Column('foo', Type::getType('string')); + + $comparator = new Comparator(); + + $this->assertEquals(array(), $comparator->diffColumn($column1, $column2)); + $this->assertEquals(array(), $comparator->diffColumn($column2, $column1)); + $this->assertEquals(array('bar'), $comparator->diffColumn($column1, $column3)); + $this->assertEquals(array('bar'), $comparator->diffColumn($column3, $column1)); + $this->assertEquals(array(), $comparator->diffColumn($column1, $column4)); + $this->assertEquals(array(), $comparator->diffColumn($column4, $column1)); + } + + public function testComplexDiffColumn() + { + $column1 = new Column('foo', Type::getType('string'), array( + 'platformOptions' => array('foo' => 'foo'), + 'customSchemaOptions' => array('foo' => 'bar'), + )); + + $column2 = new Column('foo', Type::getType('string'), array( + 'platformOptions' => array('foo' => 'bar'), + )); + + $comparator = new Comparator(); + + $this->assertEquals(array(), $comparator->diffColumn($column1, $column2)); + $this->assertEquals(array(), $comparator->diffColumn($column2, $column1)); + } } diff --git a/tests/Doctrine/Tests/DBAL/Schema/SqliteSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Schema/SqliteSchemaManagerTest.php new file mode 100644 index 00000000000..ebb638f7dd8 --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Schema/SqliteSchemaManagerTest.php @@ -0,0 +1,46 @@ +getMockBuilder('Doctrine\DBAL\Connection')->disableOriginalConstructor()->getMock(); + $conn->expects($this->any())->method('getDatabasePlatform')->will($this->returnValue(new SqlitePlatform())); + + $manager = new SqliteSchemaManager($conn); + $ref = new \ReflectionMethod($manager, 'parseColumnCollationFromSQL'); + $ref->setAccessible(true); + + $this->assertEquals($collation, $ref->invoke($manager, $column, $sql)); + } + + public function getDataColumnCollation() + { + return array( + array( + 'RTRIM', 'a', 'CREATE TABLE "a" ("a" text DEFAULT "aa" COLLATE "RTRIM" NOT NULL)' + ), + array( + 'utf-8', 'a', 'CREATE TABLE "a" ("b" text UNIQUE NOT NULL COLLATE NOCASE, "a" text DEFAULT "aa" COLLATE "utf-8" NOT NULL)' + ), + array( + 'NOCASE', 'a', 'CREATE TABLE "a" ("a" text DEFAULT (lower(ltrim(" a") || rtrim("a "))) CHECK ("a") NOT NULL COLLATE NOCASE UNIQUE, "b" text COLLATE RTRIM)' + ), + array( + false, 'a', 'CREATE TABLE "a" ("a" text CHECK ("a") NOT NULL, "b" text COLLATE RTRIM)' + ), + array( + 'RTRIM', 'a"b', 'CREATE TABLE "a" ("a""b" text COLLATE RTRIM)' + ), + ); + } +}