Browse Source

Merge pull request #351 from yiisoft/mssql

Initial MSSQL support.
tags/2.0.0-beta
Qiang Xue 12 years ago
parent
commit
66a5b9c3de
  1. 6
      framework/yii/db/Connection.php
  2. 3
      framework/yii/db/QueryBuilder.php
  3. 61
      framework/yii/db/mssql/PDO.php
  4. 81
      framework/yii/db/mssql/QueryBuilder.php
  5. 364
      framework/yii/db/mssql/Schema.php
  6. 33
      framework/yii/db/mssql/SqlsrvPDO.php
  7. 5
      framework/yii/db/mysql/QueryBuilder.php
  8. 5
      framework/yii/db/sqlite/QueryBuilder.php
  9. 6
      tests/unit/data/config.php
  10. 360
      tests/unit/data/mssql.sql
  11. 12
      tests/unit/framework/db/mssql/MssqlActiveRecordTest.php
  12. 31
      tests/unit/framework/db/mssql/MssqlCommandTest.php
  13. 43
      tests/unit/framework/db/mssql/MssqlConnectionTest.php
  14. 12
      tests/unit/framework/db/mssql/MssqlQueryTest.php

6
framework/yii/db/Connection.php

@ -237,10 +237,10 @@ class Connection extends Component
'mysql' => 'yii\db\mysql\Schema', // MySQL 'mysql' => 'yii\db\mysql\Schema', // MySQL
'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3 'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3
'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2 'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2
'mssql' => 'yii\db\dao\mssql\Schema', // Mssql driver on windows hosts 'sqlsrv' => 'yii\db\mssql\Schema', // newer MSSQL driver on MS Windows hosts
'sqlsrv' => 'yii\db\mssql\Schema', // Mssql
'oci' => 'yii\db\oci\Schema', // Oracle driver '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 * @var Transaction the currently active transaction

3
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. * Builds a SQL statement for enabling or disabling integrity check.
* @param boolean $check whether to turn on or off the 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. 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 * @return string the SQL statement for checking integrity
* @throws NotSupportedException if this is not supported by the underlying DBMS * @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.'); throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.');
} }

61
framework/yii/db/mssql/PDO.php

