Browse Source

Implement retrieving dbms constraints

tags/2.0.13
Sergey Makinen 7 years ago
parent
commit
73ac13e6d7
No known key found for this signature in database
GPG Key ID: D227B0B214A978D6
  1. 22
      framework/db/CheckConstraint.php
  2. 103
      framework/db/Command.php
  3. 28
      framework/db/Constraint.php
  4. 216
      framework/db/ConstraintFinderTrait.php
  5. 22
      framework/db/DefaultConstraint.php
  6. 38
      framework/db/ForeignKeyConstraint.php
  7. 26
      framework/db/IndexConstraint.php
  8. 109
      framework/db/QueryBuilder.php
  9. 310
      framework/db/Schema.php
  10. 296
      framework/db/SqlToken.php
  11. 378
      framework/db/SqlTokenizer.php
  12. 20
      framework/db/cubrid/QueryBuilder.php
  13. 230
      framework/db/cubrid/Schema.php
  14. 20
      framework/db/mssql/QueryBuilder.php
  15. 322
      framework/db/mssql/Schema.php
  16. 19
      framework/db/mysql/QueryBuilder.php
  17. 258
      framework/db/mysql/Schema.php
  18. 1
      framework/db/oci/QueryBuilder.php
  19. 344
      framework/db/oci/Schema.php
  20. 309
      framework/db/pgsql/Schema.php
  21. 55
      framework/db/sqlite/QueryBuilder.php
  22. 195
      framework/db/sqlite/Schema.php
  23. 288
      framework/db/sqlite/SqlTokenizer.php
  24. 43
      tests/data/cubrid.sql
  25. 43
      tests/data/mssql.sql
  26. 47
      tests/data/mysql.sql
  27. 43
      tests/data/oci.sql
  28. 44
      tests/data/postgres.sql
  29. 43
      tests/data/sqlite.sql
  30. 23
      tests/framework/db/AnyCaseValue.php
  31. 19
      tests/framework/db/AnyValue.php
  32. 30
      tests/framework/db/CommandTest.php
  33. 9
      tests/framework/db/CompareValue.php
  34. 226
      tests/framework/db/QueryBuilderTest.php
  35. 229
      tests/framework/db/SchemaTest.php
  36. 10
      tests/framework/db/cubrid/QueryBuilderTest.php
  37. 62
      tests/framework/db/cubrid/SchemaTest.php
  38. 18
      tests/framework/db/mssql/SchemaTest.php
  39. 36
      tests/framework/db/mysql/QueryBuilderTest.php
  40. 14
      tests/framework/db/mysql/SchemaTest.php
  41. 39
      tests/framework/db/oci/QueryBuilderTest.php
  42. 72
      tests/framework/db/oci/SchemaTest.php
  43. 12
      tests/framework/db/pgsql/QueryBuilderTest.php
  44. 14
      tests/framework/db/pgsql/SchemaTest.php
  45. 31
      tests/framework/db/sqlite/QueryBuilderTest.php
  46. 24
      tests/framework/db/sqlite/SchemaTest.php
  47. 1138
      tests/framework/db/sqlite/SqlTokenizerTest.php

22
framework/db/CheckConstraint.php

@ -0,0 +1,22 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* CheckConstraint represents the metadata of a table `CHECK` constraint.
*
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.13
*/
class CheckConstraint extends Constraint
{
/**
* @var string
*/
public $expression;
}

103
framework/db/Command.php

@ -726,6 +726,109 @@ class Command extends Component
}
/**
* Creates a SQL command for adding an unique constraint to an existing table.
* @param string $name the name of the unique constraint.
* The name will be properly quoted by the method.
* @param string $table the table that the unique constraint will be added to.
* The name will be properly quoted by the method.
* @param string|array $columns the name of the column to that the constraint will be added on.
* If there are multiple columns, separate them with commas.
* The name will be properly quoted by the method.
* @return $this the command object itself.
* @since 2.0.13
*/
public function addUnique($name, $table, $columns)
{
$sql = $this->db->getQueryBuilder()->addUnique($name, $table, $columns);
return $this->setSql($sql);
}
/**
* Creates a SQL command for dropping an unique constraint.
* @param string $name the name of the unique constraint to be dropped.
* The name will be properly quoted by the method.
* @param string $table the table whose unique constraint is to be dropped.
* The name will be properly quoted by the method.
* @return $this the command object itself.
* @since 2.0.13
*/
public function dropUnique($name, $table)
{
$sql = $this->db->getQueryBuilder()->dropUnique($name, $table);
return $this->setSql($sql);
}
/**
* Creates a SQL command for adding a check constraint to an existing table.
* @param string $name the name of the check constraint.
* The name will be properly quoted by the method.
* @param string $table the table that the check constraint will be added to.
* The name will be properly quoted by the method.
* @param string $check the SQL of the `CHECK` constraint.
* @return $this the command object itself.
* @since 2.0.13
*/
public function addCheck($name, $table, $check)
{
$sql = $this->db->getQueryBuilder()->addCheck($name, $table, $check);
return $this->setSql($sql);
}
/**
* Creates a SQL command for dropping a check constraint.
* @param string $name the name of the check constraint to be dropped.
* The name will be properly quoted by the method.
* @param string $table the table whose check constraint is to be dropped.
* The name will be properly quoted by the method.
* @return $this the command object itself.
* @since 2.0.13
*/
public function dropCheck($name, $table)
{
$sql = $this->db->getQueryBuilder()->dropCheck($name, $table);
return $this->setSql($sql);
}
/**
* Creates a SQL command for adding a default value constraint to an existing table.
* @param string $name the name of the default value constraint.
* The name will be properly quoted by the method.
* @param string $table the table that the default value constraint will be added to.
* The name will be properly quoted by the method.
* @param string $column the name of the column to that the constraint will be added on.
* The name will be properly quoted by the method.
* @param mixed $default default value.
* @return $this the command object itself.
* @since 2.0.13
*/
public function addDefaultValue($name, $table, $column, $default)
{
$sql = $this->db->getQueryBuilder()->addDefaultValue($name, $table, $column, $default);
return $this->setSql($sql);
}
/**
* Creates a SQL command for dropping a default value constraint.
* @param string $name the name of the default value constraint to be dropped.
* The name will be properly quoted by the method.
* @param string $table the table whose default value constraint is to be dropped.
* The name will be properly quoted by the method.
* @return $this the command object itself.
* @since 2.0.13
*/
public function dropDefaultValue($name, $table)
{
$sql = $this->db->getQueryBuilder()->dropDefaultValue($name, $table);
return $this->setSql($sql);
}
/**
* Creates a SQL command 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.

28
framework/db/Constraint.php

@ -0,0 +1,28 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
use yii\base\Object;
/**
* Constraint represents the metadata of a table constraint.
*
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.13
*/
class Constraint extends Object
{
/**
* @var string[]|null list of column names the constraint belongs to.
*/
public $columnNames;
/**
* @var string|null the constraint name.
*/
public $name;
}

216
framework/db/ConstraintFinderTrait.php

@ -0,0 +1,216 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* ConstraintFinderTrait provides methos for getting a table constraint information.
*
* @property CheckConstraint[][] $schemaChecks Check constraints for all tables in the database.
* Each array element is an array of [[CheckConstraint]] or its child classes. This property is read-only.
* @property DefaultConstraint[] $schemaDefaultValues Default value constraints for all tables in the database.
* Each array element is an array of [[DefaultConstraint]] or its child classes. This property is read-only.
* @property ForeignKeyConstraint[][] $schemaForeignKeys Foreign keys for all tables in the database. Each
* array element is an array of [[ForeignKeyConstraint]] or its child classes. This property is read-only.
* @property IndexConstraint[][] $schemaIndexes Indexes for all tables in the database. Each array element is
* an array of [[IndexConstraint]] or its child classes. This property is read-only.
* @property Constraint[] $schemaPrimaryKeys Primary keys for all tables in the database. Each array element
* is an instance of [[Constraint]] or its child class. This property is read-only.
* @property IndexConstraint[][] $schemaUniques Unique constraints for all tables in the database.
* Each array element is an array of [[IndexConstraint]] or its child classes. This property is read-only.
*
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.13
*/
trait ConstraintFinderTrait
{
/**
* Loads a primary key for the given table.
* @param string $tableName table name.
* @return Constraint|null primary key for the given table, `null` if the table has no primary key.
*/
protected abstract function loadTablePrimaryKey($tableName);
/**
* Loads all foreign keys for the given table.
* @param string $tableName table name.
* @return ForeignKeyConstraint[] foreign keys for the given table.
*/
protected abstract function loadTableForeignKeys($tableName);
/**
* Loads all indexes for the given table.
* @param string $tableName table name.
* @return IndexConstraint[] indexes for the given table.
*/
protected abstract function loadTableIndexes($tableName);
/**
* Loads all unique constraints for the given table.
* @param string $tableName table name.
* @return Constraint[] unique constraints for the given table.
*/
protected abstract function loadTableUniques($tableName);
/**
* Loads all check constraints for the given table.
* @param string $tableName table name.
* @return CheckConstraint[] check constraints for the given table.
*/
protected abstract function loadTableChecks($tableName);
/**
* Loads all default value constraints for the given table.
* @param string $tableName table name.
* @return DefaultConstraint[] default value constraints for the given table.
*/
protected abstract function loadTableDefaultValues($tableName);
/**
* Obtains the primary key for the named table.
* @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
* @param bool $refresh whether to reload the information even if it is found in the cache.
* @return Constraint|null table primary key, `null` if the table has no primary key.
*/
public function getTablePrimaryKey($name, $refresh = false)
{
return $this->getTableMetadata($name, 'primaryKey', $refresh);
}
/**
* Returns primary keys for all tables in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
* @param bool $refresh whether to fetch the latest available table schemas. If this is `false`,
* cached data may be returned if available.
* @return Constraint[] primary keys for all tables in the database.
* Each array element is an instance of [[Constraint]] or its child class.
*/
public function getSchemaPrimaryKeys($schema = '', $refresh = false)
{
return $this->getSchemaMetadata($schema, 'primaryKey', $refresh);
}
/**
* Obtains the foreign keys information for the named table.
* @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
* @param bool $refresh whether to reload the information even if it is found in the cache.
* @return ForeignKeyConstraint[] table foreign keys.
*/
public function getTableForeignKeys($name, $refresh = false)
{
return $this->getTableMetadata($name, 'foreignKeys', $refresh);
}
/**
* Returns foreign keys for all tables in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
* @param bool $refresh whether to fetch the latest available table schemas. If this is false,
* cached data may be returned if available.
* @return ForeignKeyConstraint[][] foreign keys for all tables in the database.
* Each array element is an array of [[ForeignKeyConstraint]] or its child classes.
*/
public function getSchemaForeignKeys($schema = '', $refresh = false)
{
return $this->getSchemaMetadata($schema, 'foreignKeys', $refresh);
}
/**
* Obtains the indexes information for the named table.
* @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
* @param bool $refresh whether to reload the information even if it is found in the cache.
* @return IndexConstraint[] table indexes.
*/
public function getTableIndexes($name, $refresh = false)
{
return $this->getTableMetadata($name, 'indexes', $refresh);
}
/**
* Returns indexes for all tables in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
* @param bool $refresh whether to fetch the latest available table schemas. If this is false,
* cached data may be returned if available.
* @return IndexConstraint[][] indexes for all tables in the database.
* Each array element is an array of [[IndexConstraint]] or its child classes.
*/
public function getSchemaIndexes($schema = '', $refresh = false)
{
return $this->getSchemaMetadata($schema, 'indexes', $refresh);
}
/**
* Obtains the unique constraints information for the named table.
* @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
* @param bool $refresh whether to reload the information even if it is found in the cache.
* @return Constraint[] table unique constraints.
*/
public function getTableUniques($name, $refresh = false)
{
return $this->getTableMetadata($name, 'uniques', $refresh);
}
/**
* Returns unique constraints for all tables in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
* @param bool $refresh whether to fetch the latest available table schemas. If this is false,
* cached data may be returned if available.
* @return Constraint[][] unique constraints for all tables in the database.
* Each array element is an array of [[Constraint]] or its child classes.
*/
public function getSchemaUniques($schema = '', $refresh = false)
{
return $this->getSchemaMetadata($schema, 'uniques', $refresh);
}
/**
* Obtains the check constraints information for the named table.
* @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
* @param bool $refresh whether to reload the information even if it is found in the cache.
* @return CheckConstraint[] table check constraints.
*/
public function getTableChecks($name, $refresh = false)
{
return $this->getTableMetadata($name, 'checks', $refresh);
}
/**
* Returns check constraints for all tables in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
* @param bool $refresh whether to fetch the latest available table schemas. If this is false,
* cached data may be returned if available.
* @return CheckConstraint[][] check constraints for all tables in the database.
* Each array element is an array of [[CheckConstraint]] or its child classes.
*/
public function getSchemaChecks($schema = '', $refresh = false)
{
return $this->getSchemaMetadata($schema, 'checks', $refresh);
}
/**
* Obtains the default value constraints information for the named table.
* @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
* @param bool $refresh whether to reload the information even if it is found in the cache.
* @return DefaultConstraint[] table default value constraints.
*/
public function getTableDefaultValues($name, $refresh = false)
{
return $this->getTableMetadata($name, 'defaultValues', $refresh);
}
/**
* Returns default value constraints for all tables in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
* @param bool $refresh whether to fetch the latest available table schemas. If this is false,
* cached data may be returned if available.
* @return DefaultConstraint[] default value constraints for all tables in the database.
* Each array element is an array of [[DefaultConstraint]] or its child classes.
*/
public function getSchemaDefaultValues($schema = '', $refresh = false)
{
return $this->getSchemaMetadata($schema, 'defaultValues', $refresh);
}
}

22
framework/db/DefaultConstraint.php

@ -0,0 +1,22 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* DefaultConstraint represents the metadata of a table `DEFAULT` constraint.
*
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.13
*/
class DefaultConstraint extends Constraint
{
/**
* @var mixed default value as returned by the DBMS.
*/
public $value;
}

38
framework/db/ForeignKeyConstraint.php

@ -0,0 +1,38 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* ForeignKeyConstraint represents the metadata of a table `FOREIGN KEY` constraint.
*
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.13
*/
class ForeignKeyConstraint extends Constraint
{
/**
* @var string|null referenced table schema name.
*/
public $foreignSchemaName;
/**
* @var string referenced table name.
*/
public $foreignTableName;
/**
* @var string[] list of referenced table column names.
*/
public $foreignColumnNames;
/**
* @var string|null referential action if rows in a referenced table are to be updated.
*/
public $onUpdate;
/**
* @var string|null referential action if rows in a referenced table are to be deleted.
*/
public $onDelete;
}

26
framework/db/IndexConstraint.php

@ -0,0 +1,26 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* IndexConstraint represents the metadata of a table `INDEX` constraint.
*
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.13
*/
class IndexConstraint extends Constraint
{
/**
* @var bool whether the index is unique.
*/
public $isUnique;
/**
* @var bool whether the index was created for a primary key.
*/
public $isPrimary;
}

109
framework/db/QueryBuilder.php

