From 746d9bc38f9c6c197be8c387a7195e0caf884033 Mon Sep 17 00:00:00 2001 From: jwage Date: Wed, 27 May 2009 22:14:27 +0000 Subject: [PATCH] [2.0] Adding listTableColumns() support for mysql. --- lib/Doctrine/DBAL/Platforms/MySqlPlatform.php | 7 +- .../DBAL/Schema/AbstractSchemaManager.php | 82 ++++++ .../DBAL/Schema/MySqlSchemaManager.php | 241 +++++++++++++++--- .../DBAL/Schema/SqliteSchemaManager.php | 138 ++++++++++ lib/Doctrine/DBAL/Types/TextType.php | 5 + .../Tests/DBAL/Functional/AllTests.php | 3 +- .../Functional/Schema/MySqlSchemaTest.php | 67 +++++ .../{Schemas => Schema}/SqliteSchemaTest.php | 20 +- .../Doctrine/Tests/DbalFunctionalTestCase.php | 8 + tests/dbproperties.xml.dev | 8 +- 10 files changed, 529 insertions(+), 50 deletions(-) create mode 100644 tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaTest.php rename tests/Doctrine/Tests/DBAL/Functional/{Schemas => Schema}/SqliteSchemaTest.php (56%) create mode 100644 tests/Doctrine/Tests/DbalFunctionalTestCase.php diff --git a/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php b/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php index f3694972f..31e6352aa 100644 --- a/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php @@ -272,7 +272,12 @@ class MySqlPlatform extends AbstractPlatform { return 'SHOW TABLES'; } - + + public function getListTableColumnsSql($table) + { + return 'DESCRIBE ' . $this->quoteIdentifier($table); + } + /** * create a new database * diff --git a/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php b/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php index 41aa5c904..6be1d215f 100644 --- a/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php @@ -21,6 +21,8 @@ namespace Doctrine\DBAL\Schema; +use \Doctrine\DBAL\Types; + /** * Base class for schema managers. Schema managers are used to inspect and/or * modify the database schema/structure. @@ -524,54 +526,134 @@ abstract class AbstractSchemaManager protected function _getPortableDatabasesList($databases) { + foreach ($databases as $key => $value) { + $databases[$key] = $this->_getPortableDatabaseDefinition($value); + } return $databases; } + protected function _getPortableDatabaseDefinition($database) + { + return $database; + } + protected function _getPortableFunctionsList($functions) { + foreach ($functions as $key => $value) { + $functions[$key] = $this->_getPortableFunctionDefinition($value); + } return $functions; } + protected function _getPortableFunctionDefinition($function) + { + return $function; + } + protected function _getPortableTriggersList($triggers) { + foreach ($triggers as $key => $value) { + $triggers[$key] = $this->_getPortableTriggerDefinition($value); + } return $triggers; } + protected function _getPortableTriggerDefinition($trigger) + { + return $trigger; + } + protected function _getPortableSequencesList($sequences) { + foreach ($sequences as $key => $value) { + $sequences[$key] = $this->_getPortableSequenceDefinition($value); + } return $sequences; } + protected function _getPortableSequenceDefinition($sequence) + { + return $sequence; + } + protected function _getPortableTableConstraintsList($tableConstraints) { + foreach ($tableConstraints as $key => $value) { + $tableConstraints[$key] = $this->_getPortableTableConstraintDefinition($value); + } return $tableConstraints; } + protected function _getPortableTableConstraintDefinition($tableConstraint) + { + return $tableConstraint; + } + protected function _getPortableTableColumnList($tableColumns) { + foreach ($tableColumns as $key => $value) { + $tableColumns[$key] = $this->_getPortableTableColumnDefinition($value); + } return $tableColumns; } + protected function _getPortableTableColumnDefinition($tableColumn) + { + return $tableColumn; + } + protected function _getPortableTableIndexesList($tableIndexes) { + foreach ($tableIndexes as $key => $value) { + $tableIndexes[$key] = $this->_getPortableTableIndexDefinition($value); + } return $tableIndexes; } + protected function _getPortableTableIndexDefinition($tableIndex) + { + return $tableIndex; + } + protected function _getPortableTablesList($tables) { + foreach ($tables as $key => $value) { + $tables[$key] = $this->_getPortableTableDefinition($value); + } return $tables; } + protected function _getPortableTableDefinition($table) + { + return $table; + } + protected function _getPortableUsersList($users) { + foreach ($users as $key => $value) { + $users[$key] = $this->_getPortableUserDefinition($value); + } return $users; } + protected function _getPortableUserDefinition($user) + { + return $user; + } + protected function _getPortableViewsList($views) { + foreach ($views as $key => $value) { + $views[$key] = $this->_getPortableViewDefinition($value); + } return $views; } + protected function _getPortableViewDefinition($view) + { + return $view; + } + protected function _executeSql($sql, $method = 'exec') { $result = true; diff --git a/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php b/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php index 71c2554c6..4265b5aa3 100644 --- a/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php @@ -32,6 +32,205 @@ namespace Doctrine\DBAL\Schema; */ class MySqlSchemaManager extends AbstractSchemaManager { + protected function _getPortableTableColumnDefinition($tableColumn) + { + $dbType = strtolower($tableColumn['type']); + $dbType = strtok($dbType, '(), '); + if ($dbType == 'national') { + $dbType = strtok('(), '); + } + if (isset($tableColumn['length'])) { + $length = $tableColumn['length']; + $decimal = ''; + } else { + $length = strtok('(), '); + $decimal = strtok('(), ') ? strtok('(), '):null; + } + $type = array(); + $unsigned = $fixed = null; + + if ( ! isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + $values = null; + $scale = null; + + switch ($dbType) { + case 'tinyint': + $type = 'integer'; + $type = 'boolean'; + if (preg_match('/^(is|has)/', $tableColumn['name'])) { + $type = array_reverse($type); + } + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $length = 1; + break; + case 'smallint': + $type = 'integer'; + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $length = 2; + break; + case 'mediumint': + $type = 'integer'; + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $length = 3; + break; + case 'int': + case 'integer': + $type = 'integer'; + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $length = 4; + break; + case 'bigint': + $type = 'integer'; + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $length = 8; + break; + case 'tinytext': + case 'mediumtext': + case 'longtext': + case 'text': + case 'text': + case 'varchar': + $fixed = false; + case 'string': + case 'char': + $type = 'string'; + if ($length == '1') { + $type = 'boolean'; + if (preg_match('/^(is|has)/', $tableColumn['name'])) { + $type = array_reverse($type); + } + } elseif (strstr($dbType, 'text')) { + $type = 'clob'; + if ($decimal == 'binary') { + $type = 'blob'; + } + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'enum': + $type = 'enum'; + preg_match_all('/\'((?:\'\'|[^\'])*)\'/', $tableColumn['type'], $matches); + $length = 0; + $fixed = false; + if (is_array($matches)) { + foreach ($matches[1] as &$value) { + $value = str_replace('\'\'', '\'', $value); + $length = max($length, strlen($value)); + } + if ($length == '1' && count($matches[1]) == 2) { + $type = 'boolean'; + if (preg_match('/^(is|has)/', $tableColumn['name'])) { + $type = array_reverse($type); + } + } + + $values = $matches[1]; + } + $type = 'integer'; + break; + case 'set': + $fixed = false; + $type = 'text'; + $type = 'integer'; + break; + case 'date': + $type = 'date'; + $length = null; + break; + case 'datetime': + case 'timestamp': + $type = 'timestamp'; + $length = null; + break; + case 'time': + $type = 'time'; + $length = null; + break; + case 'float': + case 'double': + case 'real': + $type = 'float'; + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + break; + case 'unknown': + case 'decimal': + if ($decimal !== null) { + $scale = $decimal; + } + case 'numeric': + $type = 'decimal'; + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + case 'binary': + case 'varbinary': + $type = 'blob'; + $length = null; + break; + case 'year': + $type = 'integer'; + $type = 'date'; + $length = null; + break; + case 'bit': + $type = 'bit'; + break; + case 'geometry': + case 'geometrycollection': + case 'point': + case 'multipoint': + case 'linestring': + case 'multilinestring': + case 'polygon': + case 'multipolygon': + $type = 'blob'; + $length = null; + break; + default: + $type = 'string'; + $length = null; + } + + $length = ((int) $length == 0) ? null : (int) $length; + $def = array( + 'type' => $type, + 'length' => $length, + 'unsigned' => (bool) $unsigned, + 'fixed' => (bool) $fixed + ); + if ($values !== null) { + $def['values'] = $values; + } + if ($scale !== null) { + $def['scale'] = $scale; + } + + $values = isset($def['values']) ? $def['values'] : array(); + + $def['type'] = \Doctrine\DBAL\Types\Type::getType($def['type']); + + $column = array( + 'name' => $tableColumn['field'], + 'values' => $values, + 'primary' => (bool) (strtolower($tableColumn['key']) == 'pri'), + 'default' => $tableColumn['default'], + 'notnull' => (bool) ($tableColumn['null'] != 'YES'), + 'autoincrement' => (bool) (strpos($tableColumn['extra'], 'auto_increment') !== false), + ); + + $column = array_merge($column, $def); + + return $column; + } + /** * lists all database sequences * @@ -118,48 +317,6 @@ class MySqlSchemaManager extends AbstractSchemaManager return $result; } - /** - * lists table constraints - * - * @param string $table database table name - * @return array - * @override - */ - public function listTableColumns($table) - { - $sql = 'DESCRIBE ' . $this->_conn->quoteIdentifier($table, true); - $result = $this->_conn->fetchAssoc($sql); - - $description = array(); - $columns = array(); - foreach ($result as $key => $val) { - - $val = array_change_key_case($val, CASE_LOWER); - - $decl = $this->_conn->getDatabasePlatform()->getPortableDeclaration($val); - - $values = isset($decl['values']) ? $decl['values'] : array(); - - $description = array( - 'name' => $val['field'], - 'type' => $decl['type'][0], - 'alltypes' => $decl['type'], - 'ntype' => $val['type'], - 'length' => $decl['length'], - 'fixed' => $decl['fixed'], - 'unsigned' => $decl['unsigned'], - 'values' => $values, - 'primary' => (strtolower($val['key']) == 'pri'), - 'default' => $val['default'], - 'notnull' => (bool) ($val['null'] != 'YES'), - 'autoincrement' => (bool) (strpos($val['extra'], 'auto_increment') !== false), - ); - $columns[$val['field']] = $description; - } - - return $columns; - } - /** * lists table constraints * diff --git a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php index 186ee772c..57dc51fd4 100644 --- a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php @@ -33,4 +33,142 @@ namespace Doctrine\DBAL\Schema; */ class SqliteSchemaManager extends AbstractSchemaManager { + protected function _getPortableTableColumnDefinition($tableColumn) + { + $e = explode('(', $tableColumn['type']); + $tableColumn['type'] = $e[0]; + if (isset($e[1])) { + $length = trim($e[1], ')'); + $tableColumn['length'] = $length; + } + + $dbType = strtolower($tableColumn['type']); + + $length = isset($tableColumn['length']) ? $tableColumn['length'] : null; + $unsigned = (boolean) isset($tableColumn['unsigned']) ? $tableColumn['unsigned'] : false; + $fixed = false; + $type = null; + $default = $tableColumn['dflt_value']; + if ($default == 'NULL') { + $default = null; + } + $notnull = (bool) $tableColumn['notnull']; + + if ( ! isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + switch ($dbType) { + case 'boolean': + $type = 'boolean'; + break; + case 'tinyint': + if (preg_match('/^(is|has)/', $tableColumn['name'])) { + $type = 'boolean'; + } else { + $type = 'integer'; + } + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $length = 1; + break; + case 'smallint': + $type = 'integer'; + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $length = 2; + break; + case 'mediumint': + $type = 'integer'; + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $length = 3; + break; + case 'int': + case 'integer': + case 'serial': + $type = 'integer'; + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $length = 4; + break; + case 'bigint': + case 'bigserial': + $type = 'integer'; + $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $length = 8; + break; + case 'clob': + case 'tinytext': + case 'mediumtext': + case 'longtext': + case 'text': + case 'varchar': + case 'varchar2': + case 'nvarchar': + case 'ntext': + case 'image': + case 'nchar': + $fixed = false; + case 'char': + $type = 'string'; + if ($length == '1') { + $type = 'boolean'; + if (preg_match('/^(is|has)/', $tableColumn['name'])) { + $type = array_reverse($type); + } + } elseif (strstr($dbType, 'text')) { + $type = 'clob'; + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'date': + $type = 'date'; + $length = null; + break; + case 'datetime': + case 'timestamp': + $type = 'timestamp'; + $length = null; + break; + case 'time': + $type = 'time'; + $length = null; + break; + case 'float': + case 'double': + case 'real': + $type = 'float'; + $length = null; + break; + case 'decimal': + case 'numeric': + $type = 'decimal'; + $length = null; + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + $type = 'blob'; + $length = null; + break; + case 'year': + $type = 'date'; + $length = null; + break; + default: + $type = 'string'; + $length = null; + } + + $type = \Doctrine\DBAL\Types\Type::getType($type); + + return array('name' => $tableColumn['name'], + 'primary' => (bool) $tableColumn['pk'], + 'type' => $type, + 'length' => $length, + 'unsigned' => (bool) $unsigned, + 'fixed' => $fixed, + 'notnull' => $notnull, + 'default' => $default); + } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Types/TextType.php b/lib/Doctrine/DBAL/Types/TextType.php index 92ac7b1bb..fab3e4ad5 100644 --- a/lib/Doctrine/DBAL/Types/TextType.php +++ b/lib/Doctrine/DBAL/Types/TextType.php @@ -14,4 +14,9 @@ class TextType extends Type { return $platform->getClobDeclarationSql($fieldDeclaration); } + + public function getName() + { + return 'text'; + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/DBAL/Functional/AllTests.php b/tests/Doctrine/Tests/DBAL/Functional/AllTests.php index 8f5ee0699..3fb4f9b23 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/AllTests.php +++ b/tests/Doctrine/Tests/DBAL/Functional/AllTests.php @@ -21,7 +21,8 @@ class AllTests { $suite = new \Doctrine\Tests\DbalFunctionalTestSuite('Doctrine Dbal Functional'); - $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schemas\SqliteSchemaTest'); + $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\SqliteSchemaTest'); + $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\MySqlSchemaTest'); return $suite; } diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaTest.php new file mode 100644 index 000000000..d848e0dcd --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaTest.php @@ -0,0 +1,67 @@ +_conn = TestUtil::getConnection(); + if ($this->_conn->getDatabasePlatform()->getName() !== 'mysql') + { + $this->markTestSkipped('The MySqlSchemaTest requires the use of mysql'); + } + $this->_sm = new Schema\MySqlSchemaManager($this->_conn); + } + + public function testListTableColumns() + { + $columns = array( + 'id' => array( + 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'autoincrement' => true, + 'primary' => true, + 'notnull' => true + ), + 'test' => array( + 'type' => new \Doctrine\DBAL\Types\StringType, + 'length' => 255 + ) + ); + + $options = array(); + + try { + $this->_sm->dropTable('list_tables_test'); + } catch (\Exception $e) {} + + $this->_sm->createTable('list_tables_test', $columns, $options); + + $columns = $this->_sm->listTableColumns('list_tables_test'); + + $this->assertEquals($columns[0]['name'], 'id'); + $this->assertEquals($columns[0]['primary'], true); + $this->assertEquals(get_class($columns[0]['type']), 'Doctrine\DBAL\Types\IntegerType'); + $this->assertEquals($columns[0]['length'], 4); + $this->assertEquals($columns[0]['unsigned'], false); + $this->assertEquals($columns[0]['fixed'], false); + $this->assertEquals($columns[0]['notnull'], true); + $this->assertEquals($columns[0]['default'], null); + + $this->assertEquals($columns[1]['name'], 'test'); + $this->assertEquals($columns[1]['primary'], false); + $this->assertEquals(get_class($columns[1]['type']), 'Doctrine\DBAL\Types\StringType'); + $this->assertEquals($columns[1]['length'], 255); + $this->assertEquals($columns[1]['unsigned'], false); + $this->assertEquals($columns[1]['fixed'], false); + $this->assertEquals($columns[1]['notnull'], false); + $this->assertEquals($columns[1]['default'], null); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schemas/SqliteSchemaTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaTest.php similarity index 56% rename from tests/Doctrine/Tests/DBAL/Functional/Schemas/SqliteSchemaTest.php rename to tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaTest.php index 6a9858e33..f7ff36808 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schemas/SqliteSchemaTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaTest.php @@ -1,6 +1,6 @@ _conn = TestUtil::getConnection(); if ($this->_conn->getDatabasePlatform()->getName() !== 'sqlite') { - $this->markTestSkipped('The SqliteSchemaTest requires the use of the pdo_sqlite'); + $this->markTestSkipped('The SqliteSchemaTest requires the use of sqlite'); } $this->_sm = new Schema\SqliteSchemaManager($this->_conn); } @@ -41,7 +41,23 @@ class SqliteSchemaTest extends \Doctrine\Tests\DbalFunctionalTestCase $this->_sm->createTable('list_tables_test', $columns, $options); $columns = $this->_sm->listTableColumns('list_tables_test'); + $this->assertEquals($columns[0]['name'], 'id'); + $this->assertEquals($columns[0]['primary'], true); + $this->assertEquals(get_class($columns[0]['type']), 'Doctrine\DBAL\Types\IntegerType'); + $this->assertEquals($columns[0]['length'], 4); + $this->assertEquals($columns[0]['unsigned'], false); + $this->assertEquals($columns[0]['fixed'], false); + $this->assertEquals($columns[0]['notnull'], true); + $this->assertEquals($columns[0]['default'], null); + $this->assertEquals($columns[1]['name'], 'test'); + $this->assertEquals($columns[1]['primary'], false); + $this->assertEquals(get_class($columns[1]['type']), 'Doctrine\DBAL\Types\StringType'); + $this->assertEquals($columns[1]['length'], 255); + $this->assertEquals($columns[1]['unsigned'], false); + $this->assertEquals($columns[1]['fixed'], false); + $this->assertEquals($columns[1]['notnull'], false); + $this->assertEquals($columns[1]['default'], null); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/DbalFunctionalTestCase.php b/tests/Doctrine/Tests/DbalFunctionalTestCase.php new file mode 100644 index 000000000..3a3210e7c --- /dev/null +++ b/tests/Doctrine/Tests/DbalFunctionalTestCase.php @@ -0,0 +1,8 @@ + - + - - - + + + \ No newline at end of file