@ -0,0 +1,61 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\mssql;
/**
* This is an extension of the default PDO class of MSSQL and DBLIB drivers.
* It provides workarounds for improperly implemented functionalities of the MSSQL and DBLIB drivers.
*
* @author Timur Ruziev <resurtm@gmail.com>
* @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;
}
}

81
framework/yii/db/mssql/QueryBuilder.php

@ -0,0 +1,81 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\mssql;
use yii\base\InvalidParamException;
/**
* QueryBuilder is the query builder for MS SQL Server databases (version 2008 and above).
*
* @author Timur Ruziev <resurtm@gmail.com>
* @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";
}
}

364
framework/yii/db/mssql/Schema.php

@ -0,0 +1,364 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\mssql;
use yii\db\TableSchema;
use yii\db\ColumnSchema;
/**
* Schema is the class for retrieving metadata from a MS SQL Server databases (version 2008 and above).
*
* @author Timur Ruziev <resurtm@gmail.com>
* @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 = <<<SQL
SELECT
[t1].*,
COLUMNPROPERTY(OBJECT_ID([t1].[table_schema] + '.' + [t1].[table_name]), [t1].[column_name], 'IsIdentity') AS IsIdentity,
CONVERT(VARCHAR, [t2].[value]) AS Comment
FROM {$columnsTableName} AS [t1]
LEFT OUTER JOIN [sys].[extended_properties] AS [t2] ON
[t1].[ordinal_position] = [t2].[minor_id] AND
OBJECT_NAME([t2].[major_id]) = [t1].[table_name] AND
[t2].[class] = 1 AND
[t2].[class_desc] = 'OBJECT_OR_COLUMN' AND
[t2].[name] = 'MS_Description'
WHERE {$whereSql}
SQL;
try {
$columns = $this->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 = <<<SQL
SELECT
[kcu].[column_name] AS [field_name]
FROM {$keyColumnUsageTableName} AS [kcu]
LEFT JOIN {$tableConstraintsTableName} AS [tc] ON
[kcu].[table_name] = [tc].[table_name] AND
[kcu].[constraint_name] = [tc].[constraint_name]
WHERE
[tc].[constraint_type] = 'PRIMARY KEY' AND
[kcu].[table_name] = :tableName AND
[kcu].[table_schema] = :schemaName
SQL;
$table->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 = <<<SQL
SELECT
[kcu1].[column_name] AS [fk_column_name],
[kcu2].[table_name] AS [uq_table_name],
[kcu2].[column_name] AS [uq_column_name]
FROM {$referentialConstraintsTableName} AS [rc]
JOIN {$keyColumnUsageTableName} AS [kcu1] ON
[kcu1].[constraint_catalog] = [rc].[constraint_catalog] AND
[kcu1].[constraint_schema] = [rc].[constraint_schema] AND
[kcu1].[constraint_name] = [rc].[constraint_name]
JOIN {$keyColumnUsageTableName} AS [kcu2] ON
[kcu2].[constraint_catalog] = [rc].[constraint_catalog] AND
[kcu2].[constraint_schema] = [rc].[constraint_schema] AND
[kcu2].[constraint_name] = [rc].[constraint_name] AND
[kcu2].[ordinal_position] = [kcu1].[ordinal_position]
WHERE [kcu1].[table_name] = :tableName
SQL;
$rows = $this->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 = <<<SQL
SELECT [t].[table]
FROM [information_schema].[tables] AS [t]
WHERE [t].[table_schema] = :schema AND [t].[table_type] = 'BASE TABLE'
SQL;
$names = $this->db->createCommand($sql, array(':schema' => $schema))->queryColumn();
if ($schema !== static::DEFAULT_SCHEMA) {
foreach ($names as $index => $name) {
$names[$index] = $schema . '.' . $name;
}
}
return $names;
}
}

33
framework/yii/db/mssql/SqlsrvPDO.php

@ -0,0 +1,33 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\mssql;
/**
* This is an extension of the default PDO class of SQLSRV driver.
* It provides workarounds for improperly implemented functionalities of the SQLSRV driver.
*
* @author Timur Ruziev <resurtm@gmail.com>
* @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);
}
}

5
framework/yii/db/mysql/QueryBuilder.php

@ -120,10 +120,11 @@ class QueryBuilder extends \yii\db\QueryBuilder
/** /**
* Builds a SQL statement for enabling or disabling integrity check. * Builds a SQL statement for enabling or disabling integrity check.
* @param boolean $check whether to turn on or off the 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 * @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);
} }

5
framework/yii/db/sqlite/QueryBuilder.php

@ -77,10 +77,11 @@ class QueryBuilder extends \yii\db\QueryBuilder
/** /**
* Enables or disables integrity check. * Enables or disables integrity check.
* @param boolean $check whether to turn on or off the 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 * @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.'); throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
} }

6
tests/unit/data/config.php

@ -14,5 +14,11 @@ return array(
'dsn' => 'sqlite::memory:', 'dsn' => 'sqlite::memory:',
'fixture' => __DIR__ . '/sqlite.sql', 'fixture' => __DIR__ . '/sqlite.sql',
), ),
'sqlsrv' => array(
'dsn' => 'sqlsrv:Server=localhost;Database=test',
'username' => '',
'password' => '',
'fixture' => __DIR__ . '/mssql.sql',
), ),
)
); );

360
tests/unit/data/mssql.sql

@ -1,306 +1,94 @@
SET ANSI_NULLS ON IF OBJECT_ID('[dbo].[tbl_order_item]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_order_item];
GO IF OBJECT_ID('[dbo].[tbl_item]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_item];
SET QUOTED_IDENTIFIER ON IF OBJECT_ID('[dbo].[tbl_order]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_order];
GO IF OBJECT_ID('[dbo].[tbl_category]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_category];
IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[categories]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1) IF OBJECT_ID('[dbo].[tbl_customer]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_customer];
BEGIN IF OBJECT_ID('[dbo].[tbl_type]', 'U') IS NOT NULL DROP TABLE [dbo].[tbl_type];
CREATE TABLE [dbo].[categories](
[id] [int] IDENTITY(1,1) NOT NULL, CREATE TABLE [dbo].[tbl_customer] (
[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,
[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](
[id] [int] IDENTITY(1,1) NOT NULL, [id] [int] IDENTITY(1,1) NOT NULL,
[username] [varchar](128) NOT NULL,
[password] [varchar](128) NOT NULL,
[email] [varchar](128) NOT NULL, [email] [varchar](128) NOT NULL,
CONSTRAINT [PK_users] PRIMARY KEY CLUSTERED [name] [varchar](128) NOT NULL,
( [address] [text],
[id] ASC [status] [int] DEFAULT 0,
) ON [PRIMARY] CONSTRAINT [PK_customer] PRIMARY KEY CLUSTERED (
) 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](
[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 [id] ASC
) ON [PRIMARY] ) ON [PRIMARY]
) ON [PRIMARY] );
END
GO CREATE TABLE [dbo].[tbl_category] (
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, [id] [int] IDENTITY(1,1) NOT NULL,
[content] [text] NOT NULL, [name] [varchar](128) NOT NULL,
[post_id] [int] NOT NULL, CONSTRAINT [PK_category] PRIMARY KEY CLUSTERED (
[author_id] [int] NOT NULL,
CONSTRAINT [PK_comments] PRIMARY KEY CLUSTERED
(
[id] ASC [id] ASC
) ON [PRIMARY] ) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] );
END
GO CREATE TABLE [dbo].[tbl_item] (
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, [id] [int] IDENTITY(1,1) NOT NULL,
[title] [varchar](128) NOT NULL, [name] [varchar](128) NOT NULL,
[create_time] [datetime] NOT NULL, [category_id] [int] NOT NULL,
[author_id] [int] NOT NULL, CONSTRAINT [PK_item] PRIMARY KEY CLUSTERED (
[content] [text] NULL,
CONSTRAINT [PK_posts] PRIMARY KEY CLUSTERED
(
[id] ASC [id] ASC
) ON [PRIMARY] ) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] );
END
GO CREATE TABLE [dbo].[tbl_order] (
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, [id] [int] IDENTITY(1,1) NOT NULL,
[first_name] [varchar](128) NOT NULL, [customer_id] [int] NOT NULL,
[last_name] [varchar](128) NOT NULL, [create_time] [int] NOT NULL,
[user_id] [int] NOT NULL, [total] [decimal](10,0) NOT NULL,
CONSTRAINT [PK_profiles] PRIMARY KEY CLUSTERED CONSTRAINT [PK_order] PRIMARY KEY CLUSTERED (
(
[id] ASC [id] ASC
) ON [PRIMARY] ) 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
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') CREATE TABLE [dbo].[tbl_order_item] (
GO [order_id] [int] NOT NULL,
INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 2','2000-01-02',2,'content 2') [item_id] [int] NOT NULL,
GO [quantity] [int] NOT NULL,
INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 3','2000-01-03',2,'content 3') [subtotal] [decimal](10,0) NOT NULL,
GO CONSTRAINT [PK_order_item] PRIMARY KEY CLUSTERED (
INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 4','2000-01-04',2,'content 4') [order_id] ASC,
GO [item_id] ASC
INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 5','2000-01-05',3,'content 5') ) ON [PRIMARY]
GO );
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
INSERT INTO categories (name, parent_id) VALUES ('cat 1',NULL) CREATE TABLE [dbo].[tbl_type] (
GO [int_col] [int] NOT NULL,
INSERT INTO categories (name, parent_id) VALUES ('cat 2',NULL) [int_col2] [int] DEFAULT '1',
GO [char_col] [char](100) NOT NULL,
INSERT INTO categories (name, parent_id) VALUES ('cat 3',NULL) [char_col2] [varchar](100) DEFAULT 'something',
GO [char_col3] [text],
INSERT INTO categories (name, parent_id) VALUES ('cat 4',1) [float_col] [decimal](4,3) NOT NULL,
GO [float_col2] [float] DEFAULT '1.23',
INSERT INTO categories (name, parent_id) VALUES ('cat 5',1) [blob_col] [binary],
GO [numeric_col] [decimal](5,2) DEFAULT '33.22',
INSERT INTO categories (name, parent_id) VALUES ('cat 6',5) [time] [datetime] NOT NULL DEFAULT '2002-01-01 00:00:00',
GO [bool_col] [tinyint] NOT NULL,
INSERT INTO categories (name, parent_id) VALUES ('cat 7',5) [bool_col2] [tinyint] DEFAULT '1'
GO );
INSERT INTO post_category (category_id, post_id) VALUES (1,1) INSERT INTO [dbo].[tbl_customer] ([email], [name], [address], [status]) VALUES ('user1@example.com', 'user1', 'address1', 1);
GO INSERT INTO [dbo].[tbl_customer] ([email], [name], [address], [status]) VALUES ('user2@example.com', 'user2', 'address2', 1);
INSERT INTO post_category (category_id, post_id) VALUES (2,1) INSERT INTO [dbo].[tbl_customer] ([email], [name], [address], [status]) VALUES ('user3@example.com', 'user3', 'address3', 2);
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_category] ([name]) VALUES ('Books');
INSERT INTO [dbo].[tbl_category] ([name]) VALUES ('Movies');
INSERT INTO orders (key1,key2,name) VALUES (1,2,'order 12') INSERT INTO [dbo].[tbl_item] ([name], [category_id]) VALUES ('Agile Web Application Development with Yii1.1 and PHP5', 1);
GO INSERT INTO [dbo].[tbl_item] ([name], [category_id]) VALUES ('Yii 1.1 Application Development Cookbook', 1);
INSERT INTO orders (key1,key2,name) VALUES (1,3,'order 13') INSERT INTO [dbo].[tbl_item] ([name], [category_id]) VALUES ('Ice Age', 2);
GO INSERT INTO [dbo].[tbl_item] ([name], [category_id]) VALUES ('Toy Story', 2);
INSERT INTO orders (key1,key2,name) VALUES (2,1,'order 21') INSERT INTO [dbo].[tbl_item] ([name], [category_id]) VALUES ('Cars', 2);
GO
INSERT INTO orders (key1,key2,name) VALUES (2,2,'order 22')
GO
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) INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (1, 1, 1, 30.0);
GO INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (1, 2, 2, 40.0);
INSERT INTO items (name,col1,col2) VALUES ('item 2',1,2) INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (2, 4, 1, 10.0);
GO INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (2, 5, 1, 15.0);
INSERT INTO items (name,col1,col2) VALUES ('item 3',1,3) INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (2, 3, 1, 8.0);
GO INSERT INTO [dbo].[tbl_order_item] ([order_id], [item_id], [quantity], [subtotal]) VALUES (3, 2, 1, 40.0);
INSERT INTO items (name,col1,col2) VALUES ('item 4',2,2)
GO
INSERT INTO items (name,col1,col2) VALUES ('item 5',2,2)
GO

12
tests/unit/framework/db/mssql/MssqlActiveRecordTest.php

@ -0,0 +1,12 @@
<?php
namespace yiiunit\framework\db\mssql;
class MssqlActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest
{
protected function setUp()
{
$this->driverName = 'sqlsrv';
parent::setUp();
}
}

31
tests/unit/framework/db/mssql/MssqlCommandTest.php

@ -0,0 +1,31 @@
<?php
namespace yiiunit\framework\db\mssql;
class MssqlCommandTest extends \yiiunit\framework\db\CommandTest
{
public function setUp()
{
$this->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();
}
}

43
tests/unit/framework/db/mssql/MssqlConnectionTest.php

@ -0,0 +1,43 @@
<?php
namespace yiiunit\framework\db\mssql;
class MssqlConnectionTest extends \yiiunit\framework\db\ConnectionTest
{
public function setUp()
{
$this->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)'));
}
}

12
tests/unit/framework/db/mssql/MssqlQueryTest.php

@ -0,0 +1,12 @@
<?php
namespace yiiunit\framework\db\mssql;
class MssqlQueryTest extends \yiiunit\framework\db\QueryTest
{
public function setUp()
{
$this->driverName = 'sqlsrv';
parent::setUp();
}
}
Loading…
Cancel
Save