@ -82,6 +82,7 @@ class QueryBuilder extends \yii\base\Object
*/
protected $likeEscapeCharacter;
/**
* Constructor.
* @param Connection $connection the database connection.
@ -450,8 +451,8 @@ class QueryBuilder extends \yii\base\Object
}
return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
. $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
. implode(', ', $columns). ' )';
. $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
. implode(', ', $columns). ')';
}
/**
@ -609,6 +610,110 @@ class QueryBuilder extends \yii\base\Object
}
/**
* Creates a SQL command for adding an unique constraint to an existing table.
* @param string $name the name of the unique constraint.
* The name will be properly quoted by the method.
* @param string $table the table that the unique constraint will be added to.
* The name will be properly quoted by the method.
* @param string|array $columns the name of the column to that the constraint will be added on.
* If there are multiple columns, separate them with commas.
* The name will be properly quoted by the method.
* @return string the SQL statement for adding an unique constraint to an existing table.
* @since 2.0.13
*/
public function addUnique($name, $table, $columns)
{
if (is_string($columns)) {
$columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
}
foreach ($columns as $i => $col) {
$columns[$i] = $this->db->quoteColumnName($col);
}
return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
. $this->db->quoteColumnName($name) . ' UNIQUE ('
. implode(', ', $columns). ')';
}
/**
* Creates a SQL command for dropping an unique constraint.
* @param string $name the name of the unique constraint to be dropped.
* The name will be properly quoted by the method.
* @param string $table the table whose unique constraint is to be dropped.
* The name will be properly quoted by the method.
* @return string the SQL statement for dropping an unique constraint.
* @since 2.0.13
*/
public function dropUnique($name, $table)
{
return 'ALTER TABLE ' . $this->db->quoteTableName($table)
. ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
}
/**
* Creates a SQL command for adding a check constraint to an existing table.
* @param string $name the name of the check constraint.
* The name will be properly quoted by the method.
* @param string $table the table that the check constraint will be added to.
* The name will be properly quoted by the method.
* @param string $check the SQL of the `CHECK` constraint.
* @return string the SQL statement for adding a check constraint to an existing table.
* @since 2.0.13
*/
public function addCheck($name, $table, $check)
{
return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
. $this->db->quoteColumnName($name) . ' CHECK (' . $this->db->quoteSql($check) . ')';
}
/**
* Creates a SQL command for dropping a check constraint.
* @param string $name the name of the check constraint to be dropped.
* The name will be properly quoted by the method.
* @param string $table the table whose check constraint is to be dropped.
* The name will be properly quoted by the method.
* @return string the SQL statement for dropping a check constraint.
* @since 2.0.13
*/
public function dropCheck($name, $table)
{
return 'ALTER TABLE ' . $this->db->quoteTableName($table)
. ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
}
/**
* Creates a SQL command for adding a default value constraint to an existing table.
* @param string $name the name of the default value constraint.
* The name will be properly quoted by the method.
* @param string $table the table that the default value constraint will be added to.
* The name will be properly quoted by the method.
* @param string $column the name of the column to that the constraint will be added on.
* The name will be properly quoted by the method.
* @param mixed $default default value.
* @return string the SQL statement for adding a default value constraint to an existing table.
* @throws NotSupportedException if this is not supported by the underlying DBMS.
* @since 2.0.13
*/
public function addDefaultValue($name, $table, $column, $default)
{
throw new NotSupportedException($this->db->getDriverName() . ' does not support adding default value constraints.');
}
/**
* Creates a SQL command for dropping a default value constraint.
* @param string $name the name of the default value constraint to be dropped.
* The name will be properly quoted by the method.
* @param string $table the table whose default value constraint is to be dropped.
* The name will be properly quoted by the method.
* @return string the SQL statement for dropping a default value constraint.
* @throws NotSupportedException if this is not supported by the underlying DBMS.
* @since 2.0.13
*/
public function dropDefaultValue($name, $table)
{
throw new NotSupportedException($this->db->getDriverName() . ' does not support dropping default value constraints.');
}
/**
* 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.

310
framework/db/Schema.php

@ -8,6 +8,7 @@
namespace yii\db;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Object;
use yii\base\NotSupportedException;
use yii\base\InvalidCallException;
@ -33,6 +34,7 @@ use yii\caching\TagDependency;
* syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. This property is write-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0
*/
abstract class Schema extends Object
@ -89,9 +91,9 @@ abstract class Schema extends Object
*/
private $_tableNames = [];
/**
* @var array list of loaded table metadata (table name => TableSchema)
* @var array list of loaded table metadata (table name => metadata type => metadata).
*/
private $_tables = [];
private $_tableMetadata = [];
/**
* @var QueryBuilder the query builder for this database
*/
@ -99,109 +101,81 @@ abstract class Schema extends Object
/**
* @return \yii\db\ColumnSchema
* @throws \yii\base\InvalidConfigException
* Resolves the table name and schema name (if any).
* @param string $name the table name
* @return TableSchema [[TableSchema]] with resolved table, schema, etc. names.
* @throws NotSupportedException if this method is not supported by the DBMS.
* @since 2.0.13
*/
protected function createColumnSchema()
protected function resolveTableName($name)
{
return Yii::createObject($this->columnSchemaClass);
throw new NotSupportedException(get_class($this) . ' does not support resolving table names.');
}
/**
* Loads the metadata for the specified table.
* @param string $name table name
* @return null|TableSchema DBMS-dependent table metadata, null if the table does not exist.
* Returns all schema names in the database, including the default one but not system schemas.
* This method should be overridden by child classes in order to support this feature
* because the default implementation simply throws an exception.
* @return array all schema names in the database, except system schemas.
* @throws NotSupportedException if this method is not supported by the DBMS.
* @since 2.0.4
*/
abstract protected function loadTableSchema($name);
protected function findSchemaNames()
{
throw new NotSupportedException(get_class($this) . ' does not support fetching all schema names.');
}
/**
* Obtains the metadata for the named table.
* @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
* @param bool $refresh whether to reload the table schema even if it is found in the cache.
* @return null|TableSchema table metadata. Null if the named table does not exist.
* Returns all table names in the database.
* This method should be overridden by child classes in order to support this feature
* because the default implementation simply throws an exception.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @return array all table names in the database. The names have NO schema name prefix.
* @throws NotSupportedException if this method is not supported by the DBMS.
*/
public function getTableSchema($name, $refresh = false)
protected function findTableNames($schema = '')
{
if (array_key_exists($name, $this->_tables) && !$refresh) {
return $this->_tables[$name];
}
$db = $this->db;
$realName = $this->getRawTableName($name);
if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
/* @var $cache Cache */
$cache = is_string($db->schemaCache) ? Yii::$app->get($db->schemaCache, false) : $db->schemaCache;
if ($cache instanceof Cache) {
$key = $this->getCacheKey($name);
if ($refresh || ($table = $cache->get($key)) === false) {
$this->_tables[$name] = $table = $this->loadTableSchema($realName);
if ($table !== null) {
$cache->set($key, $table, $db->schemaCacheDuration, new TagDependency([
'tags' => $this->getCacheTag(),
]));
}
} else {
$this->_tables[$name] = $table;
}
return $this->_tables[$name];
}
}
return $this->_tables[$name] = $this->loadTableSchema($realName);
throw new NotSupportedException(get_class($this) . ' does not support fetching all table names.');
}
/**
* Returns the cache key for the specified table name.
* @param string $name the table name
* @return mixed the cache key
* Loads the metadata for the specified table.
* @param string $name table name
* @return TableSchema|null DBMS-dependent table metadata, `null` if the table does not exist.
*/
protected function getCacheKey($name)
abstract protected function loadTableSchema($name);
/**
* @return ColumnSchema
* @throws InvalidConfigException
*/
protected function createColumnSchema()
{
return [
__CLASS__,
$this->db->dsn,
$this->db->username,
$name,
];
return Yii::createObject($this->columnSchemaClass);
}
/**
* Returns the cache tag name.
* This allows [[refresh()]] to invalidate all cached table schemas.
* @return string the cache tag name
* Obtains the metadata for the named table.
* @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
* @param bool $refresh whether to reload the table schema even if it is found in the cache.
* @return TableSchema|null table metadata. `null` if the named table does not exist.
*/
protected function getCacheTag()
public function getTableSchema($name, $refresh = false)
{
return md5(serialize([
__CLASS__,
$this->db->dsn,
$this->db->username,
]));
return $this->getTableMetadata($name, 'schema', $refresh);
}
/**
* Returns the metadata for all tables in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
* @param bool $refresh whether to fetch the latest available table schemas. If this is false,
* @param bool $refresh whether to fetch the latest available table schemas. If this is `false`,
* cached data may be returned if available.
* @return TableSchema[] the metadata for all tables in the database.
* Each array element is an instance of [[TableSchema]] or its child class.
*/
public function getTableSchemas($schema = '', $refresh = false)
{
$tables = [];
foreach ($this->getTableNames($schema, $refresh) as $name) {
if ($schema !== '') {
$name = $schema . '.' . $name;
}
if (($table = $this->getTableSchema($name, $refresh)) !== null) {
$tables[] = $table;
}
}
return $tables;
return $this->getSchemaMetadata($schema, 'schema', $refresh);
}
/**
@ -283,7 +257,7 @@ abstract class Schema extends Object
TagDependency::invalidate($cache, $this->getCacheTag());
}
$this->_tableNames = [];
$this->_tables = [];
$this->_tableMetadata = [];
}
/**
@ -295,7 +269,7 @@ abstract class Schema extends Object
*/
public function refreshTableSchema($name)
{
unset($this->_tables[$name]);
unset($this->_tableMetadata[$name]);
$this->_tableNames = [];
/* @var $cache Cache */
$cache = is_string($this->db->schemaCache) ? Yii::$app->get($this->db->schemaCache, false) : $this->db->schemaCache;
@ -330,32 +304,6 @@ abstract class Schema extends Object
}
/**
* Returns all schema names in the database, including the default one but not system schemas.
* This method should be overridden by child classes in order to support this feature
* because the default implementation simply throws an exception.
* @return array all schema names in the database, except system schemas
* @throws NotSupportedException if this method is called
* @since 2.0.4
*/
protected function findSchemaNames()
{
throw new NotSupportedException(get_class($this) . ' does not support fetching all schema names.');
}
/**
* Returns all table names in the database.
* This method should be overridden by child classes in order to support this feature
* because the default implementation simply throws an exception.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @return array all table names in the database. The names have NO schema name prefix.
* @throws NotSupportedException if this method is called
*/
protected function findTableNames($schema = '')
{
throw new NotSupportedException(get_class($this) . ' does not support fetching all table names.');
}
/**
* Returns all unique indexes for the given table.
* Each array element is of the following structure:
*
@ -646,4 +594,162 @@ abstract class Schema extends Object
$pattern = '/^\s*(SELECT|SHOW|DESCRIBE)\b/i';
return preg_match($pattern, $sql) > 0;
}
/**
* Returns the cache key for the specified table name.
* @param string $name the table name
* @return mixed the cache key
*/
protected function getCacheKey($name)
{
return [
__CLASS__,
$this->db->dsn,
$this->db->username,
$name,
];
}
/**
* Returns the cache tag name.
* This allows [[refresh()]] to invalidate all cached table schemas.
* @return string the cache tag name
*/
protected function getCacheTag()
{
return md5(serialize([
__CLASS__,
$this->db->dsn,
$this->db->username,
]));
}
/**
* Returns the metadata of the given type for the given table.
* @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
* @param string $type metadata type.
* @param bool $refresh whether to reload the table metadata even if it is found in the cache.
* @return mixed metadata.
* @since 2.0.13
*/
protected function getTableMetadata($name, $type, $refresh)
{
$cache = null;
if ($this->db->enableSchemaCache && !in_array($name, $this->db->schemaCacheExclude, true)) {
$schemaCache = is_string($this->db->schemaCache) ? Yii::$app->get($this->db->schemaCache, false) : $this->db->schemaCache;
if ($schemaCache instanceof Cache) {
$cache = $schemaCache;
}
}
if (!isset($this->_tableMetadata[$name]) || $refresh) {
$this->loadTableMetadataFromCache($cache, $name);
}
if (!array_key_exists($type, $this->_tableMetadata[$name])) {
$this->_tableMetadata[$name][$type] = $this->{'loadTable' . ucfirst($type)}($this->getRawTableName($name));
}
$this->saveTableMetadataToCache($cache, $name);
return $this->_tableMetadata[$name][$type];
}
/**
* Returns the metadata of the given type for all tables in the given schema.
* @param string $schema the schema of the metadata. Defaults to empty string, meaning the current or default schema name.
* @param string $type metadata type.
* @param bool $refresh whether to fetch the latest available table metadata. If this is `false`,
* cached data may be returned if available.
* @return array array of metadata.
* @since 2.0.13
*/
protected function getSchemaMetadata($schema, $type, $refresh)
{
$metadata = [];
$methodName = 'getTable' . ucfirst($type);
foreach ($this->getTableNames($schema, $refresh) as $name) {
if ($schema !== '') {
$name = $schema . '.' . $name;
}
$tableMetadata = $this->$methodName($name, $refresh);
if ($tableMetadata !== null) {
$metadata[] = $tableMetadata;
}
}
return $metadata;
}
/**
* Sets the metadata of the given type for the given table.
* @param string $name table name.
* @param string $type metadata type.
* @param mixed $data metadata.
* @since 2.0.13
*/
protected function setTableMetadata($name, $type, $data)
{
$this->_tableMetadata[$name][$type] = $data;
}
/**
* Changes row's array key case to lower if PDO's one is set to uppercase.
* @param array $row row's array or an array of row's arrays.
* @param bool $multiple whether multiple rows or a single row passed.
* @return array normalized row or rows.
* @since 2.0.13
*/
protected function normalizePdoRowKeyCase(array $row, $multiple)
{
if ($this->db->getSlavePdo()->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_UPPER) {
return $row;
}
if (!$multiple) {
return array_change_key_case($row, CASE_LOWER);
}
return array_map(function (array $row) {
return array_change_key_case($row, CASE_LOWER);
}, $row);
}
/**
* Tries to load and populate table metadata from cache.
* @param Cache|null $cache
* @param string $name
*/
private function loadTableMetadataFromCache($cache, $name)
{
if ($cache === null) {
$this->_tableMetadata[$name] = [];
return;
}
$metadata = $cache->get($this->getCacheKey($name));
if (!is_array($metadata) || !isset($metadata['cacheVersion']) || $metadata['cacheVersion'] !== 1) {
$this->_tableMetadata[$name] = [];
return;
}
unset($metadata['cacheVersion']);
$this->_tableMetadata[$name] = $metadata;
}
/**
* Saves table metadata to cache.
* @param Cache|null $cache
* @param string $name
*/
private function saveTableMetadataToCache($cache, $name)
{
if ($cache === null) {
return;
}
$metadata = $this->_tableMetadata[$name];
$metadata['cacheVersion'] = 1;
$cache->set(
$this->getCacheKey($name),
$metadata,
$this->db->schemaCacheDuration,
new TagDependency(['tags' => $this->getCacheTag()])
);
}
}

296
framework/db/SqlToken.php

@ -0,0 +1,296 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
use yii\base\Object;
/**
* SqlToken represents SQL tokens produced by [[SqlTokenizer]] or its child classes.
*
* @property SqlToken[] $children Child tokens.
* @property bool $hasChildren Whether the token has children. This property is read-only.
* @property bool $isCollection Whether the token represents a collection of tokens. This property is
* read-only.
* @property string $sql SQL code. This property is read-only.
*
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.13
*/
class SqlToken extends Object implements \ArrayAccess
{
const TYPE_CODE = 0;
const TYPE_STATEMENT = 1;
const TYPE_TOKEN = 2;
const TYPE_PARENTHESIS = 3;
const TYPE_KEYWORD = 4;
const TYPE_OPERATOR = 5;
const TYPE_IDENTIFIER = 6;
const TYPE_STRING_LITERAL = 7;
/**
* @var int token type.
* It has to be one of the following constants:
* - [[TYPE_CODE]]
* - [[TYPE_STATEMENT]]
* - [[TYPE_TOKEN]]
* - [[TYPE_PARENTHESIS]]
* - [[TYPE_KEYWORD]]
* - [[TYPE_OPERATOR]]
* - [[TYPE_IDENTIFIER]]
* - [[TYPE_STRING_LITERAL]]
*/
public $type = self::TYPE_TOKEN;
/**
* @var string|null token content.
*/
public $content;
/**
* @var int original SQL token start position.
*/
public $startOffset;
/**
* @var int original SQL token end position.
*/
public $endOffset;
/**
* @var SqlToken parent token.
*/
public $parent;
/**
* @var SqlToken[]
*/
private $_children = [];
/**
* Returns the SQL code representing the token.
* @return string SQL code.
*/
public function __toString()
{
return $this->getSql();
}
/**
* Returns whether there is a child token at the specified offset.
* This method is required by the SPL [[\ArrayAccess]] interface.
* It is implicitly called when you use something like `isset($token[$offset])`.
* @param int $offset child token offset.
* @return bool whether the token exists.
*/
public function offsetExists($offset)
{
return isset($this->_children[$this->calculateOffset($offset)]);
}
/**
* Returns a child token at the specified offset.
* This method is required by the SPL [[\ArrayAccess]] interface.
* It is implicitly called when you use something like `$child = $token[$offset];`.
* @param int $offset child token offset.
* @return SqlToken|null the child token at the specified offset, `null` if there's no token.
*/
public function offsetGet($offset)
{
$offset = $this->calculateOffset($offset);
return isset($this->_children[$offset]) ? $this->_children[$offset] : null;
}
/**
* Adds a child token to the token.
* This method is required by the SPL [[\ArrayAccess]] interface.
* It is implicitly called when you use something like `$token[$offset] = $child;`.
* @param int|null $offset child token offset.
* @param SqlToken $token token to be added.
*/
public function offsetSet($offset, $token)
{
$token->parent = $this;
if ($offset === null) {
$this->_children[] = $token;
} else {
$this->_children[$this->calculateOffset($offset)] = $token;
}
$this->updateCollectionOffsets();
}
/**
* Removes a child token at the specified offset.
* This method is required by the SPL [[\ArrayAccess]] interface.
* It is implicitly called when you use something like `unset($token[$offset])`.
* @param int $offset child token offset.
*/
public function offsetUnset($offset)
{
$offset = $this->calculateOffset($offset);
if (isset($this->_children[$offset])) {
array_splice($this->_children, $offset, 1);
}
$this->updateCollectionOffsets();
}
/**
* Returns child tokens.
* @return SqlToken[] child tokens.
*/
public function getChildren()
{
return $this->_children;
}
/**
* Sets a list of child tokens.
* @param SqlToken[] $children child tokens.
*/
public function setChildren($children)
{
$this->_children = [];
foreach ($children as $child) {
$child->parent = $this;
$this->_children[] = $child;
}
$this->updateCollectionOffsets();
}
/**
* Returns whether the token represents a collection of tokens.
* @return bool whether the token represents a collection of tokens.
*/
public function getIsCollection()
{
return in_array($this->type, [
self::TYPE_CODE,
self::TYPE_STATEMENT,
self::TYPE_PARENTHESIS,
], true);
}
/**
* Returns whether the token represents a collection of tokens and has non-zero number of children.
* @return bool whether the token has children.
*/
public function getHasChildren()
{
return $this->getIsCollection() && !empty($this->_children);
}
/**
* Returns the SQL code representing the token.
* @return string SQL code.
*/
public function getSql()
{
$code = $this;
while ($code->parent !== null) {
$code = $code->parent;
}
return mb_substr($code->content, $this->startOffset, $this->endOffset - $this->startOffset, 'UTF-8');
}
/**
* @param string $pattern
* @param int $offset
* @param int|null $firstMatchIndex
* @param int|null $lastMatchIndex
* @return bool
*/
public function matches($pattern, $offset = 0, &$firstMatchIndex = null, &$lastMatchIndex = null)
{
$patternToken = (new \yii\db\sqlite\SqlTokenizer($pattern))->tokenize();
if (!$patternToken->getHasChildren()) {
return false;
}
$patternToken = $patternToken[0];
return $this->tokensMatch($patternToken, $this, $offset, $firstMatchIndex, $lastMatchIndex);
}
/**
* Tests the given token to match the specified pattern token.
* @param SqlToken $patternToken
* @param SqlToken $token
* @param int $offset
* @param int|null $firstMatchIndex
* @param int|null $lastMatchIndex
* @return bool
*/
private function tokensMatch(SqlToken $patternToken, SqlToken $token, $offset = 0, &$firstMatchIndex = null, &$lastMatchIndex = null)
{
if (
$patternToken->getIsCollection() !== $token->getIsCollection()
|| (!$patternToken->getIsCollection() && $patternToken->content !== $token->content)
) {
return false;
}
if ($patternToken->children === $token->children) {
$firstMatchIndex = $lastMatchIndex = $offset;
return true;
}
$firstMatchIndex = $lastMatchIndex = null;
$wildcard = false;
for ($index = 0, $count = count($patternToken->children); $index < $count; $index++) {
if ($patternToken[$index]->content === 'any') {
$wildcard = true;
continue;
}
for ($limit = $wildcard ? count($token->children) : $offset + 1; $offset < $limit; $offset++) {
if (!$wildcard && !isset($token[$offset])) {
break;
}
if (!$this->tokensMatch($patternToken[$index],$token[$offset])) {
continue;
}
if ($firstMatchIndex === null) {
$firstMatchIndex = $offset;
$lastMatchIndex = $offset;
} else {
$lastMatchIndex = $offset;
}
$wildcard = false;
$offset++;
continue 2;
}
return false;
}
return true;
}
/**
* Returns an absolute offset in the children array.
* @param int $offset
* @return int
*/
private function calculateOffset($offset)
{
if ($offset >= 0) {
return $offset;
}
return count($this->_children) + $offset;
}
/**
* Updates token SQL code start and end offsets based on its children.
*/
private function updateCollectionOffsets()
{
if (!empty($this->_children)) {
$this->startOffset = reset($this->_children)->startOffset;
$this->endOffset = end($this->_children)->endOffset;
}
if ($this->parent !== null) {
$this->parent->updateCollectionOffsets();
}
}
}

