From 4f95fcd91f32f3b6c3722173dc4c8ce4af3179cc Mon Sep 17 00:00:00 2001
From: Carsten Brandt <mail@cebe.cc>
Date: Wed, 25 Jun 2014 03:09:28 +0200
Subject: [PATCH] added unit tests for schema detection

fixed some issues with schema detection
---
 framework/db/ColumnSchema.php                              |   2 ++
 framework/db/cubrid/Schema.php                             |  11 ++++++-----
 framework/db/mysql/Schema.php                              |  13 +++++++++----
 framework/db/sqlite/Schema.php                             |  13 ++++++++-----
 framework/views/errorHandler/exception.php                 |   1 +
 tests/unit/data/cubrid.sql                                 | 253 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------
 tests/unit/data/mysql.sql                                  |   8 +++++---
 tests/unit/data/postgres.sql                               |   3 ++-
 tests/unit/data/sqlite.sql                                 |   3 ++-
 tests/unit/framework/db/ConnectionTest.php                 |  62 +++++++++++++++++++++++++++++++-------------------------------
 tests/unit/framework/db/SchemaTest.php                     | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/unit/framework/db/cubrid/CubridSchemaTest.php        |  34 ++++++++++++++++++++++++++++++++++
 tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php |  38 +++++++++++++++++++-------------------
 tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php      |  16 ++++++++++++++++
 tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php     |  33 +++++++++++++++++++++++++++++++++
 tests/unit/framework/db/sqlite/SqliteConnectionTest.php    |  16 ++++++++--------
 tests/unit/framework/db/sqlite/SqliteSchemaTest.php        |  19 +++++++++++++++++++
 17 files changed, 529 insertions(+), 203 deletions(-)
 create mode 100644 tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php
 create mode 100644 tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php

diff --git a/framework/db/ColumnSchema.php b/framework/db/ColumnSchema.php
index e1898e2..a9efcbf 100644
--- a/framework/db/ColumnSchema.php
+++ b/framework/db/ColumnSchema.php
@@ -99,6 +99,8 @@ class ColumnSchema extends Object
                 return (integer) $value;
             case 'boolean':
                 return (boolean) $value;
+            case 'double':
+                return (double) $value;
         }
 
         return $value;
diff --git a/framework/db/cubrid/Schema.php b/framework/db/cubrid/Schema.php
index 61c13e5..3a5d231 100644
--- a/framework/db/cubrid/Schema.php
+++ b/framework/db/cubrid/Schema.php
@@ -200,18 +200,19 @@ class Schema extends \yii\db\Schema
         $column->isPrimaryKey = false; // primary key will be set by loadTableSchema() later
         $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
 
-        $column->dbType = strtolower($info['Type']);
+        $column->dbType = $info['Type'];
         $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
 
         $column->type = self::TYPE_STRING;
