diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index 064c7ee..d956691 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -237,10 +237,10 @@ class Connection extends Component 'mysql' => 'yii\db\mysql\Schema', // MySQL 'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3 'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2 - 'mssql' => 'yii\db\dao\mssql\Schema', // Mssql driver on windows hosts - 'sqlsrv' => 'yii\db\mssql\Schema', // Mssql + 'sqlsrv' => 'yii\db\mssql\Schema', // newer MSSQL driver on MS Windows hosts 'oci' => 'yii\db\oci\Schema', // Oracle driver - 'dblib' => 'yii\db\mssql\Schema', // dblib drivers on linux (and maybe others os) hosts + 'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts + 'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts ); /** * @var Transaction the currently active transaction diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index 9e7749a..c0b4223 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -429,10 +429,11 @@ class QueryBuilder extends \yii\base\Object * Builds a SQL statement for enabling or disabling integrity check. * @param boolean $check whether to turn on or off the integrity check. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @param string $table the table name. Defaults to empty string, meaning that no table will be changed. * @return string the SQL statement for checking integrity * @throws NotSupportedException if this is not supported by the underlying DBMS */ - public function checkIntegrity($check = true, $schema = '') + public function checkIntegrity($check = true, $schema = '', $table = '') { throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.'); } diff --git a/framework/yii/db/mssql/PDO.php b/framework/yii/db/mssql/PDO.php new file mode 100644 index 0000000..c29cd4c --- /dev/null +++ b/framework/yii/db/mssql/PDO.php @@ -0,0 +1,61 @@ + + * @since 2.0 + */ +class PDO extends \PDO +{ + /** + * 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) + { + return $this->query('SELECT CAST(COALESCE(SCOPE_IDENTITY(), @@IDENTITY) AS bigint)')->fetchColumn(); + } + + /** + * Starts a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not + * natively support transactions. + * @return boolean the result of a transaction start. + */ + public function beginTransaction() + { + $this->exec('BEGIN TRANSACTION'); + return true; + } + + /** + * Commits a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not + * natively support transactions. + * @return boolean the result of a transaction commit. + */ + public function commit() + { + $this->exec('COMMIT TRANSACTION'); + return true; + } + + /** + * Rollbacks a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not + * natively support transactions. + * @return boolean the result of a transaction rollback. + */ + public function rollBack() + { + $this->exec('ROLLBACK TRANSACTION'); + return true; + } +} diff --git a/framework/yii/db/mssql/QueryBuilder.php b/framework/yii/db/mssql/QueryBuilder.php new file mode 100644 index 0000000..45a7507 --- /dev/null +++ b/framework/yii/db/mssql/QueryBuilder.php @@ -0,0 +1,81 @@ + + * @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 => 'int IDENTITY PRIMARY KEY', + Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_TEXT => 'text', + Schema::TYPE_SMALLINT => 'smallint(6)', + Schema::TYPE_INTEGER => 'int(11)', + Schema::TYPE_BIGINT => 'bigint(20)', + Schema::TYPE_FLOAT => 'float', + Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_DATETIME => 'datetime', + Schema::TYPE_TIMESTAMP => 'timestamp', + Schema::TYPE_TIME => 'time', + Schema::TYPE_DATE => 'date', + Schema::TYPE_BINARY => 'binary', + Schema::TYPE_BOOLEAN => 'tinyint(1)', + Schema::TYPE_MONEY => 'decimal(19,4)', + ); + +// public function update($table, $columns, $condition, &$params) +// { +// return ''; +// } + +// public function delete($table, $condition, &$params) +// { +// return ''; +// } + +// public function buildLimit($limit, $offset) +// { +// return ''; +// } + +// public function resetSequence($table, $value = null) +// { +// return ''; +// } + + /** + * Builds a SQL statement for enabling or disabling integrity check. + * @param boolean $check whether to turn on or off the integrity check. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @param string $table the table name. Defaults to empty string, meaning that no table will be changed. + * @return string the SQL statement for checking integrity + * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table. + */ + public function checkIntegrity($check = true, $schema = '', $table = '') + { + if ($schema !== '') { + $table = "{$schema}.{$table}"; + } + $table = $this->db->quoteTableName($table); + if ($this->db->getTableSchema($table) === null) { + throw new InvalidParamException("Table not found: $table"); + } + $enable = $check ? 'CHECK' : 'NOCHECK'; + return "ALTER TABLE {$table} {$enable} CONSTRAINT ALL"; + } +} diff --git a/framework/yii/db/mssql/Schema.php b/framework/yii/db/mssql/Schema.php new file mode 100644 index 0000000..c52dfc3 --- /dev/null +++ b/framework/yii/db/mssql/Schema.php @@ -0,0 +1,364 @@ + + * @since 2.0 + */ +class Schema extends \yii\db\Schema +{ + /** + * Default schema name to be used. + */ + const DEFAULT_SCHEMA = 'dbo'; + + /** + * @var array mapping from physical column types (keys) to abstract column types (values) + */ + public $typeMap = array( + // exact numerics + 'bigint' => self::TYPE_BIGINT, + 'numeric' => self::TYPE_DECIMAL, + 'bit' => self::TYPE_SMALLINT, + 'smallint' => self::TYPE_SMALLINT, + 'decimal' => self::TYPE_DECIMAL, + 'smallmoney' => self::TYPE_MONEY, + 'int' => self::TYPE_INTEGER, + 'tinyint' => self::TYPE_SMALLINT, + 'money' => self::TYPE_MONEY, + + // approximate numerics + 'float' => self::TYPE_FLOAT, + 'real' => self::TYPE_FLOAT, + + // date and time + 'date' => self::TYPE_DATE, + 'datetimeoffset' => self::TYPE_DATETIME, + 'datetime2' => self::TYPE_DATETIME, + 'smalldatetime' => self::TYPE_DATETIME, + 'datetime' => self::TYPE_DATETIME, + 'time' => self::TYPE_TIME, + + // character strings + 'char' => self::TYPE_STRING, + 'varchar' => self::TYPE_STRING, + 'text' => self::TYPE_TEXT, + + // unicode character strings + 'nchar' => self::TYPE_STRING, + 'nvarchar' => self::TYPE_STRING, + 'ntext' => self::TYPE_TEXT, + + // binary strings + 'binary' => self::TYPE_BINARY, + 'varbinary' => self::TYPE_BINARY, + 'image' => self::TYPE_BINARY, + + // other data types + // 'cursor' type cannot be used with tables + 'timestamp' => self::TYPE_TIMESTAMP, + 'hierarchyid' => self::TYPE_STRING, + 'uniqueidentifier' => self::TYPE_STRING, + 'sql_variant' => self::TYPE_STRING, + 'xml' => self::TYPE_STRING, + 'table' => self::TYPE_STRING, + ); + + /** + * 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; + } + + /** + * Quotes a column name for use in a query. + * A simple column name has no prefix. + * @param string $name column name. + * @return string the properly quoted column name. + */ + public function quoteSimpleColumnName($name) + { + return strpos($name, '[') === false && $name !== '*' ? "[{$name}]" : $name; + } + + /** + * Creates a query builder for the MSSQL database. + * @return QueryBuilder query builder interface. + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->db); + } + + /** + * 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; + } + } + + /** + * 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(array('[', ']'), '', $name)); + $partCount = count($parts); + if ($partCount == 3) { + // catalog name, schema name and table name passed + $table->catalogName = $parts[0]; + $table->schemaName = $parts[1]; + $table->name = $parts[2]; + } elseif ($partCount == 2) { + // only schema name and table name passed + $table->schemaName = $parts[0]; + $table->name = $parts[1]; + } else { + // only schema name passed + $table->schemaName = static::DEFAULT_SCHEMA; + $table->name = $parts[0]; + } + } + + /** + * 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->name = $info['COLUMN_NAME']; + $column->allowNull = $info['IS_NULLABLE'] == 'YES'; + $column->dbType = $info['DATA_TYPE']; + $column->enumValues = array(); // mssql has only vague equivalents to enum + $column->isPrimaryKey = null; // primary key will be determined in findColumns() method + $column->autoIncrement = $info['IsIdentity'] == 1; + $column->unsigned = stripos($column->dbType, 'unsigned') !== false; + $column->comment = $info['Comment'] === null ? '' : $column['Comment']; + + $column->type = self::TYPE_STRING; + if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { + $type = $matches[1]; + if (isset($this->typeMap[$type])) { + $column->type = $this->typeMap[$type]; + } + if (!empty($matches[2])) { + $values = explode(',', $matches[2]); + $column->size = $column->precision = (int)$values[0]; + if (isset($values[1])) { + $column->scale = (int)$values[1]; + } + if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) { + $column->type = 'boolean'; + } elseif ($type === 'bit') { + if ($column->size > 32) { + $column->type = 'bigint'; + } elseif ($column->size === 32) { + $column->type = 'integer'; + } + } + } + } + + $column->phpType = $this->getColumnPhpType($column); + + if ($info['COLUMN_DEFAULT'] == '(NULL)') { + $info['COLUMN_DEFAULT'] = null; + } + if ($column->type !== 'timestamp' || $info['COLUMN_DEFAULT'] !== 'CURRENT_TIMESTAMP') { + $column->defaultValue = $column->typecast($info['COLUMN_DEFAULT']); + } + + return $column; + } + + /** + * 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) + { + $columnsTableName = 'information_schema.columns'; + $whereSql = "[t1].[table_name] = '{$table->name}'"; + if ($table->catalogName !== null) { + $columnsTableName = "{$table->catalogName}.{$columnsTableName}"; + $whereSql .= " AND [t1].[table_catalog] = '{$table->catalogName}'"; + } + if ($table->schemaName !== null) { + $whereSql .= " AND [t1].[table_schema] = '{$table->schemaName}'"; + } + $columnsTableName = $this->quoteTableName($columnsTableName); + + $sql = <<db->createCommand($sql)->queryAll(); + } catch (\Exception $e) { + return false; + } + foreach ($columns as $column) { + $column = $this->loadColumnSchema($column); + if (is_array($table->primaryKey)) { + foreach ($table->primaryKey as $primaryKeyColumn) { + if (strcasecmp($column->name, $primaryKeyColumn) === 0) { + $column->isPrimaryKey = true; + break; + } + } + } else { + $column->isPrimaryKey = strcasecmp($column->name, $table->primaryKey) === 0; + } + if ($column->isPrimaryKey && $column->autoIncrement) { + $table->sequenceName = ''; + } + $table->columns[$column->name] = $column; + } + return true; + } + + /** + * Collects the primary key column details for the given table. + * @param TableSchema $table the table metadata + */ + protected function findPrimaryKeys($table) + { + $keyColumnUsageTableName = 'information_schema.key_column_usage'; + $tableConstraintsTableName = 'information_schema.table_constraints'; + if ($table->catalogName !== null) { + $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName; + $tableConstraintsTableName = $table->catalogName . '.' . $tableConstraintsTableName; + } + $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName); + $tableConstraintsTableName = $this->quoteTableName($tableConstraintsTableName); + + $sql = <<primaryKey = $this->db + ->createCommand($sql, array(':tableName' => $table->name, ':schemaName' => $table->schemaName)) + ->queryColumn(); + } + + /** + * Collects the foreign key column details for the given table. + * @param TableSchema $table the table metadata + */ + protected function findForeignKeys($table) + { + $referentialConstraintsTableName = 'information_schema.referential_constraints'; + $keyColumnUsageTableName = 'information_schema.key_column_usage'; + if ($table->catalogName !== null) { + $referentialConstraintsTableName = $table->catalogName . '.' . $referentialConstraintsTableName; + $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName; + } + $referentialConstraintsTableName = $this->quoteTableName($referentialConstraintsTableName); + $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName); + + // please refer to the following page for more details: + // http://msdn2.microsoft.com/en-us/library/aa175805(SQL.80).aspx + $sql = <<db->createCommand($sql, array(':tableName' => $table->name))->queryAll(); + $table->foreignKeys = array(); + foreach ($rows as $row) { + $table->foreignKeys[] = array($row['uq_table_name'], $row['fk_column_name'] => $row['uq_column_name']); + } + } + + /** + * Returns all table names in the database. + * This method should be overridden by child classes in order to support this feature + * because the default implementation simply throws an exception. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @return array all table names in the database. The names have NO the schema name prefix. + */ + protected function findTableNames($schema = '') + { + if ($schema === '') { + $schema = static::DEFAULT_SCHEMA; + } + + $sql = <<db->createCommand($sql, array(':schema' => $schema))->queryColumn(); + if ($schema !== static::DEFAULT_SCHEMA) { + foreach ($names as $index => $name) { + $names[$index] = $schema . '.' . $name; + } + } + return $names; + } +} diff --git a/framework/yii/db/mssql/SqlsrvPDO.php b/framework/yii/db/mssql/SqlsrvPDO.php new file mode 100644 index 0000000..29444c5 --- /dev/null +++ b/framework/yii/db/mssql/SqlsrvPDO.php @@ -0,0 +1,33 @@ + + * @since 2.0 + */ +class SqlsrvPDO extends \PDO +{ + /** + * Returns value of the last inserted ID. + * + * SQLSRV driver implements [[PDO::lastInsertId()]] method but with a single peculiarity: + * when `$sequence` value is a null or an empty string it returns an empty string. + * But when parameter is not specified it works as expected and returns actual + * last inserted ID (like the other PDO drivers). + * @param string|null $sequence the sequence name. Defaults to null. + * @return integer last inserted ID value. + */ + public function lastInsertId($sequence = null) + { + return !$sequence ? parent::lastInsertId() : parent::lastInsertId($sequence); + } +} diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index a078b9a..7bf293b 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -120,12 +120,13 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * Builds a SQL statement for enabling or disabling integrity check. * @param boolean $check whether to turn on or off the integrity check. - * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @param string $table the table name. Meaningless for MySQL. + * @param string $schema the schema of the tables. Meaningless for MySQL. * @return string the SQL statement for checking integrity */ - public function checkIntegrity($check = true, $schema = '') + public function checkIntegrity($check = true, $schema = '', $table = '') { - return 'SET FOREIGN_KEY_CHECKS=' . ($check ? 1 : 0); + return 'SET FOREIGN_KEY_CHECKS = ' . ($check ? 1 : 0); } /** diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php index 3aa89e7..72d48f4 100644 --- a/framework/yii/db/sqlite/QueryBuilder.php +++ b/framework/yii/db/sqlite/QueryBuilder.php @@ -77,10 +77,11 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * Enables or disables integrity check. * @param boolean $check whether to turn on or off the integrity check. - * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @param string $schema the schema of the tables. Meaningless for SQLite. + * @param string $table the table name. Meaningless for SQLite. * @throws NotSupportedException this is not supported by SQLite */ - public function checkIntegrity($check = true, $schema = '') + public function checkIntegrity($check = true, $schema = '', $table = '') { throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); } diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index 3dcf845..88c8127 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -14,5 +14,11 @@ return array( 'dsn' => 'sqlite::memory:', 'fixture' => __DIR__ . '/sqlite.sql', ), - ), + 'sqlsrv' => array( + 'dsn' => 'sqlsrv:Server=localhost;Database=test', + 'username' => '', + 'password' => '', + 'fixture' => __DIR__ . '/mssql.sql', + ), + ) ); diff --git a/tests/unit/data/mssql.sql b/tests/unit/data/mssql.sql index 38967b2..d3bc8d2 100644 --- a/tests/unit/data/mssql.sql +++ b/tests/unit/data/mssql.sql @@ -1,306 +1,94 @@ -SET ANSI_NULLS ON -GO -SET QUOTED_IDENTIFIER ON -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[categories]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) -BEGIN -CREATE TABLE [dbo].[categories]( +IF OBJECT_ID('[dbo].[tbl_order_item]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_order_item]; +IF OBJECT_ID('[dbo].[tbl_item]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_item]; +IF OBJECT_ID('[dbo].[tbl_order]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_order]; +IF OBJECT_ID('[dbo].[tbl_category]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_category]; +IF OBJECT_ID('[dbo].[tbl_customer]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_customer]; +IF OBJECT_ID('[dbo].[tbl_type]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_type]; + +CREATE TABLE [dbo].[tbl_customer] ( [id] [int] IDENTITY(1,1) NOT NULL, + [email] [varchar](128) NOT NULL, [name] [varchar](128) NOT NULL, - [parent_id] [int] NULL, - CONSTRAINT [PK_categories] PRIMARY KEY CLUSTERED -( - [id] ASC -) ON [PRIMARY] -) ON [PRIMARY] -END -GO -SET ANSI_NULLS ON -GO -SET QUOTED_IDENTIFIER ON -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[orders]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) -BEGIN -CREATE TABLE [dbo].[orders]( - [key1] [int] NOT NULL, - [key2] [int] NOT NULL, + [address] [text], + [status] [int] DEFAULT 0, + CONSTRAINT [PK_customer] PRIMARY KEY CLUSTERED ( + [id] ASC + ) ON [PRIMARY] +); + +CREATE TABLE [dbo].[tbl_category] ( + [id] [int] IDENTITY(1,1) NOT NULL, [name] [varchar](128) NOT NULL, - CONSTRAINT [PK_orders] PRIMARY KEY CLUSTERED -( - [key1] ASC, - [key2] ASC -) ON [PRIMARY] -) ON [PRIMARY] -END -GO -SET ANSI_NULLS ON -GO -SET QUOTED_IDENTIFIER ON -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[types]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) -BEGIN -CREATE TABLE [dbo].[types]( - [int_col] [int] NOT NULL, - [int_col2] [int] NULL CONSTRAINT [DF_types_int_col2] DEFAULT (1), - [char_col] [char](100) NOT NULL, - [char_col2] [varchar](100) NULL CONSTRAINT [DF_types_char_col2] DEFAULT ('something'), - [char_col3] [text] NULL, - [float_col] [real] NOT NULL, - [float_col2] [float] NULL CONSTRAINT [DF_types_float_col2] DEFAULT (1.23), - [blob_col] [image] NULL, - [numeric_col] [numeric](5, 2) NULL CONSTRAINT [DF_types_numeric_col] DEFAULT (33.22), - [time] [datetime] NULL CONSTRAINT [DF_types_time] DEFAULT ('2002-01-01 00:00:00'), - [bool_col] [bit] NOT NULL, - [bool_col2] [bit] NOT NULL CONSTRAINT [DF_types_bool_col2] DEFAULT (1) -) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] -END -GO -SET ANSI_NULLS ON -GO -SET QUOTED_IDENTIFIER ON -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[users]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) -BEGIN -CREATE TABLE [dbo].[users]( + CONSTRAINT [PK_category] PRIMARY KEY CLUSTERED ( + [id] ASC + ) ON [PRIMARY] +); + +CREATE TABLE [dbo].[tbl_item] ( [id] [int] IDENTITY(1,1) NOT NULL, - [username] [varchar](128) NOT NULL, - [password] [varchar](128) NOT NULL, - [email] [varchar](128) NOT NULL, - CONSTRAINT [PK_users] PRIMARY KEY CLUSTERED -( - [id] ASC -) ON [PRIMARY] -) ON [PRIMARY] -END -GO -SET ANSI_NULLS ON -GO -SET QUOTED_IDENTIFIER ON -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[post_category]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) -BEGIN -CREATE TABLE [dbo].[post_category]( + [name] [varchar](128) NOT NULL, [category_id] [int] NOT NULL, - [post_id] [int] NOT NULL, - CONSTRAINT [PK_post_category] PRIMARY KEY CLUSTERED -( - [category_id] ASC, - [post_id] ASC -) ON [PRIMARY] -) ON [PRIMARY] -END -GO -SET ANSI_NULLS ON -GO -SET QUOTED_IDENTIFIER ON -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[items]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) -BEGIN -CREATE TABLE [dbo].[items]( - [id] [int] IDENTITY(1,1) NOT NULL, - [name] [varchar](128) NULL, - [col1] [int] NOT NULL, - [col2] [int] NOT NULL, - CONSTRAINT [PK_items] PRIMARY KEY CLUSTERED -( - [id] ASC -) ON [PRIMARY] -) ON [PRIMARY] -END -GO -SET ANSI_NULLS ON -GO -SET QUOTED_IDENTIFIER ON -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[comments]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) -BEGIN -CREATE TABLE [dbo].[comments]( - [id] [int] IDENTITY(1,1) NOT NULL, - [content] [text] NOT NULL, - [post_id] [int] NOT NULL, - [author_id] [int] NOT NULL, - CONSTRAINT [PK_comments] PRIMARY KEY CLUSTERED -( - [id] ASC -) ON [PRIMARY] -) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] -END -GO -SET ANSI_NULLS ON -GO -SET QUOTED_IDENTIFIER ON -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[posts]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) -BEGIN -CREATE TABLE [dbo].[posts]( - [id] [int] IDENTITY(1,1) NOT NULL, - [title] [varchar](128) NOT NULL, - [create_time] [datetime] NOT NULL, - [author_id] [int] NOT NULL, - [content] [text] NULL, - CONSTRAINT [PK_posts] PRIMARY KEY CLUSTERED -( - [id] ASC -) ON [PRIMARY] -) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] -END -GO -SET ANSI_NULLS ON -GO -SET QUOTED_IDENTIFIER ON -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[profiles]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) -BEGIN -CREATE TABLE [dbo].[profiles]( - [id] [int] IDENTITY(1,1) NOT NULL, - [first_name] [varchar](128) NOT NULL, - [last_name] [varchar](128) NOT NULL, - [user_id] [int] NOT NULL, - CONSTRAINT [PK_profiles] PRIMARY KEY CLUSTERED -( - [id] ASC -) ON [PRIMARY] -) ON [PRIMARY] -END -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_categories_categories]') AND type = 'F') -ALTER TABLE [dbo].[categories] WITH CHECK ADD CONSTRAINT [FK_categories_categories] FOREIGN KEY([parent_id]) -REFERENCES [dbo].[categories] ([id]) -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_post_category_categories]') AND type = 'F') -ALTER TABLE [dbo].[post_category] WITH CHECK ADD CONSTRAINT [FK_post_category_categories] FOREIGN KEY([category_id]) -REFERENCES [dbo].[categories] ([id]) -ON DELETE CASCADE -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_post_category_posts]') AND type = 'F') -ALTER TABLE [dbo].[post_category] WITH NOCHECK ADD CONSTRAINT [FK_post_category_posts] FOREIGN KEY([post_id]) -REFERENCES [dbo].[posts] ([id]) -ON DELETE CASCADE -GO -ALTER TABLE [dbo].[post_category] CHECK CONSTRAINT [FK_post_category_posts] -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_items_orders]') AND type = 'F') -ALTER TABLE [dbo].[items] WITH CHECK ADD CONSTRAINT [FK_items_orders] FOREIGN KEY([col1], [col2]) -REFERENCES [dbo].[orders] ([key1], [key2]) -ON DELETE CASCADE -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_comments_users]') AND type = 'F') -ALTER TABLE [dbo].[comments] WITH NOCHECK ADD CONSTRAINT [FK_comments_users] FOREIGN KEY([author_id]) -REFERENCES [dbo].[users] ([id]) -ON DELETE CASCADE -GO -ALTER TABLE [dbo].[comments] CHECK CONSTRAINT [FK_comments_users] -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_post_comment]') AND type = 'F') -ALTER TABLE [dbo].[comments] WITH NOCHECK ADD CONSTRAINT [FK_post_comment] FOREIGN KEY([post_id]) -REFERENCES [dbo].[posts] ([id]) -ON DELETE CASCADE -GO -ALTER TABLE [dbo].[comments] CHECK CONSTRAINT [FK_post_comment] -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_posts_users]') AND type = 'F') -ALTER TABLE [dbo].[posts] WITH NOCHECK ADD CONSTRAINT [FK_posts_users] FOREIGN KEY([author_id]) -REFERENCES [dbo].[users] ([id]) -GO -ALTER TABLE [dbo].[posts] CHECK CONSTRAINT [FK_posts_users] -GO -IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_profile_user]') AND type = 'F') -ALTER TABLE [dbo].[profiles] WITH NOCHECK ADD CONSTRAINT [FK_profile_user] FOREIGN KEY([user_id]) -REFERENCES [dbo].[users] ([id]) -ON DELETE CASCADE -GO -ALTER TABLE [dbo].[profiles] CHECK CONSTRAINT [FK_profile_user] - -INSERT INTO users (username, password, email) VALUES ('user1','pass1','email1') -GO -INSERT INTO users (username, password, email) VALUES ('user2','pass2','email2') -GO -INSERT INTO users (username, password, email) VALUES ('user3','pass3','email3') -GO + CONSTRAINT [PK_item] PRIMARY KEY CLUSTERED ( + [id] ASC + ) ON [PRIMARY] +); -INSERT INTO profiles (first_name, last_name, user_id) VALUES ('first 1','last 1',1) -GO -INSERT INTO profiles (first_name, last_name, user_id) VALUES ('first 2','last 2',2) -GO - -INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 1','2000-01-01',1,'content 1') -GO -INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 2','2000-01-02',2,'content 2') -GO -INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 3','2000-01-03',2,'content 3') -GO -INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 4','2000-01-04',2,'content 4') -GO -INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 5','2000-01-05',3,'content 5') -GO +CREATE TABLE [dbo].[tbl_order] ( + [id] [int] IDENTITY(1,1) NOT NULL, + [customer_id] [int] NOT NULL, + [create_time] [int] NOT NULL, + [total] [decimal](10,0) NOT NULL, + CONSTRAINT [PK_order] PRIMARY KEY CLUSTERED ( + [id] ASC + ) ON [PRIMARY] +); -INSERT INTO comments (content, post_id, author_id) VALUES ('comment 1',1, 2) -GO -INSERT INTO comments (content, post_id, author_id) VALUES ('comment 2',1, 2) -GO -INSERT INTO comments (content, post_id, author_id) VALUES ('comment 3',1, 2) -GO -INSERT INTO comments (content, post_id, author_id) VALUES ('comment 4',2, 2) -GO -INSERT INTO comments (content, post_id, author_id) VALUES ('comment 5',2, 2) -GO -INSERT INTO comments (content, post_id, author_id) VALUES ('comment 6',3, 2) -GO -INSERT INTO comments (content, post_id, author_id) VALUES ('comment 7',3, 2) -GO -INSERT INTO comments (content, post_id, author_id) VALUES ('comment 8',3, 2) -GO -INSERT INTO comments (content, post_id, author_id) VALUES ('comment 9',3, 2) -GO -INSERT INTO comments (content, post_id, author_id) VALUES ('comment 10',5, 3) -GO +CREATE TABLE [dbo].[tbl_order_item] ( + [order_id] [int] NOT NULL, + [item_id] [int] NOT NULL, + [quantity] [int] NOT NULL, + [subtotal] [decimal](10,0) NOT NULL, + CONSTRAINT [PK_order_item] PRIMARY KEY CLUSTERED ( + [order_id] ASC, + [item_id] ASC + ) ON [PRIMARY] +); -INSERT INTO categories (name, parent_id) VALUES ('cat 1',NULL) -GO -INSERT INTO categories (name, parent_id) VALUES ('cat 2',NULL) -GO -INSERT INTO categories (name, parent_id) VALUES ('cat 3',NULL) -GO -INSERT INTO categories (name, parent_id) VALUES ('cat 4',1) -GO -INSERT INTO categories (name, parent_id) VALUES ('cat 5',1) -GO -INSERT INTO categories (name, parent_id) VALUES ('cat 6',5) -GO -INSERT INTO categories (name, parent_id) VALUES ('cat 7',5) -GO +CREATE TABLE [dbo].[tbl_type] ( + [int_col] [int] NOT NULL, + [int_col2] [int] DEFAULT '1', + [char_col] [char](100) NOT NULL, + [char_col2] [varchar](100) DEFAULT 'something', + [char_col3] [text], + [float_col] [decimal](4,3) NOT NULL, + [float_col2] [float] DEFAULT '1.23', + [blob_col] [binary], + [numeric_col] [decimal](5,2) DEFAULT '33.22', + [time] [datetime] NOT NULL DEFAULT '2002-01-01 00:00:00', + [bool_col] [tinyint] NOT NULL, + [bool_col2] [tinyint] DEFAULT '1' +); -INSERT INTO post_category (category_id, post_id) VALUES (1,1) -GO -INSERT INTO post_category (category_id, post_id) VALUES (2,1) -GO -INSERT INTO post_category (category_id, post_id) VALUES (3,1) -GO -INSERT INTO post_category (category_id, post_id) VALUES (4,2) -GO -INSERT INTO post_category (category_id, post_id) VALUES (1,2) -GO -INSERT INTO post_category (category_id, post_id) VALUES (1,3) -GO +INSERT INTO [dbo].[tbl_customer] ([email], [name], [address], [status]) VALUES ('user1@example.com', 'user1', 'address1', 1); +INSERT INTO [dbo].[tbl_customer] ([email], [name], [address], [status]) VALUES ('user2@example.com', 'user2', 'address2', 1); +INSERT INTO [dbo].[tbl_customer] ([email], [name], [address], [status]) VALUES ('user3@example.com', 'user3', 'address3', 2); +INSERT INTO [dbo].[tbl_category] ([name]) VALUES ('Books'); +INSERT INTO [dbo].[tbl_category] ([name]) VALUES ('Movies'); -INSERT INTO orders (key1,key2,name) VALUES (1,2,'order 12') -GO -INSERT INTO orders (key1,key2,name) VALUES (1,3,'order 13') -GO -INSERT INTO orders (key1,key2,name) VALUES (2,1,'order 21') -GO -INSERT INTO orders (key1,key2,name) VALUES (2,2,'order 22') -GO +INSERT INTO [dbo].[tbl_item] ([name], [category_id]) VALUES ('Agile Web Application Development with Yii1.1 and PHP5', 1); +INSERT INTO [dbo].[tbl_item] ([name], [category_id]) VALUES ('Yii 1.1 Application Development Cookbook', 1); +INSERT INTO [dbo].[tbl_item] ([name], [category_id]) VALUES ('Ice Age', 2); +INSERT INTO [dbo].[tbl_item] ([name], [category_id]) VALUES ('Toy Story', 2); +INSERT INTO [dbo].[tbl_item] ([name], [category_id]) VALUES ('Cars', 2); +INSERT INTO [dbo].[tbl_order] ([customer_id], [create_time], [total]) VALUES (1, 1325282384, 110.0); +INSERT INTO [dbo].[tbl_order] ([customer_id], [create_time], [total]) VALUES (2, 1325334482, 33.0); +INSERT INTO [dbo].[tbl_order] ([customer_id], [create_time], [total]) VALUES (2, 1325502201, 40.0); -INSERT INTO items (name,col1,col2) VALUES ('item 1',1,2) -GO -INSERT INTO items (name,col1,col2) VALUES ('item 2',1,2) -GO -INSERT INTO items (name,col1,col2) VALUES ('item 3',1,3) -GO -INSERT INTO items (name,col1,col2) VALUES ('item 4',2,2) -GO -INSERT INTO items (name,col1,col2) VALUES ('item 5',2,2) -GO +INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (1, 1, 1, 30.0); +INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (1, 2, 2, 40.0); +INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (2, 4, 1, 10.0); +INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (2, 5, 1, 15.0); +INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (2, 3, 1, 8.0); +INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (3, 2, 1, 40.0); diff --git a/tests/unit/framework/db/mssql/MssqlActiveRecordTest.php b/tests/unit/framework/db/mssql/MssqlActiveRecordTest.php new file mode 100644 index 0000000..2cf0b01 --- /dev/null +++ b/tests/unit/framework/db/mssql/MssqlActiveRecordTest.php @@ -0,0 +1,12 @@ +driverName = 'sqlsrv'; + parent::setUp(); + } +} diff --git a/tests/unit/framework/db/mssql/MssqlCommandTest.php b/tests/unit/framework/db/mssql/MssqlCommandTest.php new file mode 100644 index 0000000..422a00c --- /dev/null +++ b/tests/unit/framework/db/mssql/MssqlCommandTest.php @@ -0,0 +1,31 @@ +driverName = 'sqlsrv'; + parent::setUp(); + } + + function testAutoQuoting() + { + $db = $this->getConnection(false); + + $sql = 'SELECT [[id]], [[t.name]] FROM {{tbl_customer}} t'; + $command = $db->createCommand($sql); + $this->assertEquals("SELECT [id], [t].[name] FROM [tbl_customer] t", $command->sql); + } + + function testPrepareCancel() + { + $this->markTestIncomplete(); + } + + function testBindParamValue() + { + $this->markTestIncomplete(); + } +} diff --git a/tests/unit/framework/db/mssql/MssqlConnectionTest.php b/tests/unit/framework/db/mssql/MssqlConnectionTest.php new file mode 100644 index 0000000..870af0a --- /dev/null +++ b/tests/unit/framework/db/mssql/MssqlConnectionTest.php @@ -0,0 +1,43 @@ +driverName = 'sqlsrv'; + parent::setUp(); + } + + 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('{{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('[[column]]', $connection->quoteColumnName('[[column]]')); + $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); + $this->assertEquals('(column)', $connection->quoteColumnName('(column)')); + } +} diff --git a/tests/unit/framework/db/mssql/MssqlQueryTest.php b/tests/unit/framework/db/mssql/MssqlQueryTest.php new file mode 100644 index 0000000..17d6681 --- /dev/null +++ b/tests/unit/framework/db/mssql/MssqlQueryTest.php @@ -0,0 +1,12 @@ +driverName = 'sqlsrv'; + parent::setUp(); + } +}