378
framework/db/SqlTokenizer.php

@ -0,0 +1,378 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
use yii\base\InvalidParamException;
use yii\base\Object;
/**
* SqlTokenizer splits SQL query into individual SQL tokens.
* It's used to obtain an addition information from an SQL code.
*
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.13
*/
abstract class SqlTokenizer extends Object
{
/**
* @var string SQL code.
*/
public $sql;
/**
* @var int SQL code string length.
*/
protected $length;
/**
* @var int SQL code string current offset.
*/
protected $offset;
/**
* @var \SplStack
*/
private $_tokenStack;
/**
* @var SqlToken
*/
private $_currentToken;
/**
* @var string[]
*/
private $_substrings;
/**
* @var string
*/
private $_buffer = '';
/**
* @var SqlToken
*/
private $_token;
/**
* Constructor.
* @param string $sql SQL code to be tokenized.
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($sql, $config = [])
{
$this->sql = $sql;
parent::__construct($config);
}
/**
* Tokenizes and returns a code type token.
* @return SqlToken code type token.
*/
public function tokenize()
{
$this->length = mb_strlen($this->sql, 'UTF-8');
$this->offset = 0;
$this->_substrings = [];
$this->_buffer = '';
$this->_token = new SqlToken([
'type' => SqlToken::TYPE_CODE,
'content' => $this->sql,
]);
$this->_tokenStack = new \SplStack();
$this->_tokenStack->push($this->_token);
$this->_token[] = new SqlToken(['type' => SqlToken::TYPE_STATEMENT]);
$this->_tokenStack->push($this->_token[0]);
$this->_currentToken = $this->_tokenStack->top();
while (!$this->isEof()) {
if ($this->isWhitespace($length) || $this->isComment($length)) {
$this->addTokenFromBuffer();
$this->advance($length);
continue;
}
if ($this->tokenizeOperator($length) || $this->tokenizeDelimitedString($length)) {
$this->advance($length);
continue;
}
$this->_buffer .= $this->substring(1);
$this->advance(1);
}
$this->addTokenFromBuffer();
if ($this->_token->getHasChildren() && !$this->_token[-1]->getHasChildren()) {
unset($this->_token[-1]);
}
return $this->_token;
}
/**
* Returns whether there's a whitespace at the current offset.
* If this methos returns `true`, it has to set the `$length` parameter to the length of the matched string.
* @param int $length length of the matched string.
* @return bool whether there's a whitespace at the current offset.
*/
protected abstract function isWhitespace(&$length);
/**
* Returns whether there's a commentary at the current offset.
* If this methos returns `true`, it has to set the `$length` parameter to the length of the matched string.
* @param int $length length of the matched string.
* @return bool whether there's a commentary at the current offset.
*/
protected abstract function isComment(&$length);
/**
* Returns whether there's an operator at the current offset.
* If this methos returns `true`, it has to set the `$length` parameter to the length of the matched string.
* It may also set `$content` to a string that will be used as a token content.
* @param int $length length of the matched string.
* @param string $content optional content instead of the matched string.
* @return bool whether there's an operator at the current offset.
*/
protected abstract function isOperator(&$length, &$content);
/**
* Returns whether there's an identifier at the current offset.
* If this methos returns `true`, it has to set the `$length` parameter to the length of the matched string.
* It may also set `$content` to a string that will be used as a token content.
* @param int $length length of the matched string.
* @param string $content optional content instead of the matched string.
* @return bool whether there's an identifier at the current offset.
*/
protected abstract function isIdentifier(&$length, &$content);
/**
* Returns whether there's a string literal at the current offset.
* If this methos returns `true`, it has to set the `$length` parameter to the length of the matched string.
* It may also set `$content` to a string that will be used as a token content.
* @param int $length length of the matched string.
* @param string $content optional content instead of the matched string.
* @return bool whether there's a string literal at the current offset.
*/
protected abstract function isStringLiteral(&$length, &$content);
/**
* Returns whether the given string is a keyword.
* The method may set `$content` to a string that will be used as a token content.
* @param string $string string to be matched.
* @param string $content optional content instead of the matched string.
* @return bool whether the given string is a keyword.
*/
protected abstract function isKeyword($string, &$content);
/**
* Returns whether the longest common prefix equals to the SQL code of the same length at the current offset.
* @param string[] $with strings to be tested.
* The method **will** modify this parameter to speed up lookups.
* @param bool $caseSensitive whether to perform a case sensitive comparison.
* @param int|null $length length of the matched string.
* @param string|null $content matched string.
* @return bool whether a match is found.
*/
protected function startsWithAnyLongest(array &$with, $caseSensitive, &$length = null, &$content = null)
{
if (empty($with)) {
return false;
}
if (!is_array(reset($with))) {
usort($with, function ($string1, $string2) {
return mb_strlen($string2, 'UTF-8') - mb_strlen($string1, 'UTF-8');
});
$map = [];
foreach ($with as $string) {
$map[mb_strlen($string, 'UTF-8')][$caseSensitive ? $string : mb_strtoupper($string, 'UTF-8')] = true;
}
$with = $map;
}
foreach ($with as $testLength => $testValues) {
$content = $this->substring($testLength, $caseSensitive);
if (isset($testValues[$content])) {
$length = $testLength;
return true;
}
}
return false;
}
/**
* Returns a string of the given length starting with the specified offset.
* @param int $length string length to be returned.
* @param bool $caseSensitive if it's `false`, the string will be uppercased.
* @param int|null $offset SQL code offset, defaults to current if `null` is passed.
* @return string result string, it may be empty if there's nothing to return.
*/
protected function substring($length, $caseSensitive = true, $offset = null)
{
if ($offset === null) {
$offset = $this->offset;
}
if ($offset + $length > $this->length) {
return '';
}
$cacheKey = $offset . ',' . $length;
if (!isset($this->_substrings[$cacheKey . ',1'])) {
$this->_substrings[$cacheKey . ',1'] = mb_substr($this->sql, $offset, $length, 'UTF-8');
}
if (!$caseSensitive && !isset($this->_substrings[$cacheKey . ',0'])) {
$this->_substrings[$cacheKey . ',0'] = mb_strtoupper($this->_substrings[$cacheKey . ',1'], 'UTF-8');
}
return $this->_substrings[$cacheKey . ',' . (int) $caseSensitive];
}
/**
* Returns an index after the given string in the SQL code starting with the specified offset.
* @param string $string string to be found.
* @param int|null $offset SQL code offset, defaults to current if `null` is passed.
* @return int index after the given string or end of string index.
*/
protected function indexAfter($string, $offset = null)
{
if ($offset === null) {
$offset = $this->offset;
}
if ($offset + mb_strlen($string, 'UTF-8') > $this->length) {
return $this->length;
}
$afterIndexOf = mb_strpos($this->sql, $string, $offset, 'UTF-8');
if ($afterIndexOf === false) {
$afterIndexOf = $this->length;
} else {
$afterIndexOf += mb_strlen($string, 'UTF-8');
}
return $afterIndexOf;
}
/**
* Determines whether there is a delimited string at the current offset and adds it to the token children.
* @param int $length
* @return bool
*/
private function tokenizeDelimitedString(&$length)
{
$isIdentifier = $this->isIdentifier($length, $content);
$isStringLiteral = !$isIdentifier && $this->isStringLiteral($length, $content);
if (!$isIdentifier && !$isStringLiteral) {
return false;
}
$this->addTokenFromBuffer();
$this->_currentToken[] = new SqlToken([
'type' => $isIdentifier ? SqlToken::TYPE_IDENTIFIER : SqlToken::TYPE_STRING_LITERAL,
'content' => is_string($content) ? $content : $this->substring($length),
'startOffset' => $this->offset,
'endOffset' => $this->offset + $length,
]);
return true;
}
/**
* Determines whether there is an operator at the current offset and adds it to the token children.
* @param int $length
* @return bool
*/
private function tokenizeOperator(&$length)
{
if (!$this->isOperator($length, $content)) {
return false;
}
$this->addTokenFromBuffer();
switch ($this->substring($length)) {
case '(':
$this->_currentToken[] = new SqlToken([
'type' => SqlToken::TYPE_OPERATOR,
'content' => is_string($content) ? $content : $this->substring($length),
'startOffset' => $this->offset,
'endOffset' => $this->offset + $length,
]);
$this->_currentToken[] = new SqlToken(['type' => SqlToken::TYPE_PARENTHESIS]);
$this->_tokenStack->push($this->_currentToken[-1]);
$this->_currentToken = $this->_tokenStack->top();
break;
case ')':
$this->_tokenStack->pop();
$this->_currentToken = $this->_tokenStack->top();
$this->_currentToken[] = new SqlToken([
'type' => SqlToken::TYPE_OPERATOR,
'content' => ')',
'startOffset' => $this->offset,
'endOffset' => $this->offset + $length,
]);
break;
case ';':
if (!$this->_currentToken->getHasChildren()) {
break;
}
$this->_currentToken[] = new SqlToken([
'type' => SqlToken::TYPE_OPERATOR,
'content' => is_string($content) ? $content : $this->substring($length),
'startOffset' => $this->offset,
'endOffset' => $this->offset + $length,
]);
$this->_tokenStack->pop();
$this->_currentToken = $this->_tokenStack->top();
$this->_currentToken[] = new SqlToken(['type' => SqlToken::TYPE_STATEMENT]);
$this->_tokenStack->push($this->_currentToken[-1]);
$this->_currentToken = $this->_tokenStack->top();
break;
default:
$this->_currentToken[] = new SqlToken([
'type' => SqlToken::TYPE_OPERATOR,
'content' => is_string($content) ? $content : $this->substring($length),
'startOffset' => $this->offset,
'endOffset' => $this->offset + $length,
]);
break;
}
return true;
}
/**
* Determines a type of text in the buffer, tokenizes it and adds it to the token children.
*/
private function addTokenFromBuffer()
{
if ($this->_buffer === '') {
return;
}
$isKeyword = $this->isKeyword($this->_buffer, $content);
$this->_currentToken[] = new SqlToken([
'type' => $isKeyword ? SqlToken::TYPE_KEYWORD : SqlToken::TYPE_TOKEN,
'content' => is_string($content) ? $content : $this->_buffer,
'startOffset' => $this->offset - mb_strlen($this->_buffer, 'UTF-8'),
'endOffset' => $this->offset,
]);
$this->_buffer = '';
}
/**
* Adds the specified length to the current offset.
* @param int $length
* @throws InvalidParamException
*/
private function advance($length)
{
if ($length <= 0) {
throw new InvalidParamException('Length must be greater than 0.');
}
$this->offset += $length;
$this->_substrings = [];
}
/**
* Returns whether the SQL code is completely traversed.
* @return bool
*/
private function isEof()
{
return $this->offset >= $this->length;
}
}

20
framework/db/cubrid/QueryBuilder.php

@ -8,6 +8,7 @@
namespace yii\db\cubrid;
use yii\base\InvalidParamException;
use yii\base\NotSupportedException;
use yii\db\Exception;
/**
@ -57,6 +58,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
'!' => '!!',
];
/**
* 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
@ -118,6 +120,24 @@ class QueryBuilder extends \yii\db\QueryBuilder
}
/**
* @inheritDoc
* @throws NotSupportedException this is not supported by CUBRID.
*/
public function addCheck($name, $table, $check)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by CUBRID.');
}
/**
* @inheritDoc
* @throws NotSupportedException this is not supported by CUBRID.
*/
public function dropCheck($name, $table)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by CUBRID.');
}
/**
* @inheritdoc
* @since 2.0.8
*/

230
framework/db/cubrid/Schema.php