-        if (preg_match('/^([\w ]+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
-            $type = $matches[1];
+        if (preg_match('/^([\w ]+)(?:\(([^\)]+)\))?$/', $column->dbType, $matches)) {
+            $type = strtolower($matches[1]);
+            $column->dbType = $type . (isset($matches[2]) ? "({$matches[2]})" : '');
             if (isset($this->typeMap[$type])) {
                 $column->type = $this->typeMap[$type];
             }
             if (!empty($matches[2])) {
                 if ($type === 'enum') {
-                    $values = explode(',', $matches[2]);
+                    $values = preg_split('/\s*,\s*/', $matches[2]);
                     foreach ($values as $i => $value) {
                         $values[$i] = trim($value, "'");
                     }
@@ -232,7 +233,7 @@ class Schema extends \yii\db\Schema
             return $column;
         }
 
-        if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP' ||
+        if ($column->type === 'timestamp' && $info['Default'] === 'SYS_TIMESTAMP' ||
             $column->type === 'datetime' && $info['Default'] === 'SYS_DATETIME' ||
             $column->type === 'date' && $info['Default'] === 'SYS_DATE' ||
             $column->type === 'time' && $info['Default'] === 'SYS_TIME'
diff --git a/framework/db/mysql/Schema.php b/framework/db/mysql/Schema.php
index b7a7a26..8b7c30a 100644
--- a/framework/db/mysql/Schema.php
+++ b/framework/db/mysql/Schema.php
@@ -7,6 +7,7 @@
 
 namespace yii\db\mysql;
 
+use yii\db\Expression;
 use yii\db\TableSchema;
 use yii\db\ColumnSchema;
 
@@ -132,11 +133,11 @@ class Schema extends \yii\db\Schema
         $column->comment = $info['Comment'];
 
         $column->dbType = $info['Type'];
-        $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
+        $column->unsigned = stripos($column->dbType, 'unsigned') !== false;
 
         $column->type = self::TYPE_STRING;
         if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
-            $type = $matches[1];
+            $type = strtolower($matches[1]);
             if (isset($this->typeMap[$type])) {
                 $column->type = $this->typeMap[$type];
             }
@@ -168,8 +169,12 @@ class Schema extends \yii\db\Schema
 
         $column->phpType = $this->getColumnPhpType($column);
 
-        if (!$column->isPrimaryKey && ($column->type !== 'timestamp' || $info['Default'] !== 'CURRENT_TIMESTAMP')) {
-            $column->defaultValue = $column->typecast($info['Default']);
+        if (!$column->isPrimaryKey) {
+            if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP') {
+                $column->defaultValue = new Expression('CURRENT_TIMESTAMP');
+            } else {
+                $column->defaultValue = $column->typecast($info['Default']);
+            }
         }
 
         return $column;
diff --git a/framework/db/sqlite/Schema.php b/framework/db/sqlite/Schema.php
index eecdfc3..e2f2f33 100644
--- a/framework/db/sqlite/Schema.php
+++ b/framework/db/sqlite/Schema.php
@@ -8,6 +8,7 @@
 namespace yii\db\sqlite;
 
 use yii\base\NotSupportedException;
+use yii\db\Expression;
 use yii\db\TableSchema;
 use yii\db\ColumnSchema;
 use yii\db\Transaction;
@@ -211,7 +212,7 @@ class Schema extends \yii\db\Schema
         $column->allowNull = !$info['notnull'];
         $column->isPrimaryKey = $info['pk'] != 0;
 
-        $column->dbType = $info['type'];
+        $column->dbType = strtolower($info['type']);
         $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
 
         $column->type = self::TYPE_STRING;
@@ -241,11 +242,13 @@ class Schema extends \yii\db\Schema
         $column->phpType = $this->getColumnPhpType($column);
 
         if (!$column->isPrimaryKey) {
-            $value = trim($info['dflt_value'], "'\"");
-            if ($column->type === 'string') {
-                $column->defaultValue = $value;
+            if ($info['dflt_value'] === 'null' || $info['dflt_value'] === '' || $info['dflt_value'] === null) {
+                $column->defaultValue = null;
+            } elseif ($column->type === 'timestamp' && $info['dflt_value'] === 'CURRENT_TIMESTAMP') {
+                $column->defaultValue = new Expression('CURRENT_TIMESTAMP');
             } else {
-                $column->defaultValue = $column->typecast(strcasecmp($value, 'null') ? $value : null);
+                $value = trim($info['dflt_value'], "'\"");
+                $column->defaultValue = $column->typecast($value);
             }
         }
 
diff --git a/framework/views/errorHandler/exception.php b/framework/views/errorHandler/exception.php
index 7007272..36cb15f 100644
--- a/framework/views/errorHandler/exception.php
+++ b/framework/views/errorHandler/exception.php
@@ -1,4 +1,5 @@
 <?php
+/* @var $this \yii\web\View */
 /* @var $exception \Exception */
 /* @var $handler \yii\web\ErrorHandler */
 ?>
diff --git a/tests/unit/data/cubrid.sql b/tests/unit/data/cubrid.sql
index ee7c618..3e31333 100644
--- a/tests/unit/data/cubrid.sql
+++ b/tests/unit/data/cubrid.sql
@@ -3,158 +3,159 @@
  * The database setup in config.php is required to perform then relevant tests:
  */
 
-DROP TABLE IF EXISTS `composite_fk`;
-DROP TABLE IF EXISTS `order_item`;
-DROP TABLE IF EXISTS `order_item_with_null_fk`;
-DROP TABLE IF EXISTS `item`;
-DROP TABLE IF EXISTS `order`;
-DROP TABLE IF EXISTS `order_with_null_fk`;
-DROP TABLE IF EXISTS `category`;
-DROP TABLE IF EXISTS `customer`;
-DROP TABLE IF EXISTS `profile`;
-DROP TABLE IF EXISTS `null_values`;
-DROP TABLE IF EXISTS `type`;
-DROP TABLE IF EXISTS `constraints`;
-
-CREATE TABLE `constraints`
+DROP TABLE IF EXISTS "composite_fk";
+DROP TABLE IF EXISTS "order_item";
+DROP TABLE IF EXISTS "order_item_with_null_fk";
+DROP TABLE IF EXISTS "item";
+DROP TABLE IF EXISTS "order";
+DROP TABLE IF EXISTS "order_with_null_fk";
+DROP TABLE IF EXISTS "category";
+DROP TABLE IF EXISTS "customer";
+DROP TABLE IF EXISTS "profile";
+DROP TABLE IF EXISTS "null_values";
+DROP TABLE IF EXISTS "type";
+DROP TABLE IF EXISTS "constraints";
+
+CREATE TABLE "constraints"
 (
-  `id` integer not null,
-  `field1` varchar(255)
+  "id" integer not null,
+  "field1" varchar(255)
 );
 
 
-CREATE TABLE `profile` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `description` varchar(128) NOT NULL,
-  PRIMARY KEY (`id`)
+CREATE TABLE "profile" (
+  "id" int(11) NOT NULL AUTO_INCREMENT,
+  "description" varchar(128) NOT NULL,
+  PRIMARY KEY ("id")
 );
 
-CREATE TABLE `customer` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `email` varchar(128) NOT NULL,
-  `name` varchar(128),
-  `address` string,
-  `status` int (11) DEFAULT 0,
-  `profile_id` int(11),
-  PRIMARY KEY (`id`)
+CREATE TABLE "customer" (
+  "id" int(11) NOT NULL AUTO_INCREMENT,
+  "email" varchar(128) NOT NULL,
+  "name" varchar(128),
+  "address" string,
+  "status" int (11) DEFAULT 0,
+  "profile_id" int(11),
+  PRIMARY KEY ("id")
 );
 
-CREATE TABLE `category` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `name` varchar(128) NOT NULL,
-  PRIMARY KEY (`id`)
+CREATE TABLE "category" (
+  "id" int(11) NOT NULL AUTO_INCREMENT,
+  "name" varchar(128) NOT NULL,
+  PRIMARY KEY ("id")
 );
 
-CREATE TABLE `item` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `name` varchar(128) NOT NULL,
-  `category_id` int(11) NOT NULL,
-  PRIMARY KEY (`id`),
-  CONSTRAINT `FK_item_category_id` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`) ON DELETE CASCADE
+CREATE TABLE "item" (
+  "id" int(11) NOT NULL AUTO_INCREMENT,
+  "name" varchar(128) NOT NULL,
+  "category_id" int(11) NOT NULL,
+  PRIMARY KEY ("id"),
+  CONSTRAINT "FK_item_category_id" FOREIGN KEY ("category_id") REFERENCES "category" ("id") ON DELETE CASCADE
 );
 
-CREATE TABLE `order` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `customer_id` int(11) NOT NULL,
-  `created_at` int(11) NOT NULL,
-  `total` decimal(10,0) NOT NULL,
-  PRIMARY KEY (`id`),
-  CONSTRAINT `FK_order_customer_id` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`id`) ON DELETE CASCADE
+CREATE TABLE "order" (
+  "id" int(11) NOT NULL AUTO_INCREMENT,
+  "customer_id" int(11) NOT NULL,
+  "created_at" int(11) NOT NULL,
+  "total" decimal(10,0) NOT NULL,
+  PRIMARY KEY ("id"),
+  CONSTRAINT "FK_order_customer_id" FOREIGN KEY ("customer_id") REFERENCES "customer" ("id") ON DELETE CASCADE
 );
 
-CREATE TABLE `order_with_null_fk` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `customer_id` int(11),
-  `created_at` int(11) NOT NULL,
-  `total` decimal(10,0) NOT NULL,
-  PRIMARY KEY (`id`)
+CREATE TABLE "order_with_null_fk" (
+  "id" int(11) NOT NULL AUTO_INCREMENT,
+  "customer_id" int(11),
+  "created_at" int(11) NOT NULL,
+  "total" decimal(10,0) NOT NULL,
+  PRIMARY KEY ("id")
 );
 
-CREATE TABLE `order_item` (
-  `order_id` int(11) NOT NULL,
-  `item_id` int(11) NOT NULL,
-  `quantity` int(11) NOT NULL,
-  `subtotal` decimal(10,0) NOT NULL,
-  PRIMARY KEY (`order_id`,`item_id`),
-  CONSTRAINT `FK_order_item_order_id` FOREIGN KEY (`order_id`) REFERENCES `order` (`id`) ON DELETE CASCADE,
-  CONSTRAINT `FK_order_item_item_id` FOREIGN KEY (`item_id`) REFERENCES `item` (`id`) ON DELETE CASCADE
+CREATE TABLE "order_item" (
+  "order_id" int(11) NOT NULL,
+  "item_id" int(11) NOT NULL,
+  "quantity" int(11) NOT NULL,
+  "subtotal" decimal(10,0) NOT NULL,
+  PRIMARY KEY ("order_id","item_id"),
+  CONSTRAINT "FK_order_item_order_id" FOREIGN KEY ("order_id") REFERENCES "order" ("id") ON DELETE CASCADE,
+  CONSTRAINT "FK_order_item_item_id" FOREIGN KEY ("item_id") REFERENCES "item" ("id") ON DELETE CASCADE
 );
 
-CREATE TABLE `order_item_with_null_fk` (
-  `order_id` int(11),
-  `item_id` int(11),
-  `quantity` int(11) NOT NULL,
-  `subtotal` decimal(10,0) NOT NULL
+CREATE TABLE "order_item_with_null_fk" (
+  "order_id" int(11),
+  "item_id" int(11),
+  "quantity" int(11) NOT NULL,
+  "subtotal" decimal(10,0) NOT NULL
 );
 
 CREATE TABLE null_values (
-  `id` INT(11) NOT NULL AUTO_INCREMENT,
-  `var1` INT NULL,
-  `var2` INT NULL,
-  `var3` INT DEFAULT NULL,
-  `stringcol` VARCHAR (32) DEFAULT NULL,
+  "id" INT(11) NOT NULL AUTO_INCREMENT,
+  "var1" INT NULL,
+  "var2" INT NULL,
+  "var3" INT DEFAULT NULL,
+  "stringcol" VARCHAR (32) DEFAULT NULL,
   PRIMARY KEY (id)
 );
 
 
-CREATE TABLE `type` (
-  `int_col` int(11) NOT NULL,
-  `int_col2` int(11) DEFAULT '1',
-  `char_col` char(100) NOT NULL,
-  `char_col2` varchar(100) DEFAULT 'something',
-  `char_col3` string,
-  `enum_col` enum('a', 'b'),
-  `float_col` double NOT NULL,
-  `float_col2` double DEFAULT '1.23',
-  `blob_col` blob,
-  `numeric_col` decimal(5,2) DEFAULT '33.22',
-  `time` timestamp NOT NULL DEFAULT '2002-01-01 00:00:00',
-  `bool_col` tinyint NOT NULL,
-  `bool_col2` tinyint DEFAULT '1'
+CREATE TABLE "type" (
+  "int_col" int(11) NOT NULL,
+  "int_col2" int(11) DEFAULT '1',
+  "char_col" char(100) NOT NULL,
+  "char_col2" varchar(100) DEFAULT 'something',
+  "char_col3" string,
+  "enum_col" enum('a','B'),
+  "float_col" double NOT NULL,
+  "float_col2" double DEFAULT '1.23',
+  "blob_col" blob,
+  "numeric_col" decimal(5,2) DEFAULT '33.22',
+  "time" timestamp NOT NULL DEFAULT '2002-01-01 00:00:00',
+  "bool_col" tinyint NOT NULL,
+  "bool_col2" tinyint DEFAULT '1',
+  "ts_default" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
 );
 
-CREATE TABLE `composite_fk` (
-  `id` int(11) NOT NULL,
-  `order_id` int(11) NOT NULL,
-  `item_id` int(11) NOT NULL,
-  PRIMARY KEY (`id`),
-  CONSTRAINT `FK_composite_fk_order_item` FOREIGN KEY (`order_id`,`item_id`) REFERENCES `order_item` (`order_id`,`item_id`) ON DELETE CASCADE
+CREATE TABLE "composite_fk" (
+  "id" int(11) NOT NULL,
+  "order_id" int(11) NOT NULL,
+  "item_id" int(11) NOT NULL,
+  PRIMARY KEY ("id"),
+  CONSTRAINT "FK_composite_fk_order_item" FOREIGN KEY ("order_id","item_id") REFERENCES "order_item" ("order_id","item_id") ON DELETE CASCADE
 );
 
-INSERT INTO `profile` (description) VALUES ('profile customer 1');
-INSERT INTO `profile` (description) VALUES ('profile customer 3');
-
-INSERT INTO `customer` (email, name, address, status, profile_id) VALUES ('user1@example.com', 'user1', 'address1', 1, 1);
-INSERT INTO `customer` (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1);
-INSERT INTO `customer` (email, name, address, status, profile_id) VALUES ('user3@example.com', 'user3', 'address3', 2, 2);
-
-INSERT INTO `category` (name) VALUES ('Books');
-INSERT INTO `category` (name) VALUES ('Movies');
-
-INSERT INTO `item` (name, category_id) VALUES ('Agile Web Application Development with Yii1.1 and PHP5', 1);
-INSERT INTO `item` (name, category_id) VALUES ('Yii 1.1 Application Development Cookbook', 1);
-INSERT INTO `item` (name, category_id) VALUES ('Ice Age', 2);
-INSERT INTO `item` (name, category_id) VALUES ('Toy Story', 2);
-INSERT INTO `item` (name, category_id) VALUES ('Cars', 2);
-
-INSERT INTO `order` (customer_id, created_at, total) VALUES (1, 1325282384, 110.0);
-INSERT INTO `order` (customer_id, created_at, total) VALUES (2, 1325334482, 33.0);
-INSERT INTO `order` (customer_id, created_at, total) VALUES (2, 1325502201, 40.0);
-
-INSERT INTO `order_with_null_fk` (customer_id, created_at, total) VALUES (1, 1325282384, 110.0);
-INSERT INTO `order_with_null_fk` (customer_id, created_at, total) VALUES (2, 1325334482, 33.0);
-INSERT INTO `order_with_null_fk` (customer_id, created_at, total) VALUES (2, 1325502201, 40.0);
-
-INSERT INTO `order_item` (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0);
-INSERT INTO `order_item` (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0);
-INSERT INTO `order_item` (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0);
-INSERT INTO `order_item` (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0);
-INSERT INTO `order_item` (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0);
-INSERT INTO `order_item` (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0);
-
-INSERT INTO `order_item_with_null_fk` (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0);
-INSERT INTO `order_item_with_null_fk` (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0);
-INSERT INTO `order_item_with_null_fk` (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0);
-INSERT INTO `order_item_with_null_fk` (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0);
-INSERT INTO `order_item_with_null_fk` (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0);
-INSERT INTO `order_item_with_null_fk` (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0);
+INSERT INTO "profile" (description) VALUES ('profile customer 1');
+INSERT INTO "profile" (description) VALUES ('profile customer 3');
+
+INSERT INTO "customer" (email, name, address, status, profile_id) VALUES ('user1@example.com', 'user1', 'address1', 1, 1);
+INSERT INTO "customer" (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1);
+INSERT INTO "customer" (email, name, address, status, profile_id) VALUES ('user3@example.com', 'user3', 'address3', 2, 2);
+
+INSERT INTO "category" (name) VALUES ('Books');
+INSERT INTO "category" (name) VALUES ('Movies');
+
+INSERT INTO "item" (name, category_id) VALUES ('Agile Web Application Development with Yii1.1 and PHP5', 1);
+INSERT INTO "item" (name, category_id) VALUES ('Yii 1.1 Application Development Cookbook', 1);
+INSERT INTO "item" (name, category_id) VALUES ('Ice Age', 2);
+INSERT INTO "item" (name, category_id) VALUES ('Toy Story', 2);
+INSERT INTO "item" (name, category_id) VALUES ('Cars', 2);
+
+INSERT INTO "order" (customer_id, created_at, total) VALUES (1, 1325282384, 110.0);
+INSERT INTO "order" (customer_id, created_at, total) VALUES (2, 1325334482, 33.0);
+INSERT INTO "order" (customer_id, created_at, total) VALUES (2, 1325502201, 40.0);
+
+INSERT INTO "order_with_null_fk" (customer_id, created_at, total) VALUES (1, 1325282384, 110.0);
+INSERT INTO "order_with_null_fk" (customer_id, created_at, total) VALUES (2, 1325334482, 33.0);
+INSERT INTO "order_with_null_fk" (customer_id, created_at, total) VALUES (2, 1325502201, 40.0);
+
+INSERT INTO "order_item" (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0);
+INSERT INTO "order_item" (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0);
+INSERT INTO "order_item" (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0);
+INSERT INTO "order_item" (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0);
+INSERT INTO "order_item" (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0);
+INSERT INTO "order_item" (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0);
+
+INSERT INTO "order_item_with_null_fk" (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0);
+INSERT INTO "order_item_with_null_fk" (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0);
+INSERT INTO "order_item_with_null_fk" (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0);
+INSERT INTO "order_item_with_null_fk" (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0);
+INSERT INTO "order_item_with_null_fk" (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0);
+INSERT INTO "order_item_with_null_fk" (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0);
diff --git a/tests/unit/data/mysql.sql b/tests/unit/data/mysql.sql
index deb03f5..aeea84e 100644
--- a/tests/unit/data/mysql.sql
+++ b/tests/unit/data/mysql.sql
@@ -108,18 +108,20 @@ CREATE TABLE null_values (
 );
 
 CREATE TABLE `type` (
-  `int_col` int(11) NOT NULL,
-  `int_col2` int(11) DEFAULT '1',
+  `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,
+  `enum_col` enum('a', 'B'),
   `float_col` double(4,3) NOT NULL,
   `float_col2` double DEFAULT '1.23',
   `blob_col` blob,
   `numeric_col` decimal(5,2) DEFAULT '33.22',
   `time` timestamp NOT NULL DEFAULT '2002-01-01 00:00:00',
   `bool_col` tinyint(1) NOT NULL,
-  `bool_col2` tinyint(1) DEFAULT '1'
+  `bool_col2` tinyint(1) DEFAULT '1',
+  `ts_default` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 INSERT INTO `profile` (description) VALUES ('profile customer 1');
diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql
index 4163d8c..b5ebf3e 100644
--- a/tests/unit/data/postgres.sql
+++ b/tests/unit/data/postgres.sql
@@ -99,7 +99,8 @@ CREATE TABLE "type" (
   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'
+  bool_col2 smallint DEFAULT '1',
+  ts_default TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
 );
 
 INSERT INTO "profile" (description) VALUES ('profile customer 1');
diff --git a/tests/unit/data/sqlite.sql b/tests/unit/data/sqlite.sql
index 09c9da5..fa0653c 100644
--- a/tests/unit/data/sqlite.sql
+++ b/tests/unit/data/sqlite.sql
@@ -103,7 +103,8 @@ CREATE TABLE "type" (
   numeric_col decimal(5,2) DEFAULT '33.22',
   time timestamp NOT NULL DEFAULT '2002-01-01 00:00:00',
   bool_col tinyint(1) NOT NULL,
-  bool_col2 tinyint(1) DEFAULT '1'
+  bool_col2 tinyint(1) DEFAULT '1',
+  ts_default TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
 );
 
 INSERT INTO "profile" (description) VALUES ('profile customer 1');
diff --git a/tests/unit/framework/db/ConnectionTest.php b/tests/unit/framework/db/ConnectionTest.php
index 26fb31a..51be3d7 100644
--- a/tests/unit/framework/db/ConnectionTest.php
+++ b/tests/unit/framework/db/ConnectionTest.php
@@ -79,46 +79,46 @@ class ConnectionTest extends DatabaseTestCase
         $this->assertEquals('(column)', $connection->quoteColumnName('(column)'));
     }
 
-	public function testTransaction()
-	{
-		$connection = $this->getConnection(false);
-		$this->assertNull($connection->transaction);
-		$transaction = $connection->beginTransaction();
-		$this->assertNotNull($connection->transaction);
-		$this->assertTrue($transaction->isActive);
+    public function testTransaction()
+    {
+        $connection = $this->getConnection(false);
+        $this->assertNull($connection->transaction);
+        $transaction = $connection->beginTransaction();
+        $this->assertNotNull($connection->transaction);
+        $this->assertTrue($transaction->isActive);
 
-		$connection->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
+        $connection->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
 
-		$transaction->rollBack();
-		$this->assertFalse($transaction->isActive);
-		$this->assertNull($connection->transaction);
+        $transaction->rollBack();
+        $this->assertFalse($transaction->isActive);
+        $this->assertNull($connection->transaction);
 
-		$this->assertEquals(0, $connection->createCommand("SELECT COUNT(*) FROM profile WHERE description = 'test transaction';")->queryScalar());
+        $this->assertEquals(0, $connection->createCommand("SELECT COUNT(*) FROM profile WHERE description = 'test transaction';")->queryScalar());
 
-		$transaction = $connection->beginTransaction();
-		$connection->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
-		$transaction->commit();
-		$this->assertFalse($transaction->isActive);
-		$this->assertNull($connection->transaction);
+        $transaction = $connection->beginTransaction();
+        $connection->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
+        $transaction->commit();
+        $this->assertFalse($transaction->isActive);
+        $this->assertNull($connection->transaction);
 
-		$this->assertEquals(1, $connection->createCommand("SELECT COUNT(*) FROM profile WHERE description = 'test transaction';")->queryScalar());
-	}
+        $this->assertEquals(1, $connection->createCommand("SELECT COUNT(*) FROM profile WHERE description = 'test transaction';")->queryScalar());
+    }
 
-	public function testTransactionIsolation()
-	{
-		$connection = $this->getConnection(true);
+    public function testTransactionIsolation()
+    {
+        $connection = $this->getConnection(true);
 
-		$transaction = $connection->beginTransaction(Transaction::READ_UNCOMMITTED);
-		$transaction->commit();
+        $transaction = $connection->beginTransaction(Transaction::READ_UNCOMMITTED);
+        $transaction->commit();
 
-		$transaction = $connection->beginTransaction(Transaction::READ_COMMITTED);
-		$transaction->commit();
+        $transaction = $connection->beginTransaction(Transaction::READ_COMMITTED);
+        $transaction->commit();
 
-		$transaction = $connection->beginTransaction(Transaction::REPEATABLE_READ);
-		$transaction->commit();
+        $transaction = $connection->beginTransaction(Transaction::REPEATABLE_READ);
+        $transaction->commit();
 
-		$transaction = $connection->beginTransaction(Transaction::SERIALIZABLE);
-		$transaction->commit();
-	}
+        $transaction = $connection->beginTransaction(Transaction::SERIALIZABLE);
+        $transaction->commit();
+    }
 
 }
diff --git a/tests/unit/framework/db/SchemaTest.php b/tests/unit/framework/db/SchemaTest.php
index b77269c..b0734d9 100644
--- a/tests/unit/framework/db/SchemaTest.php
+++ b/tests/unit/framework/db/SchemaTest.php
@@ -3,6 +3,7 @@
 namespace yiiunit\framework\db;
 
 use yii\caching\FileCache;
+use yii\db\Expression;
 use yii\db\Schema;
 
 /**
@@ -90,4 +91,210 @@ class SchemaTest extends DatabaseTestCase
         }
         fclose($fp);
     }
+
+    public function getExpectedColumns()
+    {
+        return [
+            'int_col' => [
+                'type' => 'integer',
+                'dbType' => 'int(11)',
+                'phpType' => 'integer',
+                'allowNull' => false,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => 11,
+                'precision' => 11,
+                'scale' => null,
+                'defaultValue' => null,
+            ],
+            'int_col2' => [
+                'type' => 'integer',
+                'dbType' => 'int(11)',
+                'phpType' => 'integer',
+                'allowNull' => true,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => 11,
+                'precision' => 11,
+                'scale' => null,
+                'defaultValue' => 1,
+            ],
+            'char_col' => [
+                'type' => 'string',
+                'dbType' => 'char(100)',
+                'phpType' => 'string',
+                'allowNull' => false,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => 100,
+                'precision' => 100,
+                'scale' => null,
+                'defaultValue' => null,
+            ],
+            'char_col2' => [
+                'type' => 'string',
+                'dbType' => 'varchar(100)',
+                'phpType' => 'string',
+                'allowNull' => true,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => 100,
+                'precision' => 100,
+                'scale' => null,
+                'defaultValue' => 'something',
+            ],
+            'char_col3' => [
+                'type' => 'text',
+                'dbType' => 'text',
+                'phpType' => 'string',
+                'allowNull' => true,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => null,
+                'precision' => null,
+                'scale' => null,
+                'defaultValue' => null,
+            ],
+            'enum_col' => [
+                'type' => 'string',
+                'dbType' => "enum('a','B')",
+                'phpType' => 'string',
+                'allowNull' => true,
+                'autoIncrement' => false,
+                'enumValues' => ['a', 'B'],
+                'size' => null,
+                'precision' => null,
+                'scale' => null,
+                'defaultValue' => null,
+            ],
+            'float_col' => [
+                'type' => 'float',
+                'dbType' => 'double(4,3)',
+                'phpType' => 'double',
+                'allowNull' => false,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => 4,
+                'precision' => 4,
+                'scale' => 3,
+                'defaultValue' => null,
+            ],
+            'float_col2' => [
+                'type' => 'float',
+                'dbType' => 'double',
+                'phpType' => 'double',
+                'allowNull' => true,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => null,
+                'precision' => null,
+                'scale' => null,
+                'defaultValue' => 1.23,
+            ],
+            'blob_col' => [
+                'type' => 'string',
+                'dbType' => 'blob',
+                'phpType' => 'string',
+                'allowNull' => true,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => null,
+                'precision' => null,
+                'scale' => null,
+                'defaultValue' => null,
+            ],
+            'numeric_col' => [
+                'type' => 'decimal',
+                'dbType' => 'decimal(5,2)',
+                'phpType' => 'string',
+                'allowNull' => true,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => 5,
+                'precision' => 5,
+                'scale' => 2,
+                'defaultValue' => '33.22',
+            ],
+            'time' => [
+                'type' => 'timestamp',
+                'dbType' => 'timestamp',
+                'phpType' => 'string',
+                'allowNull' => false,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => null,
+                'precision' => null,
+                'scale' => null,
+                'defaultValue' => '2002-01-01 00:00:00',
+            ],
+            'bool_col' => [
+                'type' => 'smallint',
+                'dbType' => 'tinyint(1)',
+                'phpType' => 'integer',
+                'allowNull' => false,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => 1,
+                'precision' => 1,
+                'scale' => null,
+                'defaultValue' => null,
+            ],
+            'bool_col2' => [
+                'type' => 'smallint',
+                'dbType' => 'tinyint(1)',
+                'phpType' => 'integer',
+                'allowNull' => true,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => 1,
+                'precision' => 1,
+                'scale' => null,
+                'defaultValue' => 1,
+            ],
+            'ts_default' => [
+                'type' => 'timestamp',
+                'dbType' => 'timestamp',
+                'phpType' => 'string',
+                'allowNull' => false,
+                'autoIncrement' => false,
+                'enumValues' => null,
+                'size' => null,
+                'precision' => null,
+                'scale' => null,
+                'defaultValue' => new Expression('CURRENT_TIMESTAMP'),
+            ],
+        ];
+    }
+
+    public function testColumnSchema()
+    {
+        $columns = $this->getExpectedColumns();
+
+        $table = $this->getConnection(false)->schema->getTableSchema('type', true);
+
+        $expectedColNames = array_keys($columns);
+        sort($expectedColNames);
+        $colNames = $table->columnNames;
+        sort($colNames);
+        $this->assertEquals($expectedColNames, $colNames);
+
+        foreach($table->columns as $name => $column) {
+            $expected = $columns[$name];
+            $this->assertSame($expected['dbType'], $column->dbType, "dbType of colum $name does not match. type is $column->type, dbType is $column->dbType.");
+            $this->assertSame($expected['phpType'], $column->phpType, "phpType of colum $name does not match. type is $column->type, dbType is $column->dbType.");
+            $this->assertSame($expected['type'], $column->type, "type of colum $name does not match.");
+            $this->assertSame($expected['allowNull'], $column->allowNull, "allowNull of colum $name does not match.");
+            $this->assertSame($expected['autoIncrement'], $column->autoIncrement, "autoIncrement of colum $name does not match.");
+            $this->assertSame($expected['enumValues'], $column->enumValues, "enumValues of colum $name does not match.");
+            $this->assertSame($expected['size'], $column->size, "size of colum $name does not match.");
+            $this->assertSame($expected['precision'], $column->precision, "precision of colum $name does not match.");
+            $this->assertSame($expected['scale'], $column->scale, "scale of colum $name does not match.");
+            if (is_object($expected['defaultValue'])) {
+                $this->assertTrue(is_object($column->defaultValue), "defaultValue of colum $name is expected to be an object but it is not.");
+                $this->assertEquals((string) $expected['defaultValue'], (string) $column->defaultValue, "defaultValue of colum $name does not match.");
+            } else {
+                $this->assertSame($expected['defaultValue'], $column->defaultValue, "defaultValue of colum $name does not match.");
+            }
+        }
+    }
 }
diff --git a/tests/unit/framework/db/cubrid/CubridSchemaTest.php b/tests/unit/framework/db/cubrid/CubridSchemaTest.php
index 4f85220..003b405 100644
--- a/tests/unit/framework/db/cubrid/CubridSchemaTest.php
+++ b/tests/unit/framework/db/cubrid/CubridSchemaTest.php
@@ -1,6 +1,7 @@
 <?php
 namespace yiiunit\framework\db\cubrid;
 
+use yii\db\Expression;
 use yiiunit\framework\db\SchemaTest;
 
 /**
@@ -33,4 +34,37 @@ class CubridSchemaTest extends SchemaTest
         }
         fclose($fp);
     }
+
+
+    public function getExpectedColumns()
+    {
+        $columns = parent::getExpectedColumns();
+        $columns['int_col']['dbType'] = 'integer';
+        $columns['int_col']['size'] = null;
+        $columns['int_col']['precision'] = null;
+        $columns['int_col2']['dbType'] = 'integer';
+        $columns['int_col2']['size'] = null;
+        $columns['int_col2']['precision'] = null;
+        $columns['char_col3']['type'] = 'string';
+        $columns['char_col3']['dbType'] = 'varchar(1073741823)';
+        $columns['char_col3']['size'] = 1073741823;
+        $columns['char_col3']['precision'] = 1073741823;
+        $columns['enum_col']['dbType'] = "enum('a', 'B')";
+        $columns['float_col']['dbType'] = 'double';
+        $columns['float_col']['size'] = null;
+        $columns['float_col']['precision'] = null;
+        $columns['float_col']['scale'] = null;
+        $columns['numeric_col']['dbType'] = 'numeric(5,2)';
+        $columns['blob_col']['phpType'] = 'resource';
+        $columns['blob_col']['type'] = 'binary';
+        $columns['bool_col']['dbType'] = 'short';
+        $columns['bool_col']['size'] = null;
+        $columns['bool_col']['precision'] = null;
+        $columns['bool_col2']['dbType'] = 'short';
+        $columns['bool_col2']['size'] = null;
+        $columns['bool_col2']['precision'] = null;
+        $columns['time']['defaultValue'] = '12:00:00 AM 01/01/2002';
+        $columns['ts_default']['defaultValue'] = new Expression('SYS_TIMESTAMP');
+        return $columns;
+    }
 }
diff --git a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php
index 283edfd..c569a44 100644
--- a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php
+++ b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php
@@ -50,28 +50,28 @@ class PostgreSQLConnectionTest extends ConnectionTest
         $this->assertEquals('(column)', $connection->quoteColumnName('(column)'));
     }
 
-	public function testTransactionIsolation()
-	{
-		$connection = $this->getConnection(true);
+    public function testTransactionIsolation()
+    {
+        $connection = $this->getConnection(true);
 
-		$transaction = $connection->beginTransaction();
-		$transaction->setIsolationLevel(Transaction::READ_UNCOMMITTED);
-		$transaction->commit();
+        $transaction = $connection->beginTransaction();
+        $transaction->setIsolationLevel(Transaction::READ_UNCOMMITTED);
+        $transaction->commit();
 
-		$transaction = $connection->beginTransaction();
-		$transaction->setIsolationLevel(Transaction::READ_COMMITTED);
-		$transaction->commit();
+        $transaction = $connection->beginTransaction();
+        $transaction->setIsolationLevel(Transaction::READ_COMMITTED);
+        $transaction->commit();
 
-		$transaction = $connection->beginTransaction();
-		$transaction->setIsolationLevel(Transaction::REPEATABLE_READ);
-		$transaction->commit();
+        $transaction = $connection->beginTransaction();
+        $transaction->setIsolationLevel(Transaction::REPEATABLE_READ);
+        $transaction->commit();
 
-		$transaction = $connection->beginTransaction();
-		$transaction->setIsolationLevel(Transaction::SERIALIZABLE);
-		$transaction->commit();
+        $transaction = $connection->beginTransaction();
+        $transaction->setIsolationLevel(Transaction::SERIALIZABLE);
+        $transaction->commit();
 
-		$transaction = $connection->beginTransaction();
-		$transaction->setIsolationLevel(Transaction::SERIALIZABLE . ' READ ONLY DEFERRABLE');
-		$transaction->commit();
-	}
+        $transaction = $connection->beginTransaction();
+        $transaction->setIsolationLevel(Transaction::SERIALIZABLE . ' READ ONLY DEFERRABLE');
+        $transaction->commit();
+    }
 }
diff --git a/tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php b/tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php
new file mode 100644
index 0000000..fbb8449
--- /dev/null
+++ b/tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace yiiunit\framework\db\pgsql;
+
+use yii\db\pgsql\Schema;
+use yiiunit\framework\db\QueryTest;
+use yiiunit\framework\db\SchemaTest;
+
+/**
+ * @group db
+ * @group pgsql
+ */
+class PostgreSQLQueryTest extends QueryTest
+{
+    public $driverName = 'pgsql';
+}
diff --git a/tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php b/tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php
new file mode 100644
index 0000000..98b7c2b
--- /dev/null
+++ b/tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace yiiunit\framework\db\pgsql;
+
+use yii\db\pgsql\Schema;
+use yiiunit\framework\db\SchemaTest;
+
+/**
+ * @group db
+ * @group pgsql
+ */
+class PostgreSQLSchemaTest extends SchemaTest
+{
+    public $driverName = 'pgsql';
+
+    public function getExpectedColumns()
+    {
+        $columns = parent::getExpectedColumns();
+        unset($columns['enum_col']);
+        $columns['int_col']['dbType'] = 'integer';
+        $columns['int_col']['size'] = null;
+        $columns['int_col']['precision'] = null;
+        $columns['int_col2']['dbType'] = 'integer';
+        $columns['int_col2']['size'] = null;
+        $columns['int_col2']['precision'] = null;
+        $columns['bool_col']['type'] = 'boolean';
+        $columns['bool_col']['phpType'] = 'boolean';
+        $columns['bool_col2']['type'] = 'boolean';
+        $columns['bool_col2']['phpType'] = 'boolean';
+        $columns['bool_col2']['defaultValue'] = true;
+        return $columns;
+    }
+}
diff --git a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php
index 3806712..9bc1e49 100644
--- a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php
+++ b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php
@@ -47,14 +47,14 @@ class SqliteConnectionTest extends ConnectionTest
         $this->assertEquals('(column)', $connection->quoteColumnName('(column)'));
     }
 
-	public function testTransactionIsolation()
-	{
-		$connection = $this->getConnection(true);
+    public function testTransactionIsolation()
+    {
+        $connection = $this->getConnection(true);
 
-		$transaction = $connection->beginTransaction(Transaction::READ_UNCOMMITTED);
-		$transaction->rollBack();
+        $transaction = $connection->beginTransaction(Transaction::READ_UNCOMMITTED);
+        $transaction->rollBack();
 
-		$transaction = $connection->beginTransaction(Transaction::SERIALIZABLE);
-		$transaction->rollBack();
-	}
+        $transaction = $connection->beginTransaction(Transaction::SERIALIZABLE);
+        $transaction->rollBack();
+    }
 }
diff --git a/tests/unit/framework/db/sqlite/SqliteSchemaTest.php b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php
index 1cb6c6d..7862049 100644
--- a/tests/unit/framework/db/sqlite/SqliteSchemaTest.php
+++ b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php
@@ -10,4 +10,23 @@ use yiiunit\framework\db\SchemaTest;
 class SqliteSchemaTest extends SchemaTest
 {
     protected $driverName = 'sqlite';
+
+    public function getExpectedColumns()
+    {
+        $columns = parent::getExpectedColumns();
+        unset($columns['enum_col']);
+        $columns['int_col']['dbType'] = 'integer';
+        $columns['int_col']['size'] = null;
+        $columns['int_col']['precision'] = null;
+        $columns['int_col2']['dbType'] = 'integer';
+        $columns['int_col2']['size'] = null;
+        $columns['int_col2']['precision'] = null;
+        $columns['bool_col']['type'] = 'boolean';
+        $columns['bool_col']['phpType'] = 'boolean';
+        $columns['bool_col2']['type'] = 'boolean';
+        $columns['bool_col2']['phpType'] = 'boolean';
+        $columns['bool_col2']['defaultValue'] = true;
+        return $columns;
+    }
+
 }
--
libgit2 0.27.1