Paul Klimov
7 years ago
40 changed files with 0 additions and 4080 deletions
@ -1,87 +0,0 @@
|
||||
<?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 int 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 bool 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 bool 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 bool the result of a transaction roll back. |
||||
*/ |
||||
public function rollBack() |
||||
{ |
||||
$this->exec('ROLLBACK TRANSACTION'); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Retrieve a database connection attribute. |
||||
* |
||||
* It is necessary to override PDO's method as some MSSQL PDO driver (e.g. dblib) does not |
||||
* support getting attributes. |
||||
* @param int $attribute One of the PDO::ATTR_* constants. |
||||
* @return mixed A successful call returns the value of the requested PDO attribute. |
||||
* An unsuccessful call returns null. |
||||
*/ |
||||
public function getAttribute($attribute) |
||||
{ |
||||
try { |
||||
return parent::getAttribute($attribute); |
||||
} catch (\PDOException $e) { |
||||
switch ($attribute) { |
||||
case self::ATTR_SERVER_VERSION: |
||||
return $this->query("SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR)")->fetchColumn(); |
||||
default: |
||||
throw $e; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,422 +0,0 @@
|
||||
<?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\InvalidArgumentException; |
||||
use yii\db\Constraint; |
||||
use yii\db\Expression; |
||||
|
||||
/** |
||||
* 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 = [ |
||||
Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY', |
||||
Schema::TYPE_UPK => 'int IDENTITY PRIMARY KEY', |
||||
Schema::TYPE_BIGPK => 'bigint IDENTITY PRIMARY KEY', |
||||
Schema::TYPE_UBIGPK => 'bigint IDENTITY PRIMARY KEY', |
||||
Schema::TYPE_CHAR => 'nchar(1)', |
||||
Schema::TYPE_STRING => 'nvarchar(255)', |
||||
Schema::TYPE_TEXT => 'nvarchar(max)', |
||||
Schema::TYPE_TINYINT => 'tinyint', |
||||
Schema::TYPE_SMALLINT => 'smallint', |
||||
Schema::TYPE_INTEGER => 'int', |
||||
Schema::TYPE_BIGINT => 'bigint', |
||||
Schema::TYPE_FLOAT => 'float', |
||||
Schema::TYPE_DOUBLE => 'float', |
||||
Schema::TYPE_DECIMAL => 'decimal(18,0)', |
||||
Schema::TYPE_DATETIME => 'datetime', |
||||
Schema::TYPE_TIMESTAMP => 'datetime', |
||||
Schema::TYPE_TIME => 'time', |
||||
Schema::TYPE_DATE => 'date', |
||||
Schema::TYPE_BINARY => 'varbinary(max)', |
||||
Schema::TYPE_BOOLEAN => 'bit', |
||||
Schema::TYPE_MONEY => 'decimal(19,4)', |
||||
]; |
||||
|
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function defaultExpressionBuilders() |
||||
{ |
||||
return array_merge(parent::defaultExpressionBuilders(), [ |
||||
'yii\db\conditions\InCondition' => 'yii\db\mssql\conditions\InConditionBuilder', |
||||
'yii\db\conditions\LikeCondition' => 'yii\db\mssql\conditions\LikeConditionBuilder', |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset, &$params) |
||||
{ |
||||
if (!$this->hasOffset($offset) && !$this->hasLimit($limit)) { |
||||
$orderBy = $this->buildOrderBy($orderBy, $params); |
||||
return $orderBy === '' ? $sql : $sql . $this->separator . $orderBy; |
||||
} |
||||
|
||||
if (version_compare($this->db->getSchema()->getServerVersion(), '11', '<')) { |
||||
return $this->oldBuildOrderByAndLimit($sql, $orderBy, $limit, $offset, $params); |
||||
} |
||||
|
||||
return $this->newBuildOrderByAndLimit($sql, $orderBy, $limit, $offset, $params); |
||||
} |
||||
|
||||
/** |
||||
* Builds the ORDER BY/LIMIT/OFFSET clauses for SQL SERVER 2012 or newer. |
||||
* @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET) |
||||
* @param array $orderBy the order by columns. See [[\yii\db\Query::orderBy]] for more details on how to specify this parameter. |
||||
* @param int $limit the limit number. See [[\yii\db\Query::limit]] for more details. |
||||
* @param int $offset the offset number. See [[\yii\db\Query::offset]] for more details. |
||||
* @param array $params the binding parameters to be populated |
||||
* @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any) |
||||
*/ |
||||
protected function newBuildOrderByAndLimit($sql, $orderBy, $limit, $offset, &$params) |
||||
{ |
||||
$orderBy = $this->buildOrderBy($orderBy, $params); |
||||
if ($orderBy === '') { |
||||
// ORDER BY clause is required when FETCH and OFFSET are in the SQL |
||||
$orderBy = 'ORDER BY (SELECT NULL)'; |
||||
} |
||||
$sql .= $this->separator . $orderBy; |
||||
|
||||
// http://technet.microsoft.com/en-us/library/gg699618.aspx |
||||
$offset = $this->hasOffset($offset) ? $offset : '0'; |
||||
$sql .= $this->separator . "OFFSET $offset ROWS"; |
||||
if ($this->hasLimit($limit)) { |
||||
$sql .= $this->separator . "FETCH NEXT $limit ROWS ONLY"; |
||||
} |
||||
|
||||
return $sql; |
||||
} |
||||
|
||||
/** |
||||
* Builds the ORDER BY/LIMIT/OFFSET clauses for SQL SERVER 2005 to 2008. |
||||
* @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET) |
||||
* @param array $orderBy the order by columns. See [[\yii\db\Query::orderBy]] for more details on how to specify this parameter. |
||||
* @param int $limit the limit number. See [[\yii\db\Query::limit]] for more details. |
||||
* @param int $offset the offset number. See [[\yii\db\Query::offset]] for more details. |
||||
* @param array $params the binding parameters to be populated |
||||
* @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any) |
||||
*/ |
||||
protected function oldBuildOrderByAndLimit($sql, $orderBy, $limit, $offset, &$params) |
||||
{ |
||||
$orderBy = $this->buildOrderBy($orderBy, $params); |
||||
if ($orderBy === '') { |
||||
// ROW_NUMBER() requires an ORDER BY clause |
||||
$orderBy = 'ORDER BY (SELECT NULL)'; |
||||
} |
||||
|
||||
$sql = preg_replace('/^([\s(])*SELECT(\s+DISTINCT)?(?!\s*TOP\s*\()/i', "\\1SELECT\\2 rowNum = ROW_NUMBER() over ($orderBy),", $sql); |
||||
|
||||
if ($this->hasLimit($limit)) { |
||||
$sql = "SELECT TOP $limit * FROM ($sql) sub"; |
||||
} else { |
||||
$sql = "SELECT * FROM ($sql) sub"; |
||||
} |
||||
if ($this->hasOffset($offset)) { |
||||
$sql .= $this->separator . "WHERE rowNum > $offset"; |
||||
} |
||||
|
||||
return $sql; |
||||
} |
||||
|
||||
/** |
||||
* Builds a SQL statement for renaming a DB table. |
||||
* @param string $oldName the table to be renamed. The name will be properly quoted by the method. |
||||
* @param string $newName the new table name. The name will be properly quoted by the method. |
||||
* @return string the SQL statement for renaming a DB table. |
||||
*/ |
||||
public function renameTable($oldName, $newName) |
||||
{ |
||||
return 'sp_rename ' . $this->db->quoteTableName($oldName) . ', ' . $this->db->quoteTableName($newName); |
||||
} |
||||
|
||||
/** |
||||
* Builds a SQL statement for renaming a column. |
||||
* @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. |
||||
* @param string $oldName the old name of the column. The name will be properly quoted by the method. |
||||
* @param string $newName the new name of the column. The name will be properly quoted by the method. |
||||
* @return string the SQL statement for renaming a DB column. |
||||
*/ |
||||
public function renameColumn($table, $oldName, $newName) |
||||
{ |
||||
$table = $this->db->quoteTableName($table); |
||||
$oldName = $this->db->quoteColumnName($oldName); |
||||
$newName = $this->db->quoteColumnName($newName); |
||||
return "sp_rename '{$table}.{$oldName}', {$newName}, 'COLUMN'"; |
||||
} |
||||
|
||||
/** |
||||
* Builds a SQL statement for changing the definition of a column. |
||||
* @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. |
||||
* @param string $column the name of the column to be changed. The name will be properly quoted by the method. |
||||
* @param string $type the new column type. The [[getColumnType]] method will be invoked to convert abstract column type (if any) |
||||
* into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. |
||||
* For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. |
||||
* @return string the SQL statement for changing the definition of a column. |
||||
*/ |
||||
public function alterColumn($table, $column, $type) |
||||
{ |
||||
$type = $this->getColumnType($type); |
||||
$sql = 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN ' |
||||
. $this->db->quoteColumnName($column) . ' ' |
||||
. $this->getColumnType($type); |
||||
|
||||
return $sql; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function addDefaultValue($name, $table, $column, $value) |
||||
{ |
||||
return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT ' |
||||
. $this->db->quoteColumnName($name) . ' DEFAULT ' . $this->db->quoteValue($value) . ' FOR ' |
||||
. $this->db->quoteColumnName($column); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function dropDefaultValue($name, $table) |
||||
{ |
||||
return 'ALTER TABLE ' . $this->db->quoteTableName($table) |
||||
. ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name); |
||||
} |
||||
|
||||
/** |
||||
* Creates a SQL statement for resetting the sequence value of a table's primary key. |
||||
* The sequence will be reset such that the primary key of the next new row inserted |
||||
* will have the specified value or 1. |
||||
* @param string $tableName the name of the table whose primary key sequence will be reset |
||||
* @param mixed $value the value for the primary key of the next new row inserted. If this is not set, |
||||
* the next new row's primary key will have a value 1. |
||||
* @return string the SQL statement for resetting sequence |
||||
* @throws InvalidArgumentException if the table does not exist or there is no sequence associated with the table. |
||||
*/ |
||||
public function resetSequence($tableName, $value = null) |
||||
{ |
||||
$table = $this->db->getTableSchema($tableName); |
||||
if ($table !== null && $table->sequenceName !== null) { |
||||
$tableName = $this->db->quoteTableName($tableName); |
||||
if ($value === null) { |
||||
$key = $this->db->quoteColumnName(reset($table->primaryKey)); |
||||
$value = "(SELECT COALESCE(MAX({$key}),0) FROM {$tableName})+1"; |
||||
} else { |
||||
$value = (int) $value; |
||||
} |
||||
|
||||
return "DBCC CHECKIDENT ('{$tableName}', RESEED, {$value})"; |
||||
} elseif ($table === null) { |
||||
throw new InvalidArgumentException("Table not found: $tableName"); |
||||
} |
||||
|
||||
throw new InvalidArgumentException("There is not sequence associated with table '$tableName'."); |
||||
} |
||||
|
||||
/** |
||||
* Builds a SQL statement for enabling or disabling integrity check. |
||||
* @param bool $check whether to turn on or off the integrity check. |
||||
* @param string $schema the schema of the tables. |
||||
* @param string $table the table name. |
||||
* @return string the SQL statement for checking integrity |
||||
*/ |
||||
public function checkIntegrity($check = true, $schema = '', $table = '') |
||||
{ |
||||
$enable = $check ? 'CHECK' : 'NOCHECK'; |
||||
$schema = $schema ?: $this->db->getSchema()->defaultSchema; |
||||
$tableNames = $this->db->getTableSchema($table) ? [$table] : $this->db->getSchema()->getTableNames($schema); |
||||
$viewNames = $this->db->getSchema()->getViewNames($schema); |
||||
$tableNames = array_diff($tableNames, $viewNames); |
||||
$command = ''; |
||||
|
||||
foreach ($tableNames as $tableName) { |
||||
$tableName = $this->db->quoteTableName("{$schema}.{$tableName}"); |
||||
$command .= "ALTER TABLE $tableName $enable CONSTRAINT ALL; "; |
||||
} |
||||
|
||||
return $command; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @since 2.0.8 |
||||
*/ |
||||
public function addCommentOnColumn($table, $column, $comment) |
||||
{ |
||||
return "sp_updateextendedproperty @name = N'MS_Description', @value = {$this->db->quoteValue($comment)}, @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}, @level2type = N'Column', @level2name = {$this->db->quoteColumnName($column)}"; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @since 2.0.8 |
||||
*/ |
||||
public function addCommentOnTable($table, $comment) |
||||
{ |
||||
return "sp_updateextendedproperty @name = N'MS_Description', @value = {$this->db->quoteValue($comment)}, @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}"; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @since 2.0.8 |
||||
*/ |
||||
public function dropCommentFromColumn($table, $column) |
||||
{ |
||||
return "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}, @level2type = N'Column', @level2name = {$this->db->quoteColumnName($column)}"; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @since 2.0.8 |
||||
*/ |
||||
public function dropCommentFromTable($table) |
||||
{ |
||||
return "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table', @level1name = {$this->db->quoteTableName($table)}"; |
||||
} |
||||
|
||||
/** |
||||
* Returns an array of column names given model name. |
||||
* |
||||
* @param string $modelClass name of the model class |
||||
* @return array|null array of column names |
||||
*/ |
||||
protected function getAllColumnNames($modelClass = null) |
||||
{ |
||||
if (!$modelClass) { |
||||
return null; |
||||
} |
||||
/* @var $modelClass \yii\db\ActiveRecord */ |
||||
$schema = $modelClass::getTableSchema(); |
||||
return array_keys($schema->columns); |
||||
} |
||||
|
||||
/** |
||||
* @return bool whether the version of the MSSQL being used is older than 2012. |
||||
* @throws \yii\base\InvalidConfigException |
||||
* @throws \yii\db\Exception |
||||
* @deprecated 2.0.14 Use [[Schema::getServerVersion]] with [[\version_compare()]]. |
||||
*/ |
||||
protected function isOldMssql() |
||||
{ |
||||
return version_compare($this->db->getSchema()->getServerVersion(), '11', '<'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @since 2.0.8 |
||||
*/ |
||||
public function selectExists($rawSql) |
||||
{ |
||||
return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END'; |
||||
} |
||||
|
||||
/** |
||||
* Normalizes data to be saved into the table, performing extra preparations and type converting, if necessary. |
||||
* @param string $table the table that data will be saved into. |
||||
* @param array $columns the column data (name => value) to be saved into the table. |
||||
* @return array normalized columns |
||||
*/ |
||||
private function normalizeTableRowData($table, $columns, &$params) |
||||
{ |
||||
if (($tableSchema = $this->db->getSchema()->getTableSchema($table)) !== null) { |
||||
$columnSchemas = $tableSchema->columns; |
||||
foreach ($columns as $name => $value) { |
||||
// @see https://github.com/yiisoft/yii2/issues/12599 |
||||
if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && $columnSchemas[$name]->dbType === 'varbinary' && is_string($value)) { |
||||
$exParams = []; |
||||
$phName = $this->bindParam($value, $exParams); |
||||
$columns[$name] = new Expression("CONVERT(VARBINARY, $phName)", $exParams); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $columns; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function insert($table, $columns, &$params) |
||||
{ |
||||
return parent::insert($table, $this->normalizeTableRowData($table, $columns, $params), $params); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @see https://docs.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql |
||||
* @see http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx |
||||
*/ |
||||
public function upsert($table, $insertColumns, $updateColumns, &$params) |
||||
{ |
||||
/** @var Constraint[] $constraints */ |
||||
[$uniqueNames, $insertNames, $updateNames] = $this->prepareUpsertColumns($table, $insertColumns, $updateColumns, $constraints); |
||||
if (empty($uniqueNames)) { |
||||
return $this->insert($table, $insertColumns, $params); |
||||
} |
||||
|
||||
$onCondition = ['or']; |
||||
$quotedTableName = $this->db->quoteTableName($table); |
||||
foreach ($constraints as $constraint) { |
||||
$constraintCondition = ['and']; |
||||
foreach ($constraint->columnNames as $name) { |
||||
$quotedName = $this->db->quoteColumnName($name); |
||||
$constraintCondition[] = "$quotedTableName.$quotedName=[EXCLUDED].$quotedName"; |
||||
} |
||||
$onCondition[] = $constraintCondition; |
||||
} |
||||
$on = $this->buildCondition($onCondition, $params); |
||||
[, $placeholders, $values, $params] = $this->prepareInsertValues($table, $insertColumns, $params); |
||||
$mergeSql = 'MERGE ' . $this->db->quoteTableName($table) . ' WITH (HOLDLOCK) ' |
||||
. 'USING (' . (!empty($placeholders) ? 'VALUES (' . implode(', ', $placeholders) . ')' : ltrim($values, ' ')) . ') AS [EXCLUDED] (' . implode(', ', $insertNames) . ') ' |
||||
. "ON ($on)"; |
||||
$insertValues = []; |
||||
foreach ($insertNames as $name) { |
||||
$quotedName = $this->db->quoteColumnName($name); |
||||
if (strrpos($quotedName, '.') === false) { |
||||
$quotedName = '[EXCLUDED].' . $quotedName; |
||||
} |
||||
$insertValues[] = $quotedName; |
||||
} |
||||
$insertSql = 'INSERT (' . implode(', ', $insertNames) . ')' |
||||
. ' VALUES (' . implode(', ', $insertValues) . ')'; |
||||
if ($updateColumns === false) { |
||||
return "$mergeSql WHEN NOT MATCHED THEN $insertSql;"; |
||||
} |
||||
|
||||
if ($updateColumns === true) { |
||||
$updateColumns = []; |
||||
foreach ($updateNames as $name) { |
||||
$quotedName = $this->db->quoteColumnName($name); |
||||
if (strrpos($quotedName, '.') === false) { |
||||
$quotedName = '[EXCLUDED].' . $quotedName; |
||||
} |
||||
$updateColumns[$name] = new Expression($quotedName); |
||||
} |
||||
} |
||||
[$updates, $params] = $this->prepareUpdateSets($table, $updateColumns, $params); |
||||
$updateSql = 'UPDATE SET ' . implode(', ', $updates); |
||||
return "$mergeSql WHEN MATCHED THEN $updateSql WHEN NOT MATCHED THEN $insertSql;"; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function update($table, $columns, $condition, &$params) |
||||
{ |
||||
return parent::update($table, $this->normalizeTableRowData($table, $columns, $params), $condition, $params); |
||||
} |
||||
} |
@ -1,705 +0,0 @@
|
||||
<?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\CheckConstraint; |
||||
use yii\db\ColumnSchema; |
||||
use yii\db\Constraint; |
||||
use yii\db\ConstraintFinderInterface; |
||||
use yii\db\ConstraintFinderTrait; |
||||
use yii\db\DefaultValueConstraint; |
||||
use yii\db\ForeignKeyConstraint; |
||||
use yii\db\IndexConstraint; |
||||
use yii\db\ViewFinderTrait; |
||||
use yii\helpers\ArrayHelper; |
||||
|
||||
/** |
||||
* Schema is the class for retrieving metadata from MS SQL Server databases (version 2008 and above). |
||||
* |
||||
* @author Timur Ruziev <resurtm@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class Schema extends \yii\db\Schema implements ConstraintFinderInterface |
||||
{ |
||||
use ViewFinderTrait; |
||||
use ConstraintFinderTrait; |
||||
|
||||
/** |
||||
* @var string the default schema used for the current session. |
||||
*/ |
||||
public $defaultSchema = 'dbo'; |
||||
/** |
||||
* @var array mapping from physical column types (keys) to abstract column types (values) |
||||
*/ |
||||
public $typeMap = [ |
||||
// exact numbers |
||||
'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_TINYINT, |
||||
'money' => self::TYPE_MONEY, |
||||
// approximate numbers |
||||
'float' => self::TYPE_FLOAT, |
||||
'double' => self::TYPE_DOUBLE, |
||||
'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_CHAR, |
||||
'varchar' => self::TYPE_STRING, |
||||
'text' => self::TYPE_TEXT, |
||||
// unicode character strings |
||||
'nchar' => self::TYPE_CHAR, |
||||
'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, |
||||
]; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected $tableQuoteCharacter = ['[', ']']; |
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected $columnQuoteCharacter = ['[', ']']; |
||||
|
||||
|
||||
/** |
||||
* Resolves the table name and schema name (if any). |
||||
* @param string $name the table name |
||||
* @return TableSchema resolved table, schema, etc. names. |
||||
*/ |
||||
protected function resolveTableName($name) |
||||
{ |
||||
$resolvedName = new TableSchema(); |
||||
$parts = explode('.', str_replace(['[', ']'], '', $name)); |
||||
$partCount = count($parts); |
||||
if ($partCount === 4) { |
||||
// server name, catalog name, schema name and table name passed |
||||
$resolvedName->catalogName = $parts[1]; |
||||
$resolvedName->schemaName = $parts[2]; |
||||
$resolvedName->name = $parts[3]; |
||||
$resolvedName->fullName = $resolvedName->catalogName . '.' . $resolvedName->schemaName . '.' . $resolvedName->name; |
||||
} elseif ($partCount === 3) { |
||||
// catalog name, schema name and table name passed |
||||
$resolvedName->catalogName = $parts[0]; |
||||
$resolvedName->schemaName = $parts[1]; |
||||
$resolvedName->name = $parts[2]; |
||||
$resolvedName->fullName = $resolvedName->catalogName . '.' . $resolvedName->schemaName . '.' . $resolvedName->name; |
||||
} elseif ($partCount === 2) { |
||||
// only schema name and table name passed |
||||
$resolvedName->schemaName = $parts[0]; |
||||
$resolvedName->name = $parts[1]; |
||||
$resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name; |
||||
} else { |
||||
// only table name passed |
||||
$resolvedName->schemaName = $this->defaultSchema; |
||||
$resolvedName->fullName = $resolvedName->name = $parts[0]; |
||||
} |
||||
|
||||
return $resolvedName; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @see https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-database-principals-transact-sql |
||||
*/ |
||||
protected function findSchemaNames() |
||||
{ |
||||
static $sql = <<<'SQL' |
||||
SELECT [s].[name] |
||||
FROM [sys].[schemas] AS [s] |
||||
INNER JOIN [sys].[database_principals] AS [p] ON [p].[principal_id] = [s].[principal_id] |
||||
WHERE [p].[is_fixed_role] = 0 AND [p].[sid] IS NOT NULL |
||||
ORDER BY [s].[name] ASC |
||||
SQL; |
||||
|
||||
return $this->db->createCommand($sql)->queryColumn(); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function findTableNames($schema = '') |
||||
{ |
||||
if ($schema === '') { |
||||
$schema = $this->defaultSchema; |
||||
} |
||||
|
||||
$sql = <<<'SQL' |
||||
SELECT [t].[table_name] |
||||
FROM [INFORMATION_SCHEMA].[TABLES] AS [t] |
||||
WHERE [t].[table_schema] = :schema AND [t].[table_type] IN ('BASE TABLE', 'VIEW') |
||||
ORDER BY [t].[table_name] |
||||
SQL; |
||||
|
||||
return $this->db->createCommand($sql, [':schema' => $schema])->queryColumn(); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTableSchema($name) |
||||
{ |
||||
$table = new TableSchema(); |
||||
$this->resolveTableNames($table, $name); |
||||
$this->findPrimaryKeys($table); |
||||
if ($this->findColumns($table)) { |
||||
$this->findForeignKeys($table); |
||||
return $table; |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTablePrimaryKey($tableName) |
||||
{ |
||||
return $this->loadTableConstraints($tableName, 'primaryKey'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTableForeignKeys($tableName) |
||||
{ |
||||
return $this->loadTableConstraints($tableName, 'foreignKeys'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTableIndexes($tableName) |
||||
{ |
||||
static $sql = <<<'SQL' |
||||
SELECT |
||||
[i].[name] AS [name], |
||||
[iccol].[name] AS [column_name], |
||||
[i].[is_unique] AS [index_is_unique], |
||||
[i].[is_primary_key] AS [index_is_primary] |
||||
FROM [sys].[indexes] AS [i] |
||||
INNER JOIN [sys].[index_columns] AS [ic] |
||||
ON [ic].[object_id] = [i].[object_id] AND [ic].[index_id] = [i].[index_id] |
||||
INNER JOIN [sys].[columns] AS [iccol] |
||||
ON [iccol].[object_id] = [ic].[object_id] AND [iccol].[column_id] = [ic].[column_id] |
||||
WHERE [i].[object_id] = OBJECT_ID(:fullName) |
||||
ORDER BY [ic].[key_ordinal] ASC |
||||
SQL; |
||||
|
||||
$resolvedName = $this->resolveTableName($tableName); |
||||
$indexes = $this->db->createCommand($sql, [ |
||||
':fullName' => $resolvedName->fullName, |
||||
])->queryAll(); |
||||
$indexes = $this->normalizePdoRowKeyCase($indexes, true); |
||||
$indexes = ArrayHelper::index($indexes, null, 'name'); |
||||
$result = []; |
||||
foreach ($indexes as $name => $index) { |
||||
$result[] = new IndexConstraint([ |
||||
'isPrimary' => (bool) $index[0]['index_is_primary'], |
||||
'isUnique' => (bool) $index[0]['index_is_unique'], |
||||
'name' => $name, |
||||
'columnNames' => ArrayHelper::getColumn($index, 'column_name'), |
||||
]); |
||||
} |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTableUniques($tableName) |
||||
{ |
||||
return $this->loadTableConstraints($tableName, 'uniques'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTableChecks($tableName) |
||||
{ |
||||
return $this->loadTableConstraints($tableName, 'checks'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTableDefaultValues($tableName) |
||||
{ |
||||
return $this->loadTableConstraints($tableName, 'defaults'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function createSavepoint($name) |
||||
{ |
||||
$this->db->createCommand("SAVE TRANSACTION $name")->execute(); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function releaseSavepoint($name) |
||||
{ |
||||
// does nothing as MSSQL does not support this |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function rollBackSavepoint($name) |
||||
{ |
||||
$this->db->createCommand("ROLLBACK TRANSACTION $name")->execute(); |
||||
} |
||||
|
||||
/** |
||||
* Creates a query builder for the MSSQL database. |
||||
* @return QueryBuilder query builder interface. |
||||
*/ |
||||
public function createQueryBuilder() |
||||
{ |
||||
return new QueryBuilder($this->db); |
||||
} |
||||
|
||||
/** |
||||
* Resolves the table name and schema name (if any). |
||||
* @param TableSchema $table the table metadata object |
||||
* @param string $name the table name |
||||
*/ |
||||
protected function resolveTableNames($table, $name) |
||||
{ |
||||
$parts = explode('.', str_replace(['[', ']'], '', $name)); |
||||
$partCount = count($parts); |
||||
if ($partCount === 4) { |
||||
// server name, catalog name, schema name and table name passed |
||||
$table->catalogName = $parts[1]; |
||||
$table->schemaName = $parts[2]; |
||||
$table->name = $parts[3]; |
||||
$table->fullName = $table->catalogName . '.' . $table->schemaName . '.' . $table->name; |
||||
} elseif ($partCount === 3) { |
||||
// catalog name, schema name and table name passed |
||||
$table->catalogName = $parts[0]; |
||||
$table->schemaName = $parts[1]; |
||||
$table->name = $parts[2]; |
||||
$table->fullName = $table->catalogName . '.' . $table->schemaName . '.' . $table->name; |
||||
} elseif ($partCount === 2) { |
||||
// only schema name and table name passed |
||||
$table->schemaName = $parts[0]; |
||||
$table->name = $parts[1]; |
||||
$table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name; |
||||
} else { |
||||
// only table name passed |
||||
$table->schemaName = $this->defaultSchema; |
||||
$table->fullName = $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 = $this->createColumnSchema(); |
||||
|
||||
$column->name = $info['column_name']; |
||||
$column->allowNull = $info['is_nullable'] === 'YES'; |
||||
$column->dbType = $info['data_type']; |
||||
$column->enumValues = []; // mssql has only vague equivalents to enum |
||||
$column->isPrimaryKey = null; // primary key will be determined in findColumns() method |
||||
$column->autoIncrement = $info['is_identity'] == 1; |
||||
$column->unsigned = stripos($column->dbType, 'unsigned') !== false; |
||||
$column->comment = $info['comment'] === null ? '' : $info['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->isPrimaryKey && ($column->type !== 'timestamp' || $info['column_default'] !== 'CURRENT_TIMESTAMP')) { |
||||
$column->defaultValue = $column->phpTypecast($info['column_default']); |
||||
} |
||||
|
||||
return $column; |
||||
} |
||||
|
||||
/** |
||||
* Collects the metadata of table columns. |
||||
* @param TableSchema $table the table metadata |
||||
* @return bool 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].[column_name], |
||||
[t1].[is_nullable], |
||||
[t1].[data_type], |
||||
[t1].[column_default], |
||||
COLUMNPROPERTY(OBJECT_ID([t1].[table_schema] + '.' + [t1].[table_name]), [t1].[column_name], 'IsIdentity') AS is_identity, |
||||
( |
||||
SELECT CONVERT(VARCHAR, [t2].[value]) |
||||
FROM [sys].[extended_properties] AS [t2] |
||||
WHERE |
||||
[t2].[class] = 1 AND |
||||
[t2].[class_desc] = 'OBJECT_OR_COLUMN' AND |
||||
[t2].[name] = 'MS_Description' AND |
||||
[t2].[major_id] = OBJECT_ID([t1].[TABLE_SCHEMA] + '.' + [t1].[table_name]) AND |
||||
[t2].[minor_id] = COLUMNPROPERTY(OBJECT_ID([t1].[TABLE_SCHEMA] + '.' + [t1].[TABLE_NAME]), [t1].[COLUMN_NAME], 'ColumnID') |
||||
) as comment |
||||
FROM {$columnsTableName} AS [t1] |
||||
WHERE {$whereSql} |
||||
SQL; |
||||
|
||||
try { |
||||
$columns = $this->db->createCommand($sql)->queryAll(); |
||||
if (empty($columns)) { |
||||
return false; |
||||
} |
||||
} catch (\Exception $e) { |
||||
return false; |
||||
} |
||||
foreach ($columns as $column) { |
||||
$column = $this->loadColumnSchema($column); |
||||
foreach ($table->primaryKey as $primaryKey) { |
||||
if (strcasecmp($column->name, $primaryKey) === 0) { |
||||
$column->isPrimaryKey = true; |
||||
break; |
||||
} |
||||
} |
||||
if ($column->isPrimaryKey && $column->autoIncrement) { |
||||
$table->sequenceName = ''; |
||||
} |
||||
$table->columns[$column->name] = $column; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Collects the constraint details for the given table and constraint type. |
||||
* @param TableSchema $table |
||||
* @param string $type either PRIMARY KEY or UNIQUE |
||||
* @return array each entry contains index_name and field_name |
||||
* @since 2.0.4 |
||||
*/ |
||||
protected function findTableConstraints($table, $type) |
||||
{ |
||||
$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].[constraint_name] AS [index_name], |
||||
[kcu].[column_name] AS [field_name] |
||||
FROM {$keyColumnUsageTableName} AS [kcu] |
||||
LEFT JOIN {$tableConstraintsTableName} AS [tc] ON |
||||
[kcu].[table_schema] = [tc].[table_schema] AND |
||||
[kcu].[table_name] = [tc].[table_name] AND |
||||
[kcu].[constraint_name] = [tc].[constraint_name] |
||||
WHERE |
||||
[tc].[constraint_type] = :type AND |
||||
[kcu].[table_name] = :tableName AND |
||||
[kcu].[table_schema] = :schemaName |
||||
SQL; |
||||
|
||||
return $this->db |
||||
->createCommand($sql, [ |
||||
':tableName' => $table->name, |
||||
':schemaName' => $table->schemaName, |
||||
':type' => $type, |
||||
]) |
||||
->queryAll(); |
||||
} |
||||
|
||||
/** |
||||
* Collects the primary key column details for the given table. |
||||
* @param TableSchema $table the table metadata |
||||
*/ |
||||
protected function findPrimaryKeys($table) |
||||
{ |
||||
$result = []; |
||||
foreach ($this->findTableConstraints($table, 'PRIMARY KEY') as $row) { |
||||
$result[] = $row['field_name']; |
||||
} |
||||
$table->primaryKey = $result; |
||||
} |
||||
|
||||
/** |
||||
* Collects the foreign key column details for the given table. |
||||
* @param TableSchema $table the table metadata |
||||
*/ |
||||
protected function findForeignKeys($table) |
||||
{ |
||||
$object = $table->name; |
||||
if ($table->schemaName !== null) { |
||||
$object = $table->schemaName . '.' . $object; |
||||
} |
||||
if ($table->catalogName !== null) { |
||||
$object = $table->catalogName . '.' . $object; |
||||
} |
||||
|
||||
// please refer to the following page for more details: |
||||
// http://msdn2.microsoft.com/en-us/library/aa175805(SQL.80).aspx |
||||
$sql = <<<'SQL' |
||||
SELECT |
||||
[fk].[name] AS [fk_name], |
||||
[cp].[name] AS [fk_column_name], |
||||
OBJECT_NAME([fk].[referenced_object_id]) AS [uq_table_name], |
||||
[cr].[name] AS [uq_column_name] |
||||
FROM |
||||
[sys].[foreign_keys] AS [fk] |
||||
INNER JOIN [sys].[foreign_key_columns] AS [fkc] ON |
||||
[fk].[object_id] = [fkc].[constraint_object_id] |
||||
INNER JOIN [sys].[columns] AS [cp] ON |
||||
[fk].[parent_object_id] = [cp].[object_id] AND |
||||
[fkc].[parent_column_id] = [cp].[column_id] |
||||
INNER JOIN [sys].[columns] AS [cr] ON |
||||
[fk].[referenced_object_id] = [cr].[object_id] AND |
||||
[fkc].[referenced_column_id] = [cr].[column_id] |
||||
WHERE |
||||
[fk].[parent_object_id] = OBJECT_ID(:object) |
||||
SQL; |
||||
|
||||
$rows = $this->db->createCommand($sql, [ |
||||
':object' => $object, |
||||
])->queryAll(); |
||||
|
||||
$table->foreignKeys = []; |
||||
foreach ($rows as $row) { |
||||
$table->foreignKeys[$row['fk_name']] = [$row['uq_table_name'], $row['fk_column_name'] => $row['uq_column_name']]; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function findViewNames($schema = '') |
||||
{ |
||||
if ($schema === '') { |
||||
$schema = $this->defaultSchema; |
||||
} |
||||
|
||||
$sql = <<<'SQL' |
||||
SELECT [t].[table_name] |
||||
FROM [INFORMATION_SCHEMA].[TABLES] AS [t] |
||||
WHERE [t].[table_schema] = :schema AND [t].[table_type] = 'VIEW' |
||||
ORDER BY [t].[table_name] |
||||
SQL; |
||||
|
||||
return $this->db->createCommand($sql, [':schema' => $schema])->queryColumn(); |
||||
} |
||||
|
||||
/** |
||||
* Returns all unique indexes for the given table. |
||||
* |
||||
* Each array element is of the following structure: |
||||
* |
||||
* ```php |
||||
* [ |
||||
* 'IndexName1' => ['col1' [, ...]], |
||||
* 'IndexName2' => ['col2' [, ...]], |
||||
* ] |
||||
* ``` |
||||
* |
||||
* @param TableSchema $table the table metadata |
||||
* @return array all unique indexes for the given table. |
||||
* @since 2.0.4 |
||||
*/ |
||||
public function findUniqueIndexes($table) |
||||
{ |
||||
$result = []; |
||||
foreach ($this->findTableConstraints($table, 'UNIQUE') as $row) { |
||||
$result[$row['index_name']][] = $row['field_name']; |
||||
} |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Loads multiple types of constraints and returns the specified ones. |
||||
* @param string $tableName table name. |
||||
* @param string $returnType return type: |
||||
* - primaryKey |
||||
* - foreignKeys |
||||
* - uniques |
||||
* - checks |
||||
* - defaults |
||||
* @return mixed constraints. |
||||
*/ |
||||
private function loadTableConstraints($tableName, $returnType) |
||||
{ |
||||
static $sql = <<<'SQL' |
||||
SELECT |
||||
[o].[name] AS [name], |
||||
COALESCE([ccol].[name], [dcol].[name], [fccol].[name], [kiccol].[name]) AS [column_name], |
||||
RTRIM([o].[type]) AS [type], |
||||
OBJECT_SCHEMA_NAME([f].[referenced_object_id]) AS [foreign_table_schema], |
||||
OBJECT_NAME([f].[referenced_object_id]) AS [foreign_table_name], |
||||
[ffccol].[name] AS [foreign_column_name], |
||||
[f].[update_referential_action_desc] AS [on_update], |
||||
[f].[delete_referential_action_desc] AS [on_delete], |
||||
[c].[definition] AS [check_expr], |
||||
[d].[definition] AS [default_expr] |
||||
FROM (SELECT OBJECT_ID(:fullName) AS [object_id]) AS [t] |
||||
INNER JOIN [sys].[objects] AS [o] |
||||
ON [o].[parent_object_id] = [t].[object_id] AND [o].[type] IN ('PK', 'UQ', 'C', 'D', 'F') |
||||
LEFT JOIN [sys].[check_constraints] AS [c] |
||||
ON [c].[object_id] = [o].[object_id] |
||||
LEFT JOIN [sys].[columns] AS [ccol] |
||||
ON [ccol].[object_id] = [c].[parent_object_id] AND [ccol].[column_id] = [c].[parent_column_id] |
||||
LEFT JOIN [sys].[default_constraints] AS [d] |
||||
ON [d].[object_id] = [o].[object_id] |
||||
LEFT JOIN [sys].[columns] AS [dcol] |
||||
ON [dcol].[object_id] = [d].[parent_object_id] AND [dcol].[column_id] = [d].[parent_column_id] |
||||
LEFT JOIN [sys].[key_constraints] AS [k] |
||||
ON [k].[object_id] = [o].[object_id] |
||||
LEFT JOIN [sys].[index_columns] AS [kic] |
||||
ON [kic].[object_id] = [k].[parent_object_id] AND [kic].[index_id] = [k].[unique_index_id] |
||||
LEFT JOIN [sys].[columns] AS [kiccol] |
||||
ON [kiccol].[object_id] = [kic].[object_id] AND [kiccol].[column_id] = [kic].[column_id] |
||||
LEFT JOIN [sys].[foreign_keys] AS [f] |
||||
ON [f].[object_id] = [o].[object_id] |
||||
LEFT JOIN [sys].[foreign_key_columns] AS [fc] |
||||
ON [fc].[constraint_object_id] = [o].[object_id] |
||||
LEFT JOIN [sys].[columns] AS [fccol] |
||||
ON [fccol].[object_id] = [fc].[parent_object_id] AND [fccol].[column_id] = [fc].[parent_column_id] |
||||
LEFT JOIN [sys].[columns] AS [ffccol] |
||||
ON [ffccol].[object_id] = [fc].[referenced_object_id] AND [ffccol].[column_id] = [fc].[referenced_column_id] |
||||
ORDER BY [kic].[key_ordinal] ASC, [fc].[constraint_column_id] ASC |
||||
SQL; |
||||
|
||||
$resolvedName = $this->resolveTableName($tableName); |
||||
$constraints = $this->db->createCommand($sql, [ |
||||
':fullName' => $resolvedName->fullName, |
||||
])->queryAll(); |
||||
$constraints = $this->normalizePdoRowKeyCase($constraints, true); |
||||
$constraints = ArrayHelper::index($constraints, null, ['type', 'name']); |
||||
$result = [ |
||||
'primaryKey' => null, |
||||
'foreignKeys' => [], |
||||
'uniques' => [], |
||||
'checks' => [], |
||||
'defaults' => [], |
||||
]; |
||||
foreach ($constraints as $type => $names) { |
||||
foreach ($names as $name => $constraint) { |
||||
switch ($type) { |
||||
case 'PK': |
||||
$result['primaryKey'] = new Constraint([ |
||||
'name' => $name, |
||||
'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
||||
]); |
||||
break; |
||||
case 'F': |
||||
$result['foreignKeys'][] = new ForeignKeyConstraint([ |
||||
'name' => $name, |
||||
'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
||||
'foreignSchemaName' => $constraint[0]['foreign_table_schema'], |
||||
'foreignTableName' => $constraint[0]['foreign_table_name'], |
||||
'foreignColumnNames' => ArrayHelper::getColumn($constraint, 'foreign_column_name'), |
||||
'onDelete' => str_replace('_', '', $constraint[0]['on_delete']), |
||||
'onUpdate' => str_replace('_', '', $constraint[0]['on_update']), |
||||
]); |
||||
break; |
||||
case 'UQ': |
||||
$result['uniques'][] = new Constraint([ |
||||
'name' => $name, |
||||
'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
||||
]); |
||||
break; |
||||
case 'C': |
||||
$result['checks'][] = new CheckConstraint([ |
||||
'name' => $name, |
||||
'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
||||
'expression' => $constraint[0]['check_expr'], |
||||
]); |
||||
break; |
||||
case 'D': |
||||
$result['defaults'][] = new DefaultValueConstraint([ |
||||
'name' => $name, |
||||
'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
||||
'value' => $constraint[0]['default_expr'], |
||||
]); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
foreach ($result as $type => $data) { |
||||
$this->setTableMetadata($tableName, $type, $data); |
||||
} |
||||
|
||||
return $result[$returnType]; |
||||
} |
||||
} |
@ -1,33 +0,0 @@
|
||||
<?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 int last inserted ID value. |
||||
*/ |
||||
public function lastInsertId($sequence = null) |
||||
{ |
||||
return !$sequence ? parent::lastInsertId() : parent::lastInsertId($sequence); |
||||
} |
||||
} |
@ -1,23 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\db\mssql; |
||||
|
||||
/** |
||||
* TableSchema represents the metadata of a database table. |
||||
* |
||||
* @author Qiang Xue <qiang.xue@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class TableSchema extends \yii\db\TableSchema |
||||
{ |
||||
/** |
||||
* @var string name of the catalog (database) that this table belongs to. |
||||
* Defaults to null, meaning no catalog (or the current database). |
||||
*/ |
||||
public $catalogName; |
||||
} |
@ -1,58 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\db\mssql\conditions; |
||||
|
||||
use yii\base\NotSupportedException; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* |
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com> |
||||
* @since 2.0.14 |
||||
*/ |
||||
class InConditionBuilder extends \yii\db\conditions\InConditionBuilder |
||||
{ |
||||
/** |
||||
* {@inheritdoc} |
||||
* @throws NotSupportedException if `$columns` is an array |
||||
*/ |
||||
protected function buildSubqueryInCondition($operator, $columns, $values, &$params) |
||||
{ |
||||
if (is_array($columns)) { |
||||
throw new NotSupportedException(__METHOD__ . ' is not supported by MSSQL.'); |
||||
} |
||||
|
||||
return parent::buildSubqueryInCondition($operator, $columns, $values, $params); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function buildCompositeInCondition($operator, $columns, $values, &$params) |
||||
{ |
||||
$quotedColumns = []; |
||||
foreach ($columns as $i => $column) { |
||||
$quotedColumns[$i] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column; |
||||
} |
||||
$vss = []; |
||||
foreach ($values as $value) { |
||||
$vs = []; |
||||
foreach ($columns as $i => $column) { |
||||
if (isset($value[$column])) { |
||||
$phName = $this->queryBuilder->bindParam($value[$column], $params); |
||||
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName; |
||||
} else { |
||||
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL'; |
||||
} |
||||
} |
||||
$vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')'; |
||||
} |
||||
|
||||
return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')'; |
||||
} |
||||
} |
@ -1,25 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\db\mssql\conditions; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
class LikeConditionBuilder extends \yii\db\conditions\LikeConditionBuilder |
||||
{ |
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected $escapingReplacements = [ |
||||
'%' => '[%]', |
||||
'_' => '[_]', |
||||
'[' => '[[]', |
||||
']' => '[]]', |
||||
'\\' => '[\\]', |
||||
]; |
||||
} |
@ -1,47 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\db\oci; |
||||
|
||||
use yii\db\ColumnSchemaBuilder as AbstractColumnSchemaBuilder; |
||||
|
||||
/** |
||||
* ColumnSchemaBuilder is the schema builder for Oracle databases. |
||||
* |
||||
* @author Vasenin Matvey <vaseninm@gmail.com> |
||||
* @author Chris Harris <chris@buckshotsoftware.com> |
||||
* @since 2.0.6 |
||||
*/ |
||||
class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder |
||||
{ |
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function buildUnsignedString() |
||||
{ |
||||
return $this->isUnsigned ? ' UNSIGNED' : ''; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function __toString() |
||||
{ |
||||
switch ($this->getTypeCategory()) { |
||||
case self::CATEGORY_PK: |
||||
$format = '{type}{length}{check}{append}'; |
||||
break; |
||||
case self::CATEGORY_NUMERIC: |
||||
$format = '{type}{length}{unsigned}{default}{notnull}{check}{append}'; |
||||
break; |
||||
default: |
||||
$format = '{type}{length}{default}{notnull}{check}{append}'; |
||||
} |
||||
|
||||
return $this->buildCompleteString($format); |
||||
} |
||||
} |
@ -1,365 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\db\oci; |
||||
|
||||
use yii\base\InvalidArgumentException; |
||||
use yii\db\Connection; |
||||
use yii\db\Constraint; |
||||
use yii\db\Exception; |
||||
use yii\db\Expression; |
||||
use yii\db\Query; |
||||
use yii\helpers\StringHelper; |
||||
use yii\db\ExpressionInterface; |
||||
|
||||
/** |
||||
* QueryBuilder is the query builder for Oracle databases. |
||||
* |
||||
* @author Qiang Xue <qiang.xue@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 = [ |
||||
Schema::TYPE_PK => 'NUMBER(10) NOT NULL PRIMARY KEY', |
||||
Schema::TYPE_UPK => 'NUMBER(10) UNSIGNED NOT NULL PRIMARY KEY', |
||||
Schema::TYPE_BIGPK => 'NUMBER(20) NOT NULL PRIMARY KEY', |
||||
Schema::TYPE_UBIGPK => 'NUMBER(20) UNSIGNED NOT NULL PRIMARY KEY', |
||||
Schema::TYPE_CHAR => 'CHAR(1)', |
||||
Schema::TYPE_STRING => 'VARCHAR2(255)', |
||||
Schema::TYPE_TEXT => 'CLOB', |
||||
Schema::TYPE_TINYINT => 'NUMBER(3)', |
||||
Schema::TYPE_SMALLINT => 'NUMBER(5)', |
||||
Schema::TYPE_INTEGER => 'NUMBER(10)', |
||||
Schema::TYPE_BIGINT => 'NUMBER(20)', |
||||
Schema::TYPE_FLOAT => 'NUMBER', |
||||
Schema::TYPE_DOUBLE => 'NUMBER', |
||||
Schema::TYPE_DECIMAL => 'NUMBER', |
||||
Schema::TYPE_DATETIME => 'TIMESTAMP', |
||||
Schema::TYPE_TIMESTAMP => 'TIMESTAMP', |
||||
Schema::TYPE_TIME => 'TIMESTAMP', |
||||
Schema::TYPE_DATE => 'DATE', |
||||
Schema::TYPE_BINARY => 'BLOB', |
||||
Schema::TYPE_BOOLEAN => 'NUMBER(1)', |
||||
Schema::TYPE_MONEY => 'NUMBER(19,4)', |
||||
]; |
||||
|
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function defaultExpressionBuilders() |
||||
{ |
||||
return array_merge(parent::defaultExpressionBuilders(), [ |
||||
'yii\db\conditions\InCondition' => 'yii\db\oci\conditions\InConditionBuilder', |
||||
'yii\db\conditions\LikeCondition' => 'yii\db\oci\conditions\LikeConditionBuilder', |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset, &$params) |
||||
{ |
||||
$orderBy = $this->buildOrderBy($orderBy, $params); |
||||
if ($orderBy !== '') { |
||||
$sql .= $this->separator . $orderBy; |
||||
} |
||||
|
||||
$filters = []; |
||||
if ($this->hasOffset($offset)) { |
||||
$filters[] = 'rowNumId > ' . $offset; |
||||
} |
||||
if ($this->hasLimit($limit)) { |
||||
$filters[] = 'rownum <= ' . $limit; |
||||
} |
||||
if (empty($filters)) { |
||||
return $sql; |
||||
} |
||||
|
||||
$filter = implode(' AND ', $filters); |
||||
return <<<EOD |
||||
WITH USER_SQL AS ($sql), |
||||
PAGINATION AS (SELECT USER_SQL.*, rownum as rowNumId FROM USER_SQL) |
||||
SELECT * |
||||
FROM PAGINATION |
||||
WHERE $filter |
||||
EOD; |
||||
} |
||||
|
||||
/** |
||||
* Builds a SQL statement for renaming a DB table. |
||||
* |
||||
* @param string $table the table to be renamed. The name will be properly quoted by the method. |
||||
* @param string $newName the new table name. The name will be properly quoted by the method. |
||||
* @return string the SQL statement for renaming a DB table. |
||||
*/ |
||||
public function renameTable($table, $newName) |
||||
{ |
||||
return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' RENAME TO ' . $this->db->quoteTableName($newName); |
||||
} |
||||
|
||||
/** |
||||
* Builds a SQL statement for changing the definition of a column. |
||||
* |
||||
* @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. |
||||
* @param string $column the name of the column to be changed. The name will be properly quoted by the method. |
||||
* @param string $type the new column type. The [[getColumnType]] method will be invoked to convert abstract column type (if any) |
||||
* into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. |
||||
* For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. |
||||
* @return string the SQL statement for changing the definition of a column. |
||||
*/ |
||||
public function alterColumn($table, $column, $type) |
||||
{ |
||||
$type = $this->getColumnType($type); |
||||
|
||||
return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' MODIFY ' . $this->db->quoteColumnName($column) . ' ' . $this->getColumnType($type); |
||||
} |
||||
|
||||
/** |
||||
* Builds a SQL statement for dropping an index. |
||||
* |
||||
* @param string $name the name of the index to be dropped. The name will be properly quoted by the method. |
||||
* @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. |
||||
* @return string the SQL statement for dropping an index. |
||||
*/ |
||||
public function dropIndex($name, $table) |
||||
{ |
||||
return 'DROP INDEX ' . $this->db->quoteTableName($name); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function resetSequence($table, $value = null) |
||||
{ |
||||
$tableSchema = $this->db->getTableSchema($table); |
||||
if ($tableSchema === null) { |
||||
throw new InvalidArgumentException("Unknown table: $table"); |
||||
} |
||||
if ($tableSchema->sequenceName === null) { |
||||
return ''; |
||||
} |
||||
|
||||
if ($value !== null) { |
||||
$value = (int) $value; |
||||
} else { |
||||
// use master connection to get the biggest PK value |
||||
$value = $this->db->useMaster(function (Connection $db) use ($tableSchema) { |
||||
return $db->createCommand("SELECT MAX(\"{$tableSchema->primaryKey}\") FROM \"{$tableSchema->name}\"")->queryScalar(); |
||||
}) + 1; |
||||
} |
||||
|
||||
return "DROP SEQUENCE \"{$tableSchema->name}_SEQ\";" |
||||
. "CREATE SEQUENCE \"{$tableSchema->name}_SEQ\" START WITH {$value} INCREMENT BY 1 NOMAXVALUE NOCACHE"; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) |
||||
{ |
||||
$sql = 'ALTER TABLE ' . $this->db->quoteTableName($table) |
||||
. ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name) |
||||
. ' FOREIGN KEY (' . $this->buildColumns($columns) . ')' |
||||
. ' REFERENCES ' . $this->db->quoteTableName($refTable) |
||||
. ' (' . $this->buildColumns($refColumns) . ')'; |
||||
if ($delete !== null) { |
||||
$sql .= ' ON DELETE ' . $delete; |
||||
} |
||||
if ($update !== null) { |
||||
throw new Exception('Oracle does not support ON UPDATE clause.'); |
||||
} |
||||
|
||||
return $sql; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function prepareInsertValues($table, $columns, $params = []) |
||||
{ |
||||
[$names, $placeholders, $values, $params] = parent::prepareInsertValues($table, $columns, $params); |
||||
if (!$columns instanceof Query && empty($names)) { |
||||
$tableSchema = $this->db->getSchema()->getTableSchema($table); |
||||
if ($tableSchema !== null) { |
||||
$columns = !empty($tableSchema->primaryKey) ? $tableSchema->primaryKey : [reset($tableSchema->columns)->name]; |
||||
foreach ($columns as $name) { |
||||
$names[] = $this->db->quoteColumnName($name); |
||||
$placeholders[] = 'DEFAULT'; |
||||
} |
||||
} |
||||
} |
||||
return [$names, $placeholders, $values, $params]; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @see https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_9016.htm#SQLRF01606 |
||||
*/ |
||||
public function upsert($table, $insertColumns, $updateColumns, &$params) |
||||
{ |
||||
/** @var Constraint[] $constraints */ |
||||
[$uniqueNames, $insertNames, $updateNames] = $this->prepareUpsertColumns($table, $insertColumns, $updateColumns, $constraints); |
||||
if (empty($uniqueNames)) { |
||||
return $this->insert($table, $insertColumns, $params); |
||||
} |
||||
|
||||
$onCondition = ['or']; |
||||
$quotedTableName = $this->db->quoteTableName($table); |
||||
foreach ($constraints as $constraint) { |
||||
$constraintCondition = ['and']; |
||||
foreach ($constraint->columnNames as $name) { |
||||
$quotedName = $this->db->quoteColumnName($name); |
||||
$constraintCondition[] = "$quotedTableName.$quotedName=\"EXCLUDED\".$quotedName"; |
||||
} |
||||
$onCondition[] = $constraintCondition; |
||||
} |
||||
$on = $this->buildCondition($onCondition, $params); |
||||
[, $placeholders, $values, $params] = $this->prepareInsertValues($table, $insertColumns, $params); |
||||
if (!empty($placeholders)) { |
||||
$usingSelectValues = []; |
||||
foreach ($insertNames as $index => $name) { |
||||
$usingSelectValues[$name] = new Expression($placeholders[$index]); |
||||
} |
||||
$usingSubQuery = (new Query()) |
||||
->select($usingSelectValues) |
||||
->from('DUAL'); |
||||
[$usingValues, $params] = $this->build($usingSubQuery, $params); |
||||
} |
||||
$mergeSql = 'MERGE INTO ' . $this->db->quoteTableName($table) . ' ' |
||||
. 'USING (' . (isset($usingValues) ? $usingValues : ltrim($values, ' ')) . ') "EXCLUDED" ' |
||||
. "ON ($on)"; |
||||
$insertValues = []; |
||||
foreach ($insertNames as $name) { |
||||
$quotedName = $this->db->quoteColumnName($name); |
||||
if (strrpos($quotedName, '.') === false) { |
||||
$quotedName = '"EXCLUDED".' . $quotedName; |
||||
} |
||||
$insertValues[] = $quotedName; |
||||
} |
||||
$insertSql = 'INSERT (' . implode(', ', $insertNames) . ')' |
||||
. ' VALUES (' . implode(', ', $insertValues) . ')'; |
||||
if ($updateColumns === false) { |
||||
return "$mergeSql WHEN NOT MATCHED THEN $insertSql"; |
||||
} |
||||
|
||||
if ($updateColumns === true) { |
||||
$updateColumns = []; |
||||
foreach ($updateNames as $name) { |
||||
$quotedName = $this->db->quoteColumnName($name); |
||||
if (strrpos($quotedName, '.') === false) { |
||||
$quotedName = '"EXCLUDED".' . $quotedName; |
||||
} |
||||
$updateColumns[$name] = new Expression($quotedName); |
||||
} |
||||
} |
||||
[$updates, $params] = $this->prepareUpdateSets($table, $updateColumns, $params); |
||||
$updateSql = 'UPDATE SET ' . implode(', ', $updates); |
||||
return "$mergeSql WHEN MATCHED THEN $updateSql WHEN NOT MATCHED THEN $insertSql"; |
||||
} |
||||
|
||||
/** |
||||
* Generates a batch INSERT SQL statement. |
||||
* |
||||
* For example, |
||||
* |
||||
* ```php |
||||
* $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [ |
||||
* ['Tom', 30], |
||||
* ['Jane', 20], |
||||
* ['Linda', 25], |
||||
* ]); |
||||
* ``` |
||||
* |
||||
* Note that the values in each row must match the corresponding column names. |
||||
* |
||||
* @param string $table the table that new rows will be inserted into. |
||||
* @param array $columns the column names |
||||
* @param array|\Generator $rows the rows to be batch inserted into the table |
||||
* @return string the batch INSERT SQL statement |
||||
*/ |
||||
public function batchInsert($table, $columns, $rows, &$params = []) |
||||
{ |
||||
if (empty($rows)) { |
||||
return ''; |
||||
} |
||||
|
||||
$schema = $this->db->getSchema(); |
||||
if (($tableSchema = $schema->getTableSchema($table)) !== null) { |
||||
$columnSchemas = $tableSchema->columns; |
||||
} else { |
||||
$columnSchemas = []; |
||||
} |
||||
|
||||
$values = []; |
||||
foreach ($rows as $row) { |
||||
$vs = []; |
||||
foreach ($row as $i => $value) { |
||||
if (isset($columns[$i], $columnSchemas[$columns[$i]])) { |
||||
$value = $columnSchemas[$columns[$i]]->dbTypecast($value); |
||||
} |
||||
if (is_string($value)) { |
||||
$value = $schema->quoteValue($value); |
||||
} elseif (is_float($value)) { |
||||
// ensure type cast always has . as decimal separator in all locales |
||||
$value = StringHelper::floatToString($value); |
||||
} elseif ($value === false) { |
||||
$value = 0; |
||||
} elseif ($value === null) { |
||||
$value = 'NULL'; |
||||
} elseif ($value instanceof ExpressionInterface) { |
||||
$value = $this->buildExpression($value, $params); |
||||
} |
||||
$vs[] = $value; |
||||
} |
||||
$values[] = '(' . implode(', ', $vs) . ')'; |
||||
} |
||||
if (empty($values)) { |
||||
return ''; |
||||
} |
||||
|
||||
foreach ($columns as $i => $name) { |
||||
$columns[$i] = $schema->quoteColumnName($name); |
||||
} |
||||
|
||||
$tableAndColumns = ' INTO ' . $schema->quoteTableName($table) |
||||
. ' (' . implode(', ', $columns) . ') VALUES '; |
||||
|
||||
return 'INSERT ALL ' . $tableAndColumns . implode($tableAndColumns, $values) . ' SELECT 1 FROM SYS.DUAL'; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @since 2.0.8 |
||||
*/ |
||||
public function selectExists($rawSql) |
||||
{ |
||||
return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END FROM DUAL'; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @since 2.0.8 |
||||
*/ |
||||
public function dropCommentFromColumn($table, $column) |
||||
{ |
||||
return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . " IS ''"; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @since 2.0.8 |
||||
*/ |
||||
public function dropCommentFromTable($table) |
||||
{ |
||||
return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . " IS ''"; |
||||
} |
||||
} |
@ -1,739 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\db\oci; |
||||
|
||||
use yii\base\InvalidCallException; |
||||
use yii\base\NotSupportedException; |
||||
use yii\db\CheckConstraint; |
||||
use yii\db\ColumnSchema; |
||||
use yii\db\Connection; |
||||
use yii\db\Constraint; |
||||
use yii\db\ConstraintFinderInterface; |
||||
use yii\db\ConstraintFinderTrait; |
||||
use yii\db\Expression; |
||||
use yii\db\ForeignKeyConstraint; |
||||
use yii\db\IndexConstraint; |
||||
use yii\db\TableSchema; |
||||
use yii\helpers\ArrayHelper; |
||||
use yii\db\IntegrityException; |
||||
|
||||
/** |
||||
* Schema is the class for retrieving metadata from an Oracle database. |
||||
* |
||||
* @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the |
||||
* sequence object. This property is read-only. |
||||
* |
||||
* @author Qiang Xue <qiang.xue@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class Schema extends \yii\db\Schema implements ConstraintFinderInterface |
||||
{ |
||||
use ConstraintFinderTrait; |
||||
|
||||
/** |
||||
* @var array map of DB errors and corresponding exceptions |
||||
* If left part is found in DB error message exception class from the right part is used. |
||||
*/ |
||||
public $exceptionMap = [ |
||||
'ORA-00001: unique constraint' => IntegrityException::class, |
||||
]; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected $tableQuoteCharacter = '"'; |
||||
|
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function init() |
||||
{ |
||||
parent::init(); |
||||
if ($this->defaultSchema === null) { |
||||
$username = $this->db->username; |
||||
if (empty($username)) { |
||||
$username = isset($this->db->masters[0]['username']) ? $this->db->masters[0]['username'] : ''; |
||||
} |
||||
$this->defaultSchema = strtoupper($username); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function resolveTableName($name) |
||||
{ |
||||
$resolvedName = new TableSchema(); |
||||
$parts = explode('.', str_replace('"', '', $name)); |
||||
if (isset($parts[1])) { |
||||
$resolvedName->schemaName = $parts[0]; |
||||
$resolvedName->name = $parts[1]; |
||||
} else { |
||||
$resolvedName->schemaName = $this->defaultSchema; |
||||
$resolvedName->name = $name; |
||||
} |
||||
$resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name; |
||||
return $resolvedName; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @see https://docs.oracle.com/cd/B28359_01/server.111/b28337/tdpsg_user_accounts.htm |
||||
*/ |
||||
protected function findSchemaNames() |
||||
{ |
||||
static $sql = <<<'SQL' |
||||
SELECT "u"."USERNAME" |
||||
FROM "DBA_USERS" "u" |
||||
WHERE "u"."DEFAULT_TABLESPACE" NOT IN ('SYSTEM', 'SYSAUX') |
||||
ORDER BY "u"."USERNAME" ASC |
||||
SQL; |
||||
|
||||
return $this->db->createCommand($sql)->queryColumn(); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function findTableNames($schema = '') |
||||
{ |
||||
if ($schema === '') { |
||||
$sql = <<<'SQL' |
||||
SELECT |
||||
TABLE_NAME |
||||
FROM USER_TABLES |
||||
UNION ALL |
||||
SELECT |
||||
VIEW_NAME AS TABLE_NAME |
||||
FROM USER_VIEWS |
||||
UNION ALL |
||||
SELECT |
||||
MVIEW_NAME AS TABLE_NAME |
||||
FROM USER_MVIEWS |
||||
ORDER BY TABLE_NAME |
||||
SQL; |
||||
$command = $this->db->createCommand($sql); |
||||
} else { |
||||
$sql = <<<'SQL' |
||||
SELECT |
||||
OBJECT_NAME AS TABLE_NAME |
||||
FROM ALL_OBJECTS |
||||
WHERE |
||||
OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW') |
||||
AND OWNER = :schema |
||||
ORDER BY OBJECT_NAME |
||||
SQL; |
||||
$command = $this->db->createCommand($sql, [':schema' => $schema]); |
||||
} |
||||
|
||||
$rows = $command->queryAll(); |
||||
$names = []; |
||||
foreach ($rows as $row) { |
||||
if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) { |
||||
$row = array_change_key_case($row, CASE_UPPER); |
||||
} |
||||
$names[] = $row['TABLE_NAME']; |
||||
} |
||||
|
||||
return $names; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTableSchema($name) |
||||
{ |
||||
$table = new TableSchema(); |
||||
$this->resolveTableNames($table, $name); |
||||
if ($this->findColumns($table)) { |
||||
$this->findConstraints($table); |
||||
return $table; |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTablePrimaryKey($tableName) |
||||
{ |
||||
return $this->loadTableConstraints($tableName, 'primaryKey'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTableForeignKeys($tableName) |
||||
{ |
||||
return $this->loadTableConstraints($tableName, 'foreignKeys'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTableIndexes($tableName) |
||||
{ |
||||
static $sql = <<<'SQL' |
||||
SELECT |
||||
/*+ PUSH_PRED("ui") PUSH_PRED("uicol") PUSH_PRED("uc") */ |
||||
"ui"."INDEX_NAME" AS "name", |
||||
"uicol"."COLUMN_NAME" AS "column_name", |
||||
CASE "ui"."UNIQUENESS" WHEN 'UNIQUE' THEN 1 ELSE 0 END AS "index_is_unique", |
||||
CASE WHEN "uc"."CONSTRAINT_NAME" IS NOT NULL THEN 1 ELSE 0 END AS "index_is_primary" |
||||
FROM "SYS"."USER_INDEXES" "ui" |
||||
LEFT JOIN "SYS"."USER_IND_COLUMNS" "uicol" |
||||
ON "uicol"."INDEX_NAME" = "ui"."INDEX_NAME" |
||||
LEFT JOIN "SYS"."USER_CONSTRAINTS" "uc" |
||||
ON "uc"."OWNER" = "ui"."TABLE_OWNER" AND "uc"."CONSTRAINT_NAME" = "ui"."INDEX_NAME" AND "uc"."CONSTRAINT_TYPE" = 'P' |
||||
WHERE "ui"."TABLE_OWNER" = :schemaName AND "ui"."TABLE_NAME" = :tableName |
||||
ORDER BY "uicol"."COLUMN_POSITION" ASC |
||||
SQL; |
||||
|
||||
$resolvedName = $this->resolveTableName($tableName); |
||||
$indexes = $this->db->createCommand($sql, [ |
||||
':schemaName' => $resolvedName->schemaName, |
||||
':tableName' => $resolvedName->name, |
||||
])->queryAll(); |
||||
$indexes = $this->normalizePdoRowKeyCase($indexes, true); |
||||
$indexes = ArrayHelper::index($indexes, null, 'name'); |
||||
$result = []; |
||||
foreach ($indexes as $name => $index) { |
||||
$result[] = new IndexConstraint([ |
||||
'isPrimary' => (bool) $index[0]['index_is_primary'], |
||||
'isUnique' => (bool) $index[0]['index_is_unique'], |
||||
'name' => $name, |
||||
'columnNames' => ArrayHelper::getColumn($index, 'column_name'), |
||||
]); |
||||
} |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTableUniques($tableName) |
||||
{ |
||||
return $this->loadTableConstraints($tableName, 'uniques'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected function loadTableChecks($tableName) |
||||
{ |
||||
return $this->loadTableConstraints($tableName, 'checks'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @throws NotSupportedException if this method is called. |
||||
*/ |
||||
protected function loadTableDefaultValues($tableName) |
||||
{ |
||||
throw new NotSupportedException('Oracle does not support default value constraints.'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function releaseSavepoint($name) |
||||
{ |
||||
// does nothing as Oracle does not support this |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function quoteSimpleTableName($name) |
||||
{ |
||||
return strpos($name, '"') !== false ? $name : '"' . $name . '"'; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function createQueryBuilder() |
||||
{ |
||||
return new QueryBuilder($this->db); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function createColumnSchemaBuilder($type, $length = null) |
||||
{ |
||||
return new ColumnSchemaBuilder($type, $length, $this->db); |
||||
} |
||||
|
||||
/** |
||||
* Resolves the table name and schema name (if any). |
||||
* |
||||
* @param TableSchema $table the table metadata object |
||||
* @param string $name the table name |
||||
*/ |
||||
protected function resolveTableNames($table, $name) |
||||
{ |
||||
$parts = explode('.', str_replace('"', '', $name)); |
||||
if (isset($parts[1])) { |
||||
$table->schemaName = $parts[0]; |
||||
$table->name = $parts[1]; |
||||
} else { |
||||
$table->schemaName = $this->defaultSchema; |
||||
$table->name = $name; |
||||
} |
||||
|
||||
$table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name; |
||||
} |
||||
|
||||
/** |
||||
* Collects the table column metadata. |
||||
* @param TableSchema $table the table schema |
||||
* @return bool whether the table exists |
||||
*/ |
||||
protected function findColumns($table) |
||||
{ |
||||
$sql = <<<'SQL' |
||||
SELECT |
||||
A.COLUMN_NAME, |
||||
A.DATA_TYPE, |
||||
A.DATA_PRECISION, |
||||
A.DATA_SCALE, |
||||
( |
||||
CASE A.CHAR_USED WHEN 'C' THEN A.CHAR_LENGTH |
||||
ELSE A.DATA_LENGTH |
||||
END |
||||
) AS DATA_LENGTH, |
||||
A.NULLABLE, |
||||
A.DATA_DEFAULT, |
||||
COM.COMMENTS AS COLUMN_COMMENT |
||||
FROM ALL_TAB_COLUMNS A |
||||
INNER JOIN ALL_OBJECTS B ON B.OWNER = A.OWNER AND LTRIM(B.OBJECT_NAME) = LTRIM(A.TABLE_NAME) |
||||
LEFT JOIN ALL_COL_COMMENTS COM ON (A.OWNER = COM.OWNER AND A.TABLE_NAME = COM.TABLE_NAME AND A.COLUMN_NAME = COM.COLUMN_NAME) |
||||
WHERE |
||||
A.OWNER = :schemaName |
||||
AND B.OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW') |
||||
AND B.OBJECT_NAME = :tableName |
||||
ORDER BY A.COLUMN_ID |
||||
SQL; |
||||
|
||||
try { |
||||
$columns = $this->db->createCommand($sql, [ |
||||
':tableName' => $table->name, |
||||
':schemaName' => $table->schemaName, |
||||
])->queryAll(); |
||||
} catch (\Exception $e) { |
||||
return false; |
||||
} |
||||
|
||||
if (empty($columns)) { |
||||
return false; |
||||
} |
||||
|
||||
foreach ($columns as $column) { |
||||
if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) { |
||||
$column = array_change_key_case($column, CASE_UPPER); |
||||
} |
||||
$c = $this->createColumn($column); |
||||
$table->columns[$c->name] = $c; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Sequence name of table. |
||||
* |
||||
* @param string $tableName |
||||
* @internal param \yii\db\TableSchema $table->name the table schema |
||||
* @return string|null whether the sequence exists |
||||
*/ |
||||
protected function getTableSequenceName($tableName) |
||||
{ |
||||
$sequenceNameSql = <<<'SQL' |
||||
SELECT |
||||
UD.REFERENCED_NAME AS SEQUENCE_NAME |
||||
FROM USER_DEPENDENCIES UD |
||||
JOIN USER_TRIGGERS UT ON (UT.TRIGGER_NAME = UD.NAME) |
||||
WHERE |
||||
UT.TABLE_NAME = :tableName |
||||
AND UD.TYPE = 'TRIGGER' |
||||
AND UD.REFERENCED_TYPE = 'SEQUENCE' |
||||
SQL; |
||||
$sequenceName = $this->db->createCommand($sequenceNameSql, [':tableName' => $tableName])->queryScalar(); |
||||
return $sequenceName === false ? null : $sequenceName; |
||||
} |
||||
|
||||
/** |
||||
* @Overrides method in class 'Schema' |
||||
* @see http://www.php.net/manual/en/function.PDO-lastInsertId.php -> Oracle does not support this |
||||
* |
||||
* Returns the ID of the last inserted row or sequence value. |
||||
* @param string $sequenceName name of the sequence object (required by some DBMS) |
||||
* @return string the row ID of the last row inserted, or the last value retrieved from the sequence object |
||||
* @throws InvalidCallException if the DB connection is not active |
||||
*/ |
||||
public function getLastInsertID($sequenceName = '') |
||||
{ |
||||
if ($this->db->isActive) { |
||||
// get the last insert id from the master connection |
||||
$sequenceName = $this->quoteSimpleTableName($sequenceName); |
||||
return $this->db->useMaster(function (Connection $db) use ($sequenceName) { |
||||
return $db->createCommand("SELECT {$sequenceName}.CURRVAL FROM DUAL")->queryScalar(); |
||||
}); |
||||
} else { |
||||
throw new InvalidCallException('DB Connection is not active.'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates ColumnSchema instance. |
||||
* |
||||
* @param array $column |
||||
* @return ColumnSchema |
||||
*/ |
||||
protected function createColumn($column) |
||||
{ |
||||
$c = $this->createColumnSchema(); |
||||
$c->name = $column['COLUMN_NAME']; |
||||
$c->allowNull = $column['NULLABLE'] === 'Y'; |
||||
$c->comment = $column['COLUMN_COMMENT'] === null ? '' : $column['COLUMN_COMMENT']; |
||||
$c->isPrimaryKey = false; |
||||
$this->extractColumnType($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']); |
||||
$this->extractColumnSize($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']); |
||||
|
||||
$c->phpType = $this->getColumnPhpType($c); |
||||
|
||||
if (!$c->isPrimaryKey) { |
||||
if (stripos($column['DATA_DEFAULT'], 'timestamp') !== false) { |
||||
$c->defaultValue = null; |
||||
} else { |
||||
$defaultValue = $column['DATA_DEFAULT']; |
||||
if ($c->type === 'timestamp' && $defaultValue === 'CURRENT_TIMESTAMP') { |
||||
$c->defaultValue = new Expression('CURRENT_TIMESTAMP'); |
||||
} else { |
||||
if ($defaultValue !== null) { |
||||
if (($len = strlen($defaultValue)) > 2 && $defaultValue[0] === "'" |
||||
&& $defaultValue[$len - 1] === "'" |
||||
) { |
||||
$defaultValue = substr($column['DATA_DEFAULT'], 1, -1); |
||||
} else { |
||||
$defaultValue = trim($defaultValue); |
||||
} |
||||
} |
||||
$c->defaultValue = $c->phpTypecast($defaultValue); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $c; |
||||
} |
||||
|
||||
/** |
||||
* Finds constraints and fills them into TableSchema object passed. |
||||
* @param TableSchema $table |
||||
*/ |
||||
protected function findConstraints($table) |
||||
{ |
||||
$sql = <<<'SQL' |
||||
SELECT |
||||
/*+ PUSH_PRED(C) PUSH_PRED(D) PUSH_PRED(E) */ |
||||
D.CONSTRAINT_NAME, |
||||
D.CONSTRAINT_TYPE, |
||||
C.COLUMN_NAME, |
||||
C.POSITION, |
||||
D.R_CONSTRAINT_NAME, |
||||
E.TABLE_NAME AS TABLE_REF, |
||||
F.COLUMN_NAME AS COLUMN_REF, |
||||
C.TABLE_NAME |
||||
FROM ALL_CONS_COLUMNS C |
||||
INNER JOIN ALL_CONSTRAINTS D ON D.OWNER = C.OWNER AND D.CONSTRAINT_NAME = C.CONSTRAINT_NAME |
||||
LEFT JOIN ALL_CONSTRAINTS E ON E.OWNER = D.R_OWNER AND E.CONSTRAINT_NAME = D.R_CONSTRAINT_NAME |
||||
LEFT JOIN ALL_CONS_COLUMNS F ON F.OWNER = E.OWNER AND F.CONSTRAINT_NAME = E.CONSTRAINT_NAME AND F.POSITION = C.POSITION |
||||
WHERE |
||||
C.OWNER = :schemaName |
||||
AND C.TABLE_NAME = :tableName |
||||
ORDER BY D.CONSTRAINT_NAME, C.POSITION |
||||
SQL; |
||||
$command = $this->db->createCommand($sql, [ |
||||
':tableName' => $table->name, |
||||
':schemaName' => $table->schemaName, |
||||
]); |
||||
$constraints = []; |
||||
foreach ($command->queryAll() as $row) { |
||||
if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) { |
||||
$row = array_change_key_case($row, CASE_UPPER); |
||||
} |
||||
|
||||
if ($row['CONSTRAINT_TYPE'] === 'P') { |
||||
$table->columns[$row['COLUMN_NAME']]->isPrimaryKey = true; |
||||
$table->primaryKey[] = $row['COLUMN_NAME']; |
||||
if (empty($table->sequenceName)) { |
||||
$table->sequenceName = $this->getTableSequenceName($table->name); |
||||
} |
||||
} |
||||
|
||||
if ($row['CONSTRAINT_TYPE'] !== 'R') { |
||||
// this condition is not checked in SQL WHERE because of an Oracle Bug: |
||||
// see https://github.com/yiisoft/yii2/pull/8844 |
||||
continue; |
||||
} |
||||
|
||||
$name = $row['CONSTRAINT_NAME']; |
||||
if (!isset($constraints[$name])) { |
||||
$constraints[$name] = [ |
||||
'tableName' => $row['TABLE_REF'], |
||||
'columns' => [], |
||||
]; |
||||
} |
||||
$constraints[$name]['columns'][$row['COLUMN_NAME']] = $row['COLUMN_REF']; |
||||
} |
||||
|
||||
foreach ($constraints as $constraint) { |
||||
$name = current(array_keys($constraint)); |
||||
|
||||
$table->foreignKeys[$name] = array_merge([$constraint['tableName']], $constraint['columns']); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns all unique indexes for the given table. |
||||
* Each array element is of the following structure:. |
||||
* |
||||
* ```php |
||||
* [ |
||||
* 'IndexName1' => ['col1' [, ...]], |
||||
* 'IndexName2' => ['col2' [, ...]], |
||||
* ] |
||||
* ``` |
||||
* |
||||
* @param TableSchema $table the table metadata |
||||
* @return array all unique indexes for the given table. |
||||
* @since 2.0.4 |
||||
*/ |
||||
public function findUniqueIndexes($table) |
||||
{ |
||||
$query = <<<'SQL' |
||||
SELECT |
||||
DIC.INDEX_NAME, |
||||
DIC.COLUMN_NAME |
||||
FROM ALL_INDEXES DI |
||||
INNER JOIN ALL_IND_COLUMNS DIC ON DI.TABLE_NAME = DIC.TABLE_NAME AND DI.INDEX_NAME = DIC.INDEX_NAME |
||||
WHERE |
||||
DI.UNIQUENESS = 'UNIQUE' |
||||
AND DIC.TABLE_OWNER = :schemaName |
||||
AND DIC.TABLE_NAME = :tableName |
||||
ORDER BY DIC.TABLE_NAME, DIC.INDEX_NAME, DIC.COLUMN_POSITION |
||||
SQL; |
||||
$result = []; |
||||
$command = $this->db->createCommand($query, [ |
||||
':tableName' => $table->name, |
||||
':schemaName' => $table->schemaName, |
||||
]); |
||||
foreach ($command->queryAll() as $row) { |
||||
$result[$row['INDEX_NAME']][] = $row['COLUMN_NAME']; |
||||
} |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Extracts the data types for the given column. |
||||
* @param ColumnSchema $column |
||||
* @param string $dbType DB type |
||||
* @param string $precision total number of digits. |
||||
* This parameter is available since version 2.0.4. |
||||
* @param string $scale number of digits on the right of the decimal separator. |
||||
* This parameter is available since version 2.0.4. |
||||
* @param string $length length for character types. |
||||
* This parameter is available since version 2.0.4. |
||||
*/ |
||||
protected function extractColumnType($column, $dbType, $precision, $scale, $length) |
||||
{ |
||||
$column->dbType = $dbType; |
||||
|
||||
if (strpos($dbType, 'FLOAT') !== false || strpos($dbType, 'DOUBLE') !== false) { |
||||
$column->type = 'double'; |
||||
} elseif (strpos($dbType, 'NUMBER') !== false) { |
||||
if ($scale === null || $scale > 0) { |
||||
$column->type = 'decimal'; |
||||
} else { |
||||
$column->type = 'integer'; |
||||
} |
||||
} elseif (strpos($dbType, 'INTEGER') !== false) { |
||||
$column->type = 'integer'; |
||||
} elseif (strpos($dbType, 'BLOB') !== false) { |
||||
$column->type = 'binary'; |
||||
} elseif (strpos($dbType, 'CLOB') !== false) { |
||||
$column->type = 'text'; |
||||
} elseif (strpos($dbType, 'TIMESTAMP') !== false) { |
||||
$column->type = 'timestamp'; |
||||
} else { |
||||
$column->type = 'string'; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Extracts size, precision and scale information from column's DB type. |
||||
* @param ColumnSchema $column |
||||
* @param string $dbType the column's DB type |
||||
* @param string $precision total number of digits. |
||||
* This parameter is available since version 2.0.4. |
||||
* @param string $scale number of digits on the right of the decimal separator. |
||||
* This parameter is available since version 2.0.4. |
||||
* @param string $length length for character types. |
||||
* This parameter is available since version 2.0.4. |
||||
*/ |
||||
protected function extractColumnSize($column, $dbType, $precision, $scale, $length) |
||||
{ |
||||
$column->size = trim($length) === '' ? null : (int) $length; |
||||
$column->precision = trim($precision) === '' ? null : (int) $precision; |
||||
$column->scale = trim($scale) === '' ? null : (int) $scale; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function insert($table, $columns) |
||||
{ |
||||
$params = []; |
||||
$returnParams = []; |
||||
$sql = $this->db->getQueryBuilder()->insert($table, $columns, $params); |
||||
$tableSchema = $this->getTableSchema($table); |
||||
$returnColumns = $tableSchema->primaryKey; |
||||
if (!empty($returnColumns)) { |
||||
$columnSchemas = $tableSchema->columns; |
||||
$returning = []; |
||||
foreach ((array) $returnColumns as $name) { |
||||
$phName = QueryBuilder::PARAM_PREFIX . (count($params) + count($returnParams)); |
||||
$returnParams[$phName] = [ |
||||
'column' => $name, |
||||
'value' => null, |
||||
]; |
||||
if (!isset($columnSchemas[$name]) || $columnSchemas[$name]->phpType !== 'integer') { |
||||
$returnParams[$phName]['dataType'] = \PDO::PARAM_STR; |
||||
} else { |
||||
$returnParams[$phName]['dataType'] = \PDO::PARAM_INT; |
||||
} |
||||
$returnParams[$phName]['size'] = isset($columnSchemas[$name]) && isset($columnSchemas[$name]->size) ? $columnSchemas[$name]->size : -1; |
||||
$returning[] = $this->quoteColumnName($name); |
||||
} |
||||
$sql .= ' RETURNING ' . implode(', ', $returning) . ' INTO ' . implode(', ', array_keys($returnParams)); |
||||
} |
||||
|
||||
$command = $this->db->createCommand($sql, $params); |
||||
$command->prepare(false); |
||||
|
||||
foreach ($returnParams as $name => &$value) { |
||||
$command->pdoStatement->bindParam($name, $value['value'], $value['dataType'], $value['size']); |
||||
} |
||||
|
||||
if (!$command->execute()) { |
||||
return false; |
||||
} |
||||
|
||||
$result = []; |
||||
foreach ($returnParams as $value) { |
||||
$result[$value['column']] = $value['value']; |
||||
} |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Loads multiple types of constraints and returns the specified ones. |
||||
* @param string $tableName table name. |
||||
* @param string $returnType return type: |
||||
* - primaryKey |
||||
* - foreignKeys |
||||
* - uniques |
||||
* - checks |
||||
* @return mixed constraints. |
||||
*/ |
||||
private function loadTableConstraints($tableName, $returnType) |
||||
{ |
||||
static $sql = <<<'SQL' |
||||
SELECT |
||||
/*+ PUSH_PRED("uc") PUSH_PRED("uccol") PUSH_PRED("fuc") */ |
||||
"uc"."CONSTRAINT_NAME" AS "name", |
||||
"uccol"."COLUMN_NAME" AS "column_name", |
||||
"uc"."CONSTRAINT_TYPE" AS "type", |
||||
"fuc"."OWNER" AS "foreign_table_schema", |
||||
"fuc"."TABLE_NAME" AS "foreign_table_name", |
||||
"fuccol"."COLUMN_NAME" AS "foreign_column_name", |
||||
"uc"."DELETE_RULE" AS "on_delete", |
||||
"uc"."SEARCH_CONDITION" AS "check_expr" |
||||
FROM "USER_CONSTRAINTS" "uc" |
||||
INNER JOIN "USER_CONS_COLUMNS" "uccol" |
||||
ON "uccol"."OWNER" = "uc"."OWNER" AND "uccol"."CONSTRAINT_NAME" = "uc"."CONSTRAINT_NAME" |
||||
LEFT JOIN "USER_CONSTRAINTS" "fuc" |
||||
ON "fuc"."OWNER" = "uc"."R_OWNER" AND "fuc"."CONSTRAINT_NAME" = "uc"."R_CONSTRAINT_NAME" |
||||
LEFT JOIN "USER_CONS_COLUMNS" "fuccol" |
||||
ON "fuccol"."OWNER" = "fuc"."OWNER" AND "fuccol"."CONSTRAINT_NAME" = "fuc"."CONSTRAINT_NAME" AND "fuccol"."POSITION" = "uccol"."POSITION" |
||||
WHERE "uc"."OWNER" = :schemaName AND "uc"."TABLE_NAME" = :tableName |
||||
ORDER BY "uccol"."POSITION" ASC |
||||
SQL; |
||||
|
||||
$resolvedName = $this->resolveTableName($tableName); |
||||
$constraints = $this->db->createCommand($sql, [ |
||||
':schemaName' => $resolvedName->schemaName, |
||||
':tableName' => $resolvedName->name, |
||||
])->queryAll(); |
||||
$constraints = $this->normalizePdoRowKeyCase($constraints, true); |
||||
$constraints = ArrayHelper::index($constraints, null, ['type', 'name']); |
||||
$result = [ |
||||
'primaryKey' => null, |
||||
'foreignKeys' => [], |
||||
'uniques' => [], |
||||
'checks' => [], |
||||
]; |
||||
foreach ($constraints as $type => $names) { |
||||
foreach ($names as $name => $constraint) { |
||||
switch ($type) { |
||||
case 'P': |
||||
$result['primaryKey'] = new Constraint([ |
||||
'name' => $name, |
||||
'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
||||
]); |
||||
break; |
||||
case 'R': |
||||
$result['foreignKeys'][] = new ForeignKeyConstraint([ |
||||
'name' => $name, |
||||
'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
||||
'foreignSchemaName' => $constraint[0]['foreign_table_schema'], |
||||
'foreignTableName' => $constraint[0]['foreign_table_name'], |
||||
'foreignColumnNames' => ArrayHelper::getColumn($constraint, 'foreign_column_name'), |
||||
'onDelete' => $constraint[0]['on_delete'], |
||||
'onUpdate' => null, |
||||
]); |
||||
break; |
||||
case 'U': |
||||
$result['uniques'][] = new Constraint([ |
||||
'name' => $name, |
||||
'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
||||
]); |
||||
break; |
||||
case 'C': |
||||
$result['checks'][] = new CheckConstraint([ |
||||
'name' => $name, |
||||
'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'), |
||||
'expression' => $constraint[0]['check_expr'], |
||||
]); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
foreach ($result as $type => $data) { |
||||
$this->setTableMetadata($tableName, $type, $data); |
||||
} |
||||
|
||||
return $result[$returnType]; |
||||
} |
||||
} |
@ -1,71 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\db\oci\conditions; |
||||
|
||||
use yii\db\conditions\InCondition; |
||||
use yii\db\ExpressionInterface; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
class InConditionBuilder extends \yii\db\conditions\InConditionBuilder |
||||
{ |
||||
/** |
||||
* Method builds the raw SQL from the $expression that will not be additionally |
||||
* escaped or quoted. |
||||
* |
||||
* @param ExpressionInterface|InCondition $expression the expression to be built. |
||||
* @param array $params the binding parameters. |
||||
* @return string the raw SQL that will not be additionally escaped or quoted. |
||||
*/ |
||||
public function build(ExpressionInterface $expression, array &$params = []) |
||||
{ |
||||
$splitCondition = $this->splitCondition($expression, $params); |
||||
if ($splitCondition !== null) { |
||||
return $splitCondition; |
||||
} |
||||
|
||||
return parent::build($expression, $params); |
||||
} |
||||
|
||||
/** |
||||
* Oracle DBMS does not support more than 1000 parameters in `IN` condition. |
||||
* This method splits long `IN` condition into series of smaller ones. |
||||
* |
||||
* @param ExpressionInterface|InCondition $condition the expression to be built. |
||||
* @param array $params the binding parameters. |
||||
* @return null|string null when split is not required. Otherwise - built SQL condition. |
||||
*/ |
||||
protected function splitCondition(InCondition $condition, &$params) |
||||
{ |
||||
$operator = $condition->getOperator(); |
||||
$values = $condition->getValues(); |
||||
$column = $condition->getColumn(); |
||||
|
||||
if ($values instanceof \Traversable) { |
||||
$values = iterator_to_array($values); |
||||
} |
||||
|
||||
if (!is_array($values)) { |
||||
return null; |
||||
} |
||||
|
||||
$maxParameters = 1000; |
||||
$count = count($values); |
||||
if ($count <= $maxParameters) { |
||||
return null; |
||||
} |
||||
|
||||
$slices = []; |
||||
for ($i = 0; $i < $count; $i += $maxParameters) { |
||||
$slices[] = $this->queryBuilder->createConditionFromArray([$operator, $column, array_slice($values, $i, $maxParameters)]); |
||||
} |
||||
|
||||
return $this->queryBuilder->buildCondition([($operator === 'IN') ? 'OR' : 'AND', $slices], $params); |
||||
} |
||||
} |
@ -1,48 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\db\oci\conditions; |
||||
|
||||
use yii\db\ExpressionInterface; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
class LikeConditionBuilder extends \yii\db\conditions\LikeConditionBuilder |
||||
{ |
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected $escapeCharacter = '!'; |
||||
/** |
||||
* `\` is initialized in [[buildLikeCondition()]] method since |
||||
* we need to choose replacement value based on [[\yii\db\Schema::quoteValue()]]. |
||||
* {@inheritdoc} |
||||
*/ |
||||
protected $escapingReplacements = [ |
||||
'%' => '!%', |
||||
'_' => '!_', |
||||
'!' => '!!', |
||||
]; |
||||
|
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function build(ExpressionInterface $expression, array &$params = []) |
||||
{ |
||||
if (!isset($this->escapingReplacements['\\'])) { |
||||
/* |
||||
* Different pdo_oci8 versions may or may not implement PDO::quote(), so |
||||
* yii\db\Schema::quoteValue() may or may not quote \. |
||||
*/ |
||||
$this->escapingReplacements['\\'] = substr($this->queryBuilder->db->quoteValue('\\'), 1, -1); |
||||
} |
||||
|
||||
return parent::build($expression, $params); |
||||
} |
||||
} |
@ -1,18 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
* @group data |
||||
*/ |
||||
class ActiveDataProviderTest extends \yiiunit\framework\data\ActiveDataProviderTest |
||||
{ |
||||
public $driverName = 'sqlsrv'; |
||||
} |
@ -1,18 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
* @group test |
||||
*/ |
||||
class ActiveFixtureTest extends \yiiunit\framework\test\ActiveFixtureTest |
||||
{ |
||||
public $driverName = 'sqlsrv'; |
||||
} |
@ -1,17 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
*/ |
||||
class ActiveQueryTest extends \yiiunit\framework\db\ActiveQueryTest |
||||
{ |
||||
public $driverName = 'sqlsrv'; |
||||
} |
@ -1,22 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
*/ |
||||
class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest |
||||
{ |
||||
public $driverName = 'sqlsrv'; |
||||
|
||||
public function testExplicitPkOnAutoIncrement() |
||||
{ |
||||
$this->markTestSkipped('MSSQL does not support explicit value for an IDENTITY column.'); |
||||
} |
||||
} |
@ -1,17 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
*/ |
||||
class BatchQueryResultTest extends \yiiunit\framework\db\BatchQueryResultTest |
||||
{ |
||||
public $driverName = 'sqlsrv'; |
||||
} |
@ -1,30 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
use yii\db\ColumnSchemaBuilder; |
||||
|
||||
/** |
||||
* ColumnSchemaBuilderTest tests ColumnSchemaBuilder for MSSQL. |
||||
* @group db |
||||
* @group mssql |
||||
*/ |
||||
class ColumnSchemaBuilderTest extends \yiiunit\framework\db\ColumnSchemaBuilderTest |
||||
{ |
||||
public $driverName = 'sqlsrv'; |
||||
|
||||
/** |
||||
* @param string $type |
||||
* @param int $length |
||||
* @return ColumnSchemaBuilder |
||||
*/ |
||||
public function getColumnSchemaBuilder($type, $length = null) |
||||
{ |
||||
return new ColumnSchemaBuilder($type, $length, $this->getConnection()); |
||||
} |
||||
} |
@ -1,128 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
*/ |
||||
class CommandTest extends \yiiunit\framework\db\CommandTest |
||||
{ |
||||
protected $driverName = 'sqlsrv'; |
||||
|
||||
public function testAutoQuoting() |
||||
{ |
||||
$db = $this->getConnection(false); |
||||
|
||||
$sql = 'SELECT [[id]], [[t.name]] FROM {{customer}} t'; |
||||
$command = $db->createCommand($sql); |
||||
$this->assertEquals('SELECT [id], [t].[name] FROM [customer] t', $command->sql); |
||||
} |
||||
|
||||
public function testPrepareCancel() |
||||
{ |
||||
$this->markTestSkipped('MSSQL driver does not support this feature.'); |
||||
} |
||||
|
||||
public function testBindParamValue() |
||||
{ |
||||
$db = $this->getConnection(); |
||||
|
||||
// bindParam |
||||
$sql = 'INSERT INTO customer(email, name, address) VALUES (:email, :name, :address)'; |
||||
$command = $db->createCommand($sql); |
||||
$email = 'user4@example.com'; |
||||
$name = 'user4'; |
||||
$address = 'address4'; |
||||
$command->bindParam(':email', $email); |
||||
$command->bindParam(':name', $name); |
||||
$command->bindParam(':address', $address); |
||||
$command->execute(); |
||||
|
||||
$sql = 'SELECT name FROM customer WHERE email=:email'; |
||||
$command = $db->createCommand($sql); |
||||
$command->bindParam(':email', $email); |
||||
$this->assertEquals($name, $command->queryScalar()); |
||||
|
||||
$sql = 'INSERT INTO type (int_col, char_col, float_col, blob_col, numeric_col, bool_col) VALUES (:int_col, :char_col, :float_col, CONVERT([varbinary], :blob_col), :numeric_col, :bool_col)'; |
||||
$command = $db->createCommand($sql); |
||||
$intCol = 123; |
||||
$charCol = 'abc'; |
||||
$floatCol = 1.23; |
||||
$blobCol = "\x10\x11\x12"; |
||||
$numericCol = '1.23'; |
||||
$boolCol = false; |
||||
$command->bindParam(':int_col', $intCol); |
||||
$command->bindParam(':char_col', $charCol); |
||||
$command->bindParam(':float_col', $floatCol); |
||||
$command->bindParam(':blob_col', $blobCol); |
||||
$command->bindParam(':numeric_col', $numericCol); |
||||
$command->bindParam(':bool_col', $boolCol); |
||||
$this->assertEquals(1, $command->execute()); |
||||
|
||||
$sql = 'SELECT int_col, char_col, float_col, CONVERT([nvarchar], blob_col) AS blob_col, numeric_col FROM type'; |
||||
$row = $db->createCommand($sql)->queryOne(); |
||||
$this->assertEquals($intCol, $row['int_col']); |
||||
$this->assertEquals($charCol, trim($row['char_col'])); |
||||
$this->assertEquals($floatCol, $row['float_col']); |
||||
$this->assertEquals($blobCol, $row['blob_col']); |
||||
$this->assertEquals($numericCol, $row['numeric_col']); |
||||
|
||||
// bindValue |
||||
$sql = 'INSERT INTO customer(email, name, address) VALUES (:email, \'user5\', \'address5\')'; |
||||
$command = $db->createCommand($sql); |
||||
$command->bindValue(':email', 'user5@example.com'); |
||||
$command->execute(); |
||||
|
||||
$sql = 'SELECT email FROM customer WHERE name=:name'; |
||||
$command = $db->createCommand($sql); |
||||
$command->bindValue(':name', 'user5'); |
||||
$this->assertEquals('user5@example.com', $command->queryScalar()); |
||||
} |
||||
|
||||
public function paramsNonWhereProvider() |
||||
{ |
||||
return[ |
||||
['SELECT SUBSTRING(name, :len, 6) AS name FROM {{customer}} WHERE [[email]] = :email GROUP BY name'], |
||||
['SELECT SUBSTRING(name, :len, 6) as name FROM {{customer}} WHERE [[email]] = :email ORDER BY name'], |
||||
['SELECT SUBSTRING(name, :len, 6) FROM {{customer}} WHERE [[email]] = :email'], |
||||
]; |
||||
} |
||||
|
||||
public function testAddDropDefaultValue() |
||||
{ |
||||
$db = $this->getConnection(false); |
||||
$tableName = 'test_def'; |
||||
$name = 'test_def_constraint'; |
||||
/** @var \yii\db\pgsql\Schema $schema */ |
||||
$schema = $db->getSchema(); |
||||
|
||||
if ($schema->getTableSchema($tableName) !== null) { |
||||
$db->createCommand()->dropTable($tableName)->execute(); |
||||
} |
||||
$db->createCommand()->createTable($tableName, [ |
||||
'int1' => 'integer', |
||||
])->execute(); |
||||
|
||||
$this->assertEmpty($schema->getTableDefaultValues($tableName, true)); |
||||
$db->createCommand()->addDefaultValue($name, $tableName, 'int1', 41)->execute(); |
||||
$this->assertRegExp('/^.*41.*$/', $schema->getTableDefaultValues($tableName, true)[0]->value); |
||||
|
||||
$db->createCommand()->dropDefaultValue($name, $tableName)->execute(); |
||||
$this->assertEmpty($schema->getTableDefaultValues($tableName, true)); |
||||
} |
||||
|
||||
public function batchInsertSqlProvider() |
||||
{ |
||||
$data = parent::batchInsertSqlProvider(); |
||||
$data['issue11242']['expected'] = 'INSERT INTO [type] ([int_col], [float_col], [char_col]) VALUES (NULL, NULL, \'Kyiv {{city}}, Ukraine\')'; |
||||
$data['wrongBehavior']['expected'] = 'INSERT INTO [type] ([int_col], [float_col], [char_col]) VALUES (\'\', \'\', \'Kyiv {{city}}, Ukraine\')'; |
||||
|
||||
return $data; |
||||
} |
||||
} |
@ -1,72 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
*/ |
||||
class ConnectionTest extends \yiiunit\framework\db\ConnectionTest |
||||
{ |
||||
protected $driverName = 'sqlsrv'; |
||||
|
||||
public 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")); |
||||
} |
||||
|
||||
public function testQuoteTableName() |
||||
{ |
||||
$connection = $this->getConnection(false); |
||||
$this->assertEquals('[table]', $connection->quoteTableName('table')); |
||||
$this->assertEquals('[table]', $connection->quoteTableName('[table]')); |
||||
$this->assertEquals('[schema].[table]', $connection->quoteTableName('schema.table')); |
||||
$this->assertEquals('[schema].[table]', $connection->quoteTableName('schema.[table]')); |
||||
$this->assertEquals('[schema].[table]', $connection->quoteTableName('[schema].[table]')); |
||||
$this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}')); |
||||
$this->assertEquals('(table)', $connection->quoteTableName('(table)')); |
||||
} |
||||
|
||||
public function testQuoteColumnName() |
||||
{ |
||||
$connection = $this->getConnection(false); |
||||
$this->assertEquals('[column]', $connection->quoteColumnName('column')); |
||||
$this->assertEquals('[column]', $connection->quoteColumnName('[column]')); |
||||
$this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); |
||||
$this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); |
||||
$this->assertEquals('(column)', $connection->quoteColumnName('(column)')); |
||||
|
||||
$this->assertEquals('[column]', $connection->quoteSql('[[column]]')); |
||||
$this->assertEquals('[column]', $connection->quoteSql('{{column}}')); |
||||
} |
||||
|
||||
public function testQuoteFullColumnName() |
||||
{ |
||||
$connection = $this->getConnection(false, false); |
||||
$this->assertEquals('[table].[column]', $connection->quoteColumnName('table.column')); |
||||
$this->assertEquals('[table].[column]', $connection->quoteColumnName('table.[column]')); |
||||
$this->assertEquals('[table].[column]', $connection->quoteColumnName('[table].column')); |
||||
$this->assertEquals('[table].[column]', $connection->quoteColumnName('[table].[column]')); |
||||
|
||||
$this->assertEquals('[[table.column]]', $connection->quoteColumnName('[[table.column]]')); |
||||
$this->assertEquals('{{table}}.[column]', $connection->quoteColumnName('{{table}}.column')); |
||||
$this->assertEquals('{{table}}.[column]', $connection->quoteColumnName('{{table}}.[column]')); |
||||
$this->assertEquals('{{table}}.[[column]]', $connection->quoteColumnName('{{table}}.[[column]]')); |
||||
$this->assertEquals('{{%table}}.[column]', $connection->quoteColumnName('{{%table}}.column')); |
||||
$this->assertEquals('{{%table}}.[column]', $connection->quoteColumnName('{{%table}}.[column]')); |
||||
|
||||
$this->assertEquals('[table].[column]', $connection->quoteSql('[[table.column]]')); |
||||
$this->assertEquals('[table].[column]', $connection->quoteSql('{{table}}.[[column]]')); |
||||
$this->assertEquals('[table].[column]', $connection->quoteSql('{{table}}.[column]')); |
||||
$this->assertEquals('[table].[column]', $connection->quoteSql('{{%table}}.[[column]]')); |
||||
$this->assertEquals('[table].[column]', $connection->quoteSql('{{%table}}.[column]')); |
||||
} |
||||
} |
@ -1,18 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
* @group validators |
||||
*/ |
||||
class ExistValidatorTest extends \yiiunit\framework\validators\ExistValidatorTest |
||||
{ |
||||
public $driverName = 'sqlsrv'; |
||||
} |
@ -1,172 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
use yii\db\Query; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
*/ |
||||
class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest |
||||
{ |
||||
public $driverName = 'sqlsrv'; |
||||
|
||||
protected $likeParameterReplacements = [ |
||||
'\%' => '[%]', |
||||
'\_' => '[_]', |
||||
'[' => '[[]', |
||||
']' => '[]]', |
||||
'\\\\' => '[\\]', |
||||
]; |
||||
|
||||
public function testOffsetLimit() |
||||
{ |
||||
$expectedQuerySql = 'SELECT [id] FROM [example] ORDER BY (SELECT NULL) OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY'; |
||||
$expectedQueryParams = []; |
||||
|
||||
$query = new Query(); |
||||
$query->select('id')->from('example')->limit(10)->offset(5); |
||||
|
||||
[$actualQuerySql, $actualQueryParams] = $this->getQueryBuilder()->build($query); |
||||
|
||||
$this->assertEquals($expectedQuerySql, $actualQuerySql); |
||||
$this->assertEquals($expectedQueryParams, $actualQueryParams); |
||||
} |
||||
|
||||
public function testLimit() |
||||
{ |
||||
$expectedQuerySql = 'SELECT [id] FROM [example] ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY'; |
||||
$expectedQueryParams = []; |
||||
|
||||
$query = new Query(); |
||||
$query->select('id')->from('example')->limit(10); |
||||
|
||||
[$actualQuerySql, $actualQueryParams] = $this->getQueryBuilder()->build($query); |
||||
|
||||
$this->assertEquals($expectedQuerySql, $actualQuerySql); |
||||
$this->assertEquals($expectedQueryParams, $actualQueryParams); |
||||
} |
||||
|
||||
public function testOffset() |
||||
{ |
||||
$expectedQuerySql = 'SELECT [id] FROM [example] ORDER BY (SELECT NULL) OFFSET 10 ROWS'; |
||||
$expectedQueryParams = []; |
||||
|
||||
$query = new Query(); |
||||
$query->select('id')->from('example')->offset(10); |
||||
|
||||
[$actualQuerySql, $actualQueryParams] = $this->getQueryBuilder()->build($query); |
||||
|
||||
$this->assertEquals($expectedQuerySql, $actualQuerySql); |
||||
$this->assertEquals($expectedQueryParams, $actualQueryParams); |
||||
} |
||||
|
||||
public function testCommentColumn() |
||||
{ |
||||
$qb = $this->getQueryBuilder(); |
||||
|
||||
$expected = "sp_updateextendedproperty @name = N'MS_Description', @value = 'This is my column.', @level1type = N'Table', @level1name = comment, @level2type = N'Column', @level2name = text"; |
||||
$sql = $qb->addCommentOnColumn('comment', 'text', 'This is my column.'); |
||||
$this->assertEquals($expected, $sql); |
||||
|
||||
$expected = "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table', @level1name = comment, @level2type = N'Column', @level2name = text"; |
||||
$sql = $qb->dropCommentFromColumn('comment', 'text'); |
||||
$this->assertEquals($expected, $sql); |
||||
} |
||||
|
||||
public function testCommentTable() |
||||
{ |
||||
$qb = $this->getQueryBuilder(); |
||||
|
||||
$expected = "sp_updateextendedproperty @name = N'MS_Description', @value = 'This is my table.', @level1type = N'Table', @level1name = comment"; |
||||
$sql = $qb->addCommentOnTable('comment', 'This is my table.'); |
||||
$this->assertEquals($expected, $sql); |
||||
|
||||
$expected = "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table', @level1name = comment"; |
||||
$sql = $qb->dropCommentFromTable('comment'); |
||||
$this->assertEquals($expected, $sql); |
||||
} |
||||
|
||||
/** |
||||
* This is not used as a dataprovider for testGetColumnType to speed up the test |
||||
* when used as dataprovider every single line will cause a reconnect with the database which is not needed here. |
||||
*/ |
||||
public function columnTypes() |
||||
{ |
||||
return array_merge(parent::columnTypes(), []); |
||||
} |
||||
|
||||
public function batchInsertProvider() |
||||
{ |
||||
$data = parent::batchInsertProvider(); |
||||
|
||||
$data['escape-danger-chars']['expected'] = 'INSERT INTO [customer] ([address]) VALUES ("SQL-danger chars are escaped: \'); --")'; |
||||
$data['bool-false, bool2-null']['expected'] = 'INSERT INTO [type] ([bool_col], [bool_col2]) VALUES (FALSE, NULL)'; |
||||
$data['bool-false, time-now()']['expected'] = 'INSERT INTO {{%type}} ({{%type}}.[[bool_col]], [[time]]) VALUES (FALSE, now())'; |
||||
|
||||
return $data; |
||||
} |
||||
|
||||
public function testResetSequence() |
||||
{ |
||||
$qb = $this->getQueryBuilder(); |
||||
|
||||
$expected = "DBCC CHECKIDENT ('[item]', RESEED, (SELECT COALESCE(MAX([id]),0) FROM [item])+1)"; |
||||
$sql = $qb->resetSequence('item'); |
||||
$this->assertEquals($expected, $sql); |
||||
|
||||
$expected = "DBCC CHECKIDENT ('[item], RESEED, 4)"; |
||||
$sql = $qb->resetSequence('item', 4); |
||||
$this->assertEquals($expected, $sql); |
||||
} |
||||
|
||||
public function upsertProvider() |
||||
{ |
||||
$concreteData = [ |
||||
'regular values' => [ |
||||
3 => 'MERGE [T_upsert] WITH (HOLDLOCK) USING (VALUES (:qp0, :qp1, :qp2, :qp3)) AS [EXCLUDED] ([email], [address], [status], [profile_id]) ON ([T_upsert].[email]=[EXCLUDED].[email]) WHEN MATCHED THEN UPDATE SET [address]=[EXCLUDED].[address], [status]=[EXCLUDED].[status], [profile_id]=[EXCLUDED].[profile_id] WHEN NOT MATCHED THEN INSERT ([email], [address], [status], [profile_id]) VALUES ([EXCLUDED].[email], [EXCLUDED].[address], [EXCLUDED].[status], [EXCLUDED].[profile_id]);', |
||||
], |
||||
'regular values with update part' => [ |
||||
3 => 'MERGE [T_upsert] WITH (HOLDLOCK) USING (VALUES (:qp0, :qp1, :qp2, :qp3)) AS [EXCLUDED] ([email], [address], [status], [profile_id]) ON ([T_upsert].[email]=[EXCLUDED].[email]) WHEN MATCHED THEN UPDATE SET [address]=:qp4, [status]=:qp5, [orders]=T_upsert.orders + 1 WHEN NOT MATCHED THEN INSERT ([email], [address], [status], [profile_id]) VALUES ([EXCLUDED].[email], [EXCLUDED].[address], [EXCLUDED].[status], [EXCLUDED].[profile_id]);', |
||||
], |
||||
'regular values without update part' => [ |
||||
3 => 'MERGE [T_upsert] WITH (HOLDLOCK) USING (VALUES (:qp0, :qp1, :qp2, :qp3)) AS [EXCLUDED] ([email], [address], [status], [profile_id]) ON ([T_upsert].[email]=[EXCLUDED].[email]) WHEN NOT MATCHED THEN INSERT ([email], [address], [status], [profile_id]) VALUES ([EXCLUDED].[email], [EXCLUDED].[address], [EXCLUDED].[status], [EXCLUDED].[profile_id]);', |
||||
], |
||||
'query' => [ |
||||
3 => 'MERGE [T_upsert] WITH (HOLDLOCK) USING (SELECT [email], 2 AS [status] FROM [customer] WHERE [name]=:qp0 ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS [EXCLUDED] ([email], [status]) ON ([T_upsert].[email]=[EXCLUDED].[email]) WHEN MATCHED THEN UPDATE SET [status]=[EXCLUDED].[status] WHEN NOT MATCHED THEN INSERT ([email], [status]) VALUES ([EXCLUDED].[email], [EXCLUDED].[status]);', |
||||
], |
||||
'query with update part' => [ |
||||
3 => 'MERGE [T_upsert] WITH (HOLDLOCK) USING (SELECT [email], 2 AS [status] FROM [customer] WHERE [name]=:qp0 ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS [EXCLUDED] ([email], [status]) ON ([T_upsert].[email]=[EXCLUDED].[email]) WHEN MATCHED THEN UPDATE SET [address]=:qp1, [status]=:qp2, [orders]=T_upsert.orders + 1 WHEN NOT MATCHED THEN INSERT ([email], [status]) VALUES ([EXCLUDED].[email], [EXCLUDED].[status]);', |
||||
], |
||||
'query without update part' => [ |
||||
3 => 'MERGE [T_upsert] WITH (HOLDLOCK) USING (SELECT [email], 2 AS [status] FROM [customer] WHERE [name]=:qp0 ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS [EXCLUDED] ([email], [status]) ON ([T_upsert].[email]=[EXCLUDED].[email]) WHEN NOT MATCHED THEN INSERT ([email], [status]) VALUES ([EXCLUDED].[email], [EXCLUDED].[status]);', |
||||
], |
||||
'values and expressions' => [ |
||||
3 => 'INSERT INTO {{%T_upsert}} ({{%T_upsert}}.[[email]], [[ts]]) VALUES (:qp0, now())', |
||||
], |
||||
'values and expressions with update part' => [ |
||||
3 => 'INSERT INTO {{%T_upsert}} ({{%T_upsert}}.[[email]], [[ts]]) VALUES (:qp0, now())', |
||||
], |
||||
'values and expressions without update part' => [ |
||||
3 => 'INSERT INTO {{%T_upsert}} ({{%T_upsert}}.[[email]], [[ts]]) VALUES (:qp0, now())', |
||||
], |
||||
'query, values and expressions with update part' => [ |
||||
3 => 'MERGE {{%T_upsert}} WITH (HOLDLOCK) USING (SELECT :phEmail AS [email], now() AS [[time]]) AS [EXCLUDED] ([email], [[time]]) ON ({{%T_upsert}}.[email]=[EXCLUDED].[email]) WHEN MATCHED THEN UPDATE SET [ts]=:qp1, [[orders]]=T_upsert.orders + 1 WHEN NOT MATCHED THEN INSERT ([email], [[time]]) VALUES ([EXCLUDED].[email], [EXCLUDED].[[time]]);', |
||||
], |
||||
'query, values and expressions without update part' => [ |
||||
3 => 'MERGE {{%T_upsert}} WITH (HOLDLOCK) USING (SELECT :phEmail AS [email], now() AS [[time]]) AS [EXCLUDED] ([email], [[time]]) ON ({{%T_upsert}}.[email]=[EXCLUDED].[email]) WHEN MATCHED THEN UPDATE SET [ts]=:qp1, [[orders]]=T_upsert.orders + 1 WHEN NOT MATCHED THEN INSERT ([email], [[time]]) VALUES ([EXCLUDED].[email], [EXCLUDED].[[time]]);', |
||||
], |
||||
]; |
||||
$newData = parent::upsertProvider(); |
||||
foreach ($concreteData as $testName => $data) { |
||||
$newData[$testName] = array_replace($newData[$testName], $data); |
||||
} |
||||
return $newData; |
||||
} |
||||
} |
@ -1,17 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
*/ |
||||
class QueryTest extends \yiiunit\framework\db\QueryTest |
||||
{ |
||||
protected $driverName = 'sqlsrv'; |
||||
} |
@ -1,45 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
use yii\db\DefaultValueConstraint; |
||||
use yiiunit\framework\db\AnyValue; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
*/ |
||||
class SchemaTest extends \yiiunit\framework\db\SchemaTest |
||||
{ |
||||
public $driverName = 'sqlsrv'; |
||||
|
||||
protected $expectedSchemas = [ |
||||
'dbo', |
||||
]; |
||||
|
||||
public function constraintsProvider() |
||||
{ |
||||
$result = parent::constraintsProvider(); |
||||
$result['1: check'][2][0]->expression = '([C_check]<>\'\')'; |
||||
$result['1: default'][2] = []; |
||||
$result['1: default'][2][] = new DefaultValueConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_default'], |
||||
'value' => '((0))', |
||||
]); |
||||
|
||||
$result['2: default'][2] = []; |
||||
|
||||
$result['3: foreign key'][2][0]->foreignSchemaName = 'dbo'; |
||||
$result['3: index'][2] = []; |
||||
$result['3: default'][2] = []; |
||||
|
||||
$result['4: default'][2] = []; |
||||
return $result; |
||||
} |
||||
} |
@ -1,18 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\mssql; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group mssql |
||||
* @group validators |
||||
*/ |
||||
class UniqueValidatorTest extends \yiiunit\framework\validators\UniqueValidatorTest |
||||
{ |
||||
public $driverName = 'sqlsrv'; |
||||
} |
@ -1,18 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
* @group data |
||||
*/ |
||||
class ActiveDataProviderTest extends \yiiunit\framework\data\ActiveDataProviderTest |
||||
{ |
||||
public $driverName = 'oci'; |
||||
} |
@ -1,18 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
* @group test |
||||
*/ |
||||
class ActiveFixtureTest extends \yiiunit\framework\test\ActiveFixtureTest |
||||
{ |
||||
public $driverName = 'oci'; |
||||
} |
@ -1,17 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
*/ |
||||
class ActiveQueryTest extends \yiiunit\framework\db\ActiveQueryTest |
||||
{ |
||||
public $driverName = 'oci'; |
||||
} |
@ -1,124 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
use yiiunit\data\ar\DefaultPk; |
||||
use yiiunit\data\ar\Type; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
*/ |
||||
class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest |
||||
{ |
||||
protected $driverName = 'oci'; |
||||
|
||||
public function testCastValues() |
||||
{ |
||||
// pass, because boolean casting is not available |
||||
return; |
||||
$model = new Type(); |
||||
$model->int_col = 123; |
||||
$model->int_col2 = 456; |
||||
$model->smallint_col = 42; |
||||
$model->char_col = '1337'; |
||||
$model->char_col2 = 'test'; |
||||
$model->char_col3 = 'test123'; |
||||
$model->float_col = 3.742; |
||||
$model->float_col2 = 42.1337; |
||||
$model->bool_col = 1; |
||||
$model->bool_col2 = 0; |
||||
$model->save(false); |
||||
|
||||
/* @var $model Type */ |
||||
$model = Type::find()->one(); |
||||
$this->assertSame(123, $model->int_col); |
||||
$this->assertSame(456, $model->int_col2); |
||||
$this->assertSame(42, $model->smallint_col); |
||||
$this->assertSame('1337', trim($model->char_col)); |
||||
$this->assertSame('test', $model->char_col2); |
||||
$this->assertSame('test123', $model->char_col3); |
||||
// $this->assertSame(1337.42, $model->float_col); |
||||
// $this->assertSame(42.1337, $model->float_col2); |
||||
// $this->assertTrue($model->bool_col); |
||||
// $this->assertFalse($model->bool_col2); |
||||
} |
||||
|
||||
public function testDefaultValues() |
||||
{ |
||||
$model = new Type(); |
||||
$model->loadDefaultValues(); |
||||
$this->assertEquals(1, $model->int_col2); |
||||
$this->assertEquals('something', $model->char_col2); |
||||
$this->assertEquals(1.23, $model->float_col2); |
||||
$this->assertEquals(33.22, $model->numeric_col); |
||||
$this->assertTrue($model->bool_col2); |
||||
|
||||
// not testing $model->time, because oci\Schema can't read default value |
||||
|
||||
$model = new Type(); |
||||
$model->char_col2 = 'not something'; |
||||
|
||||
$model->loadDefaultValues(); |
||||
$this->assertEquals('not something', $model->char_col2); |
||||
|
||||
$model = new Type(); |
||||
$model->char_col2 = 'not something'; |
||||
|
||||
$model->loadDefaultValues(false); |
||||
$this->assertEquals('something', $model->char_col2); |
||||
} |
||||
|
||||
public function testFindAsArray() |
||||
{ |
||||
/* @var $customerClass \yii\db\ActiveRecordInterface */ |
||||
$customerClass = $this->getCustomerClass(); |
||||
|
||||
// asArray |
||||
$customer = $customerClass::find()->where(['id' => 2])->asArray()->one(); |
||||
$this->assertEquals([ |
||||
'id' => 2, |
||||
'email' => 'user2@example.com', |
||||
'name' => 'user2', |
||||
'address' => 'address2', |
||||
'status' => 1, |
||||
'profile_id' => null, |
||||
'bool_status' => true, |
||||
], $customer); |
||||
|
||||
// find all asArray |
||||
$customers = $customerClass::find()->asArray()->all(); |
||||
$this->assertCount(3, $customers); |
||||
$this->assertArrayHasKey('id', $customers[0]); |
||||
$this->assertArrayHasKey('name', $customers[0]); |
||||
$this->assertArrayHasKey('email', $customers[0]); |
||||
$this->assertArrayHasKey('address', $customers[0]); |
||||
$this->assertArrayHasKey('status', $customers[0]); |
||||
$this->assertArrayHasKey('bool_status', $customers[0]); |
||||
$this->assertArrayHasKey('id', $customers[1]); |
||||
$this->assertArrayHasKey('name', $customers[1]); |
||||
$this->assertArrayHasKey('email', $customers[1]); |
||||
$this->assertArrayHasKey('address', $customers[1]); |
||||
$this->assertArrayHasKey('status', $customers[1]); |
||||
$this->assertArrayHasKey('bool_status', $customers[1]); |
||||
$this->assertArrayHasKey('id', $customers[2]); |
||||
$this->assertArrayHasKey('name', $customers[2]); |
||||
$this->assertArrayHasKey('email', $customers[2]); |
||||
$this->assertArrayHasKey('address', $customers[2]); |
||||
$this->assertArrayHasKey('status', $customers[2]); |
||||
$this->assertArrayHasKey('bool_status', $customers[2]); |
||||
} |
||||
|
||||
public function testPrimaryKeyAfterSave() |
||||
{ |
||||
$record = new DefaultPk(); |
||||
$record->type = 'type'; |
||||
$record->save(false); |
||||
$this->assertEquals(5, $record->primaryKey); |
||||
} |
||||
} |
@ -1,17 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
*/ |
||||
class BatchQueryResultTest extends \yiiunit\framework\db\BatchQueryResultTest |
||||
{ |
||||
public $driverName = 'oci'; |
||||
} |
@ -1,46 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
use yii\db\oci\ColumnSchemaBuilder; |
||||
use yii\db\Schema; |
||||
|
||||
/** |
||||
* ColumnSchemaBuilderTest tests ColumnSchemaBuilder for Oracle. |
||||
* @group db |
||||
* @group oci |
||||
*/ |
||||
class ColumnSchemaBuilderTest extends \yiiunit\framework\db\ColumnSchemaBuilderTest |
||||
{ |
||||
public $driverName = 'oci'; |
||||
|
||||
/** |
||||
* @param string $type |
||||
* @param int $length |
||||
* @return ColumnSchemaBuilder |
||||
*/ |
||||
public function getColumnSchemaBuilder($type, $length = null) |
||||
{ |
||||
return new ColumnSchemaBuilder($type, $length, $this->getConnection()); |
||||
} |
||||
|
||||
/** |
||||
* @return array |
||||
*/ |
||||
public function typesProvider() |
||||
{ |
||||
return [ |
||||
['integer UNSIGNED', Schema::TYPE_INTEGER, null, [ |
||||
['unsigned'], |
||||
]], |
||||
['integer(10) UNSIGNED', Schema::TYPE_INTEGER, 10, [ |
||||
['unsigned'], |
||||
]], |
||||
]; |
||||
} |
||||
} |
@ -1,45 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
*/ |
||||
class CommandTest extends \yiiunit\framework\db\CommandTest |
||||
{ |
||||
protected $driverName = 'oci'; |
||||
|
||||
public function testAutoQuoting() |
||||
{ |
||||
$db = $this->getConnection(false); |
||||
|
||||
$sql = 'SELECT [[id]], [[t.name]] FROM {{customer}} t'; |
||||
$command = $db->createCommand($sql); |
||||
$this->assertEquals('SELECT "id", "t"."name" FROM "customer" t', $command->sql); |
||||
} |
||||
|
||||
public function testLastInsertId() |
||||
{ |
||||
$db = $this->getConnection(); |
||||
|
||||
$sql = 'INSERT INTO {{profile}}([[description]]) VALUES (\'non duplicate\')'; |
||||
$command = $db->createCommand($sql); |
||||
$command->execute(); |
||||
$this->assertEquals(3, $db->getSchema()->getLastInsertID('profile_SEQ')); |
||||
} |
||||
|
||||
public function batchInsertSqlProvider() |
||||
{ |
||||
$data = parent::batchInsertSqlProvider(); |
||||
$data['issue11242']['expected'] = 'INSERT INTO "type" ("int_col", "float_col", "char_col") VALUES (NULL, NULL, \'Kyiv {{city}}, Ukraine\')'; |
||||
$data['wrongBehavior']['expected'] = 'INSERT INTO "type" ("type"."int_col", "float_col", "char_col") VALUES (\'\', \'\', \'Kyiv {{city}}, Ukraine\')'; |
||||
|
||||
return $data; |
||||
} |
||||
} |
@ -1,88 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
use yii\db\Transaction; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
*/ |
||||
class ConnectionTest extends \yiiunit\framework\db\ConnectionTest |
||||
{ |
||||
protected $driverName = 'oci'; |
||||
|
||||
public function testSerialize() |
||||
{ |
||||
$connection = $this->getConnection(false, false); |
||||
$connection->open(); |
||||
$serialized = serialize($connection); |
||||
$unserialized = unserialize($serialized); |
||||
$this->assertInstanceOf('yii\db\Connection', $unserialized); |
||||
|
||||
$this->assertEquals(123, $unserialized->createCommand('SELECT 123 FROM DUAL')->queryScalar()); |
||||
} |
||||
|
||||
public function testQuoteTableName() |
||||
{ |
||||
$connection = $this->getConnection(false); |
||||
$this->assertEquals('"table"', $connection->quoteTableName('table')); |
||||
$this->assertEquals('"table"', $connection->quoteTableName('"table"')); |
||||
$this->assertEquals('"schema"."table"', $connection->quoteTableName('schema.table')); |
||||
$this->assertEquals('"schema"."table"', $connection->quoteTableName('schema."table"')); |
||||
$this->assertEquals('"schema"."table"', $connection->quoteTableName('"schema"."table"')); |
||||
$this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}')); |
||||
$this->assertEquals('(table)', $connection->quoteTableName('(table)')); |
||||
} |
||||
|
||||
public function testQuoteColumnName() |
||||
{ |
||||
$connection = $this->getConnection(false); |
||||
$this->assertEquals('"column"', $connection->quoteColumnName('column')); |
||||
$this->assertEquals('"column"', $connection->quoteColumnName('"column"')); |
||||
$this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); |
||||
$this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); |
||||
$this->assertEquals('(column)', $connection->quoteColumnName('(column)')); |
||||
|
||||
$this->assertEquals('"column"', $connection->quoteSql('[[column]]')); |
||||
$this->assertEquals('"column"', $connection->quoteSql('{{column}}')); |
||||
} |
||||
|
||||
public function testQuoteFullColumnName() |
||||
{ |
||||
$connection = $this->getConnection(false, false); |
||||
$this->assertEquals('"table"."column"', $connection->quoteColumnName('table.column')); |
||||
$this->assertEquals('"table"."column"', $connection->quoteColumnName('table."column"')); |
||||
$this->assertEquals('"table"."column"', $connection->quoteColumnName('"table".column')); |
||||
$this->assertEquals('"table"."column"', $connection->quoteColumnName('"table"."column"')); |
||||
|
||||
$this->assertEquals('[[table.column]]', $connection->quoteColumnName('[[table.column]]')); |
||||
$this->assertEquals('{{table}}."column"', $connection->quoteColumnName('{{table}}.column')); |
||||
$this->assertEquals('{{table}}."column"', $connection->quoteColumnName('{{table}}."column"')); |
||||
$this->assertEquals('{{table}}.[[column]]', $connection->quoteColumnName('{{table}}.[[column]]')); |
||||
$this->assertEquals('{{%table}}."column"', $connection->quoteColumnName('{{%table}}.column')); |
||||
$this->assertEquals('{{%table}}."column"', $connection->quoteColumnName('{{%table}}."column"')); |
||||
|
||||
$this->assertEquals('"table"."column"', $connection->quoteSql('[[table.column]]')); |
||||
$this->assertEquals('"table"."column"', $connection->quoteSql('{{table}}.[[column]]')); |
||||
$this->assertEquals('"table"."column"', $connection->quoteSql('{{table}}."column"')); |
||||
$this->assertEquals('"table"."column"', $connection->quoteSql('{{%table}}.[[column]]')); |
||||
$this->assertEquals('"table"."column"', $connection->quoteSql('{{%table}}."column"')); |
||||
} |
||||
|
||||
public function testTransactionIsolation() |
||||
{ |
||||
$connection = $this->getConnection(true); |
||||
|
||||
$transaction = $connection->beginTransaction(Transaction::READ_COMMITTED); |
||||
$transaction->commit(); |
||||
|
||||
$transaction = $connection->beginTransaction(Transaction::SERIALIZABLE); |
||||
$transaction->commit(); |
||||
} |
||||
} |
@ -1,18 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
* @group validators |
||||
*/ |
||||
class ExistValidatorTest extends \yiiunit\framework\validators\ExistValidatorTest |
||||
{ |
||||
public $driverName = 'oci'; |
||||
} |
@ -1,239 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
use yii\db\oci\QueryBuilder; |
||||
use yii\db\oci\Schema; |
||||
use yiiunit\data\base\TraversableObject; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
*/ |
||||
class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest |
||||
{ |
||||
public $driverName = 'oci'; |
||||
|
||||
protected $likeEscapeCharSql = " ESCAPE '!'"; |
||||
protected $likeParameterReplacements = [ |
||||
'\%' => '!%', |
||||
'\_' => '!_', |
||||
'!' => '!!', |
||||
]; |
||||
|
||||
/** |
||||
* This is not used as a dataprovider for testGetColumnType to speed up the test |
||||
* when used as dataprovider every single line will cause a reconnect with the database which is not needed here. |
||||
*/ |
||||
public function columnTypes() |
||||
{ |
||||
return array_merge(parent::columnTypes(), [ |
||||
[ |
||||
Schema::TYPE_BOOLEAN . ' DEFAULT 1 NOT NULL', |
||||
$this->boolean()->notNull()->defaultValue(1), |
||||
'NUMBER(1) DEFAULT 1 NOT NULL', |
||||
], |
||||
]); |
||||
} |
||||
|
||||
public function foreignKeysProvider() |
||||
{ |
||||
$tableName = 'T_constraints_3'; |
||||
$name = 'CN_constraints_3'; |
||||
$pkTableName = 'T_constraints_2'; |
||||
return [ |
||||
'drop' => [ |
||||
"ALTER TABLE {{{$tableName}}} DROP CONSTRAINT [[$name]]", |
||||
function (QueryBuilder $qb) use ($tableName, $name) { |
||||
return $qb->dropForeignKey($name, $tableName); |
||||
}, |
||||
], |
||||
'add' => [ |
||||
"ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] FOREIGN KEY ([[C_fk_id_1]]) REFERENCES {{{$pkTableName}}} ([[C_id_1]]) ON DELETE CASCADE", |
||||
function (QueryBuilder $qb) use ($tableName, $name, $pkTableName) { |
||||
return $qb->addForeignKey($name, $tableName, 'C_fk_id_1', $pkTableName, 'C_id_1', 'CASCADE'); |
||||
}, |
||||
], |
||||
'add (2 columns)' => [ |
||||
"ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] FOREIGN KEY ([[C_fk_id_1]], [[C_fk_id_2]]) REFERENCES {{{$pkTableName}}} ([[C_id_1]], [[C_id_2]]) ON DELETE CASCADE", |
||||
function (QueryBuilder $qb) use ($tableName, $name, $pkTableName) { |
||||
return $qb->addForeignKey($name, $tableName, 'C_fk_id_1, C_fk_id_2', $pkTableName, 'C_id_1, C_id_2', 'CASCADE'); |
||||
}, |
||||
], |
||||
]; |
||||
} |
||||
|
||||
public function indexesProvider() |
||||
{ |
||||
$result = parent::indexesProvider(); |
||||
$result['drop'][0] = 'DROP INDEX [[CN_constraints_2_single]]'; |
||||
return $result; |
||||
} |
||||
|
||||
public function testAddDropDefaultValue($sql, \Closure $builder) |
||||
{ |
||||
$this->markTestSkipped('Adding/dropping default constraints is not supported in Oracle.'); |
||||
} |
||||
|
||||
public function testCommentColumn() |
||||
{ |
||||
$qb = $this->getQueryBuilder(); |
||||
|
||||
$expected = "COMMENT ON COLUMN [[comment]].[[text]] IS 'This is my column.'"; |
||||
$sql = $qb->addCommentOnColumn('comment', 'text', 'This is my column.'); |
||||
$this->assertEquals($this->replaceQuotes($expected), $sql); |
||||
|
||||
$expected = "COMMENT ON COLUMN [[comment]].[[text]] IS ''"; |
||||
$sql = $qb->dropCommentFromColumn('comment', 'text'); |
||||
$this->assertEquals($this->replaceQuotes($expected), $sql); |
||||
} |
||||
|
||||
public function testCommentTable() |
||||
{ |
||||
$qb = $this->getQueryBuilder(); |
||||
|
||||
$expected = "COMMENT ON TABLE [[comment]] IS 'This is my table.'"; |
||||
$sql = $qb->addCommentOnTable('comment', 'This is my table.'); |
||||
$this->assertEquals($this->replaceQuotes($expected), $sql); |
||||
|
||||
$expected = "COMMENT ON TABLE [[comment]] IS ''"; |
||||
$sql = $qb->dropCommentFromTable('comment'); |
||||
$this->assertEquals($this->replaceQuotes($expected), $sql); |
||||
} |
||||
|
||||
public function testResetSequence() |
||||
{ |
||||
$qb = $this->getQueryBuilder(); |
||||
|
||||
$expected = 'DROP SEQUENCE "item_SEQ";' |
||||
. 'CREATE SEQUENCE "item_SEQ" START WITH 6 INCREMENT BY 1 NOMAXVALUE NOCACHE'; |
||||
$sql = $qb->resetSequence('item'); |
||||
$this->assertEquals($expected, $sql); |
||||
|
||||
$expected = 'DROP SEQUENCE "item_SEQ";' |
||||
. 'CREATE SEQUENCE "item_SEQ" START WITH 4 INCREMENT BY 1 NOMAXVALUE NOCACHE'; |
||||
$sql = $qb->resetSequence('item', 4); |
||||
$this->assertEquals($expected, $sql); |
||||
} |
||||
|
||||
public function likeConditionProvider() |
||||
{ |
||||
/* |
||||
* Different pdo_oci8 versions may or may not implement PDO::quote(), so |
||||
* yii\db\Schema::quoteValue() may or may not quote \. |
||||
*/ |
||||
$encodedBackslash = substr($this->getDb()->quoteValue('\\'), 1, -1); |
||||
$this->likeParameterReplacements[$encodedBackslash] = '\\'; |
||||
return parent::likeConditionProvider(); |
||||
} |
||||
|
||||
public function conditionProvider() |
||||
{ |
||||
return array_merge(parent::conditionProvider(), [ |
||||
[ |
||||
['in', 'id', range(0, 2500)], |
||||
|
||||
' (' |
||||
. '([[id]] IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 0, 999)) . '))' |
||||
. ' OR ([[id]] IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 1000, 1999)) . '))' |
||||
. ' OR ([[id]] IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 2000, 2500)) . '))' |
||||
. ')', |
||||
|
||||
array_flip($this->generateSprintfSeries(':qp%d', 0, 2500)), |
||||
], |
||||
[ |
||||
['not in', 'id', range(0, 2500)], |
||||
|
||||
'(' |
||||
. '([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 0, 999)) . '))' |
||||
. ' AND ([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 1000, 1999)) . '))' |
||||
. ' AND ([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 2000, 2500)) . '))' |
||||
. ')', |
||||
|
||||
array_flip($this->generateSprintfSeries(':qp%d', 0, 2500)), |
||||
], |
||||
[ |
||||
['not in', 'id', new TraversableObject(range(0, 2500))], |
||||
|
||||
'(' |
||||
. '([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 0, 999)) . '))' |
||||
. ' AND ([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 1000, 1999)) . '))' |
||||
. ' AND ([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 2000, 2500)) . '))' |
||||
. ')', |
||||
|
||||
array_flip($this->generateSprintfSeries(':qp%d', 0, 2500)), |
||||
], |
||||
]); |
||||
} |
||||
|
||||
protected function generateSprintfSeries($pattern, $from, $to) |
||||
{ |
||||
$items = []; |
||||
for ($i = $from; $i <= $to; $i++) { |
||||
$items[] = sprintf($pattern, $i); |
||||
} |
||||
|
||||
return $items; |
||||
} |
||||
|
||||
public function upsertProvider() |
||||
{ |
||||
$concreteData = [ |
||||
'regular values' => [ |
||||
3 => 'MERGE INTO "T_upsert" USING (SELECT :qp0 AS "email", :qp1 AS "address", :qp2 AS "status", :qp3 AS "profile_id" FROM "DUAL") "EXCLUDED" ON ("T_upsert"."email"="EXCLUDED"."email") WHEN MATCHED THEN UPDATE SET "address"="EXCLUDED"."address", "status"="EXCLUDED"."status", "profile_id"="EXCLUDED"."profile_id" WHEN NOT MATCHED THEN INSERT ("email", "address", "status", "profile_id") VALUES ("EXCLUDED"."email", "EXCLUDED"."address", "EXCLUDED"."status", "EXCLUDED"."profile_id")', |
||||
], |
||||
'regular values with update part' => [ |
||||
3 => 'MERGE INTO "T_upsert" USING (SELECT :qp0 AS "email", :qp1 AS "address", :qp2 AS "status", :qp3 AS "profile_id" FROM "DUAL") "EXCLUDED" ON ("T_upsert"."email"="EXCLUDED"."email") WHEN MATCHED THEN UPDATE SET "address"=:qp4, "status"=:qp5, "orders"=T_upsert.orders + 1 WHEN NOT MATCHED THEN INSERT ("email", "address", "status", "profile_id") VALUES ("EXCLUDED"."email", "EXCLUDED"."address", "EXCLUDED"."status", "EXCLUDED"."profile_id")', |
||||
], |
||||
'regular values without update part' => [ |
||||
3 => 'MERGE INTO "T_upsert" USING (SELECT :qp0 AS "email", :qp1 AS "address", :qp2 AS "status", :qp3 AS "profile_id" FROM "DUAL") "EXCLUDED" ON ("T_upsert"."email"="EXCLUDED"."email") WHEN NOT MATCHED THEN INSERT ("email", "address", "status", "profile_id") VALUES ("EXCLUDED"."email", "EXCLUDED"."address", "EXCLUDED"."status", "EXCLUDED"."profile_id")', |
||||
], |
||||
'query' => [ |
||||
3 => 'MERGE INTO "T_upsert" USING (WITH USER_SQL AS (SELECT "email", 2 AS "status" FROM "customer" WHERE "name"=:qp0), |
||||
PAGINATION AS (SELECT USER_SQL.*, rownum as rowNumId FROM USER_SQL) |
||||
SELECT * |
||||
FROM PAGINATION |
||||
WHERE rownum <= 1) "EXCLUDED" ON ("T_upsert"."email"="EXCLUDED"."email") WHEN MATCHED THEN UPDATE SET "status"="EXCLUDED"."status" WHEN NOT MATCHED THEN INSERT ("email", "status") VALUES ("EXCLUDED"."email", "EXCLUDED"."status")', |
||||
], |
||||
'query with update part' => [ |
||||
3 => 'MERGE INTO "T_upsert" USING (WITH USER_SQL AS (SELECT "email", 2 AS "status" FROM "customer" WHERE "name"=:qp0), |
||||
PAGINATION AS (SELECT USER_SQL.*, rownum as rowNumId FROM USER_SQL) |
||||
SELECT * |
||||
FROM PAGINATION |
||||
WHERE rownum <= 1) "EXCLUDED" ON ("T_upsert"."email"="EXCLUDED"."email") WHEN MATCHED THEN UPDATE SET "address"=:qp1, "status"=:qp2, "orders"=T_upsert.orders + 1 WHEN NOT MATCHED THEN INSERT ("email", "status") VALUES ("EXCLUDED"."email", "EXCLUDED"."status")', |
||||
], |
||||
'query without update part' => [ |
||||
3 => 'MERGE INTO "T_upsert" USING (WITH USER_SQL AS (SELECT "email", 2 AS "status" FROM "customer" WHERE "name"=:qp0), |
||||
PAGINATION AS (SELECT USER_SQL.*, rownum as rowNumId FROM USER_SQL) |
||||
SELECT * |
||||
FROM PAGINATION |
||||
WHERE rownum <= 1) "EXCLUDED" ON ("T_upsert"."email"="EXCLUDED"."email") WHEN NOT MATCHED THEN INSERT ("email", "status") VALUES ("EXCLUDED"."email", "EXCLUDED"."status")', |
||||
], |
||||
'values and expressions' => [ |
||||
3 => 'INSERT INTO {{%T_upsert}} ({{%T_upsert}}.[[email]], [[ts]]) VALUES (:qp0, now())', |
||||
], |
||||
'values and expressions with update part' => [ |
||||
3 => 'INSERT INTO {{%T_upsert}} ({{%T_upsert}}.[[email]], [[ts]]) VALUES (:qp0, now())', |
||||
], |
||||
'values and expressions without update part' => [ |
||||
3 => 'INSERT INTO {{%T_upsert}} ({{%T_upsert}}.[[email]], [[ts]]) VALUES (:qp0, now())', |
||||
], |
||||
'query, values and expressions with update part' => [ |
||||
3 => 'MERGE INTO {{%T_upsert}} USING (SELECT :phEmail AS "email", now() AS [[time]]) "EXCLUDED" ON ({{%T_upsert}}."email"="EXCLUDED"."email") WHEN MATCHED THEN UPDATE SET "ts"=:qp1, [[orders]]=T_upsert.orders + 1 WHEN NOT MATCHED THEN INSERT ("email", [[time]]) VALUES ("EXCLUDED"."email", "EXCLUDED".[[time]])', |
||||
], |
||||
'query, values and expressions without update part' => [ |
||||
3 => 'MERGE INTO {{%T_upsert}} USING (SELECT :phEmail AS "email", now() AS [[time]]) "EXCLUDED" ON ({{%T_upsert}}."email"="EXCLUDED"."email") WHEN MATCHED THEN UPDATE SET "ts"=:qp1, [[orders]]=T_upsert.orders + 1 WHEN NOT MATCHED THEN INSERT ("email", [[time]]) VALUES ("EXCLUDED"."email", "EXCLUDED".[[time]])', |
||||
], |
||||
]; |
||||
$newData = parent::upsertProvider(); |
||||
foreach ($concreteData as $testName => $data) { |
||||
$newData[$testName] = array_replace($newData[$testName], $data); |
||||
} |
||||
return $newData; |
||||
} |
||||
} |
@ -1,30 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
use yii\db\Query; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
*/ |
||||
class QueryTest extends \yiiunit\framework\db\QueryTest |
||||
{ |
||||
protected $driverName = 'oci'; |
||||
|
||||
public function testOne() |
||||
{ |
||||
$db = $this->getConnection(); |
||||
|
||||
$result = (new Query())->from('customer')->where(['[[status]]' => 2])->one($db); |
||||
$this->assertEquals('user3', $result['name']); |
||||
|
||||
$result = (new Query())->from('customer')->where(['[[status]]' => 3])->one($db); |
||||
$this->assertFalse($result); |
||||
} |
||||
} |
@ -1,177 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
use yii\db\CheckConstraint; |
||||
use yiiunit\framework\db\AnyValue; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
*/ |
||||
class SchemaTest extends \yiiunit\framework\db\SchemaTest |
||||
{ |
||||
public $driverName = 'oci'; |
||||
|
||||
protected $expectedSchemas = []; |
||||
|
||||
public function getExpectedColumns() |
||||
{ |
||||
$columns = parent::getExpectedColumns(); |
||||
unset($columns['enum_col']); |
||||
unset($columns['json_col']); |
||||
$columns['int_col']['dbType'] = 'NUMBER'; |
||||
$columns['int_col']['size'] = 22; |
||||
$columns['int_col']['precision'] = null; |
||||
$columns['int_col']['scale'] = 0; |
||||
$columns['int_col2']['dbType'] = 'NUMBER'; |
||||
$columns['int_col2']['size'] = 22; |
||||
$columns['int_col2']['precision'] = null; |
||||
$columns['int_col2']['scale'] = 0; |
||||
$columns['tinyint_col']['dbType'] = 'NUMBER'; |
||||
$columns['tinyint_col']['type'] = 'integer'; |
||||
$columns['tinyint_col']['size'] = 22; |
||||
$columns['tinyint_col']['precision'] = 3; |
||||
$columns['tinyint_col']['scale'] = 0; |
||||
$columns['smallint_col']['dbType'] = 'NUMBER'; |
||||
$columns['smallint_col']['type'] = 'integer'; |
||||
$columns['smallint_col']['size'] = 22; |
||||
$columns['smallint_col']['precision'] = null; |
||||
$columns['smallint_col']['scale'] = 0; |
||||
$columns['char_col']['dbType'] = 'CHAR'; |
||||
$columns['char_col']['precision'] = null; |
||||
$columns['char_col']['size'] = 100; |
||||
$columns['char_col2']['dbType'] = 'VARCHAR2'; |
||||
$columns['char_col2']['precision'] = null; |
||||
$columns['char_col2']['size'] = 100; |
||||
$columns['char_col3']['type'] = 'string'; |
||||
$columns['char_col3']['dbType'] = 'VARCHAR2'; |
||||
$columns['char_col3']['precision'] = null; |
||||
$columns['char_col3']['size'] = 4000; |
||||
$columns['float_col']['dbType'] = 'FLOAT'; |
||||
$columns['float_col']['precision'] = 126; |
||||
$columns['float_col']['scale'] = null; |
||||
$columns['float_col']['size'] = 22; |
||||
$columns['float_col2']['dbType'] = 'FLOAT'; |
||||
$columns['float_col2']['precision'] = 126; |
||||
$columns['float_col2']['scale'] = null; |
||||
$columns['float_col2']['size'] = 22; |
||||
$columns['blob_col']['dbType'] = 'BLOB'; |
||||
$columns['blob_col']['phpType'] = 'resource'; |
||||
$columns['blob_col']['type'] = 'binary'; |
||||
$columns['blob_col']['size'] = 4000; |
||||
$columns['numeric_col']['dbType'] = 'NUMBER'; |
||||
$columns['numeric_col']['size'] = 22; |
||||
$columns['time']['dbType'] = 'TIMESTAMP(6)'; |
||||
$columns['time']['size'] = 11; |
||||
$columns['time']['scale'] = 6; |
||||
$columns['time']['defaultValue'] = null; |
||||
$columns['bool_col']['type'] = 'string'; |
||||
$columns['bool_col']['phpType'] = 'string'; |
||||
$columns['bool_col']['dbType'] = 'CHAR'; |
||||
$columns['bool_col']['size'] = 1; |
||||
$columns['bool_col']['precision'] = null; |
||||
$columns['bool_col2']['type'] = 'string'; |
||||
$columns['bool_col2']['phpType'] = 'string'; |
||||
$columns['bool_col2']['dbType'] = 'CHAR'; |
||||
$columns['bool_col2']['size'] = 1; |
||||
$columns['bool_col2']['precision'] = null; |
||||
$columns['bool_col2']['defaultValue'] = '1'; |
||||
$columns['ts_default']['type'] = 'timestamp'; |
||||
$columns['ts_default']['phpType'] = 'string'; |
||||
$columns['ts_default']['dbType'] = 'TIMESTAMP(6)'; |
||||
$columns['ts_default']['scale'] = 6; |
||||
$columns['ts_default']['size'] = 11; |
||||
$columns['ts_default']['defaultValue'] = null; |
||||
$columns['bit_col']['type'] = 'string'; |
||||
$columns['bit_col']['phpType'] = 'string'; |
||||
$columns['bit_col']['dbType'] = 'CHAR'; |
||||
$columns['bit_col']['size'] = 3; |
||||
$columns['bit_col']['precision'] = null; |
||||
$columns['bit_col']['defaultValue'] = '130'; |
||||
return $columns; |
||||
} |
||||
|
||||
/** |
||||
* Autoincrement columns detection should be disabled for Oracle |
||||
* because there is no way of associating a column with a sequence. |
||||
*/ |
||||
public function testAutoincrementDisabled() |
||||
{ |
||||
$table = $this->getConnection(false)->schema->getTableSchema('order', true); |
||||
$this->assertFalse($table->columns['id']->autoIncrement); |
||||
} |
||||
|
||||
public function constraintsProvider() |
||||
{ |
||||
$result = parent::constraintsProvider(); |
||||
$result['1: check'][2][0]->expression = '"C_check" <> \'\''; |
||||
$result['1: check'][2][] = new CheckConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_id'], |
||||
'expression' => '"C_id" IS NOT NULL', |
||||
]); |
||||
$result['1: check'][2][] = new CheckConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_not_null'], |
||||
'expression' => '"C_not_null" IS NOT NULL', |
||||
]); |
||||
$result['1: check'][2][] = new CheckConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_unique'], |
||||
'expression' => '"C_unique" IS NOT NULL', |
||||
]); |
||||
$result['1: check'][2][] = new CheckConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_default'], |
||||
'expression' => '"C_default" IS NOT NULL', |
||||
]); |
||||
|
||||
$result['2: check'][2][] = new CheckConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_id_1'], |
||||
'expression' => '"C_id_1" IS NOT NULL', |
||||
]); |
||||
$result['2: check'][2][] = new CheckConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_id_2'], |
||||
'expression' => '"C_id_2" IS NOT NULL', |
||||
]); |
||||
|
||||
$result['3: foreign key'][2][0]->foreignSchemaName = AnyValue::getInstance(); |
||||
$result['3: foreign key'][2][0]->onUpdate = null; |
||||
$result['3: index'][2] = []; |
||||
$result['3: check'][2][] = new CheckConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_fk_id_1'], |
||||
'expression' => '"C_fk_id_1" IS NOT NULL', |
||||
]); |
||||
$result['3: check'][2][] = new CheckConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_fk_id_2'], |
||||
'expression' => '"C_fk_id_2" IS NOT NULL', |
||||
]); |
||||
$result['3: check'][2][] = new CheckConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_id'], |
||||
'expression' => '"C_id" IS NOT NULL', |
||||
]); |
||||
|
||||
$result['4: check'][2][] = new CheckConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_id'], |
||||
'expression' => '"C_id" IS NOT NULL', |
||||
]); |
||||
$result['4: check'][2][] = new CheckConstraint([ |
||||
'name' => AnyValue::getInstance(), |
||||
'columnNames' => ['C_col_2'], |
||||
'expression' => '"C_col_2" IS NOT NULL', |
||||
]); |
||||
return $result; |
||||
} |
||||
} |
@ -1,18 +0,0 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yiiunit\framework\db\oci; |
||||
|
||||
/** |
||||
* @group db |
||||
* @group oci |
||||
* @group validators |
||||
*/ |
||||
class UniqueValidatorTest extends \yiiunit\framework\validators\UniqueValidatorTest |
||||
{ |
||||
public $driverName = 'oci'; |
||||
} |
Loading…
Reference in new issue