@ -7,10 +7,15 @@
namespace yii\db\cubrid;
use yii\db\Constraint;
use yii\db\ConstraintFinderTrait;
use yii\db\Expression;
use yii\db\ForeignKeyConstraint;
use yii\db\IndexConstraint;
use yii\db\TableSchema;
use yii\db\ColumnSchema;
use yii\db\Transaction;
use yii\helpers\ArrayHelper;
/**
* Schema is the class for retrieving metadata from a CUBRID database (version 9.3.x and higher).
@ -20,6 +25,8 @@ use yii\db\Transaction;
*/
class Schema extends \yii\db\Schema
{
use ConstraintFinderTrait;
/**
* @var array mapping from physical column types (keys) to abstract column types (values)
* Please refer to [CUBRID manual](http://www.cubrid.org/manual/91/en/sql/datatype.html) for
@ -74,48 +81,25 @@ class Schema extends \yii\db\Schema
/**
* @inheritdoc
* @inheritDoc
*/
public function releaseSavepoint($name)
{
// does nothing as cubrid does not support this
}
/**
* Quotes a table name for use in a query.
* A simple table name has no schema prefix.
* @param string $name table name
* @return string the properly quoted table name
*/
public function quoteSimpleTableName($name)
{
return strpos($name, '"') !== false ? $name : '"' . $name . '"';
}
/**
* Quotes a column name for use in a query.
* A simple column name has no prefix.
* @param string $name column name
* @return string the properly quoted column name
*/
public function quoteSimpleColumnName($name)
protected function findTableNames($schema = '')
{
return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"';
}
$pdo = $this->db->getSlavePdo();
$tables = $pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE);
$tableNames = [];
foreach ($tables as $table) {
// do not list system tables
if ($table['TYPE'] != 0) {
$tableNames[] = $table['NAME'];
}
}
/**
* Creates a query builder for the CUBRID database.
* @return QueryBuilder query builder instance
*/
public function createQueryBuilder()
{
return new QueryBuilder($this->db);
return $tableNames;
}
/**
* Loads the metadata for the specified table.
* @param string $name table name
* @return TableSchema driver dependent table metadata. Null if the table does not exist.
* @inheritDoc
*/
protected function loadTableSchema($name)
{
@ -164,6 +148,123 @@ class Schema extends \yii\db\Schema
}
/**
* @inheritDoc
*/
protected function loadTablePrimaryKey($tableName)
{
$primaryKey = $this->db->getSlavePdo()->cubrid_schema(\PDO::CUBRID_SCH_PRIMARY_KEY, $tableName);
if (empty($primaryKey)) {
return null;
}
ArrayHelper::multisort($primaryKey, 'KEY_SEQ', SORT_ASC, SORT_NUMERIC);
return new Constraint([
'name' => $primaryKey[0]['KEY_NAME'],
'columnNames' => ArrayHelper::getColumn($primaryKey, 'ATTR_NAME'),
]);
}
/**
* @inheritDoc
*/
protected function loadTableForeignKeys($tableName)
{
static $actionTypes = [
0 => 'CASCADE',
1 => 'RESTRICT',
2 => 'NO ACTION',
3 => 'SET NULL',
];
$foreignKeys = $this->db->getSlavePdo()->cubrid_schema(\PDO::CUBRID_SCH_IMPORTED_KEYS, $tableName);
$foreignKeys = ArrayHelper::index($foreignKeys, null, 'FK_NAME');
ArrayHelper::multisort($foreignKeys, 'KEY_SEQ', SORT_ASC, SORT_NUMERIC);
$result = [];
foreach ($foreignKeys as $name => $foreignKey) {
$result[] = new ForeignKeyConstraint([
'name' => $name,
'columnNames' => ArrayHelper::getColumn($foreignKey, 'FKCOLUMN_NAME'),
'foreignTableName' => $foreignKey[0]['PKTABLE_NAME'],
'foreignColumnNames' => ArrayHelper::getColumn($foreignKey, 'PKCOLUMN_NAME'),
'onDelete' => isset($actionTypes[$foreignKey[0]['DELETE_RULE']]) ? $actionTypes[$foreignKey[0]['DELETE_RULE']] : null,
'onUpdate' => isset($actionTypes[$foreignKey[0]['UPDATE_RULE']]) ? $actionTypes[$foreignKey[0]['UPDATE_RULE']] : null,
]);
}
return $result;
}
/**
* @inheritDoc
*/
protected function loadTableIndexes($tableName)
{
return $this->loadTableConstraints($tableName, 'indexes');
}
/**
* @inheritDoc
*/
protected function loadTableUniques($tableName)
{
return $this->loadTableConstraints($tableName, 'uniques');
}
/**
* @inheritDoc
*/
protected function loadTableChecks($tableName)
{
return [];
}
/**
* @inheritDoc
*/
protected function loadTableDefaultValues($tableName)
{
return [];
}
/**
* @inheritdoc
*/
public function releaseSavepoint($name)
{
// does nothing as cubrid does not support this
}
/**
* Quotes a table name for use in a query.
* A simple table name has no schema prefix.
* @param string $name table name
* @return string the properly quoted table name
*/
public function quoteSimpleTableName($name)
{
return strpos($name, '"') !== false ? $name : '"' . $name . '"';
}
/**
* Quotes a column name for use in a query.
* A simple column name has no prefix.
* @param string $name column name
* @return string the properly quoted column name
*/
public function quoteSimpleColumnName($name)
{
return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"';
}
/**
* Creates a query builder for the CUBRID database.
* @return QueryBuilder query builder instance
*/
public function createQueryBuilder()
{
return new QueryBuilder($this->db);
}
/**
* Loads the column information into a [[ColumnSchema]] object.
* @param array $info column information
* @return ColumnSchema the column schema object
@ -235,26 +336,6 @@ class Schema extends \yii\db\Schema
}
/**
* Returns all table names in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @return array all table names in the database. The names have NO schema name prefix.
*/
protected function findTableNames($schema = '')
{
$pdo = $this->db->getSlavePdo();
$tables = $pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE);
$tableNames = [];
foreach ($tables as $table) {
// do not list system tables
if ($table['TYPE'] != 0) {
$tableNames[] = $table['NAME'];
}
}
return $tableNames;
}
/**
* Determines the PDO type for the given PHP data value.
* @param mixed $data the data whose PDO type is to be determined
* @return int the PDO type
@ -306,4 +387,41 @@ class Schema extends \yii\db\Schema
{
return new ColumnSchemaBuilder($type, $length, $this->db);
}
/**
* @param string $tableName
* @param string $returnType
* @return mixed
*/
private function loadTableConstraints($tableName, $returnType)
{
$constraints = $this->db->getSlavePdo()->cubrid_schema(\PDO::CUBRID_SCH_CONSTRAINT, $tableName);
$constraints = ArrayHelper::index($constraints, null, ['TYPE', 'NAME']);
ArrayHelper::multisort($columns, 'KEY_ORDER', SORT_ASC, SORT_NUMERIC);
$result = [
'indexes' => [],
'uniques' => [],
];
foreach ($constraints as $type => $names) {
foreach ($names as $name => $constraint) {
$isUnique = in_array((int) $type, [0, 2], true);
$result['indexes'][] = new IndexConstraint([
'isPrimary' => (bool) $constraint[0]['PRIMARY_KEY'],
'isUnique' => $isUnique,
'name' => $name,
'columnNames' => ArrayHelper::getColumn($constraint, 'ATTR_NAME'),
]);
if ($isUnique) {
$result['uniques'][] = new Constraint([
'name' => $name,
'columnNames' => ArrayHelper::getColumn($constraint, 'ATTR_NAME'),
]);
}
}
}
foreach ($result as $type => $data) {
$this->setTableMetadata($tableName, $type, $data);
}
return $result[$returnType];
}
}

20
framework/db/mssql/QueryBuilder.php

@ -56,6 +56,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
'\\' => '[\\]',
];
/**
* @inheritdoc
*/
@ -176,6 +177,25 @@ class QueryBuilder extends \yii\db\QueryBuilder
}
/**
* @inheritDoc
*/
public function addDefaultValue($name, $table, $column, $default)
{
return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
. $this->db->quoteColumnName($name) . ' DEFAULT ' . $this->db->quoteValue($default) . ' 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.

322
framework/db/mssql/Schema.php

@ -7,11 +7,18 @@
namespace yii\db\mssql;
use yii\db\CheckConstraint;
use yii\db\ColumnSchema;
use yii\db\Constraint;
use yii\db\ConstraintFinderTrait;
use yii\db\DefaultConstraint;
use yii\db\ForeignKeyConstraint;
use yii\db\IndexConstraint;
use yii\db\ViewFinderTrait;
use yii\helpers\ArrayHelper;
/**
* Schema is the class for retrieving metadata from a MS SQL Server databases (version 2008 and above).
* 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
@ -19,6 +26,7 @@ use yii\db\ViewFinderTrait;
class Schema extends \yii\db\Schema
{
use ViewFinderTrait;
use ConstraintFinderTrait;
/**
* @var string the default schema used for the current session.
@ -73,6 +81,169 @@ class Schema extends \yii\db\Schema
/**
* 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
*/
protected function findSchemaNames()
{
$sql = <<<SQL
SELECT ns.nspname AS schema_name
FROM pg_namespace ns
WHERE ns.nspname != 'information_schema' AND ns.nspname NOT LIKE 'pg_%'
ORDER BY ns.nspname
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;
} else {
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)
@ -128,25 +299,6 @@ class Schema extends \yii\db\Schema
}
/**
* Loads the metadata for the specified table.
* @param string $name table name
* @return TableSchema|null driver dependent table metadata. Null if the table does not exist.
*/
public function loadTableSchema($name)
{
$table = new TableSchema();
$this->resolveTableNames($table, $name);
$this->findPrimaryKeys($table);
if ($this->findColumns($table)) {
$this->findForeignKeys($table);
return $table;
} else {
return null;
}
}
/**
* Resolves the table name and schema name (if any).
* @param TableSchema $table the table metadata object
* @param string $name the table name
@ -399,27 +551,6 @@ SQL;
}
/**
* Returns all table names in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @return array all table names in the database. The names have NO schema name prefix.
*/
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 findViewNames($schema = '')
@ -461,4 +592,113 @@ SQL;
}
return $result;
}
/**
* @param string $tableName
* @param string $returnType
* @return mixed
*/
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 DefaultConstraint([
'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];
}
}

19
framework/db/mysql/QueryBuilder.php

