From c30eae62d25677c095aebc6819c5c95573d3606d Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Wed, 29 May 2013 00:01:02 +0200 Subject: [PATCH 01/19] Added initial unit testing files for the pgsql driver. --- framework/yii/db/pgsql/Schema.php | 36 ++++ tests/unit/data/config.php | 6 + tests/unit/data/postgres.sql | 218 +++++++-------------- .../db/pgsql/PostgreSQLActiveRecordTest.php | 14 ++ .../db/pgsql/PostgreSQLConnectionTest.php | 18 ++ 5 files changed, 143 insertions(+), 149 deletions(-) create mode 100644 framework/yii/db/pgsql/Schema.php create mode 100644 tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php create mode 100644 tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php new file mode 100644 index 0000000..4ccf33a --- /dev/null +++ b/framework/yii/db/pgsql/Schema.php @@ -0,0 +1,36 @@ + + * @since 2.0 + */ +class Schema extends \yii\db\Schema +{ + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return TableSchema|null driver dependent table metadata. Null if the table does not exist. + */ + public function loadTableSchema($name) + { + $table = new TableSchema(); + $this->resolveTableNames($table, $name); + $this->findPrimaryKeys($table); + if ($this->findColumns($table)) { + $this->findForeignKeys($table); + return $table; + } + } +} \ No newline at end of file diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index 88c8127..cb612d4 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -20,5 +20,11 @@ return array( 'password' => '', 'fixture' => __DIR__ . '/mssql.sql', ), + 'pgsql' => array( + 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432', + 'username' => 'postgres', + 'password' => 'postgres', + 'fixture' => __DIR__ . '/postgres.sql', + ) ) ); diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index e46b284..ce75206 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -1,165 +1,85 @@ /** * This is the database schema for testing PostgreSQL support of yii Active Record. - * To test this feature, you need to create a database named 'yii' on 'localhost' - * and create an account 'test/test' which owns this test database. + * To test this feature, you need to create a database named 'yiitest' on 'localhost' + * and create an account 'postgres/postgres' which owns this test database. */ -CREATE SCHEMA test; -CREATE TABLE test.users -( - id SERIAL NOT NULL PRIMARY KEY, - username VARCHAR(128) NOT NULL, - password VARCHAR(128) NOT NULL, - email VARCHAR(128) NOT NULL +DROP TABLE IF EXISTS tbl_order_item CASCADE; +DROP TABLE IF EXISTS tbl_item CASCADE; +DROP TABLE IF EXISTS tbl_order CASCADE; +DROP TABLE IF EXISTS tbl_category CASCADE; +DROP TABLE IF EXISTS tbl_customer CASCADE; +DROP TABLE IF EXISTS tbl_type CASCADE; + +CREATE TABLE tbl_customer ( + id serial not null primary key, + email varchar(128) NOT NULL, + name varchar(128) NOT NULL, + address text, + status integer DEFAULT 0 ); -INSERT INTO test.users (username, password, email) VALUES ('user1','pass1','email1'); -INSERT INTO test.users (username, password, email) VALUES ('user2','pass2','email2'); -INSERT INTO test.users (username, password, email) VALUES ('user3','pass3','email3'); - -CREATE TABLE test.user_friends -( - id INTEGER NOT NULL, - friend INTEGER NOT NULL, - PRIMARY KEY (id, friend), - CONSTRAINT FK_user_id FOREIGN KEY (id) - REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT FK_friend_id FOREIGN KEY (friend) - REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT -); - -INSERT INTO test.user_friends VALUES (1,2); -INSERT INTO test.user_friends VALUES (1,3); -INSERT INTO test.user_friends VALUES (2,3); - -CREATE TABLE test.profiles -( - id SERIAL NOT NULL PRIMARY KEY, - first_name VARCHAR(128) NOT NULL, - last_name VARCHAR(128) NOT NULL, - user_id INTEGER NOT NULL, - CONSTRAINT FK_profile_user FOREIGN KEY (user_id) - REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT +CREATE TABLE tbl_category ( + id serial not null primary key, + name varchar(128) NOT NULL ); -INSERT INTO test.profiles (first_name, last_name, user_id) VALUES ('first 1','last 1',1); -INSERT INTO test.profiles (first_name, last_name, user_id) VALUES ('first 2','last 2',2); - -CREATE TABLE test.posts -( - id SERIAL NOT NULL PRIMARY KEY, - title VARCHAR(128) NOT NULL, - create_time TIMESTAMP NOT NULL, - author_id INTEGER NOT NULL, - content TEXT, - CONSTRAINT FK_post_author FOREIGN KEY (author_id) - REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT +CREATE TABLE tbl_item ( + id serial not null primary key, + name varchar(128) NOT NULL, + category_id integer NOT NULL references tbl_category(id) on UPDATE CASCADE on DELETE CASCADE ); -INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 1',TIMESTAMP '2004-10-19 10:23:54',1,'content 1'); -INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 2',TIMESTAMP '2004-10-19 10:23:54',2,'content 2'); -INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 3',TIMESTAMP '2004-10-19 10:23:54',2,'content 3'); -INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 4',TIMESTAMP '2004-10-19 10:23:54',2,'content 4'); -INSERT INTO test.posts (title, create_time, author_id, content) VALUES ('post 5',TIMESTAMP '2004-10-19 10:23:54',3,'content 5'); - -CREATE TABLE test.comments -( - id SERIAL NOT NULL PRIMARY KEY, - content TEXT NOT NULL, - post_id INTEGER NOT NULL, - author_id INTEGER NOT NULL, - CONSTRAINT FK_post_comment FOREIGN KEY (post_id) - REFERENCES test.posts (id) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT FK_user_comment FOREIGN KEY (author_id) - REFERENCES test.users (id) ON DELETE CASCADE ON UPDATE RESTRICT +CREATE TABLE tbl_order ( + id serial not null primary key, + customer_id integer NOT NULL references tbl_customer(id) on UPDATE CASCADE on DELETE CASCADE, + create_time integer NOT NULL, + total decimal(10,0) NOT NULL ); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 1',1, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 2',1, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 3',1, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 4',2, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 5',2, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 6',3, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 7',3, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 8',3, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 9',3, 2); -INSERT INTO test.comments (content, post_id, author_id) VALUES ('comment 10',5, 3); - -CREATE TABLE test.categories -( - id SERIAL NOT NULL PRIMARY KEY, - name VARCHAR(128) NOT NULL, - parent_id INTEGER, - CONSTRAINT FK_category_category FOREIGN KEY (parent_id) - REFERENCES test.categories (id) ON DELETE CASCADE ON UPDATE RESTRICT +CREATE TABLE tbl_order_item ( + order_id integer NOT NULL references tbl_order(id) on UPDATE CASCADE on DELETE CASCADE, + item_id integer NOT NULL references tbl_item(id) on UPDATE CASCADE on DELETE CASCADE, + quantity integer NOT NULL, + subtotal decimal(10,0) NOT NULL, + PRIMARY KEY (order_id,item_id) ); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 1',NULL); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 2',NULL); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 3',NULL); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 4',1); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 5',1); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 6',5); -INSERT INTO test.categories (name, parent_id) VALUES ('cat 7',5); - -CREATE TABLE test.post_category -( - category_id INTEGER NOT NULL, - post_id INTEGER NOT NULL, - PRIMARY KEY (category_id, post_id), - CONSTRAINT FK_post_category_post FOREIGN KEY (post_id) - REFERENCES test.posts (id) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT FK_post_category_category FOREIGN KEY (category_id) - REFERENCES test.categories (id) ON DELETE CASCADE ON UPDATE RESTRICT +CREATE TABLE tbl_type ( + int_col integer NOT NULL, + int_col2 integer DEFAULT '1', + char_col char(100) NOT NULL, + char_col2 varchar(100) DEFAULT 'something', + char_col3 text, + float_col double precision NOT NULL, + float_col2 double precision DEFAULT '1.23', + blob_col bytea, + numeric_col decimal(5,2) DEFAULT '33.22', + time timestamp NOT NULL DEFAULT '2002-01-01 00:00:00', + bool_col smallint NOT NULL, + bool_col2 smallint DEFAULT '1' ); -INSERT INTO test.post_category (category_id, post_id) VALUES (1,1); -INSERT INTO test.post_category (category_id, post_id) VALUES (2,1); -INSERT INTO test.post_category (category_id, post_id) VALUES (3,1); -INSERT INTO test.post_category (category_id, post_id) VALUES (4,2); -INSERT INTO test.post_category (category_id, post_id) VALUES (1,2); -INSERT INTO test.post_category (category_id, post_id) VALUES (1,3); - -CREATE TABLE test.orders -( - key1 INTEGER NOT NULL, - key2 INTEGER NOT NULL, - name VARCHAR(128), - PRIMARY KEY (key1, key2) -); - -INSERT INTO test.orders (key1,key2,name) VALUES (1,2,'order 12'); -INSERT INTO test.orders (key1,key2,name) VALUES (1,3,'order 13'); -INSERT INTO test.orders (key1,key2,name) VALUES (2,1,'order 21'); -INSERT INTO test.orders (key1,key2,name) VALUES (2,2,'order 22'); - -CREATE TABLE test.items -( - id SERIAL NOT NULL PRIMARY KEY, - name VARCHAR(128), - col1 INTEGER NOT NULL, - col2 INTEGER NOT NULL, - CONSTRAINT FK_order_item FOREIGN KEY (col1,col2) - REFERENCES test.orders (key1,key2) ON DELETE CASCADE ON UPDATE RESTRICT -); - -INSERT INTO test.items (name,col1,col2) VALUES ('item 1',1,2); -INSERT INTO test.items (name,col1,col2) VALUES ('item 2',1,2); -INSERT INTO test.items (name,col1,col2) VALUES ('item 3',1,3); -INSERT INTO test.items (name,col1,col2) VALUES ('item 4',2,2); -INSERT INTO test.items (name,col1,col2) VALUES ('item 5',2,2); - -CREATE TABLE public.yii_types -( - int_col INT NOT NULL, - int_col2 INTEGER DEFAULT 1, - char_col CHAR(100) NOT NULL, - char_col2 VARCHAR(100) DEFAULT 'something', - char_col3 TEXT, - numeric_col NUMERIC(4,3) NOT NULL, - real_col REAL DEFAULT 1.23, - blob_col BYTEA, - time TIMESTAMP, - bool_col BOOL NOT NULL, - bool_col2 BOOLEAN DEFAULT TRUE -); \ No newline at end of file +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user1@example.com', 'user1', 'address1', 1); +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1); +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user3@example.com', 'user3', 'address3', 2); + +INSERT INTO tbl_category (name) VALUES ('Books'); +INSERT INTO tbl_category (name) VALUES ('Movies'); + +INSERT INTO tbl_item (name, category_id) VALUES ('Agile Web Application Development with Yii1.1 and PHP5', 1); +INSERT INTO tbl_item (name, category_id) VALUES ('Yii 1.1 Application Development Cookbook', 1); +INSERT INTO tbl_item (name, category_id) VALUES ('Ice Age', 2); +INSERT INTO tbl_item (name, category_id) VALUES ('Toy Story', 2); +INSERT INTO tbl_item (name, category_id) VALUES ('Cars', 2); + +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (1, 1325282384, 110.0); +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325334482, 33.0); +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325502201, 40.0); + +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); diff --git a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php new file mode 100644 index 0000000..18bfbd8 --- /dev/null +++ b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php @@ -0,0 +1,14 @@ +driverName = 'pgsql'; + parent::setUp(); + } +} diff --git a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php new file mode 100644 index 0000000..64ceb05 --- /dev/null +++ b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php @@ -0,0 +1,18 @@ +driverName = 'pgsql'; + parent::setUp(); + } + + public function testConnection() { + $connection = $this->getConnection(true); + } +} From 4602b575ad46143312a9faa4cedc992e8d24ab36 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Wed, 29 May 2013 17:44:53 +0200 Subject: [PATCH 02/19] Save point: WIP implementing db schema functionality. --- framework/yii/db/Connection.php | 2 + framework/yii/db/pgsql/PDO.php | 61 ++++++++++++++++++++++ framework/yii/db/pgsql/Schema.php | 36 +++++++++++-- tests/unit/data/config.php | 5 +- tests/unit/framework/db/DatabaseTestCase.php | 3 ++ .../db/pgsql/PostgreSQLConnectionTest.php | 55 +++++++++++++++---- 6 files changed, 145 insertions(+), 17 deletions(-) create mode 100644 framework/yii/db/pgsql/PDO.php diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index e14eeb7..014bbaa 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -352,6 +352,8 @@ class Connection extends Component $driver = strtolower(substr($this->dsn, 0, $pos)); if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') { $pdoClass = 'yii\db\mssql\PDO'; + } else if ($driver === 'pgsql') { + $pdoClass = 'yii\db\pgsql\PDO'; } } return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes); diff --git a/framework/yii/db/pgsql/PDO.php b/framework/yii/db/pgsql/PDO.php new file mode 100644 index 0000000..3ff9d01 --- /dev/null +++ b/framework/yii/db/pgsql/PDO.php @@ -0,0 +1,61 @@ + + * @since 2.0 + */ +class PDO extends \PDO { + + /** + * Here we override the default PDO constructor in order to + * find and set the default schema search path. + */ + public function __construct($dsn, $username, $passwd, $options) { + $searchPath = null; + if (is_array($options) && isset($options['search_path'])) { + $matches = null; + if (preg_match("/(\s?)+(\w)+((\s+)?,(\s+)?\w+)*/", $options['search_path'], $matches) === 1) { + $searchPath = $matches[0]; + } + } + parent::__construct($dsn, $username, $passwd, $options); + if (!is_null($searchPath)) { + $this->setSchemaSearchPath($searchPath); + } + } + + /** + * Sets the schema search path of the current users session. + * The syntax of the path is a comma separated string with + * your custom search path at the beginning and the "public" + * schema at the end. + * + * This method automatically adds the "public" schema at the + * end of the search path if it is not provied. + * @param string custom schema search path. defaults to public + */ + public function setSchemaSearchPath($searchPath = 'public') { + $schemas = explode(',', str_replace(' ', '', $searchPath)); + if (end($schemas) !== 'public') { + $schemas[] = 'public'; + } + foreach ($schemas as $k => $item) { + $schemas[$k] = '"' . str_replace(array('"', "'", ';'), '', $item) . '"'; + } + $path = implode(', ', $schemas); + $this->exec('SET search_path TO ' . $path); + } + +} diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 4ccf33a..32044ee 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -1,4 +1,5 @@ * @since 2.0 */ -class Schema extends \yii\db\Schema -{ +class Schema extends \yii\db\Schema { + + /** + * Resolves the table name and schema name (if any). + * @param TableSchema $table the table metadata object + * @param string $name the table name + */ + protected function resolveTableNames($table, $name) { + $parts = explode('.', str_replace('"', '', $name)); + if (isset($parts[1])) { + $table->schemaName = $parts[0]; + $table->name = $parts[1]; + } else { + $table->name = $parts[0]; + } + } + + /** + * Quotes a table name for use in a query. + * A simple table name has no schema prefix. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteSimpleTableName($name) { + return strpos($name, '"') !== false ? $name : '"' . $name . '"'; + } + /** * Loads the metadata for the specified table. * @param string $name table name * @return TableSchema|null driver dependent table metadata. Null if the table does not exist. */ - public function loadTableSchema($name) - { + public function loadTableSchema($name) { $table = new TableSchema(); $this->resolveTableNames($table, $name); $this->findPrimaryKeys($table); @@ -32,5 +57,6 @@ class Schema extends \yii\db\Schema $this->findForeignKeys($table); return $table; } - } + } + } \ No newline at end of file diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index cb612d4..330a464 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -21,9 +21,12 @@ return array( 'fixture' => __DIR__ . '/mssql.sql', ), 'pgsql' => array( - 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432', + 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432;', 'username' => 'postgres', 'password' => 'postgres', + 'attributes' => array( + 'search_path' => 'master,hello' + ), 'fixture' => __DIR__ . '/postgres.sql', ) ) diff --git a/tests/unit/framework/db/DatabaseTestCase.php b/tests/unit/framework/db/DatabaseTestCase.php index 429d961..7ce9bec 100644 --- a/tests/unit/framework/db/DatabaseTestCase.php +++ b/tests/unit/framework/db/DatabaseTestCase.php @@ -37,6 +37,9 @@ abstract class DatabaseTestCase extends TestCase $db->username = $this->database['username']; $db->password = $this->database['password']; } + if (isset($this->database['attributes'])) { + $db->attributes = $this->database['attributes']; + } if ($open) { $db->open(); $lines = explode(';', file_get_contents($this->database['fixture'])); diff --git a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php index 64ceb05..678b197 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php @@ -4,15 +4,48 @@ namespace yiiunit\framework\db\pgsql; use yiiunit\framework\db\ConnectionTest; -class PostgreSQLConnectionTest extends ConnectionTest -{ - public function setUp() - { - $this->driverName = 'pgsql'; - parent::setUp(); - } - - public function testConnection() { - $connection = $this->getConnection(true); - } +class PostgreSQLConnectionTest extends ConnectionTest { + + public function setUp() { + $this->driverName = 'pgsql'; + parent::setUp(); + } + + public function testConnection() { + $connection = $this->getConnection(true); + } + + function testQuoteValue() { + $connection = $this->getConnection(false); + $this->assertEquals(123, $connection->quoteValue(123)); + $this->assertEquals("'string'", $connection->quoteValue('string')); + $this->assertEquals("'It''s interesting'", $connection->quoteValue("It's interesting")); + } + + function testQuoteTableName() + { + $connection = $this->getConnection(false); + $this->assertEquals('"table"', $connection->quoteTableName('table')); + $this->assertEquals('"table"', $connection->quoteTableName('"table"')); + $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema.table')); + $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema."table"')); + $this->assertEquals('"schema"."table"', $connection->quoteTableName('"schema"."table"')); + $this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}')); + $this->assertEquals('(table)', $connection->quoteTableName('(table)')); + } + + function testQuoteColumnName() + { + $connection = $this->getConnection(false); + $this->assertEquals('"column"', $connection->quoteColumnName('column')); + $this->assertEquals('"column"', $connection->quoteColumnName('"column"')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table.column')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table."column"')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('"table"."column"')); + $this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); + $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); + $this->assertEquals('(column)', $connection->quoteColumnName('(column)')); + } + + } From 103621ad053123b86fff31d4d40f903cdfc128a3 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Thu, 30 May 2013 16:53:57 +0200 Subject: [PATCH 03/19] Savepoint: WIP implementing columns --- framework/yii/db/pgsql/PDO.php | 35 ++++++++- framework/yii/db/pgsql/Schema.php | 161 +++++++++++++++++++++++++++++++++++++- tests/unit/data/postgres.sql | 14 +++- 3 files changed, 203 insertions(+), 7 deletions(-) diff --git a/framework/yii/db/pgsql/PDO.php b/framework/yii/db/pgsql/PDO.php index 3ff9d01..75c20a3 100644 --- a/framework/yii/db/pgsql/PDO.php +++ b/framework/yii/db/pgsql/PDO.php @@ -18,16 +18,33 @@ namespace yii\db\pgsql; */ class PDO extends \PDO { + const OPT_SEARCH_PATH = 'search_path'; + const OPT_DEFAULT_SCHEMA = 'default_schema'; + const DEFAULT_SCHEMA = 'public'; + + private $_currentDatabase = null; + /** * Here we override the default PDO constructor in order to * find and set the default schema search path. */ public function __construct($dsn, $username, $passwd, $options) { $searchPath = null; - if (is_array($options) && isset($options['search_path'])) { - $matches = null; - if (preg_match("/(\s?)+(\w)+((\s+)?,(\s+)?\w+)*/", $options['search_path'], $matches) === 1) { - $searchPath = $matches[0]; + if (is_array($options)) { + if (isset($options[self::OPT_SEARCH_PATH])) { + $matches = null; + if (preg_match("/(\s?)+(\w)+((\s+)?,(\s+)?\w+)*/", $options[self::OPT_SEARCH_PATH], $matches) === 1) { + $searchPath = $matches[0]; + } + } + if (isset($options[self::OPT_DEFAULT_SCHEMA])) { + $schema = trim($options[self::OPT_DEFAULT_SCHEMA]); + if ($schema !== '') { + Schema::$DEFAULT_SCHEMA = $schema; + } + } + if (Schema::$DEFAULT_SCHEMA === null || Schema::$DEFAULT_SCHEMA === '') { + Schema::$DEFAULT_SCHEMA = self::DEFAULT_SCHEMA; } } parent::__construct($dsn, $username, $passwd, $options); @@ -37,6 +54,16 @@ class PDO extends \PDO { } /** + * Returns the name of the current (connected) database + * @return string + */ + public function getCurrentDatabase() { + if (is_null($this->_currentDatabase)) { + return $this->query('select current_database()')->fetchColumn(); + } + } + + /** * Sets the schema search path of the current users session. * The syntax of the path is a comma separated string with * your custom search path at the beginning and the "public" diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 32044ee..f7329bd 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -12,7 +12,8 @@ use yii\db\TableSchema; use yii\db\ColumnSchema; /** - * Schema is the class for retrieving metadata from a PostgreSQL database (version 9.x and above). + * Schema is the class for retrieving metadata from a PostgreSQL database + * (version 9.x and above). * * @author Gevik Babakhani * @since 2.0 @@ -20,6 +21,85 @@ use yii\db\ColumnSchema; class Schema extends \yii\db\Schema { /** + * The default schema used for the current session. This value is + * automatically set to "public" by the PDO driver. + * @var string + */ + public static $DEFAULT_SCHEMA; + + /** + * @var array mapping from physical column types (keys) to abstract + * column types (values) + */ + public $typeMap = array( + 'abstime' => self::TYPE_TIMESTAMP, + //'aclitem' => self::TYPE_STRING, + 'bit' => self::TYPE_STRING, + 'boolean' => self::TYPE_BOOLEAN, + 'box' => self::TYPE_STRING, + 'character' => self::TYPE_STRING, + 'bytea' => self::TYPE_BINARY, + 'char' => self::TYPE_STRING, + //'cid' => self::TYPE_STRING, + 'cidr' => self::TYPE_STRING, + 'circle' => self::TYPE_STRING, + 'date' => self::TYPE_DATE, + //'daterange' => self::TYPE_STRING, + 'real' => self::TYPE_FLOAT, + 'double precision' => self::TYPE_DECIMAL, + //'gtsvector' => self::TYPE_STRING, + 'inet' => self::TYPE_STRING, + 'smallint' => self::TYPE_SMALLINT, + 'integer' => self::TYPE_INTEGER, + //'int4range' => self::TYPE_STRING, //unknown + 'bigint' => self::TYPE_BIGINT, + //'int8range' => self::TYPE_STRING, // unknown + 'interval' => self::TYPE_STRING, + 'json' => self::TYPE_STRING, + 'line' => self::TYPE_STRING, + //'lseg' => self::TYPE_STRING, + 'macaddr' => self::TYPE_STRING, + 'money' => self::TYPE_MONEY, + 'name' => self::TYPE_STRING, + 'numeric' => self::TYPE_STRING, + 'numrange' => self::TYPE_DECIMAL, + 'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal! + 'path' => self::TYPE_STRING, + //'pg_node_tree' => self::TYPE_STRING, + 'point' => self::TYPE_STRING, + 'polygon' => self::TYPE_STRING, + //'refcursor' => self::TYPE_STRING, + //'regclass' => self::TYPE_STRING, + //'regconfig' => self::TYPE_STRING, + //'regdictionary' => self::TYPE_STRING, + //'regoper' => self::TYPE_STRING, + //'regoperator' => self::TYPE_STRING, + //'regproc' => self::TYPE_STRING, + //'regprocedure' => self::TYPE_STRING, + //'regtype' => self::TYPE_STRING, + //'reltime' => self::TYPE_STRING, + //'smgr' => self::TYPE_STRING, + 'text' => self::TYPE_TEXT, + //'tid' => self::TYPE_STRING, + 'time without time zone' => self::TYPE_TIME, + 'timestamp without time zone' => self::TYPE_TIMESTAMP, + 'timestamp with time zone' => self::TYPE_TIMESTAMP, + 'time with time zone' => self::TYPE_TIMESTAMP, + //'tinterval' => self::TYPE_STRING, + //'tsquery' => self::TYPE_STRING, + //'tsrange' => self::TYPE_STRING, + //'tstzrange' => self::TYPE_STRING, + //'tsvector' => self::TYPE_STRING, + //'txid_snapshot' => self::TYPE_STRING, + 'unknown' => self::TYPE_STRING, + 'uuid' => self::TYPE_STRING, + 'bit varying' => self::TYPE_STRING, + 'character varying' => self::TYPE_STRING, + //'xid' => self::TYPE_STRING, + 'xml' => self::TYPE_STRING + ); + + /** * Resolves the table name and schema name (if any). * @param TableSchema $table the table metadata object * @param string $name the table name @@ -32,6 +112,9 @@ class Schema extends \yii\db\Schema { } else { $table->name = $parts[0]; } + if ($table->schemaName === null) { + $table->schemaName = self::$DEFAULT_SCHEMA; + } } /** @@ -52,11 +135,85 @@ class Schema extends \yii\db\Schema { public function loadTableSchema($name) { $table = new TableSchema(); $this->resolveTableNames($table, $name); - $this->findPrimaryKeys($table); if ($this->findColumns($table)) { $this->findForeignKeys($table); return $table; } } + /** + * Collects the metadata of table columns. + * @param TableSchema $table the table metadata + * @return boolean whether the table exists in the database + */ + protected function findColumns($table) { + $dbname = $this->db->quoteValue($this->db->pdo->getCurrentDatabase()); + $tableName = $this->db->quoteValue($table->name); + $schemaName = $this->db->quoteValue($table->schemaName); + $sql = << 0 + and c.relname = {$tableName} + and d.nspname = {$schemaName} + and current_database() = {$dbname} +ORDER BY + a.attnum; +SQL; + + try { + $columns = $this->db->createCommand($sql)->queryAll(); + } catch (\Exception $e) { + return false; + } + foreach ($columns as $column) { + $column = $this->loadColumnSchema($column); + if ($column->name == 'numbers') + print_r($column); + } + die(); + } + + /** + * Loads the column information into a [[ColumnSchema]] object. + * @param array $info column information + * @return ColumnSchema the column schema object + */ + protected function loadColumnSchema($info) { + $column = new ColumnSchema(); + $column->allowNull = $info['is_nullable']; + $column->autoIncrement = $info['is_autoinc']; + $column->comment = $info['column_comment']; + $column->dbType = $info['data_type']; + $column->defaultValue = $info['column_default']; + $column->enumValues = explode(',', str_replace(array("''"), array("'"), $info['enum_values'])); +//$column->isPrimaryKey + $column->name = $info['column_name']; + //$column->phpType +//$column->precision +//$column->scale +//$column->size; +//$column->type +//$column->unsigned + return $column; + } + } \ No newline at end of file diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index ce75206..f158e58 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -11,14 +11,26 @@ DROP TABLE IF EXISTS tbl_category CASCADE; DROP TABLE IF EXISTS tbl_customer CASCADE; DROP TABLE IF EXISTS tbl_type CASCADE; +drop type if exists fullname cascade; +create type fullname as (firstname varchar,lastname varchar); + +drop type if exists mood cascade; +create type mood as enum ('sad','ok','happy',E'own\'s',E'\"quoted\"'); + + CREATE TABLE tbl_customer ( id serial not null primary key, email varchar(128) NOT NULL, name varchar(128) NOT NULL, address text, - status integer DEFAULT 0 + status integer DEFAULT 0, + fullname fullname, + mood mood, + numbers integer[] ); +comment on column public.tbl_customer.email is 'someone@example.com'; + CREATE TABLE tbl_category ( id serial not null primary key, name varchar(128) NOT NULL From 97270a389ad78f83190d8ff4cb595a696e6559a0 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Fri, 31 May 2013 14:25:56 +0200 Subject: [PATCH 04/19] Savepoint, implementing fkeys. --- framework/yii/db/pgsql/Schema.php | 76 ++++++++++++++++++++++++++++++++------- tests/unit/data/postgres.sql | 5 ++- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index f7329bd..f42c132 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -136,12 +136,30 @@ class Schema extends \yii\db\Schema { $table = new TableSchema(); $this->resolveTableNames($table, $name); if ($this->findColumns($table)) { - $this->findForeignKeys($table); + $this->findConstraints($table); return $table; } } /** + * Collects the foreign key column details for the given table. + * @param TableSchema $table the table metadata + */ + protected function findConstraints($table) { + + try { + $constraints = $this->db->createCommand($sql)->queryAll(); + } catch (\Exception $e) { + return false; + } + foreach ($constraints as $constraint) { + $column = $this->loadColumnSchema($column); + $table->columns[$column->name] = $column; + } + return true; + } + + /** * Collects the metadata of table columns. * @param TableSchema $table the table metadata * @return boolean whether the table exists in the database @@ -163,13 +181,41 @@ SELECT a.attnotnull = false AS is_nullable, CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default, coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc, - array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values + array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values, + CASE atttypid + WHEN 21 /*int2*/ THEN 16 + WHEN 23 /*int4*/ THEN 32 + WHEN 20 /*int8*/ THEN 64 + WHEN 1700 /*numeric*/ THEN + CASE WHEN atttypmod = -1 + THEN null + ELSE ((atttypmod - 4) >> 16) & 65535 + END + WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/ + WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/ + ELSE null + END AS numeric_precision, + CASE + WHEN atttypid IN (21, 23, 20) THEN 0 + WHEN atttypid IN (1700) THEN + CASE + WHEN atttypmod = -1 THEN null + ELSE (atttypmod - 4) & 65535 + END + ELSE null + END AS numeric_scale, + CAST( + information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t)) + AS numeric + ) AS size, + a.attnum = any (ct.conkey) as is_pkey FROM pg_class c LEFT JOIN pg_attribute a ON a.attrelid = c.oid LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum LEFT JOIN pg_type t ON a.atttypid = t.oid - LEFT JOIN pg_namespace d ON d.oid = c.relnamespace + LEFT JOIN pg_namespace d ON d.oid = c.relnamespace + LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p' WHERE a.attnum > 0 and c.relname = {$tableName} @@ -186,10 +232,9 @@ SQL; } foreach ($columns as $column) { $column = $this->loadColumnSchema($column); - if ($column->name == 'numbers') - print_r($column); + $table->columns[$column->name] = $column; } - die(); + return true; } /** @@ -205,14 +250,19 @@ SQL; $column->dbType = $info['data_type']; $column->defaultValue = $info['column_default']; $column->enumValues = explode(',', str_replace(array("''"), array("'"), $info['enum_values'])); -//$column->isPrimaryKey + $column->unsigned = false; // has no meanining in PG + $column->isPrimaryKey = $info['is_pkey']; $column->name = $info['column_name']; - //$column->phpType -//$column->precision -//$column->scale -//$column->size; -//$column->type -//$column->unsigned + $column->precision = $info['numeric_precision']; + $column->scale = $info['numeric_scale']; + $column->size = $info['size']; + + if (isset($this->typeMap[$column->dbType])) { + $column->type = $this->typeMap[$column->dbType]; + } else { + $column->type = self::TYPE_STRING; + } + $column->phpType = $this->getColumnPhpType($column); return $column; } diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index f158e58..6a02691 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -22,11 +22,14 @@ CREATE TABLE tbl_customer ( id serial not null primary key, email varchar(128) NOT NULL, name varchar(128) NOT NULL, + age numeric(3), + zipcode varchar(6), address text, status integer DEFAULT 0, fullname fullname, mood mood, - numbers integer[] + numbers integer[], + amount numeric(6,4) ); comment on column public.tbl_customer.email is 'someone@example.com'; From 04ebd12277112879bfe7c2047fdb2e2c9d08534b Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Sat, 1 Jun 2013 12:36:19 +0200 Subject: [PATCH 05/19] Added last insert value to pgsql PDO --- framework/yii/db/pgsql/PDO.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/framework/yii/db/pgsql/PDO.php b/framework/yii/db/pgsql/PDO.php index 75c20a3..67387dd 100644 --- a/framework/yii/db/pgsql/PDO.php +++ b/framework/yii/db/pgsql/PDO.php @@ -25,6 +25,20 @@ class PDO extends \PDO { private $_currentDatabase = null; /** + * Returns value of the last inserted ID. + * @param string|null $sequence the sequence name. Defaults to null. + * @return integer last inserted ID value. + */ + public function lastInsertId($sequence = null) { + if ($sequence !== null) { + $sequence = $this->quote($sequence); + return $this->query("SELECT currval({$sequence})")->fetchColumn(); + } else { + return null; + } + } + + /** * Here we override the default PDO constructor in order to * find and set the default schema search path. */ From cbde22af55c5458b854e788da2eea31d3a47e0f9 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Sat, 1 Jun 2013 12:36:55 +0200 Subject: [PATCH 06/19] Savepoint WIP QueryBuilder! --- framework/yii/db/pgsql/QueryBuilder.php | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 framework/yii/db/pgsql/QueryBuilder.php diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php new file mode 100644 index 0000000..c0d5089 --- /dev/null +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -0,0 +1,48 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder { + + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = array( + Schema::TYPE_PK => 'bigserial not null primary key', + Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_TEXT => 'text', + Schema::TYPE_SMALLINT => 'smallint', + Schema::TYPE_INTEGER => 'integer', + Schema::TYPE_BIGINT => 'bigint', + Schema::TYPE_FLOAT => 'real', + Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_DATETIME => 'timestamp', + Schema::TYPE_TIMESTAMP => 'timestamp', + Schema::TYPE_TIME => 'time', + Schema::TYPE_DATE => 'date', + Schema::TYPE_BINARY => 'bytea', + Schema::TYPE_BOOLEAN => 'boolean', + Schema::TYPE_MONEY => 'numeric(19,4)', + ); + + public function insert($table, $columns, &$params) { + $sql = parent::insert($table, $columns, $params); + return $sql . ' RETURNING *'; + } + +} From 5a380360c72e7f9c88e2881ded45d6af77c82bf6 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Sat, 1 Jun 2013 12:37:30 +0200 Subject: [PATCH 07/19] Added table based sequence. --- framework/yii/db/pgsql/Schema.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index f42c132..5203ceb 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -100,6 +100,14 @@ class Schema extends \yii\db\Schema { ); /** + * Creates a query builder for the MySQL database. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() { + return new QueryBuilder($this->db); + } + + /** * Resolves the table name and schema name (if any). * @param TableSchema $table the table metadata object * @param string $name the table name @@ -146,7 +154,7 @@ class Schema extends \yii\db\Schema { * @param TableSchema $table the table metadata */ protected function findConstraints($table) { - + try { $constraints = $this->db->createCommand($sql)->queryAll(); } catch (\Exception $e) { @@ -233,6 +241,12 @@ SQL; foreach ($columns as $column) { $column = $this->loadColumnSchema($column); $table->columns[$column->name] = $column; + if ($column->isPrimaryKey === true) { + $table->primaryKey[] = $column->name; + if ($table->sequenceName === null && preg_match("/nextval\('\w+'(::regclass)?\)/", $column->defaultValue) === 1) { + $table->sequenceName = preg_replace(array('/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'), '', $column->defaultValue); + } + } } return true; } From 18218c41328dd70d1bff3e1202f20729125b2548 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Sat, 1 Jun 2013 12:37:47 +0200 Subject: [PATCH 08/19] Removed unused columns. --- tests/unit/data/postgres.sql | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index 6a02691..52fad0f 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -11,25 +11,12 @@ DROP TABLE IF EXISTS tbl_category CASCADE; DROP TABLE IF EXISTS tbl_customer CASCADE; DROP TABLE IF EXISTS tbl_type CASCADE; -drop type if exists fullname cascade; -create type fullname as (firstname varchar,lastname varchar); - -drop type if exists mood cascade; -create type mood as enum ('sad','ok','happy',E'own\'s',E'\"quoted\"'); - - CREATE TABLE tbl_customer ( id serial not null primary key, email varchar(128) NOT NULL, name varchar(128) NOT NULL, - age numeric(3), - zipcode varchar(6), address text, - status integer DEFAULT 0, - fullname fullname, - mood mood, - numbers integer[], - amount numeric(6,4) + status integer DEFAULT 0 ); comment on column public.tbl_customer.email is 'someone@example.com'; From 70e973129da2beaa4608175e08526e7b96952853 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Thu, 6 Jun 2013 11:29:28 +0200 Subject: [PATCH 09/19] Added foreign keys and cleanup internal pg types --- framework/yii/db/pgsql/QueryBuilder.php | 10 +--- framework/yii/db/pgsql/Schema.php | 99 +++++++++++++++++++++++---------- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index c0d5089..0cd9d94 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -8,9 +8,6 @@ namespace yii\db\pgsql; -use yii\db\Exception; -use yii\base\InvalidParamException; - /** * QueryBuilder is the query builder for PostgreSQL databases. * @@ -24,7 +21,7 @@ class QueryBuilder extends \yii\db\QueryBuilder { */ public $typeMap = array( Schema::TYPE_PK => 'bigserial not null primary key', - Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_STRING => 'varchar', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', Schema::TYPE_INTEGER => 'integer', @@ -40,9 +37,4 @@ class QueryBuilder extends \yii\db\QueryBuilder { Schema::TYPE_MONEY => 'numeric(19,4)', ); - public function insert($table, $columns, &$params) { - $sql = parent::insert($table, $columns, $params); - return $sql . ' RETURNING *'; - } - } diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 5203ceb..1e19383 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -33,31 +33,24 @@ class Schema extends \yii\db\Schema { */ public $typeMap = array( 'abstime' => self::TYPE_TIMESTAMP, - //'aclitem' => self::TYPE_STRING, 'bit' => self::TYPE_STRING, 'boolean' => self::TYPE_BOOLEAN, 'box' => self::TYPE_STRING, 'character' => self::TYPE_STRING, 'bytea' => self::TYPE_BINARY, 'char' => self::TYPE_STRING, - //'cid' => self::TYPE_STRING, 'cidr' => self::TYPE_STRING, 'circle' => self::TYPE_STRING, 'date' => self::TYPE_DATE, - //'daterange' => self::TYPE_STRING, 'real' => self::TYPE_FLOAT, 'double precision' => self::TYPE_DECIMAL, - //'gtsvector' => self::TYPE_STRING, 'inet' => self::TYPE_STRING, 'smallint' => self::TYPE_SMALLINT, 'integer' => self::TYPE_INTEGER, - //'int4range' => self::TYPE_STRING, //unknown 'bigint' => self::TYPE_BIGINT, - //'int8range' => self::TYPE_STRING, // unknown 'interval' => self::TYPE_STRING, 'json' => self::TYPE_STRING, 'line' => self::TYPE_STRING, - //'lseg' => self::TYPE_STRING, 'macaddr' => self::TYPE_STRING, 'money' => self::TYPE_MONEY, 'name' => self::TYPE_STRING, @@ -65,38 +58,49 @@ class Schema extends \yii\db\Schema { 'numrange' => self::TYPE_DECIMAL, 'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal! 'path' => self::TYPE_STRING, - //'pg_node_tree' => self::TYPE_STRING, 'point' => self::TYPE_STRING, 'polygon' => self::TYPE_STRING, - //'refcursor' => self::TYPE_STRING, - //'regclass' => self::TYPE_STRING, - //'regconfig' => self::TYPE_STRING, - //'regdictionary' => self::TYPE_STRING, - //'regoper' => self::TYPE_STRING, - //'regoperator' => self::TYPE_STRING, - //'regproc' => self::TYPE_STRING, - //'regprocedure' => self::TYPE_STRING, - //'regtype' => self::TYPE_STRING, - //'reltime' => self::TYPE_STRING, - //'smgr' => self::TYPE_STRING, 'text' => self::TYPE_TEXT, - //'tid' => self::TYPE_STRING, 'time without time zone' => self::TYPE_TIME, 'timestamp without time zone' => self::TYPE_TIMESTAMP, 'timestamp with time zone' => self::TYPE_TIMESTAMP, 'time with time zone' => self::TYPE_TIMESTAMP, - //'tinterval' => self::TYPE_STRING, - //'tsquery' => self::TYPE_STRING, - //'tsrange' => self::TYPE_STRING, - //'tstzrange' => self::TYPE_STRING, - //'tsvector' => self::TYPE_STRING, - //'txid_snapshot' => self::TYPE_STRING, 'unknown' => self::TYPE_STRING, 'uuid' => self::TYPE_STRING, 'bit varying' => self::TYPE_STRING, 'character varying' => self::TYPE_STRING, - //'xid' => self::TYPE_STRING, 'xml' => self::TYPE_STRING + + /* + * internal PG types + * 'aclitem' => self::TYPE_STRING, + * 'cid' => self::TYPE_STRING, + * 'daterange' => self::TYPE_STRING, + * 'gtsvector' => self::TYPE_STRING, + * 'int4range' => self::TYPE_STRING, //unknown + * 'lseg' => self::TYPE_STRING, + * 'int8range' => self::TYPE_STRING, // unknown + * 'pg_node_tree' => self::TYPE_STRING, + * 'refcursor' => self::TYPE_STRING, + * 'regclass' => self::TYPE_STRING, + * 'regconfig' => self::TYPE_STRING, + * 'regdictionary' => self::TYPE_STRING, + * 'regoper' => self::TYPE_STRING, + * 'regoperator' => self::TYPE_STRING, + * 'regproc' => self::TYPE_STRING, + * 'regprocedure' => self::TYPE_STRING, + * 'regtype' => self::TYPE_STRING, + * 'reltime' => self::TYPE_STRING, + * 'smgr' => self::TYPE_STRING, + * 'tid' => self::TYPE_STRING, + * 'xid' => self::TYPE_STRING, + * 'tinterval' => self::TYPE_STRING, + * 'tsquery' => self::TYPE_STRING, + * 'tsrange' => self::TYPE_STRING, + * 'tstzrange' => self::TYPE_STRING, + * 'tsvector' => self::TYPE_STRING, + * 'txid_snapshot' => self::TYPE_STRING + */ ); /** @@ -155,14 +159,51 @@ class Schema extends \yii\db\Schema { */ protected function findConstraints($table) { + $tableName = $this->quoteValue($table->name); + $tableSchema = $this->quoteValue($table->schemaName); + $database = $this->quoteValue($this->db->pdo->getCurrentDatabase()); + + //We need to extract the constraints de hard way since: + //http://www.postgresql.org/message-id/26677.1086673982@sss.pgh.pa.us + + $sql = <<db->createCommand($sql)->queryAll(); } catch (\Exception $e) { return false; } foreach ($constraints as $constraint) { - $column = $this->loadColumnSchema($column); - $table->columns[$column->name] = $column; + $columns = explode(',', $constraint['columns']); + $fcolumns = explode(',', $constraint['foreign_columns']); + $citem = array($constraint['foreign_table_name']); + foreach ($columns as $idx => $column) { + $citem[] = array($fcolumns[$idx] => $column); + } + $table->foreignKeys[] = $citem; } return true; } From 5a587d16f0b70d01b42bbb6d40a1528a50bb6eaa Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Thu, 6 Jun 2013 16:29:17 +0200 Subject: [PATCH 10/19] Fixed several formatting issues. Refactored == null to is_null == '' to empty(...) --- framework/yii/db/pgsql/PDO.php | 9 +++++---- framework/yii/db/pgsql/QueryBuilder.php | 3 ++- framework/yii/db/pgsql/Schema.php | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/framework/yii/db/pgsql/PDO.php b/framework/yii/db/pgsql/PDO.php index 67387dd..3f5996a 100644 --- a/framework/yii/db/pgsql/PDO.php +++ b/framework/yii/db/pgsql/PDO.php @@ -16,13 +16,14 @@ namespace yii\db\pgsql; * @author Gevik babakhani * @since 2.0 */ -class PDO extends \PDO { +class PDO extends \PDO +{ const OPT_SEARCH_PATH = 'search_path'; const OPT_DEFAULT_SCHEMA = 'default_schema'; const DEFAULT_SCHEMA = 'public'; - private $_currentDatabase = null; + private $_currentDatabase; /** * Returns value of the last inserted ID. @@ -53,11 +54,11 @@ class PDO extends \PDO { } if (isset($options[self::OPT_DEFAULT_SCHEMA])) { $schema = trim($options[self::OPT_DEFAULT_SCHEMA]); - if ($schema !== '') { + if (!empty($schema)) { Schema::$DEFAULT_SCHEMA = $schema; } } - if (Schema::$DEFAULT_SCHEMA === null || Schema::$DEFAULT_SCHEMA === '') { + if (is_null(Schema::$DEFAULT_SCHEMA) || empty(Schema::$DEFAULT_SCHEMA)) { Schema::$DEFAULT_SCHEMA = self::DEFAULT_SCHEMA; } } diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 0cd9d94..f45c753 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -14,7 +14,8 @@ namespace yii\db\pgsql; * @author Gevik Babakhani * @since 2.0 */ -class QueryBuilder extends \yii\db\QueryBuilder { +class QueryBuilder extends \yii\db\QueryBuilder +{ /** * @var array mapping from abstract column types (keys) to physical column types (values). diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 1e19383..07238c4 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -18,7 +18,8 @@ use yii\db\ColumnSchema; * @author Gevik Babakhani * @since 2.0 */ -class Schema extends \yii\db\Schema { +class Schema extends \yii\db\Schema +{ /** * The default schema used for the current session. This value is From e5055664461b327c710ac3e4249deada2713ee01 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Thu, 6 Jun 2013 19:51:22 +0200 Subject: [PATCH 11/19] Refactored types to be compatible with 1.1 --- framework/yii/db/pgsql/QueryBuilder.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index f45c753..3417ad9 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -21,14 +21,14 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'bigserial not null primary key', + Schema::TYPE_PK => 'serial not null primary key', Schema::TYPE_STRING => 'varchar', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', Schema::TYPE_INTEGER => 'integer', Schema::TYPE_BIGINT => 'bigint', - Schema::TYPE_FLOAT => 'real', - Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_FLOAT => 'double precision', + Schema::TYPE_DECIMAL => 'numeric', Schema::TYPE_DATETIME => 'timestamp', Schema::TYPE_TIMESTAMP => 'timestamp', Schema::TYPE_TIME => 'time', From 9982d93369624ef06ad946bcf8d45409b9cbae3f Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Thu, 6 Jun 2013 21:14:24 +0200 Subject: [PATCH 12/19] Removed commented and unused pgsql datatype mappings. --- framework/yii/db/pgsql/Schema.php | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 07238c4..97a3ef4 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -71,37 +71,6 @@ class Schema extends \yii\db\Schema 'bit varying' => self::TYPE_STRING, 'character varying' => self::TYPE_STRING, 'xml' => self::TYPE_STRING - - /* - * internal PG types - * 'aclitem' => self::TYPE_STRING, - * 'cid' => self::TYPE_STRING, - * 'daterange' => self::TYPE_STRING, - * 'gtsvector' => self::TYPE_STRING, - * 'int4range' => self::TYPE_STRING, //unknown - * 'lseg' => self::TYPE_STRING, - * 'int8range' => self::TYPE_STRING, // unknown - * 'pg_node_tree' => self::TYPE_STRING, - * 'refcursor' => self::TYPE_STRING, - * 'regclass' => self::TYPE_STRING, - * 'regconfig' => self::TYPE_STRING, - * 'regdictionary' => self::TYPE_STRING, - * 'regoper' => self::TYPE_STRING, - * 'regoperator' => self::TYPE_STRING, - * 'regproc' => self::TYPE_STRING, - * 'regprocedure' => self::TYPE_STRING, - * 'regtype' => self::TYPE_STRING, - * 'reltime' => self::TYPE_STRING, - * 'smgr' => self::TYPE_STRING, - * 'tid' => self::TYPE_STRING, - * 'xid' => self::TYPE_STRING, - * 'tinterval' => self::TYPE_STRING, - * 'tsquery' => self::TYPE_STRING, - * 'tsrange' => self::TYPE_STRING, - * 'tstzrange' => self::TYPE_STRING, - * 'tsvector' => self::TYPE_STRING, - * 'txid_snapshot' => self::TYPE_STRING - */ ); /** From 0fd390cac555b00149d1c000aea7796edd47d0f6 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Thu, 6 Jun 2013 21:19:17 +0200 Subject: [PATCH 13/19] Replaces spaces with tabs. --- tests/unit/data/config.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index f1c7bc0..1b40513 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -18,14 +18,14 @@ return array( 'password' => '', 'fixture' => __DIR__ . '/mssql.sql', ), - 'pgsql' => array( + 'pgsql' => array( 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432;', 'username' => 'postgres', 'password' => 'postgres', 'attributes' => array( - 'search_path' => 'master,hello' + 'search_path' => 'master,hello' ), 'fixture' => __DIR__ . '/postgres.sql', - ) + ) ) ); From b5513c84a0d353c1b1067f51ad3de674967854b3 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Thu, 6 Jun 2013 22:19:03 +0200 Subject: [PATCH 14/19] Added db creation for travis CI This needs to be done in two seperate command since postgres does not accept db creation in multiline statement. --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e4b8278..b7ea5ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,10 @@ php: env: - DB=mysql + - DB=postgres before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi" - + - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'drop database if exists yiitest;'; fi" + - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'create database yiitest;'; fi" script: phpunit \ No newline at end of file From dbe84acfddb16e3fee5290204cd4ea0fdf6e2ca0 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Thu, 6 Jun 2013 22:26:43 +0200 Subject: [PATCH 15/19] Added missing postgres user to the travis config. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b7ea5ce..8dff4a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,6 @@ env: before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi" - - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'drop database if exists yiitest;'; fi" - - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'create database yiitest;'; fi" + - sh -c "if [ '$DB' = 'postgres' ]; then psql -U postgres -c 'drop database if exists yiitest;'; fi" + - sh -c "if [ '$DB' = 'postgres' ]; then psql -U postgres -c 'create database yiitest;'; fi" script: phpunit \ No newline at end of file From 8b61ab7f71a4c355540cfa680a19b77e10076761 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Thu, 6 Jun 2013 22:59:46 +0200 Subject: [PATCH 16/19] Force travis to create the pgsql test db --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8dff4a3..01abd50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,9 @@ php: env: - DB=mysql - - DB=postgres before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi" - - sh -c "if [ '$DB' = 'postgres' ]; then psql -U postgres -c 'drop database if exists yiitest;'; fi" - - sh -c "if [ '$DB' = 'postgres' ]; then psql -U postgres -c 'create database yiitest;'; fi" + - psql -U postgres -c 'drop database if exists yiitest;'; + - psql -U postgres -c 'create database yiitest;'; script: phpunit \ No newline at end of file From 879b494acbf4463781f37437f16b92d6c10dced0 Mon Sep 17 00:00:00 2001 From: Gevik Babakhani Date: Fri, 7 Jun 2013 21:28:58 +0200 Subject: [PATCH 17/19] Removed custom pgsql PDO and added defaultSchema as public property. --- framework/yii/db/Connection.php | 2 - framework/yii/db/pgsql/PDO.php | 103 -------------------------------------- framework/yii/db/pgsql/Schema.php | 15 ++---- 3 files changed, 5 insertions(+), 115 deletions(-) delete mode 100644 framework/yii/db/pgsql/PDO.php diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index 6a252bf..3a4d0ad 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -343,8 +343,6 @@ class Connection extends Component $driver = strtolower(substr($this->dsn, 0, $pos)); if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') { $pdoClass = 'yii\db\mssql\PDO'; - } else if ($driver === 'pgsql') { - $pdoClass = 'yii\db\pgsql\PDO'; } } return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes); diff --git a/framework/yii/db/pgsql/PDO.php b/framework/yii/db/pgsql/PDO.php deleted file mode 100644 index 3f5996a..0000000 --- a/framework/yii/db/pgsql/PDO.php +++ /dev/null @@ -1,103 +0,0 @@ - - * @since 2.0 - */ -class PDO extends \PDO -{ - - const OPT_SEARCH_PATH = 'search_path'; - const OPT_DEFAULT_SCHEMA = 'default_schema'; - const DEFAULT_SCHEMA = 'public'; - - private $_currentDatabase; - - /** - * Returns value of the last inserted ID. - * @param string|null $sequence the sequence name. Defaults to null. - * @return integer last inserted ID value. - */ - public function lastInsertId($sequence = null) { - if ($sequence !== null) { - $sequence = $this->quote($sequence); - return $this->query("SELECT currval({$sequence})")->fetchColumn(); - } else { - return null; - } - } - - /** - * Here we override the default PDO constructor in order to - * find and set the default schema search path. - */ - public function __construct($dsn, $username, $passwd, $options) { - $searchPath = null; - if (is_array($options)) { - if (isset($options[self::OPT_SEARCH_PATH])) { - $matches = null; - if (preg_match("/(\s?)+(\w)+((\s+)?,(\s+)?\w+)*/", $options[self::OPT_SEARCH_PATH], $matches) === 1) { - $searchPath = $matches[0]; - } - } - if (isset($options[self::OPT_DEFAULT_SCHEMA])) { - $schema = trim($options[self::OPT_DEFAULT_SCHEMA]); - if (!empty($schema)) { - Schema::$DEFAULT_SCHEMA = $schema; - } - } - if (is_null(Schema::$DEFAULT_SCHEMA) || empty(Schema::$DEFAULT_SCHEMA)) { - Schema::$DEFAULT_SCHEMA = self::DEFAULT_SCHEMA; - } - } - parent::__construct($dsn, $username, $passwd, $options); - if (!is_null($searchPath)) { - $this->setSchemaSearchPath($searchPath); - } - } - - /** - * Returns the name of the current (connected) database - * @return string - */ - public function getCurrentDatabase() { - if (is_null($this->_currentDatabase)) { - return $this->query('select current_database()')->fetchColumn(); - } - } - - /** - * Sets the schema search path of the current users session. - * The syntax of the path is a comma separated string with - * your custom search path at the beginning and the "public" - * schema at the end. - * - * This method automatically adds the "public" schema at the - * end of the search path if it is not provied. - * @param string custom schema search path. defaults to public - */ - public function setSchemaSearchPath($searchPath = 'public') { - $schemas = explode(',', str_replace(' ', '', $searchPath)); - if (end($schemas) !== 'public') { - $schemas[] = 'public'; - } - foreach ($schemas as $k => $item) { - $schemas[$k] = '"' . str_replace(array('"', "'", ';'), '', $item) . '"'; - } - $path = implode(', ', $schemas); - $this->exec('SET search_path TO ' . $path); - } - -} diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 97a3ef4..80a7e33 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -22,11 +22,10 @@ class Schema extends \yii\db\Schema { /** - * The default schema used for the current session. This value is - * automatically set to "public" by the PDO driver. + * The default schema used for the current session. * @var string */ - public static $DEFAULT_SCHEMA; + public $defaultSchema = 'public'; /** * @var array mapping from physical column types (keys) to abstract @@ -95,10 +94,10 @@ class Schema extends \yii\db\Schema $table->name = $parts[0]; } if ($table->schemaName === null) { - $table->schemaName = self::$DEFAULT_SCHEMA; + $table->schemaName = $this->defaultSchema; } } - + /** * Quotes a table name for use in a query. * A simple table name has no schema prefix. @@ -122,7 +121,7 @@ class Schema extends \yii\db\Schema return $table; } } - + /** * Collects the foreign key column details for the given table. * @param TableSchema $table the table metadata @@ -131,7 +130,6 @@ class Schema extends \yii\db\Schema $tableName = $this->quoteValue($table->name); $tableSchema = $this->quoteValue($table->schemaName); - $database = $this->quoteValue($this->db->pdo->getCurrentDatabase()); //We need to extract the constraints de hard way since: //http://www.postgresql.org/message-id/26677.1086673982@sss.pgh.pa.us @@ -158,7 +156,6 @@ where ct.contype='f' and c.relname={$tableName} and ns.nspname={$tableSchema} - and current_database() = {$database} SQL; try { @@ -184,7 +181,6 @@ SQL; * @return boolean whether the table exists in the database */ protected function findColumns($table) { - $dbname = $this->db->quoteValue($this->db->pdo->getCurrentDatabase()); $tableName = $this->db->quoteValue($table->name); $schemaName = $this->db->quoteValue($table->schemaName); $sql = << 0 and c.relname = {$tableName} and d.nspname = {$schemaName} - and current_database() = {$dbname} ORDER BY a.attnum; SQL; From d07fd39f7ee2fefa2164348eaa8e15339bcdf3ab Mon Sep 17 00:00:00 2001 From: gevik Date: Fri, 7 Jun 2013 22:54:04 +0200 Subject: [PATCH 18/19] Removed false exception catching. --- framework/yii/db/pgsql/Schema.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 80a7e33..50d01aa 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -97,7 +97,7 @@ class Schema extends \yii\db\Schema $table->schemaName = $this->defaultSchema; } } - + /** * Quotes a table name for use in a query. * A simple table name has no schema prefix. @@ -121,7 +121,7 @@ class Schema extends \yii\db\Schema return $table; } } - + /** * Collects the foreign key column details for the given table. * @param TableSchema $table the table metadata @@ -158,11 +158,7 @@ where and ns.nspname={$tableSchema} SQL; - try { - $constraints = $this->db->createCommand($sql)->queryAll(); - } catch (\Exception $e) { - return false; - } + $constraints = $this->db->createCommand($sql)->queryAll(); foreach ($constraints as $constraint) { $columns = explode(',', $constraint['columns']); $fcolumns = explode(',', $constraint['foreign_columns']); @@ -172,7 +168,6 @@ SQL; } $table->foreignKeys[] = $citem; } - return true; } /** From b6d5a9002b96f36b627164217f9a9c7d7120563d Mon Sep 17 00:00:00 2001 From: gevik Date: Fri, 7 Jun 2013 23:01:01 +0200 Subject: [PATCH 19/19] Removed the config setting that should not have been commited. --- tests/unit/data/config.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index 1b40513..036624b 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -22,9 +22,6 @@ return array( 'dsn' => 'pgsql:host=localhost;dbname=yiitest;port=5432;', 'username' => 'postgres', 'password' => 'postgres', - 'attributes' => array( - 'search_path' => 'master,hello' - ), 'fixture' => __DIR__ . '/postgres.sql', ) )