diff --git a/lib/Cake/Database/Dialect/PostgresDialectTrait.php b/lib/Cake/Database/Dialect/PostgresDialectTrait.php index 4dbd12f4573..8a9363e7cdf 100644 --- a/lib/Cake/Database/Dialect/PostgresDialectTrait.php +++ b/lib/Cake/Database/Dialect/PostgresDialectTrait.php @@ -147,144 +147,6 @@ protected function _transformFunctionExpression(FunctionExpression $expression) } } -/** - * Get the SQL to list the tables - * - * @param array $config The connection configuration to use for - * getting tables from. - * @return array An array of (sql, params) to execute. - */ - public function listTablesSql($config) { - $sql = "SELECT table_name as name FROM INFORMATION_SCHEMA.tables WHERE table_schema = ? ORDER BY name"; - $schema = empty($config['schema']) ? 'public' : $config['schema']; - return [$sql, [$schema]]; - } - -/** - * Get the SQL to describe a table in Postgres. - * - * @param string $table The table name to describe - * @param array $config The connection configuration to use - * @return array An array of (sql, params) to execute. - */ - public function describeTableSql($table, $config) { - $sql = - "SELECT DISTINCT table_schema AS schema, column_name AS name, data_type AS type, - is_nullable AS null, column_default AS default, ordinal_position AS position, - character_maximum_length AS char_length, character_octet_length AS oct_length, - d.description as comment, i.indisprimary = 't' as pk - FROM information_schema.columns c - INNER JOIN pg_catalog.pg_namespace ns ON (ns.nspname = table_schema) - INNER JOIN pg_catalog.pg_class cl ON (cl.relnamespace = ns.oid AND cl.relname = table_name) - LEFT JOIN pg_catalog.pg_index i ON (i.indrelid = cl.oid AND i.indkey[0] = c.ordinal_position) - LEFT JOIN pg_catalog.pg_description d on (cl.oid = d.objoid AND d.objsubid = c.ordinal_position) - WHERE table_name = ? AND table_schema = ? ORDER BY position"; - $schema = empty($config['schema']) ? 'public' : $config['schema']; - return [$sql, [$table, $schema]]; - } - -/** - * Convert a column definition to the abstract types. - * - * The returned type will be a type that - * Cake\Database\Type can handle. - * - * @param string $column The column type + length - * @return array List of (type, length) - */ - public function convertColumn($column) { - $col = strtolower($column); - if (in_array($col, array('date', 'time', 'boolean'))) { - return [$col, null]; - } - if (strpos($col, 'timestamp') !== false) { - return ['datetime', null]; - } - if ($col === 'serial' || $col === 'integer') { - return ['integer', 10]; - } - if ($col === 'bigserial' || $col === 'bigint') { - return ['biginteger', 20]; - } - if ($col === 'smallint') { - return ['integer', 5]; - } - if ($col === 'inet') { - return ['string', 39]; - } - if ($col === 'uuid') { - return ['string', 36]; - } - if (strpos($col, 'char') !== false) { - return ['string', null]; - } - if (strpos($col, 'text') !== false) { - return ['text', null]; - } - if ($col === 'bytea') { - return ['binary', null]; - } - if ($col === 'real' || strpos($col, 'double') !== false) { - return ['float', null]; - } - if ( - strpos($col, 'numeric') !== false || - strpos($col, 'money') !== false || - strpos($col, 'decimal') !== false - ) { - return ['decimal', null]; - } - return ['text', null]; - } - -/** - * Get additional column meta data used in schema reflections. - * - * @return array - */ - public function extraSchemaColumns() { - return [ - 'comment' => [ - 'column' => 'comment', - ] - ]; - } - -/** - * Convert field description results into abstract schema fields. - * - * @return array An array of with the key/values of schema data. - */ - public function convertFieldDescription($row, $fieldParams = []) { - list($type, $length) = $this->convertColumn($row['type']); - - if ($type === 'boolean') { - if ($row['default'] === 'true') { - $row['default'] = 1; - } - if ($row['default'] === 'false') { - $row['default'] = 0; - } - } - - $schema = []; - $schema[$row['name']] = [ - 'type' => $type, - 'null' => $row['null'] === 'YES' ? true : false, - 'default' => $row['default'], - 'length' => $row['char_length'] ?: $length, - ]; - if (!empty($row['pk'])) { - $schema[$row['name']]['key'] = 'primary'; - } - foreach ($fieldParams as $key => $metadata) { - if (!empty($row[$metadata['column']])) { - $schema[$row['name']][$key] = $row[$metadata['column']]; - } - } - return $schema; - } - /** * Get the schema dialect. * diff --git a/lib/Cake/Database/Schema/Dialect/Postgres.php b/lib/Cake/Database/Schema/Dialect/Postgres.php index e69de29bb2d..cab20813840 100644 --- a/lib/Cake/Database/Schema/Dialect/Postgres.php +++ b/lib/Cake/Database/Schema/Dialect/Postgres.php @@ -0,0 +1,170 @@ +_driver = $driver; + } + +/** + * Get the SQL to list the tables + * + * @param array $config The connection configuration to use for + * getting tables from. + * @return array An array of (sql, params) to execute. + */ + public function listTablesSql($config) { + $sql = "SELECT table_name as name FROM INFORMATION_SCHEMA.tables WHERE table_schema = ? ORDER BY name"; + $schema = empty($config['schema']) ? 'public' : $config['schema']; + return [$sql, [$schema]]; + } + +/** + * Get the SQL to describe a table in Postgres. + * + * @param string $table The table name to describe + * @param array $config The connection configuration to use + * @return array An array of (sql, params) to execute. + */ + public function describeTableSql($table, $config) { + $sql = + "SELECT DISTINCT table_schema AS schema, column_name AS name, data_type AS type, + is_nullable AS null, column_default AS default, ordinal_position AS position, + character_maximum_length AS char_length, character_octet_length AS oct_length, + d.description as comment, i.indisprimary = 't' as pk + FROM information_schema.columns c + INNER JOIN pg_catalog.pg_namespace ns ON (ns.nspname = table_schema) + INNER JOIN pg_catalog.pg_class cl ON (cl.relnamespace = ns.oid AND cl.relname = table_name) + LEFT JOIN pg_catalog.pg_index i ON (i.indrelid = cl.oid AND i.indkey[0] = c.ordinal_position) + LEFT JOIN pg_catalog.pg_description d on (cl.oid = d.objoid AND d.objsubid = c.ordinal_position) + WHERE table_name = ? AND table_schema = ? ORDER BY position"; + $schema = empty($config['schema']) ? 'public' : $config['schema']; + return [$sql, [$table, $schema]]; + } + +/** + * Convert a column definition to the abstract types. + * + * The returned type will be a type that + * Cake\Database\Type can handle. + * + * @param string $column The column type + length + * @return array List of (type, length) + */ + public function convertColumn($column) { + $col = strtolower($column); + if (in_array($col, array('date', 'time', 'boolean'))) { + return [$col, null]; + } + if (strpos($col, 'timestamp') !== false) { + return ['datetime', null]; + } + if ($col === 'serial' || $col === 'integer') { + return ['integer', 10]; + } + if ($col === 'bigserial' || $col === 'bigint') { + return ['biginteger', 20]; + } + if ($col === 'smallint') { + return ['integer', 5]; + } + if ($col === 'inet') { + return ['string', 39]; + } + if ($col === 'uuid') { + return ['string', 36]; + } + if (strpos($col, 'char') !== false) { + return ['string', null]; + } + if (strpos($col, 'text') !== false) { + return ['text', null]; + } + if ($col === 'bytea') { + return ['binary', null]; + } + if ($col === 'real' || strpos($col, 'double') !== false) { + return ['float', null]; + } + if ( + strpos($col, 'numeric') !== false || + strpos($col, 'money') !== false || + strpos($col, 'decimal') !== false + ) { + return ['decimal', null]; + } + return ['text', null]; + } + +/** + * Get additional column meta data used in schema reflections. + * + * @return array + */ + public function extraSchemaColumns() { + return [ + 'comment' => [ + 'column' => 'comment', + ] + ]; + } + +/** + * Convert field description results into abstract schema fields. + * + * @return array An array of with the key/values of schema data. + */ + public function convertFieldDescription($row, $fieldParams = []) { + list($type, $length) = $this->convertColumn($row['type']); + + if ($type === 'boolean') { + if ($row['default'] === 'true') { + $row['default'] = 1; + } + if ($row['default'] === 'false') { + $row['default'] = 0; + } + } + + $schema = []; + $schema[$row['name']] = [ + 'type' => $type, + 'null' => $row['null'] === 'YES' ? true : false, + 'default' => $row['default'], + 'length' => $row['char_length'] ?: $length, + ]; + if (!empty($row['pk'])) { + $schema[$row['name']]['key'] = 'primary'; + } + foreach ($fieldParams as $key => $metadata) { + if (!empty($row[$metadata['column']])) { + $schema[$row['name']][$key] = $row[$metadata['column']]; + } + } + return $schema; + } + +} diff --git a/lib/Cake/Test/TestCase/Database/Driver/PostgresTest.php b/lib/Cake/Test/TestCase/Database/Driver/PostgresTest.php index 755cf979b08..5a11f962dcb 100644 --- a/lib/Cake/Test/TestCase/Database/Driver/PostgresTest.php +++ b/lib/Cake/Test/TestCase/Database/Driver/PostgresTest.php @@ -129,233 +129,4 @@ public function testConnectionConfigCustom() { $driver->connect($config); } -/** - * Helper method for skipping tests that need a real connection. - * - * @return void - */ - protected function _needsConnection() { - $config = Configure::read('Datasource.test'); - $this->skipIf(strpos($config['datasource'], 'Postgres') === false, 'Not using Postgres for test config'); - } - -/** - * Helper method for testing methods. - * - * @return void - */ - protected function _createTables($connection) { - $this->_needsConnection(); - $connection->execute('DROP TABLE IF EXISTS articles'); - $connection->execute('DROP TABLE IF EXISTS authors'); - - $table = <<execute($table); - - $table = <<execute($table); - $connection->execute('COMMENT ON COLUMN "articles"."title" IS \'a title\''); - } - -/** - * Dataprovider for column testing - * - * @return array - */ - public static function columnProvider() { - return [ - [ - 'TIMESTAMP', - ['datetime', null] - ], - [ - 'TIMESTAMP WITHOUT TIME ZONE', - ['datetime', null] - ], - [ - 'DATE', - ['date', null] - ], - [ - 'TIME', - ['time', null] - ], - [ - 'SMALLINT', - ['integer', 5] - ], - [ - 'INTEGER', - ['integer', 10] - ], - [ - 'SERIAL', - ['integer', 10] - ], - [ - 'BIGINT', - ['biginteger', 20] - ], - [ - 'NUMERIC', - ['decimal', null] - ], - [ - 'DECIMAL(10,2)', - ['decimal', null] - ], - [ - 'MONEY', - ['decimal', null] - ], - [ - 'VARCHAR', - ['string', null] - ], - [ - 'CHARACTER VARYING', - ['string', null] - ], - [ - 'CHAR', - ['string', null] - ], - [ - 'UUID', - ['string', 36] - ], - [ - 'CHARACTER', - ['string', null] - ], - [ - 'INET', - ['string', 39] - ], - [ - 'TEXT', - ['text', null] - ], - [ - 'BYTEA', - ['binary', null] - ], - [ - 'REAL', - ['float', null] - ], - [ - 'DOUBLE PRECISION', - ['float', null] - ], - [ - 'BIGSERIAL', - ['biginteger', 20] - ], - ]; - } - -/** - * Test parsing Postgres column types. - * - * @dataProvider columnProvider - * @return void - */ - public function testConvertColumnType($input, $expected) { - $driver = new Postgres(); - $this->assertEquals($expected, $driver->convertColumn($input)); - } - -/** - * Test listing tables with Postgres - * - * @return void - */ - public function testListTables() { - $connection = new Connection(Configure::read('Datasource.test')); - $this->_createTables($connection); - - $result = $connection->listTables(); - $this->assertInternalType('array', $result); - $this->assertCount(2, $result); - $this->assertEquals('articles', $result[0]); - $this->assertEquals('authors', $result[1]); - } - -/** - * Test describing a table with Postgres - * - * @return void - */ - public function testDescribeTable() { - $connection = new Connection(Configure::read('Datasource.test')); - $this->_createTables($connection); - - $result = $connection->describe('articles'); - $expected = [ - 'id' => [ - 'type' => 'biginteger', - 'null' => false, - 'default' => null, - 'length' => 20, - 'key' => 'primary', - ], - 'title' => [ - 'type' => 'string', - 'null' => true, - 'default' => null, - 'length' => 20, - 'comment' => 'a title', - ], - 'body' => [ - 'type' => 'text', - 'null' => true, - 'default' => null, - 'length' => null, - ], - 'author_id' => [ - 'type' => 'integer', - 'null' => false, - 'default' => null, - 'length' => 10, - ], - 'published' => [ - 'type' => 'boolean', - 'null' => true, - 'default' => 0, - 'length' => null, - ], - 'views' => [ - 'type' => 'integer', - 'null' => true, - 'default' => 0, - 'length' => 5, - ], - 'created' => [ - 'type' => 'datetime', - 'null' => true, - 'default' => null, - 'length' => null, - ], - ]; - $this->assertEquals($expected, $result); - } - } diff --git a/lib/Cake/Test/TestCase/Database/Schema/Dialect/PostgresTest.php b/lib/Cake/Test/TestCase/Database/Schema/Dialect/PostgresTest.php new file mode 100644 index 00000000000..0a2501b61aa --- /dev/null +++ b/lib/Cake/Test/TestCase/Database/Schema/Dialect/PostgresTest.php @@ -0,0 +1,290 @@ +skipIf(strpos($config['datasource'], 'Postgres') === false, 'Not using Postgres for test config'); + } + +/** + * Helper method for testing methods. + * + * @return void + */ + protected function _createTables($connection) { + $this->_needsConnection(); + $connection->execute('DROP TABLE IF EXISTS articles'); + $connection->execute('DROP TABLE IF EXISTS authors'); + + $table = <<execute($table); + + $table = <<execute($table); + $connection->execute('COMMENT ON COLUMN "articles"."title" IS \'a title\''); + } + +/** + * Dataprovider for column testing + * + * @return array + */ + public static function columnProvider() { + return [ + [ + 'TIMESTAMP', + ['datetime', null] + ], + [ + 'TIMESTAMP WITHOUT TIME ZONE', + ['datetime', null] + ], + [ + 'DATE', + ['date', null] + ], + [ + 'TIME', + ['time', null] + ], + [ + 'SMALLINT', + ['integer', 5] + ], + [ + 'INTEGER', + ['integer', 10] + ], + [ + 'SERIAL', + ['integer', 10] + ], + [ + 'BIGINT', + ['biginteger', 20] + ], + [ + 'NUMERIC', + ['decimal', null] + ], + [ + 'DECIMAL(10,2)', + ['decimal', null] + ], + [ + 'MONEY', + ['decimal', null] + ], + [ + 'VARCHAR', + ['string', null] + ], + [ + 'CHARACTER VARYING', + ['string', null] + ], + [ + 'CHAR', + ['string', null] + ], + [ + 'UUID', + ['string', 36] + ], + [ + 'CHARACTER', + ['string', null] + ], + [ + 'INET', + ['string', 39] + ], + [ + 'TEXT', + ['text', null] + ], + [ + 'BYTEA', + ['binary', null] + ], + [ + 'REAL', + ['float', null] + ], + [ + 'DOUBLE PRECISION', + ['float', null] + ], + [ + 'BIGSERIAL', + ['biginteger', 20] + ], + ]; + } + +/** + * Test parsing Postgres column types. + * + * @dataProvider columnProvider + * @return void + */ + public function testConvertColumnType($input, $expected) { + $driver = $this->getMock('Cake\Database\Driver\Postgres'); + $dialect = new Postgres($driver); + $this->assertEquals($expected, $dialect->convertColumn($input)); + } + +/** + * Test listing tables with Postgres + * + * @return void + */ + public function testListTables() { + $connection = new Connection(Configure::read('Datasource.test')); + $this->_createTables($connection); + + $schema = new SchemaCollection($connection); + $result = $schema->listTables(); + $this->assertInternalType('array', $result); + $this->assertCount(2, $result); + $this->assertEquals('articles', $result[0]); + $this->assertEquals('authors', $result[1]); + } + +/** + * Test describing a table with Postgres + * + * @return void + */ + public function testDescribeTable() { + $connection = new Connection(Configure::read('Datasource.test')); + $this->_createTables($connection); + + $schema = new SchemaCollection($connection); + $result = $schema->describe('articles'); + $expected = [ + 'id' => [ + 'type' => 'biginteger', + 'null' => false, + 'default' => null, + 'length' => 20, + 'fixed' => null, + 'comment' => null, + 'collate' => null, + 'charset' => null, + ], + 'title' => [ + 'type' => 'string', + 'null' => true, + 'default' => null, + 'length' => 20, + 'comment' => 'a title', + 'fixed' => null, + 'collate' => null, + 'charset' => null, + ], + 'body' => [ + 'type' => 'text', + 'null' => true, + 'default' => null, + 'length' => null, + 'fixed' => null, + 'comment' => null, + 'collate' => null, + 'charset' => null, + ], + 'author_id' => [ + 'type' => 'integer', + 'null' => false, + 'default' => null, + 'length' => 10, + 'fixed' => null, + 'comment' => null, + 'collate' => null, + 'charset' => null, + ], + 'published' => [ + 'type' => 'boolean', + 'null' => true, + 'default' => 0, + 'length' => null, + 'fixed' => null, + 'comment' => null, + 'collate' => null, + 'charset' => null, + ], + 'views' => [ + 'type' => 'integer', + 'null' => true, + 'default' => 0, + 'length' => 5, + 'fixed' => null, + 'comment' => null, + 'collate' => null, + 'charset' => null, + ], + 'created' => [ + 'type' => 'datetime', + 'null' => true, + 'default' => null, + 'length' => null, + 'fixed' => null, + 'comment' => null, + 'collate' => null, + 'charset' => null, + ], + ]; + foreach ($expected as $field => $definition) { + $this->assertEquals($definition, $result->column($field)); + } + } +}