@ -8,6 +8,7 @@
namespace yii\db\mysql;
use yii\base\InvalidParamException;
use yii\base\NotSupportedException;
use yii\db\Exception;
use yii\db\Expression;
@ -120,6 +121,24 @@ class QueryBuilder extends \yii\db\QueryBuilder
}
/**
* @inheritDoc
* @throws NotSupportedException this is not supported by MySQL.
*/
public function addCheck($name, $table, $check)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by MySQL.');
}
/**
* @inheritDoc
* @throws NotSupportedException this is not supported by MySQL.
*/
public function dropCheck($name, $table)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by MySQL.');
}
/**
* 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.

258
framework/db/mysql/Schema.php

@ -7,9 +7,16 @@
namespace yii\db\mysql;
use yii\base\InvalidConfigException;
use yii\db\ColumnSchema;
use yii\db\Constraint;
use yii\db\ConstraintFinderTrait;
use yii\db\Exception;
use yii\db\Expression;
use yii\db\ForeignKeyConstraint;
use yii\db\IndexConstraint;
use yii\db\TableSchema;
use yii\db\ColumnSchema;
use yii\helpers\ArrayHelper;
/**
* Schema is the class for retrieving metadata from a MySQL database (version 4.1.x and 5.x).
@ -19,6 +26,14 @@ use yii\db\ColumnSchema;
*/
class Schema extends \yii\db\Schema
{
use ConstraintFinderTrait;
/**
* @var bool whether MySQL used is older than 5.1.
*/
private $_oldMysql;
/**
* @var array mapping from physical column types (keys) to abstract column types (values)
*/
@ -53,6 +68,128 @@ class Schema extends \yii\db\Schema
'varbinary' => self::TYPE_BINARY,
];
/**
* @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
*/
protected function findTableNames($schema = '')
{
$sql = 'SHOW TABLES';
if ($schema !== '') {
$sql .= ' FROM ' . $this->quoteSimpleTableName($schema);
}
return $this->db->createCommand($sql)->queryColumn();
}
/**
* @inheritDoc
*/
protected function loadTableSchema($name)
{
$table = new TableSchema;
$this->resolveTableNames($table, $name);
if ($this->findColumns($table)) {
$this->findConstraints($table);
return $table;
} else {
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
`s`.`INDEX_NAME` AS `name`,
`s`.`COLUMN_NAME` AS `column_name`,
`s`.`NON_UNIQUE` ^ 1 AS `index_is_unique`,
`s`.`INDEX_NAME` = 'PRIMARY' AS `index_is_primary`
FROM `information_schema`.`STATISTICS` AS `s`
WHERE `s`.`TABLE_SCHEMA` = COALESCE(:schemaName, DATABASE()) AND `s`.`INDEX_SCHEMA` = `s`.`TABLE_SCHEMA` AND `s`.`TABLE_NAME` = :tableName
ORDER BY `s`.`SEQ_IN_INDEX` 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 !== 'PRIMARY' ? $name : null,
'columnNames' => ArrayHelper::getColumn($index, 'column_name'),
]);
}
return $result;
}
/**
* @inheritDoc
*/
protected function loadTableUniques($tableName)
{
return $this->loadTableConstraints($tableName, 'uniques');
}
/**
* @inheritDoc
*/
protected function loadTableChecks($tableName)
{
return [];
}
/**
* @inheritDoc
*/
protected function loadTableDefaultValues($tableName)
{
return [];
}
/**
* Quotes a table name for use in a query.
@ -86,25 +223,6 @@ class Schema extends \yii\db\Schema
}
/**
* Loads the metadata for the specified table.
* @param string $name table name
* @return TableSchema driver dependent table metadata. Null if the table does not exist.
*/
protected function loadTableSchema($name)
{
$table = new TableSchema;
$this->resolveTableNames($table, $name);
if ($this->findColumns($table)) {
$this->findConstraints($table);
return $table;
} else {
return null;
}
}
/**
* Resolves the table name and schema name (if any).
* @param TableSchema $table the table metadata object
* @param string $name the table name
@ -338,25 +456,101 @@ SQL;
}
/**
* Returns all table names in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @return array all table names in the database. The names have NO schema name prefix.
* @inheritdoc
*/
protected function findTableNames($schema = '')
public function createColumnSchemaBuilder($type, $length = null)
{
$sql = 'SHOW TABLES';
if ($schema !== '') {
$sql .= ' FROM ' . $this->quoteSimpleTableName($schema);
}
return new ColumnSchemaBuilder($type, $length, $this->db);
}
return $this->db->createCommand($sql)->queryColumn();
/**
* @return bool whether the version of the MySQL being used is older than 5.1.
* @throws InvalidConfigException
* @throws Exception
* @since 2.0.13
*/
protected function isOldMysql()
{
if ($this->_oldMysql === null) {
$version = $this->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
$this->_oldMysql = version_compare($version, '5.1', '<=');
}
return $this->_oldMysql;
}
/**
* @inheritdoc
* @param string $tableName
* @param string $returnType
* @return mixed
*/
public function createColumnSchemaBuilder($type, $length = null)
private function loadTableConstraints($tableName, $returnType)
{
return new ColumnSchemaBuilder($type, $length, $this->db);
static $sql = <<<SQL
SELECT DISTINCT
`kcu`.`CONSTRAINT_NAME` AS `name`,
`kcu`.`COLUMN_NAME` AS `column_name`,
`tc`.`CONSTRAINT_TYPE` AS `type`,
CASE
WHEN :schemaName IS NULL AND `kcu`.`REFERENCED_TABLE_SCHEMA` = `sch`.`name` THEN NULL
ELSE `kcu`.`REFERENCED_TABLE_SCHEMA`
END AS `foreign_table_schema`,
`kcu`.`REFERENCED_TABLE_NAME` AS `foreign_table_name`,
`kcu`.`REFERENCED_COLUMN_NAME` AS `foreign_column_name`,
`rc`.`UPDATE_RULE` AS `on_update`,
`rc`.`DELETE_RULE` AS `on_delete`
FROM (SELECT DATABASE() AS `name`) AS `sch`
INNER JOIN `information_schema`.`KEY_COLUMN_USAGE` AS `kcu`
ON `kcu`.`TABLE_SCHEMA` = COALESCE(:schemaName, `sch`.`name`) AND `kcu`.`CONSTRAINT_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `kcu`.`TABLE_NAME` = :tableName
LEFT JOIN `information_schema`.`REFERENTIAL_CONSTRAINTS` AS `rc`
ON `rc`.`CONSTRAINT_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `rc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME`
LEFT JOIN `information_schema`.`TABLE_CONSTRAINTS` AS `tc`
ON `tc`.`TABLE_SCHEMA` = `kcu`.`TABLE_SCHEMA` AND `tc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME`
ORDER BY `kcu`.`ORDINAL_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' => [],
];
foreach ($constraints as $type => $names) {
foreach ($names as $name => $constraint) {
switch ($type) {
case 'PRIMARY KEY':
$result['primaryKey'] = new Constraint([
'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
]);
break;
case 'FOREIGN KEY':
$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' => $constraint[0]['on_update'],
]);
break;
case 'UNIQUE':
$result['uniques'][] = new Constraint([
'name' => $name,
'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
]);
break;
}
}
}
foreach ($result as $type => $data) {
$this->setTableMetadata($tableName, $type, $data);
}
return $result[$returnType];
}
}

1
framework/db/oci/QueryBuilder.php

@ -61,6 +61,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
'!' => '!!',
];
/**
* @inheritdoc
*/

344
framework/db/oci/Schema.php

@ -8,10 +8,16 @@
namespace yii\db\oci;
use yii\base\InvalidCallException;
use yii\db\CheckConstraint;
use yii\db\ColumnSchema;
use yii\db\Connection;
use yii\db\Constraint;
use yii\db\ConstraintFinderTrait;
use yii\db\Expression;
use yii\db\ForeignKeyConstraint;
use yii\db\IndexConstraint;
use yii\db\TableSchema;
use yii\helpers\ArrayHelper;
/**
* Schema is the class for retrieving metadata from an Oracle database
@ -24,6 +30,8 @@ use yii\db\TableSchema;
*/
class Schema extends \yii\db\Schema
{
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.
@ -45,6 +53,180 @@ class Schema extends \yii\db\Schema
}
/**
* @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
*/
protected function findSchemaNames()
{
$sql = <<<SQL
SELECT
USERNAME
FROM DBA_USERS U
WHERE
EXISTS (SELECT 1 FROM DBA_OBJECTS O WHERE O.OWNER = U.USERNAME)
AND DEFAULT_TABLESPACE NOT IN ('SYSTEM','SYSAUX')
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;
} else {
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
*/
protected function loadTableDefaultValues($tableName)
{
return [];
}
/**
* @inheritdoc
*/
public function releaseSavepoint($name)
@ -77,23 +259,6 @@ class Schema extends \yii\db\Schema
}
/**
* @inheritdoc
*/
public function loadTableSchema($name)
{
$table = new TableSchema();
$this->resolveTableNames($table, $name);
if ($this->findColumns($table)) {
$this->findConstraints($table);
return $table;
} else {
return null;
}
}
/**
* Resolves the table name and schema name (if any).
*
* @param TableSchema $table the table metadata object
@ -321,67 +486,6 @@ SQL;
}
/**
* @inheritdoc
*/
protected function findSchemaNames()
{
$sql = <<<SQL
SELECT
USERNAME
FROM DBA_USERS U
WHERE
EXISTS (SELECT 1 FROM DBA_OBJECTS O WHERE O.OWNER = U.USERNAME)
AND DEFAULT_TABLESPACE NOT IN ('SYSTEM','SYSAUX')
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;
}
/**
* Returns all unique indexes for the given table.
* Each array element is of the following structure:
*
@ -523,4 +627,88 @@ SQL;
return $result;
}
/**
* @param string $tableName
* @param string $returnType
* @return mixed
*/
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];
}
}

309
framework/db/pgsql/Schema.php

@ -7,23 +7,28 @@
namespace yii\db\pgsql;
use yii\db\CheckConstraint;
use yii\db\Constraint;
use yii\db\ConstraintFinderTrait;
use yii\db\Expression;
use yii\db\ForeignKeyConstraint;
use yii\db\IndexConstraint;
use yii\db\TableSchema;
use yii\db\ColumnSchema;
use yii\db\ViewFinderTrait;
use yii\helpers\ArrayHelper;
/**
* Schema is the class for retrieving metadata from a PostgreSQL database
* (version 9.x and above).
*
* @property string[] $viewNames All view names in the database. This property is read-only.
*
* @author Gevik Babakhani <gevikb@gmail.com>
* @since 2.0
*/
class Schema extends \yii\db\Schema
{
use ViewFinderTrait;
use ConstraintFinderTrait;
/**
* @var string the default schema used for the current session.
@ -115,51 +120,59 @@ class Schema extends \yii\db\Schema
/**
* Creates a query builder for the PostgreSQL database.
* @return QueryBuilder query builder instance
*/
public function createQueryBuilder()
{
return new QueryBuilder($this->db);
}
/**
* Resolves the table name and schema name (if any).
* @param TableSchema $table the table metadata object
* @param string $name the table name
* @inheritDoc
*/
protected function resolveTableNames($table, $name)
protected function resolveTableName($name)
{
$resolvedName = new TableSchema();
$parts = explode('.', str_replace('"', '', $name));
if (isset($parts[1])) {
$table->schemaName = $parts[0];
$table->name = $parts[1];
$resolvedName->schemaName = $parts[0];
$resolvedName->name = $parts[1];
} else {
$table->schemaName = $this->defaultSchema;
$table->name = $name;
$resolvedName->schemaName = $this->defaultSchema;
$resolvedName->name = $name;
}
$resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name;
return $resolvedName;
}
$table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
/**
* @inheritDoc
*/
protected function findSchemaNames()
{
$sql = <<<SQL
SELECT ns.nspname AS schema_name
FROM pg_namespace ns
WHERE ns.nspname != 'information_schema' AND ns.nspname NOT LIKE 'pg_%'
ORDER BY ns.nspname
SQL;
return $this->db->createCommand($sql)->queryColumn();
}
/**
* Quotes a table name for use in a query.
* A simple table name has no schema prefix.
* @param string $name table name
* @return string the properly quoted table name
* @inheritDoc
*/
public function quoteSimpleTableName($name)
protected function findTableNames($schema = '')
{
return strpos($name, '"') !== false ? $name : '"' . $name . '"';
if ($schema === '') {
$schema = $this->defaultSchema;
}
$sql = <<<SQL
SELECT c.relname AS table_name
FROM pg_class c
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
WHERE ns.nspname = :schemaName AND c.relkind IN ('r','v','m','f')
ORDER BY c.relname
SQL;
return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();
}
/**
* Loads the metadata for the specified table.
* @param string $name table name
* @return TableSchema|null driver dependent table metadata. Null if the table does not exist.
* @inheritDoc
*/
public function loadTableSchema($name)
protected function loadTableSchema($name)
{
$table = new TableSchema();
$this->resolveTableNames($table, $name);
@ -172,41 +185,126 @@ class Schema extends \yii\db\Schema
}
/**
* Returns all schema names in the database, including the default one but not system schemas.
* This method should be overridden by child classes in order to support this feature
* because the default implementation simply throws an exception.
* @return array all schema names in the database, except system schemas
* @since 2.0.4
* @inheritDoc
*/
protected function findSchemaNames()
protected function loadTablePrimaryKey($tableName)
{
$sql = <<<SQL
SELECT ns.nspname AS schema_name
FROM pg_namespace ns
WHERE ns.nspname != 'information_schema' AND ns.nspname NOT LIKE 'pg_%'
ORDER BY ns.nspname
return $this->loadTableConstraints($tableName, 'primaryKey');
}
/**
* @inheritDoc
*/
protected function loadTableForeignKeys($tableName)
{
return $this->loadTableConstraints($tableName, 'foreignKeys');
}
/**
* @inheritDoc
*/
protected function loadTableIndexes($tableName)
{
static $sql = <<<SQL
SELECT
"ic"."relname" AS "name",
"ia"."attname" AS "column_name",
"i"."indisunique" AS "index_is_unique",
"i"."indisprimary" AS "index_is_primary"
FROM "pg_class" AS "tc"
INNER JOIN "pg_namespace" AS "tcns"
ON "tcns"."oid" = "tc"."relnamespace"
INNER JOIN "pg_index" AS "i"
ON "i"."indrelid" = "tc"."oid"
INNER JOIN "pg_class" AS "ic"
ON "ic"."oid" = "i"."indexrelid"
INNER JOIN "pg_attribute" AS "ia"
ON "ia"."attrelid" = "i"."indrelid" AND "ia"."attnum" = ANY ("i"."indkey")
WHERE "tcns"."nspname" = :schemaName AND "tc"."relname" = :tableName
ORDER BY "ia"."attnum" ASC
SQL;
return $this->db->createCommand($sql)->queryColumn();
$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;
}
/**
* Returns all table names in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @return array all table names in the database. The names have NO schema name prefix.
* @inheritDoc
*/
protected function findTableNames($schema = '')
protected function loadTableUniques($tableName)
{
if ($schema === '') {
$schema = $this->defaultSchema;
return $this->loadTableConstraints($tableName, 'uniques');
}
/**
* @inheritDoc
*/
protected function loadTableChecks($tableName)
{
return $this->loadTableConstraints($tableName, 'checks');
}
/**
* @inheritDoc
*/
protected function loadTableDefaultValues($tableName)
{
return [];
}
/**
* Creates a query builder for the PostgreSQL database.
* @return QueryBuilder query builder instance
*/
public function createQueryBuilder()
{
return new QueryBuilder($this->db);
}
/**
* Resolves the table name and schema name (if any).
* @param TableSchema $table the table metadata object
* @param string $name the table name
*/
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;
}
$sql = <<<SQL
SELECT c.relname AS table_name
FROM pg_class c
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
WHERE ns.nspname = :schemaName AND c.relkind IN ('r','v','m','f')
ORDER BY c.relname
SQL;
return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();
$table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
}
/**
* Quotes a table name for use in a query.
* A simple table name has no schema prefix.
* @param string $name table name
* @return string the properly quoted table name
*/
public function quoteSimpleTableName($name)
{
return strpos($name, '"') !== false ? $name : '"' . $name . '"';
}
/**
@ -508,4 +606,101 @@ SQL;
return !$command->pdoStatement->rowCount() ? false : $result;
}
/**
* @param string $tableName
* @param string $returnType
* @return mixed
*/
private function loadTableConstraints($tableName, $returnType)
{
static $sql = <<<SQL
SELECT
"c"."conname" AS "name",
"a"."attname" AS "column_name",
"c"."contype" AS "type",
"ftcns"."nspname" AS "foreign_table_schema",
"ftc"."relname" AS "foreign_table_name",
"fa"."attname" AS "foreign_column_name",
"c"."confupdtype" AS "on_update",
"c"."confdeltype" AS "on_delete",
"c"."consrc" AS "check_expr"
FROM "pg_class" AS "tc"
INNER JOIN "pg_namespace" AS "tcns"
ON "tcns"."oid" = "tc"."relnamespace"
INNER JOIN "pg_constraint" AS "c"
ON "c"."conrelid" = "tc"."oid"
INNER JOIN "pg_attribute" AS "a"
ON "a"."attrelid" = "c"."conrelid" AND "a"."attnum" = ANY ("c"."conkey")
LEFT JOIN "pg_class" AS "ftc"
ON "ftc"."oid" = "c"."confrelid"
LEFT JOIN "pg_namespace" AS "ftcns"
ON "ftcns"."oid" = "ftc"."relnamespace"
LEFT JOIN "pg_attribute" "fa"
ON "fa"."attrelid" = "c"."confrelid" AND "fa"."attnum" = ANY ("c"."confkey")
WHERE "tcns"."nspname" = :schemaName AND "tc"."relname" = :tableName
ORDER BY "a"."attnum" ASC, "fa"."attnum" ASC
SQL;
static $actionTypes = [
'a' => 'NO ACTION',
'r' => 'RESTRICT',
'c' => 'CASCADE',
'n' => 'SET NULL',
'd' => 'SET DEFAULT',
];
$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 'f':
$result['foreignKeys'][] = new ForeignKeyConstraint([
'name' => $name,
'columnNames' => array_keys(array_count_values(ArrayHelper::getColumn($constraint, 'column_name'))),
'foreignSchemaName' => $constraint[0]['foreign_table_schema'],
'foreignTableName' => $constraint[0]['foreign_table_name'],
'foreignColumnNames' => array_keys(array_count_values(ArrayHelper::getColumn($constraint, 'foreign_column_name'))),
'onDelete' => isset($actionTypes[$constraint[0]['on_delete']]) ? $actionTypes[$constraint[0]['on_delete']] : null,
'onUpdate' => isset($actionTypes[$constraint[0]['on_update']]) ? $actionTypes[$constraint[0]['on_update']] : 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];
}
}

55
framework/db/sqlite/QueryBuilder.php

@ -53,6 +53,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
*/
protected $likeEscapeCharacter = '\\';
/**
* Generates a batch INSERT SQL statement.
* For example,
@ -300,6 +301,60 @@ class QueryBuilder extends \yii\db\QueryBuilder
}
/**
* @inheritDoc
* @throws NotSupportedException this is not supported by SQLite.
*/
public function addUnique($name, $table, $columns)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* @inheritDoc
* @throws NotSupportedException this is not supported by SQLite.
*/
public function dropUnique($name, $table)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* @inheritDoc
* @throws NotSupportedException this is not supported by SQLite.
*/
public function addCheck($name, $table, $check)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* @inheritDoc
* @throws NotSupportedException this is not supported by SQLite.
*/
public function dropCheck($name, $table)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* @inheritDoc
* @throws NotSupportedException this is not supported by SQLite.
*/
public function addDefaultValue($name, $table, $column, $default)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* @inheritDoc
* @throws NotSupportedException this is not supported by SQLite.
*/
public function dropDefaultValue($name, $table)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* @inheritdoc
* @throws NotSupportedException
* @since 2.0.8

195
framework/db/sqlite/Schema.php

@ -8,10 +8,16 @@
namespace yii\db\sqlite;
use yii\base\NotSupportedException;
use yii\db\CheckConstraint;
use yii\db\Constraint;
use yii\db\ConstraintFinderTrait;
use yii\db\Expression;
use yii\db\ForeignKeyConstraint;
use yii\db\IndexConstraint;
use yii\db\TableSchema;
use yii\db\ColumnSchema;
use yii\db\Transaction;
use yii\helpers\ArrayHelper;
/**
* Schema is the class for retrieving metadata from a SQLite (2/3) database.
@ -24,6 +30,8 @@ use yii\db\Transaction;
*/
class Schema extends \yii\db\Schema
{
use ConstraintFinderTrait;
/**
* @var array mapping from physical column types (keys) to abstract column types (values)
*/
@ -58,6 +66,120 @@ class Schema extends \yii\db\Schema
'enum' => self::TYPE_STRING,
];
/**
* @inheritDoc
*/
protected function findTableNames($schema = '')
{
$sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence' ORDER BY tbl_name";
return $this->db->createCommand($sql)->queryColumn();
}
/**
* @inheritDoc
*/
protected function loadTableSchema($name)
{
$table = new TableSchema;
$table->name = $name;
$table->fullName = $name;
if ($this->findColumns($table)) {
$this->findConstraints($table);
return $table;
} else {
return null;
}
}
/**
* @inheritDoc
*/
protected function loadTablePrimaryKey($tableName)
{
return $this->loadTableConstraints($tableName, 'primaryKey');
}
/**
* @inheritDoc
*/
protected function loadTableForeignKeys($tableName)
{
$foreignKeys = $this->db->createCommand('PRAGMA FOREIGN_KEY_LIST (' . $this->quoteValue($tableName) . ')')->queryAll();
$foreignKeys = $this->normalizePdoRowKeyCase($foreignKeys, true);
$foreignKeys = ArrayHelper::index($foreignKeys, null, 'table');
ArrayHelper::multisort($foreignKeys, 'seq', SORT_ASC, SORT_NUMERIC);
$result = [];
foreach ($foreignKeys as $table => $foreignKey) {
$result[] = new ForeignKeyConstraint([
'columnNames' => ArrayHelper::getColumn($foreignKey, 'from'),
'foreignTableName' => $table,
'foreignColumnNames' => ArrayHelper::getColumn($foreignKey, 'to'),
'onDelete' => isset($foreignKey[0]['on_delete']) ? $foreignKey[0]['on_delete'] : null,
'onUpdate' => isset($foreignKey[0]['on_update']) ? $foreignKey[0]['on_update'] : null,
]);
}
return $result;
}
/**
* @inheritDoc
*/
protected function loadTableIndexes($tableName)
{
return $this->loadTableConstraints($tableName, 'indexes');
}
/**
* @inheritDoc
*/
protected function loadTableUniques($tableName)
{
return $this->loadTableConstraints($tableName, 'uniques');
}
/**
* @inheritDoc
*/
protected function loadTableChecks($tableName)
{
$sql = $this->db->createCommand('SELECT `sql` FROM `sqlite_master` WHERE name = :tableName', [
':tableName' => $tableName,
])->queryScalar();
$code = (new SqlTokenizer($sql))->tokenize();
if (!$code[0]->matches('any CREATE any TABLE any()', 0, $firstMatchIndex, $lastMatchIndex)) {
return [];
}
$createTableToken = $code[0][$lastMatchIndex - 1];
$result = [];
$offset = 0;
while (true) {
if (!$createTableToken->matches('any CHECK()', $offset, $firstMatchIndex, $offset)) {
break;
}
$checkSql = $createTableToken[$offset - 1]->getSql();
$name = null;
if (isset($createTableToken[$firstMatchIndex - 2]) && $createTableToken->matches('CONSTRAINT any', $firstMatchIndex - 2)) {
$name = $createTableToken[$firstMatchIndex - 1]->content;
}
$result[] = new CheckConstraint([
'name' => $name,
'expression' => $checkSql,
]);
}
return $result;
}
/**
* @inheritDoc
*/
protected function loadTableDefaultValues($tableName)
{
return [];
}
/**
* Quotes a table name for use in a query.
@ -101,38 +223,6 @@ class Schema extends \yii\db\Schema
}
/**
* Returns all table names in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @return array all table names in the database. The names have NO schema name prefix.
*/
protected function findTableNames($schema = '')
{
$sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence' ORDER BY tbl_name";
return $this->db->createCommand($sql)->queryColumn();
}
/**
* Loads the metadata for the specified table.
* @param string $name table name
* @return TableSchema driver dependent table metadata. Null if the table does not exist.
*/
protected function loadTableSchema($name)
{
$table = new TableSchema;
$table->name = $name;
$table->fullName = $name;
if ($this->findColumns($table)) {
$this->findConstraints($table);
return $table;
} else {
return null;
}
}
/**
* Collects the table column metadata.
* @param TableSchema $table the table metadata
* @return bool whether the table exists in the database
@ -290,4 +380,45 @@ class Schema extends \yii\db\Schema
throw new NotSupportedException(get_class($this) . ' only supports transaction isolation levels READ UNCOMMITTED and SERIALIZABLE.');
}
}
/**
* @param string $tableName
* @param string $returnType
* @return mixed
*/
private function loadTableConstraints($tableName, $returnType)
{
$indexes = $this->db->createCommand('PRAGMA INDEX_LIST (' . $this->quoteValue($tableName) . ')')->queryAll();
$indexes = $this->normalizePdoRowKeyCase($indexes, true);
$result = [
'primaryKey' => null,
'indexes' => [],
'uniques' => [],
];
foreach ($indexes as $index) {
$columns = $this->db->createCommand('PRAGMA INDEX_INFO (' . $this->quoteValue($index['name']) . ')')->queryAll();
$columns = $this->normalizePdoRowKeyCase($columns, true);
ArrayHelper::multisort($columns, 'seqno', SORT_ASC, SORT_NUMERIC);
$result['indexes'][] = new IndexConstraint([
'isPrimary' => $index['origin'] === 'pk',
'isUnique' => (bool) $index['unique'],
'name' => $index['name'],
'columnNames' => ArrayHelper::getColumn($columns, 'name'),
]);
if ($index['origin'] === 'u') {
$result['uniques'][] = new Constraint([
'name' => $index['name'],
'columnNames' => ArrayHelper::getColumn($columns, 'name'),
]);
} elseif ($index['origin'] === 'pk') {
$result['primaryKey'] = new Constraint([
'columnNames' => ArrayHelper::getColumn($columns, 'name'),
]);
}
}
foreach ($result as $type => $data) {
$this->setTableMetadata($tableName, $type, $data);
}
return $result[$returnType];
}
}

288
framework/db/sqlite/SqlTokenizer.php

@ -0,0 +1,288 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\sqlite;
/**
* SqlTokenizer splits SQLite query into individual SQL tokens.
* It's used to obtain a `CHECK` constraint information from a `CREATE TABLE` SQL code.
*
* @see http://www.sqlite.org/draft/tokenreq.html
* @see https://sqlite.org/lang.html
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.13
*/
class SqlTokenizer extends \yii\db\SqlTokenizer
{
/**
* @inheritDoc
*/
protected function isWhitespace(&$length)
{
static $whitespaces = [
"\f" => true,
"\n" => true,
"\r" => true,
"\t" => true,
' ' => true,
];
$length = 1;
return isset($whitespaces[$this->substring($length)]);
}
/**
* @inheritDoc
*/
protected function isComment(&$length)
{
static $comments = [
'--' => true,
'/*' => true,
];
$length = 2;
if (!isset($comments[$this->substring($length)])) {
return false;
}
if ($this->substring($length) === '--') {
$length = $this->indexAfter("\n") - $this->offset;
} else {
$length = $this->indexAfter('*/') - $this->offset;
}
return true;
}
/**
* @inheritDoc
*/
protected function isOperator(&$length, &$content)
{
static $operators = [
'!=',
'%',
'&',
'(',
')',
'*',
'+',
',',
'-',
'.',
'/',
';',
'<',
'<<',
'<=',
'<>',
'=',
'==',
'>',
'>=',
'>>',
'|',
'||',
'~',
];
return $this->startsWithAnyLongest($operators, true, $length);
}
/**
* @inheritDoc
*/
protected function isIdentifier(&$length, &$content)
{
static $identifierDelimiters = [
'"' => '"',
'[' => ']',
'`' => '`',
];
if (!isset($identifierDelimiters[$this->substring(1)])) {
return false;
}
$delimiter = $identifierDelimiters[$this->substring(1)];
$offset = $this->offset;
while (true) {
$offset = $this->indexAfter($delimiter, $offset + 1);
if ($delimiter === ']' || $this->substring(1, true, $offset) !== $delimiter) {
break;
}
}
$length = $offset - $this->offset;
$content = $this->substring($length - 2,true, $this->offset + 1);
if ($delimiter !== ']') {
$content = strtr($content, ["$delimiter$delimiter" => $delimiter]);
}
return true;
}
/**
* @inheritDoc
*/
protected function isStringLiteral(&$length, &$content)
{
if ($this->substring(1) !== "'") {
return false;
}
$offset = $this->offset;
while (true) {
$offset = $this->indexAfter("'", $offset + 1);
if ($this->substring(1, true, $offset) !== "'") {
break;
}
}
$length = $offset - $this->offset;
$content = strtr($this->substring($length - 2,true, $this->offset + 1), ["''" => "'"]);
return true;
}
/**
* @inheritDoc
*/
protected function isKeyword($string, &$content)
{
static $keywords = [
'ABORT' => true,
'ACTION' => true,
'ADD' => true,
'AFTER' => true,
'ALL' => true,
'ALTER' => true,
'ANALYZE' => true,
'AND' => true,
'AS' => true,
'ASC' => true,
'ATTACH' => true,
'AUTOINCREMENT' => true,
'BEFORE' => true,
'BEGIN' => true,
'BETWEEN' => true,
'BY' => true,
'CASCADE' => true,
'CASE' => true,
'CAST' => true,
'CHECK' => true,
'COLLATE' => true,
'COLUMN' => true,
'COMMIT' => true,
'CONFLICT' => true,
'CONSTRAINT' => true,
'CREATE' => true,
'CROSS' => true,
'CURRENT_DATE' => true,
'CURRENT_TIME' => true,
'CURRENT_TIMESTAMP' => true,
'DATABASE' => true,
'DEFAULT' => true,
'DEFERRABLE' => true,
'DEFERRED' => true,
'DELETE' => true,
'DESC' => true,
'DETACH' => true,
'DISTINCT' => true,
'DROP' => true,
'EACH' => true,
'ELSE' => true,
'END' => true,
'ESCAPE' => true,
'EXCEPT' => true,
'EXCLUSIVE' => true,
'EXISTS' => true,
'EXPLAIN' => true,
'FAIL' => true,
'FOR' => true,
'FOREIGN' => true,
'FROM' => true,
'FULL' => true,
'GLOB' => true,
'GROUP' => true,
'HAVING' => true,
'IF' => true,
'IGNORE' => true,
'IMMEDIATE' => true,
'IN' => true,
'INDEX' => true,
'INDEXED' => true,
'INITIALLY' => true,
'INNER' => true,
'INSERT' => true,
'INSTEAD' => true,
'INTERSECT' => true,
'INTO' => true,
'IS' => true,
'ISNULL' => true,
'JOIN' => true,
'KEY' => true,
'LEFT' => true,
'LIKE' => true,
'LIMIT' => true,
'MATCH' => true,
'NATURAL' => true,
'NO' => true,
'NOT' => true,
'NOTNULL' => true,
'NULL' => true,
'OF' => true,
'OFFSET' => true,
'ON' => true,
'OR' => true,
'ORDER' => true,
'OUTER' => true,
'PLAN' => true,
'PRAGMA' => true,
'PRIMARY' => true,
'QUERY' => true,
'RAISE' => true,
'RECURSIVE' => true,
'REFERENCES' => true,
'REGEXP' => true,
'REINDEX' => true,
'RELEASE' => true,
'RENAME' => true,
'REPLACE' => true,
'RESTRICT' => true,
'RIGHT' => true,
'ROLLBACK' => true,
'ROW' => true,
'SAVEPOINT' => true,
'SELECT' => true,
'SET' => true,
'TABLE' => true,
'TEMP' => true,
'TEMPORARY' => true,
'THEN' => true,
'TO' => true,
'TRANSACTION' => true,
'TRIGGER' => true,
'UNION' => true,
'UNIQUE' => true,
'UPDATE' => true,
'USING' => true,
'VACUUM' => true,
'VALUES' => true,
'VIEW' => true,
'VIRTUAL' => true,
'WHEN' => true,
'WHERE' => true,
'WITH' => true,
'WITHOUT' => true,
];
$string = mb_strtoupper($string, 'UTF-8');
if (!isset($keywords[$string])) {
return false;
}
$content = $string;
return true;
}
}

43
tests/data/cubrid.sql

@ -20,6 +20,10 @@ DROP TABLE IF EXISTS "animal";
DROP TABLE IF EXISTS "default_pk";
DROP TABLE IF EXISTS "document";
DROP VIEW IF EXISTS "animal_view";
DROP TABLE IF EXISTS "T_constraints_4";
DROP TABLE IF EXISTS "T_constraints_3";
DROP TABLE IF EXISTS "T_constraints_2";
DROP TABLE IF EXISTS "T_constraints_1";
CREATE TABLE "constraints"
(
@ -213,3 +217,42 @@ CREATE TABLE `bit_values` (
);
INSERT INTO `bit_values` (id, val) VALUES (1, b'0'), (2, b'1');
CREATE TABLE "T_constraints_1"
(
"C_id" INT NOT NULL PRIMARY KEY,
"C_not_null" INT NOT NULL,
"C_check" VARCHAR(255) NULL CHECK ("C_check" <> ''),
"C_unique" INT NOT NULL,
"C_default" INT NOT NULL DEFAULT 0,
CONSTRAINT "CN_unique" UNIQUE ("C_unique")
);
CREATE TABLE "T_constraints_2"
(
"C_id_1" INT NOT NULL,
"C_id_2" INT NOT NULL,
"C_index_1" INT NULL,
"C_index_2_1" INT NULL,
"C_index_2_2" INT NULL,
CONSTRAINT "CN_constraints_2_multi" UNIQUE ("C_index_2_1", "C_index_2_2"),
CONSTRAINT "CN_pk" PRIMARY KEY ("C_id_1", "C_id_2")
);
CREATE INDEX "CN_constraints_2_single" ON "T_constraints_2" ("C_index_1");
CREATE TABLE "T_constraints_3"
(
"C_id" INT NOT NULL,
"C_fk_id_1" INT NOT NULL,
"C_fk_id_2" INT NOT NULL,
CONSTRAINT "CN_constraints_3" FOREIGN KEY ("C_fk_id_1", "C_fk_id_2") REFERENCES "T_constraints_2" ("C_id_1", "C_id_2") ON DELETE RESTRICT ON UPDATE RESTRICT
);
CREATE TABLE "T_constraints_4"
(
"C_id" INT NOT NULL PRIMARY KEY,
"C_col_1" INT NULL,
"C_col_2" INT NOT NULL,
CONSTRAINT "CN_constraints_4" UNIQUE ("C_col_1", "C_col_2")
);

43
tests/data/mssql.sql

@ -13,6 +13,10 @@ IF OBJECT_ID('[dbo].[animal]', 'U') IS NOT NULL DROP TABLE [dbo].[animal];
IF OBJECT_ID('[dbo].[default_pk]', 'U') IS NOT NULL DROP TABLE [dbo].[default_pk];
IF OBJECT_ID('[dbo].[document]', 'U') IS NOT NULL DROP TABLE [dbo].[document];
IF OBJECT_ID('[dbo].[animal_view]', 'V') IS NOT NULL DROP VIEW [dbo].[animal_view];
IF OBJECT_ID('[T_constraints_4]', 'U') IS NOT NULL DROP TABLE [T_constraints_4];
IF OBJECT_ID('[T_constraints_3]', 'U') IS NOT NULL DROP TABLE [T_constraints_3];
IF OBJECT_ID('[T_constraints_2]', 'U') IS NOT NULL DROP TABLE [T_constraints_2];
IF OBJECT_ID('[T_constraints_1]', 'U') IS NOT NULL DROP TABLE [T_constraints_1];
CREATE TABLE [dbo].[profile] (
[id] [int] IDENTITY NOT NULL,
@ -205,3 +209,42 @@ CREATE TABLE [dbo].[bit_values] (
);
INSERT INTO [dbo].[bit_values] ([val]) VALUES (0), (1);
CREATE TABLE [T_constraints_1]
(
[C_id] INT NOT NULL IDENTITY PRIMARY KEY,
[C_not_null] INT NOT NULL,
[C_check] VARCHAR(255) NULL CHECK ([C_check] <> ''),
[C_unique] INT NOT NULL,
[C_default] INT NOT NULL DEFAULT 0,
CONSTRAINT [CN_unique] UNIQUE ([C_unique])
);
CREATE TABLE [T_constraints_2]
(
[C_id_1] INT NOT NULL,
[C_id_2] INT NOT NULL,
[C_index_1] INT NULL,
[C_index_2_1] INT NULL,
[C_index_2_2] INT NULL,
CONSTRAINT [CN_constraints_2_multi] UNIQUE ([C_index_2_1], [C_index_2_2]),
CONSTRAINT [CN_pk] PRIMARY KEY ([C_id_1], [C_id_2])
);
CREATE INDEX [CN_constraints_2_single] ON [T_constraints_2] ([C_index_1]);
CREATE TABLE [T_constraints_3]
(
[C_id] INT NOT NULL,
[C_fk_id_1] INT NOT NULL,
[C_fk_id_2] INT NOT NULL,
CONSTRAINT [CN_constraints_3] FOREIGN KEY ([C_fk_id_1], [C_fk_id_2]) REFERENCES [T_constraints_2] ([C_id_1], [C_id_2]) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE [T_constraints_4]
(
[C_id] INT NOT NULL IDENTITY PRIMARY KEY,
[C_col_1] INT NULL,
[C_col_2] INT NOT NULL,
CONSTRAINT [CN_constraints_4] UNIQUE ([C_col_1], [C_col_2])
);

47
tests/data/mysql.sql

@ -21,6 +21,10 @@ DROP TABLE IF EXISTS `default_pk` CASCADE;
DROP TABLE IF EXISTS `document` CASCADE;
DROP TABLE IF EXISTS `comment` CASCADE;
DROP VIEW IF EXISTS `animal_view`;
DROP TABLE IF EXISTS `T_constraints_4` CASCADE;
DROP TABLE IF EXISTS `T_constraints_3` CASCADE;
DROP TABLE IF EXISTS `T_constraints_2` CASCADE;
DROP TABLE IF EXISTS `T_constraints_1` CASCADE;
CREATE TABLE `constraints`
(
@ -257,3 +261,46 @@ CREATE TABLE `bit_values` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `bit_values` (id, val) VALUES (1, b'0'), (2, b'1');
CREATE TABLE `T_constraints_1`
(
`C_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`C_not_null` INT NOT NULL,
`C_check` VARCHAR(255) NULL CHECK (`C_check` <> ''),
`C_unique` INT NOT NULL,
`C_default` INT NOT NULL DEFAULT 0,
CONSTRAINT `CN_unique` UNIQUE (`C_unique`)
)
ENGINE = 'InnoDB' DEFAULT CHARSET = 'utf8';
CREATE TABLE `T_constraints_2`
(
`C_id_1` INT NOT NULL,
`C_id_2` INT NOT NULL,
`C_index_1` INT NULL,
`C_index_2_1` INT NULL,
`C_index_2_2` INT NULL,
CONSTRAINT `CN_constraints_2_multi` UNIQUE (`C_index_2_1`, `C_index_2_2`),
CONSTRAINT `CN_pk` PRIMARY KEY (`C_id_1`, `C_id_2`)
)
ENGINE = 'InnoDB' DEFAULT CHARSET = 'utf8';
CREATE INDEX `CN_constraints_2_single` ON `T_constraints_2` (`C_index_1`);
CREATE TABLE `T_constraints_3`
(
`C_id` INT NOT NULL,
`C_fk_id_1` INT NOT NULL,
`C_fk_id_2` INT NOT NULL,
CONSTRAINT `CN_constraints_3` FOREIGN KEY (`C_fk_id_1`, `C_fk_id_2`) REFERENCES `T_constraints_2` (`C_id_1`, `C_id_2`) ON DELETE CASCADE ON UPDATE CASCADE
)
ENGINE = 'InnoDB' DEFAULT CHARSET = 'utf8';
CREATE TABLE `T_constraints_4`
(
`C_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`C_col_1` INT NULL,
`C_col_2` INT NOT NULL,
CONSTRAINT `CN_constraints_4` UNIQUE (`C_col_1`, `C_col_2`)
)
ENGINE = 'InnoDB' DEFAULT CHARSET = 'utf8';

43
tests/data/oci.sql

@ -23,6 +23,10 @@ BEGIN EXECUTE IMMEDIATE 'DROP VIEW "animal_view"'; EXCEPTION WHEN OTHERS THEN IF
BEGIN EXECUTE IMMEDIATE 'DROP TABLE "validator_main"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;--
BEGIN EXECUTE IMMEDIATE 'DROP TABLE "validator_ref"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;--
BEGIN EXECUTE IMMEDIATE 'DROP TABLE "bit_values"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END; --
BEGIN EXECUTE IMMEDIATE 'DROP TABLE "T_constraints_4"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;--
BEGIN EXECUTE IMMEDIATE 'DROP TABLE "T_constraints_3"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;--
BEGIN EXECUTE IMMEDIATE 'DROP TABLE "T_constraints_2"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;--
BEGIN EXECUTE IMMEDIATE 'DROP TABLE "T_constraints_1"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;--
BEGIN EXECUTE IMMEDIATE 'DROP SEQUENCE "profile_SEQ"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -2289 THEN RAISE; END IF; END;--
BEGIN EXECUTE IMMEDIATE 'DROP SEQUENCE "customer_SEQ"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -2289 THEN RAISE; END IF; END;--
@ -199,6 +203,45 @@ CREATE TABLE "bit_values" (
CONSTRAINT "bit_values_val" CHECK ("val" IN ('1','0'))
);
CREATE TABLE "T_constraints_1"
(
"C_id" INT NOT NULL PRIMARY KEY,
"C_not_null" INT NOT NULL,
"C_check" VARCHAR(255) NULL CHECK ("C_check" <> ''),
"C_unique" INT NOT NULL,
"C_default" INT DEFAULT 0 NOT NULL,
CONSTRAINT "CN_unique" UNIQUE ("C_unique")
);
CREATE TABLE "T_constraints_2"
(
"C_id_1" INT NOT NULL,
"C_id_2" INT NOT NULL,
"C_index_1" INT NULL,
"C_index_2_1" INT NULL,
"C_index_2_2" INT NULL,
CONSTRAINT "CN_constraints_2_multi" UNIQUE ("C_index_2_1", "C_index_2_2"),
CONSTRAINT "CN_pk" PRIMARY KEY ("C_id_1", "C_id_2")
);
CREATE INDEX "CN_constraints_2_single" ON "T_constraints_2" ("C_index_1");
CREATE TABLE "T_constraints_3"
(
"C_id" INT NOT NULL,
"C_fk_id_1" INT NOT NULL,
"C_fk_id_2" INT NOT NULL,
CONSTRAINT "CN_constraints_3" FOREIGN KEY ("C_fk_id_1", "C_fk_id_2") REFERENCES "T_constraints_2" ("C_id_1", "C_id_2") ON DELETE CASCADE
);
CREATE TABLE "T_constraints_4"
(
"C_id" INT NOT NULL PRIMARY KEY,
"C_col_1" INT NULL,
"C_col_2" INT NOT NULL,
CONSTRAINT "CN_constraints_4" UNIQUE ("C_col_1", "C_col_2")
);
/**
* (Postgres-)Database Schema for validator tests
*/

44
tests/data/postgres.sql

@ -23,6 +23,11 @@ DROP TABLE IF EXISTS "default_pk" CASCADE;
DROP TABLE IF EXISTS "document" CASCADE;
DROP TABLE IF EXISTS "comment" CASCADE;
DROP VIEW IF EXISTS "animal_view";
DROP TABLE IF EXISTS "T_constraints_4";
DROP TABLE IF EXISTS "T_constraints_3";
DROP TABLE IF EXISTS "T_constraints_2";
DROP TABLE IF EXISTS "T_constraints_1";
DROP SCHEMA IF EXISTS "schema1" CASCADE;
DROP SCHEMA IF EXISTS "schema2" CASCADE;
@ -259,3 +264,42 @@ CREATE TABLE "bit_values" (
);
INSERT INTO "bit_values" (id, val) VALUES (1, '0'), (2, '1');
CREATE TABLE "T_constraints_1"
(
"C_id" INT NOT NULL PRIMARY KEY,
"C_not_null" INT NOT NULL,
"C_check" VARCHAR(255) NULL CHECK ("C_check" <> ''),
"C_unique" INT NOT NULL,
"C_default" INT NOT NULL DEFAULT 0,
CONSTRAINT "CN_unique" UNIQUE ("C_unique")
);
CREATE TABLE "T_constraints_2"
(
"C_id_1" INT NOT NULL,
"C_id_2" INT NOT NULL,
"C_index_1" INT NULL,
"C_index_2_1" INT NULL,
"C_index_2_2" INT NULL,
CONSTRAINT "CN_constraints_2_multi" UNIQUE ("C_index_2_1", "C_index_2_2"),
CONSTRAINT "CN_pk" PRIMARY KEY ("C_id_1", "C_id_2")
);
CREATE INDEX "CN_constraints_2_single" ON "T_constraints_2" ("C_index_1");
CREATE TABLE "T_constraints_3"
(
"C_id" INT NOT NULL,
"C_fk_id_1" INT NOT NULL,
"C_fk_id_2" INT NOT NULL,
CONSTRAINT "CN_constraints_3" FOREIGN KEY ("C_fk_id_1", "C_fk_id_2") REFERENCES "T_constraints_2" ("C_id_1", "C_id_2") ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE "T_constraints_4"
(
"C_id" INT NOT NULL PRIMARY KEY,
"C_col_1" INT NULL,
"C_col_2" INT NOT NULL,
CONSTRAINT "CN_constraints_4" UNIQUE ("C_col_1", "C_col_2")
);

43
tests/data/sqlite.sql

@ -19,6 +19,10 @@ DROP TABLE IF EXISTS "animal";
DROP TABLE IF EXISTS "default_pk";
DROP TABLE IF EXISTS "document";
DROP VIEW IF EXISTS "animal_view";
DROP TABLE IF EXISTS "T_constraints_4";
DROP TABLE IF EXISTS "T_constraints_3";
DROP TABLE IF EXISTS "T_constraints_2";
DROP TABLE IF EXISTS "T_constraints_1";
CREATE TABLE "profile" (
id INTEGER NOT NULL,
@ -227,3 +231,42 @@ CREATE TABLE "bit_values" (
INSERT INTO "bit_values" (id, val) VALUES (1, 0);
INSERT INTO "bit_values" (id, val) VALUES (2, 1);
CREATE TABLE "T_constraints_1"
(
"C_id" INT NOT NULL PRIMARY KEY,
"C_not_null" INT NOT NULL,
"C_check" VARCHAR(255) NULL CHECK ("C_check" <> ''),
"C_unique" INT NOT NULL,
"C_default" INT NOT NULL DEFAULT 0,
CONSTRAINT "CN_unique" UNIQUE ("C_unique")
);
CREATE TABLE "T_constraints_2"
(
"C_id_1" INT NOT NULL,
"C_id_2" INT NOT NULL,
"C_index_1" INT NULL,
"C_index_2_1" INT NULL,
"C_index_2_2" INT NULL,
CONSTRAINT "CN_pk" PRIMARY KEY ("C_id_1", "C_id_2"),
CONSTRAINT "CN_constraints_2_multi" UNIQUE ("C_index_2_1", "C_index_2_2")
);
CREATE INDEX "CN_constraints_2_single" ON "T_constraints_2" ("C_index_1");
CREATE TABLE "T_constraints_3"
(
"C_id" INT NOT NULL,
"C_fk_id_1" INT NOT NULL,
"C_fk_id_2" INT NOT NULL,
CONSTRAINT "CN_constraints_3" FOREIGN KEY ("C_fk_id_1", "C_fk_id_2") REFERENCES "T_constraints_2" ("C_id_1", "C_id_2") ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE "T_constraints_4"
(
"C_id" INT NOT NULL PRIMARY KEY,
"C_col_1" INT NULL,
"C_col_2" INT NOT NULL,
CONSTRAINT "CN_constraints_4" UNIQUE ("C_col_1", "C_col_2")
);

23
tests/framework/db/AnyCaseValue.php

@ -0,0 +1,23 @@
<?php
namespace yiiunit\framework\db;
class AnyCaseValue extends CompareValue
{
public $value;
/**
* Constructor.
* @param string|string[] $value
* @param array $config
*/
public function __construct($value, $config = [])
{
if (is_array($value)) {
$this->value = array_map('strtolower', $value);
} else {
$this->value = strtolower($value);
}
parent::__construct($config);
}
}

19
tests/framework/db/AnyValue.php

@ -0,0 +1,19 @@
<?php
namespace yiiunit\framework\db;
class AnyValue extends CompareValue
{
/**
* @var self
*/
private static $_instance;
public static function getInstance()
{
if (self::$_instance === null) {
self::$_instance = new self();
}
return self::$_instance;
}
}

30
tests/framework/db/CommandTest.php

@ -659,6 +659,36 @@ SQL;
}
*/
public function testAddDropPrimaryKey()
{
// @TODO Will be implemented in this PR.
}
public function testAddDropForeignKey()
{
// @TODO Will be implemented in this PR.
}
public function testCreateDropIndex()
{
// @TODO Will be implemented in this PR.
}
public function testAddDropUnique()
{
// @TODO Will be implemented in this PR.
}
public function testAddDropCheck()
{
// @TODO Will be implemented in this PR.
}
public function testAddDropDefaultValue()
{
// @TODO Will be implemented in this PR.
}
public function testIntegrityViolation()
{
$this->expectException('\yii\db\IntegrityException');

9
tests/framework/db/CompareValue.php

@ -0,0 +1,9 @@
<?php
namespace yiiunit\framework\db;
use yii\base\Object;
abstract class CompareValue extends Object
{
}

226
tests/framework/db/QueryBuilderTest.php

@ -1222,34 +1222,212 @@ abstract class QueryBuilderTest extends DatabaseTestCase
$this->assertEquals($expectedParams, $params);
}
public function testAddDropPrimaryKey()
public function primaryKeysProvider()
{
$tableName = 'constraints';
$pkeyName = $tableName . "_pkey";
$tableName = 'T_constraints_1';
$name = 'CN_pk';
return [
'drop' => [
"ALTER TABLE {{{$tableName}}} DROP CONSTRAINT [[$name]]",
function (QueryBuilder $qb) use ($tableName, $name) {
return $qb->dropPrimaryKey($name, $tableName);
}
],
'add' => [
"ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] PRIMARY KEY ([[C_id_1]])",
function (QueryBuilder $qb) use ($tableName, $name) {
return $qb->addPrimaryKey($name, $tableName, 'C_id_1');
}
],
'add (2 columns)' => [
"ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] PRIMARY KEY ([[C_id_1]], [[C_id_2]])",
function (QueryBuilder $qb) use ($tableName, $name) {
return $qb->addPrimaryKey($name, $tableName, 'C_id_1, C_id_2');
}
],
];
}
// ADD
$qb = $this->getQueryBuilder();
$qb->db->createCommand()->addPrimaryKey($pkeyName, $tableName, ['id'])->execute();
$tableSchema = $qb->db->getSchema()->getTableSchema($tableName);
$this->assertCount(1, $tableSchema->primaryKey);
/**
* @dataProvider primaryKeysProvider
*/
public function testAddDropPrimaryKey($sql, \Closure $builder)
{
$this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
}
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 ON UPDATE CASCADE",
function (QueryBuilder $qb) use ($tableName, $name, $pkTableName) {
return $qb->addForeignKey($name, $tableName, 'C_fk_id_1', $pkTableName, 'C_id_1', 'CASCADE', '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 ON UPDATE 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', 'CASCADE');
}
],
];
}
// DROP
$qb->db->createCommand()->dropPrimaryKey($pkeyName, $tableName)->execute();
$qb = $this->getQueryBuilder(); // resets the schema
$tableSchema = $qb->db->getSchema()->getTableSchema($tableName);
$this->assertCount(0, $tableSchema->primaryKey);
/**
* @dataProvider foreignKeysProvider
*/
public function testAddDropForeignKey($sql, \Closure $builder)
{
$this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
}
// ADD (2 columns)
$qb = $this->getQueryBuilder();
$qb->db->createCommand()->addPrimaryKey($pkeyName, $tableName, 'id, field1')->execute();
$tableSchema = $qb->db->getSchema()->getTableSchema($tableName);
$this->assertCount(2, $tableSchema->primaryKey);
// DROP (2 columns)
$qb->db->createCommand()->dropPrimaryKey($pkeyName, $tableName)->execute();
$qb = $this->getQueryBuilder(); // resets the schema
$tableSchema = $qb->db->getSchema()->getTableSchema($tableName);
$this->assertCount(0, $tableSchema->primaryKey);
public function indexesProvider()
{
$tableName = 'T_constraints_2';
$name1 = 'CN_constraints_2_single';
$name2 = 'CN_constraints_2_multi';
return [
'drop' => [
"DROP INDEX [[$name1]] ON {{{$tableName}}}",
function (QueryBuilder $qb) use ($tableName, $name1) {
return $qb->dropIndex($name1, $tableName);
}
],
'create' => [
"CREATE INDEX [[$name1]] ON {{{$tableName}}} ([[C_index_1]])",
function (QueryBuilder $qb) use ($tableName, $name1) {
return $qb->createIndex($name1, $tableName, 'C_index_1');
}
],
'create (2 columns)' => [
"CREATE INDEX [[$name2]] ON {{{$tableName}}} ([[C_index_2_1]], [[C_index_2_2]])",
function (QueryBuilder $qb) use ($tableName, $name2) {
return $qb->createIndex($name2, $tableName, 'C_index_2_1, C_index_2_2');
}
],
'create unique' => [
"CREATE UNIQUE INDEX [[$name1]] ON {{{$tableName}}} ([[C_index_1]])",
function (QueryBuilder $qb) use ($tableName, $name1) {
return $qb->createIndex($name1, $tableName, 'C_index_1', true);
}
],
'create unique (2 columns)' => [
"CREATE UNIQUE INDEX [[$name2]] ON {{{$tableName}}} ([[C_index_2_1]], [[C_index_2_2]])",
function (QueryBuilder $qb) use ($tableName, $name2) {
return $qb->createIndex($name2, $tableName, 'C_index_2_1, C_index_2_2', true);
}
],
];
}
/**
* @dataProvider indexesProvider
*/
public function testCreateDropIndex($sql, \Closure $builder)
{
$this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
}
public function uniquesProvider()
{
$tableName1 = 'T_constraints_1';
$name1 = 'CN_unique';
$tableName2 = 'T_constraints_2';
$name2 = 'CN_constraints_2_multi';
return [
'drop' => [
"ALTER TABLE {{{$tableName1}}} DROP CONSTRAINT [[$name1]]",
function (QueryBuilder $qb) use ($tableName1, $name1) {
return $qb->dropUnique($name1, $tableName1);
}
],
'add' => [
"ALTER TABLE {{{$tableName1}}} ADD CONSTRAINT [[$name1]] UNIQUE ([[C_unique]])",
function (QueryBuilder $qb) use ($tableName1, $name1) {
return $qb->addUnique($name1, $tableName1, 'C_unique');
}
],
'add (2 columns)' => [
"ALTER TABLE {{{$tableName2}}} ADD CONSTRAINT [[$name2]] UNIQUE ([[C_index_2_1]], [[C_index_2_2]])",
function (QueryBuilder $qb) use ($tableName2, $name2) {
return $qb->addUnique($name2, $tableName2, 'C_index_2_1, C_index_2_2');
}
],
];
}
/**
* @dataProvider uniquesProvider
*/
public function testAddDropUnique($sql, \Closure $builder)
{
$this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
}
public function checksProvider()
{
$tableName = 'T_constraints_1';
$name = 'CN_check';
return [
'drop' => [
"ALTER TABLE {{{$tableName}}} DROP CONSTRAINT [[$name]]",
function (QueryBuilder $qb) use ($tableName, $name) {
return $qb->dropCheck($name, $tableName);
}
],
'add' => [
"ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] CHECK ([[C_not_null]] > 100)",
function (QueryBuilder $qb) use ($tableName, $name) {
return $qb->addCheck($name, $tableName, '[[C_not_null]] > 100');
}
],
];
}
/**
* @dataProvider checksProvider
*/
public function testAddDropCheck($sql, \Closure $builder)
{
$this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
}
public function defaultValuesProvider()
{
$tableName = 'T_constraints_1';
$name = 'CN_default';
return [
'drop' => [
"ALTER TABLE {{{$tableName}}} DROP CONSTRAINT [[$name]]",
function (QueryBuilder $qb) use ($tableName, $name) {
return $qb->dropDefaultValue($name, $tableName);
}
],
'add' => [
"ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] DEFAULT 0 FOR [[C_default]]",
function (QueryBuilder $qb) use ($tableName, $name) {
return $qb->addDefaultValue($name, $tableName, 'C_default', 0);
}
],
];
}
/**
* @dataProvider defaultValuesProvider
*/
public function testAddDropDefaultValue($sql, \Closure $builder)
{
$this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
}
public function existsParamsProvider()

229
tests/framework/db/SchemaTest.php

@ -4,8 +4,12 @@ namespace yiiunit\framework\db;
use PDO;
use yii\caching\FileCache;
use yii\db\CheckConstraint;
use yii\db\ColumnSchema;
use yii\db\Constraint;
use yii\db\Expression;
use yii\db\ForeignKeyConstraint;
use yii\db\IndexConstraint;
use yii\db\Schema;
abstract class SchemaTest extends DatabaseTestCase
@ -426,4 +430,229 @@ abstract class SchemaTest extends DatabaseTestCase
'someCol2Unique' => ['someCol2'],
], $uniqueIndexes);
}
public function testContraintTablesExistance()
{
$tableNames = [
'T_constraints_1',
'T_constraints_2',
'T_constraints_3',
'T_constraints_4',
];
$schema = $this->getConnection()->getSchema();
foreach ($tableNames as $tableName) {
$tableSchema = $schema->getTableSchema($tableName);
$this->assertInstanceOf('yii\db\TableSchema', $tableSchema, $tableName);
}
}
public function constraintsProvider()
{
return [
'1: primary key' => ['T_constraints_1', 'primaryKey', new Constraint([
'name' => AnyValue::getInstance(),
'columnNames' => ['C_id'],
])],
'1: check' => ['T_constraints_1', 'checks', [
new CheckConstraint([
'name' => AnyValue::getInstance(),
'columnNames' => ['C_check'],
'expression' => "C_check <> ''"
])
]],
'1: unique' => ['T_constraints_1', 'uniques', [
new Constraint([
'name' => 'CN_unique',
'columnNames' => ['C_unique'],
])
]],
'1: index' => ['T_constraints_1', 'indexes', [
new IndexConstraint([
'name' => AnyValue::getInstance(),
'columnNames' => ['C_id'],
'isUnique' => true,
'isPrimary' => true,
]),
new IndexConstraint([
'name' => 'CN_unique',
'columnNames' => ['C_unique'],
'isPrimary' => false,
'isUnique' => true,
])
]],
'1: default' => ['T_constraints_1', 'defaultValues', []],
'2: primary key' => ['T_constraints_2', 'primaryKey', new Constraint([
'name' => 'CN_pk',
'columnNames' => ['C_id_1', 'C_id_2'],
])],
'2: unique' => ['T_constraints_2', 'uniques', [
new Constraint([
'name' => 'CN_constraints_2_multi',
'columnNames' => ['C_index_2_1', 'C_index_2_2'],
])
]],
'2: index' => ['T_constraints_2', 'indexes', [
new IndexConstraint([
'name' => AnyValue::getInstance(),
'columnNames' => ['C_id_1', 'C_id_2'],
'isUnique' => true,
'isPrimary' => true,
]),
new IndexConstraint([
'name' => 'CN_constraints_2_single',
'columnNames' => ['C_index_1'],
'isPrimary' => false,
'isUnique' => false,
]),
new IndexConstraint([
'name' => 'CN_constraints_2_multi',
'columnNames' => ['C_index_2_1', 'C_index_2_2'],
'isPrimary' => false,
'isUnique' => true,
])
]],
'2: check' => ['T_constraints_2', 'checks', []],
'2: default' => ['T_constraints_2', 'defaultValues', []],
'3: primary key' => ['T_constraints_3', 'primaryKey', null],
'3: foreign key' => ['T_constraints_3', 'foreignKeys', [
new ForeignKeyConstraint([
'name' => 'CN_constraints_3',
'columnNames' => ['C_fk_id_1', 'C_fk_id_2'],
'foreignTableName' => 'T_constraints_2',
'foreignColumnNames' => ['C_id_1', 'C_id_2'],
'onDelete' => 'CASCADE',
'onUpdate' => 'CASCADE',
])
]],
'3: unique' => ['T_constraints_3', 'uniques', []],
'3: index' => ['T_constraints_3', 'indexes', [
new IndexConstraint([
'name' => 'CN_constraints_3',
'columnNames' => ['C_fk_id_1', 'C_fk_id_2'],
'isUnique' => false,
'isPrimary' => false,
]),
]],
'3: check' => ['T_constraints_3', 'checks', []],
'3: default' => ['T_constraints_3', 'defaultValues', []],
'4: primary key' => ['T_constraints_4', 'primaryKey', new Constraint([
'name' => AnyValue::getInstance(),
'columnNames' => ['C_id'],
])],
'4: unique' => ['T_constraints_4', 'uniques', [
new Constraint([
'name' => 'CN_constraints_4',
'columnNames' => ['C_col_1', 'C_col_2'],
])
]],
'4: check' => ['T_constraints_4', 'checks', []],
'4: default' => ['T_constraints_4', 'defaultValues', []],
];
}
/**
* @dataProvider constraintsProvider
* @depends testContraintTablesExistance
*/
public function testTableSchemaConstraints($tableName, $type, $expected)
{
$constraints = $this->getConnection(false)->getSchema()->{'getTable' . ucfirst($type)}($tableName);
$this->assertMetadataEquals($expected, $constraints);
}
/**
* @dataProvider constraintsProvider
* @depends testTableSchemaConstraints
*/
public function testTableSchemaConstraintsWithPdoUpperCase($tableName, $type, $expected)
{
$connection = $this->getConnection(false);
$connection->getSlavePdo()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
$constraints = $connection->getSchema()->{'getTable' . ucfirst($type)}($tableName, true);
$this->assertMetadataEquals($expected, $constraints);
}
/**
* @dataProvider constraintsProvider
* @depends testTableSchemaConstraints
*/
public function testTableSchemaConstraintsWithPdoLowerCase($tableName, $type, $expected)
{
$connection = $this->getConnection(false);
$connection->getSlavePdo()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
$constraints = $connection->getSchema()->{'getTable' . ucfirst($type)}($tableName, true);
$this->assertMetadataEquals($expected, $constraints);
}
private function assertMetadataEquals($expected, $actual)
{
$this->assertInternalType(strtolower(gettype($expected)), $actual);
if (is_array($expected)) {
$this->normalizeArrayKeys($expected, false);
$this->normalizeArrayKeys($actual, false);
}
$this->normalizeConstraints($expected, $actual);
if (is_array($expected)) {
$this->normalizeArrayKeys($expected, true);
$this->normalizeArrayKeys($actual, true);
}
$this->assertEquals($expected, $actual);
}
private function normalizeArrayKeys(array &$array, $caseSensitive)
{
$newArray = [];
foreach ($array as $value) {
if ($value instanceof Constraint) {
$key = (array) $value;
unset($key['name']);
foreach ($key as $keyName => $keyValue) {
if ($keyValue instanceof AnyCaseValue) {
$key[$keyName] = $keyValue->value;
} elseif ($keyValue instanceof AnyValue) {
$key[$keyName] = '[AnyValue]';
}
}
ksort($key, SORT_STRING);
$newArray[$caseSensitive ? json_encode($key) : strtolower(json_encode($key))] = $value;
} else {
$newArray[] = $value;
}
}
ksort($newArray, SORT_STRING);
$array = $newArray;
}
private function normalizeConstraints(&$expected, &$actual)
{
if (is_array($expected)) {
foreach ($expected as $key => $value) {
if (!$value instanceof Constraint || !isset($actual[$key]) || !$actual[$key] instanceof Constraint) {
continue;
}
$this->normalizeConstraintPair($value, $actual[$key]);
}
} elseif ($expected instanceof Constraint && $actual instanceof Constraint) {
$this->normalizeConstraintPair($expected, $actual);
}
}
private function normalizeConstraintPair(Constraint $expectedConstraint, Constraint $actualConstraint)
{
if ($expectedConstraint::className() !== $actualConstraint::className()) {
return;
}
foreach (array_keys((array) $expectedConstraint) as $name) {
if ($expectedConstraint->$name instanceof AnyValue) {
$actualConstraint->$name = $expectedConstraint->$name;
} elseif ($expectedConstraint->$name instanceof AnyCaseValue) {
$actualConstraint->$name = new AnyCaseValue($actualConstraint->$name);
}
}
}
}

10
tests/framework/db/cubrid/QueryBuilderTest.php

@ -29,6 +29,16 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
return array_merge(parent::columnTypes(), []);
}
public function checksProvider()
{
$this->markTestSkipped('Adding/dropping check constraints is not supported in CUBRID.');
}
public function defaultValuesProvider()
{
$this->markTestSkipped('Adding/dropping default constraints is not supported in CUBRID.');
}
public function testResetSequence()
{
$qb = $this->getQueryBuilder();

62
tests/framework/db/cubrid/SchemaTest.php

@ -3,6 +3,7 @@
namespace yiiunit\framework\db\cubrid;
use yii\db\Expression;
use yiiunit\framework\db\AnyCaseValue;
/**
* @group db
@ -26,7 +27,6 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
[$fp = fopen(__FILE__, 'rb'), \PDO::PARAM_LOB],
];
/* @var $schema Schema */
$schema = $this->getConnection()->schema;
foreach ($values as $value) {
@ -70,4 +70,64 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
$columns['ts_default']['defaultValue'] = new Expression('SYS_TIMESTAMP');
return $columns;
}
public function constraintsProvider()
{
$result = parent::constraintsProvider();
foreach ($result as $name => $constraints) {
$result[$name][2] = $this->convertPropertiesToAnycase($constraints[2]);
}
$result['1: check'][2] = [];
unset($result['1: index'][2][0]);
unset($result['2: index'][2][0]);
$result['3: foreign key'][2][0]->onDelete = 'RESTRICT';
$result['3: foreign key'][2][0]->onUpdate = 'RESTRICT';
$result['3: index'][2] = [];
return $result;
}
/**
* @dataProvider constraintsProvider
* @depends testTableSchemaConstraints
*/
public function testTableSchemaConstraintsWithPdoUpperCase($tableName, $type, $expected)
{
$this->markTestSkipped('This test hangs on CUBRID.');
}
/**
* @dataProvider constraintsProvider
* @depends testTableSchemaConstraints
*/
public function testTableSchemaConstraintsWithPdoLowerCase($tableName, $type, $expected)
{
$this->markTestSkipped('This test hangs on CUBRID.');
}
/**
* @param array|object|string $object
* @param bool $isProperty
* @return array|object|string
*/
private function convertPropertiesToAnycase($object, $isProperty = false)
{
if (!$isProperty && is_array($object)) {
$result = [];
foreach ($object as $name => $value) {
$result[] = $this->convertPropertiesToAnycase($value);
}
return $result;
}
if (is_object($object)) {
foreach (array_keys((array) $object) as $name) {
$object->$name = $this->convertPropertiesToAnycase($object->$name, true);
}
} elseif (is_array($object) || is_string($object)) {
$object = new AnyCaseValue($object);
}
return $object;
}
}

18
tests/framework/db/mssql/SchemaTest.php

@ -2,6 +2,9 @@
namespace yiiunit\framework\db\mssql;
use yii\db\DefaultConstraint;
use yiiunit\framework\db\AnyValue;
/**
* @group db
* @group mssql
@ -9,4 +12,19 @@ namespace yiiunit\framework\db\mssql;
class SchemaTest extends \yiiunit\framework\db\SchemaTest
{
public $driverName = 'sqlsrv';
public function constraintsProvider()
{
$result = parent::constraintsProvider();
$result['1: check'][2][0]->expression = '([C_check]<>\'\')';
$result['1: default'][2][] = new DefaultConstraint([
'name' => AnyValue::getInstance(),
'columnNames' => ['C_default'],
'value' => '((0))',
]);
$result['3: foreign key'][2][0]->foreignSchemaName = 'dbo';
$result['3: index'][2] = [];
return $result;
}
}

36
tests/framework/db/mysql/QueryBuilderTest.php

@ -57,6 +57,42 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
]);
}
public function primaryKeysProvider()
{
$result = parent::primaryKeysProvider();
$result['drop'][0] = 'ALTER TABLE {{T_constraints_1}} DROP PRIMARY KEY';
$result['add'][0] = 'ALTER TABLE {{T_constraints_1}} ADD CONSTRAINT [[CN_pk]] PRIMARY KEY ([[C_id_1]])';
$result['add (2 columns)'][0] = 'ALTER TABLE {{T_constraints_1}} ADD CONSTRAINT [[CN_pk]] PRIMARY KEY ([[C_id_1]], [[C_id_2]])';
return $result;
}
public function foreignKeysProvider()
{
$result = parent::foreignKeysProvider();
$result['drop'][0] = 'ALTER TABLE {{T_constraints_3}} DROP FOREIGN KEY [[CN_constraints_3]]';
return $result;
}
public function indexesProvider()
{
$result = parent::indexesProvider();
$result['create'][0] = 'ALTER TABLE {{T_constraints_2}} ADD INDEX [[CN_constraints_2_single]] ([[C_index_1]])';
$result['create (2 columns)'][0] = 'ALTER TABLE {{T_constraints_2}} ADD INDEX [[CN_constraints_2_multi]] ([[C_index_2_1]], [[C_index_2_2]])';
$result['create unique'][0] = 'ALTER TABLE {{T_constraints_2}} ADD UNIQUE INDEX [[CN_constraints_2_single]] ([[C_index_1]])';
$result['create unique (2 columns)'][0] = 'ALTER TABLE {{T_constraints_2}} ADD UNIQUE INDEX [[CN_constraints_2_multi]] ([[C_index_2_1]], [[C_index_2_2]])';
return $result;
}
public function checksProvider()
{
$this->markTestSkipped('Adding/dropping check constraints is not supported in MySQL.');
}
public function defaultValuesProvider()
{
$this->markTestSkipped('Adding/dropping default constraints is not supported in MySQL.');
}
public function testResetSequence()
{
$qb = $this->getQueryBuilder();

14
tests/framework/db/mysql/SchemaTest.php

@ -2,6 +2,8 @@
namespace yiiunit\framework\db\mysql;
use yiiunit\framework\db\AnyCaseValue;
/**
* @group db
* @group mysql
@ -9,4 +11,16 @@ namespace yiiunit\framework\db\mysql;
class SchemaTest extends \yiiunit\framework\db\SchemaTest
{
public $driverName = 'mysql';
public function constraintsProvider()
{
$result = parent::constraintsProvider();
$result['1: check'][2] = [];
$result['2: primary key'][2]->name = null;
// Work aroung bug in MySQL 5.1 - it creates only this table in lowercase. O_o
$result['3: foreign key'][2][0]->foreignTableName = new AnyCaseValue('T_constraints_2');
return $result;
}
}

39
tests/framework/db/oci/QueryBuilderTest.php

@ -2,6 +2,7 @@
namespace yiiunit\framework\db\oci;
use yii\db\oci\QueryBuilder;
use yii\db\oci\Schema;
use yiiunit\data\base\TraversableObject;
@ -35,6 +36,44 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
]);
}
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()
{

72
tests/framework/db/oci/SchemaTest.php

@ -2,8 +2,8 @@
namespace yiiunit\framework\db\oci;
use yii\db\Expression;
use yii\db\oci\Schema;
use yii\db\CheckConstraint;
use yiiunit\framework\db\AnyValue;
/**
* @group db
@ -93,4 +93,72 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
$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 = 'SYSTEM';
$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;
}
}

12
tests/framework/db/pgsql/QueryBuilderTest.php

@ -95,6 +95,18 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
$this->assertEquals($expected, $sql);
}
public function indexesProvider()
{
$result = parent::indexesProvider();
$result['drop'][0] = 'DROP INDEX [[CN_constraints_2_single]]';
return $result;
}
public function defaultValuesProvider()
{
$this->markTestSkipped('Adding/dropping default constraints is not supported in PostgreSQL.');
}
public function testCommentColumn()
{
$qb = $this->getQueryBuilder();

14
tests/framework/db/pgsql/SchemaTest.php

@ -3,7 +3,6 @@
namespace yiiunit\framework\db\pgsql;
use yii\db\Expression;
use yii\db\pgsql\Schema;
use yiiunit\data\ar\ActiveRecord;
use yiiunit\data\ar\Type;
@ -83,7 +82,6 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
public function testCompositeFk()
{
/* @var $schema Schema */
$schema = $this->getConnection()->schema;
$table = $schema->getTableSchema('composite_fk');
@ -109,7 +107,6 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
[$fp = fopen(__FILE__, 'rb'), \PDO::PARAM_LOB],
];
/* @var $schema Schema */
$schema = $this->getConnection()->schema;
foreach ($values as $value) {
@ -120,7 +117,6 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
public function testBooleanDefaultValues()
{
/* @var $schema Schema */
$schema = $this->getConnection()->schema;
$table = $schema->getTableSchema('bool_values');
@ -194,4 +190,14 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
$this->assertEquals('numeric', $column->dbType);
$this->assertEquals(0, $column->defaultValue);
}
public function constraintsProvider()
{
$result = parent::constraintsProvider();
$result['1: check'][2][0]->expression = '(("C_check")::text <> \'\'::text)';
$result['3: foreign key'][2][0]->foreignSchemaName = 'public';
$result['3: index'][2] = [];
return $result;
}
}

31
tests/framework/db/sqlite/QueryBuilderTest.php

@ -46,9 +46,36 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
]);
}
public function testAddDropPrimaryKey()
public function primaryKeysProvider()
{
$this->markTestSkipped('Comments are not supported in SQLite');
$this->markTestSkipped('Adding/dropping primary keys is not supported in SQLite.');
}
public function foreignKeysProvider()
{
$this->markTestSkipped('Adding/dropping foreign keys is not supported in SQLite.');
}
public function indexesProvider()
{
$result = parent::indexesProvider();
$result['drop'][0] = 'DROP INDEX [[CN_constraints_2_single]]';
return $result;
}
public function uniquesProvider()
{
$this->markTestSkipped('Adding/dropping unique constraints is not supported in SQLite.');
}
public function checksProvider()
{
$this->markTestSkipped('Adding/dropping check constraints is not supported in SQLite.');
}
public function defaultValuesProvider()
{
$this->markTestSkipped('Adding/dropping default constraints is not supported in SQLite.');
}
public function testCommentColumn()

24
tests/framework/db/sqlite/SchemaTest.php

@ -2,6 +2,8 @@
namespace yiiunit\framework\db\sqlite;
use yiiunit\framework\db\AnyValue;
/**
* @group db
* @group sqlite
@ -31,7 +33,6 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
public function testCompositeFk()
{
/* @var $schema Schema */
$schema = $this->getConnection()->schema;
$table = $schema->getTableSchema('composite_fk');
@ -42,4 +43,25 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
$this->assertEquals('order_id', $table->foreignKeys[0]['order_id']);
$this->assertEquals('item_id', $table->foreignKeys[0]['item_id']);
}
public function constraintsProvider()
{
$result = parent::constraintsProvider();
$result['1: primary key'][2]->name = null;
$result['1: check'][2][0]->columnNames = null;
$result['1: check'][2][0]->expression = '"C_check" <> \'\'';
$result['1: unique'][2][0]->name = AnyValue::getInstance();
$result['1: index'][2][1]->name = AnyValue::getInstance();
$result['2: primary key'][2]->name = null;
$result['2: unique'][2][0]->name = AnyValue::getInstance();
$result['2: index'][2][2]->name = AnyValue::getInstance();
$result['3: foreign key'][2][0]->name = null;
$result['3: index'][2] = [];
$result['4: primary key'][2]->name = null;
$result['4: unique'][2][0]->name = AnyValue::getInstance();
return $result;
}
}

1138
tests/framework/db/sqlite/SqlTokenizerTest.php

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save