This commit is contained in:
2020-10-06 14:27:47 +07:00
commit 586be80cf6
16613 changed files with 3274099 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\sqlite;
use yii\db\ColumnSchemaBuilder as AbstractColumnSchemaBuilder;
/**
* ColumnSchemaBuilder is the schema builder for Sqlite databases.
*
* @author Chris Harris <chris@buckshotsoftware.com>
* @since 2.0.8
*/
class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder
{
/**
* {@inheritdoc}
*/
protected function buildUnsignedString()
{
return $this->isUnsigned ? ' UNSIGNED' : '';
}
/**
* {@inheritdoc}
*/
public function __toString()
{
switch ($this->getTypeCategory()) {
case self::CATEGORY_PK:
$format = '{type}{check}{append}';
break;
case self::CATEGORY_NUMERIC:
$format = '{type}{length}{unsigned}{notnull}{unique}{check}{default}{append}';
break;
default:
$format = '{type}{length}{notnull}{unique}{check}{default}{append}';
}
return $this->buildCompleteString($format);
}
}

View File

@@ -0,0 +1,116 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\sqlite;
use yii\db\SqlToken;
use yii\helpers\StringHelper;
/**
* Command represents an SQLite's SQL statement to be executed against a database.
*
* {@inheritdoc}
*
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.14
*/
class Command extends \yii\db\Command
{
/**
* {@inheritdoc}
*/
public function execute()
{
$sql = $this->getSql();
$params = $this->params;
$statements = $this->splitStatements($sql, $params);
if ($statements === false) {
return parent::execute();
}
$result = null;
foreach ($statements as $statement) {
list($statementSql, $statementParams) = $statement;
$this->setSql($statementSql)->bindValues($statementParams);
$result = parent::execute();
}
$this->setSql($sql)->bindValues($params);
return $result;
}
/**
* {@inheritdoc}
*/
protected function queryInternal($method, $fetchMode = null)
{
$sql = $this->getSql();
$params = $this->params;
$statements = $this->splitStatements($sql, $params);
if ($statements === false) {
return parent::queryInternal($method, $fetchMode);
}
list($lastStatementSql, $lastStatementParams) = array_pop($statements);
foreach ($statements as $statement) {
list($statementSql, $statementParams) = $statement;
$this->setSql($statementSql)->bindValues($statementParams);
parent::execute();
}
$this->setSql($lastStatementSql)->bindValues($lastStatementParams);
$result = parent::queryInternal($method, $fetchMode);
$this->setSql($sql)->bindValues($params);
return $result;
}
/**
* Splits the specified SQL code into individual SQL statements and returns them
* or `false` if there's a single statement.
* @param string $sql
* @param array $params
* @return string[]|false
*/
private function splitStatements($sql, $params)
{
$semicolonIndex = strpos($sql, ';');
if ($semicolonIndex === false || $semicolonIndex === StringHelper::byteLength($sql) - 1) {
return false;
}
$tokenizer = new SqlTokenizer($sql);
$codeToken = $tokenizer->tokenize();
if (count($codeToken->getChildren()) === 1) {
return false;
}
$statements = [];
foreach ($codeToken->getChildren() as $statement) {
$statements[] = [$statement->getSql(), $this->extractUsedParams($statement, $params)];
}
return $statements;
}
/**
* Returns named bindings used in the specified statement token.
* @param SqlToken $statement
* @param array $params
* @return array
*/
private function extractUsedParams(SqlToken $statement, $params)
{
preg_match_all('/(?P<placeholder>[:][a-zA-Z0-9_]+)/', $statement->getSql(), $matches, PREG_SET_ORDER);
$result = [];
foreach ($matches as $match) {
$phName = ltrim($match['placeholder'], ':');
if (isset($params[$phName])) {
$result[$phName] = $params[$phName];
} elseif (isset($params[':' . $phName])) {
$result[':' . $phName] = $params[':' . $phName];
}
}
return $result;
}
}

View File

@@ -0,0 +1,544 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\sqlite;
use yii\base\InvalidArgumentException;
use yii\base\NotSupportedException;
use yii\db\Connection;
use yii\db\Constraint;
use yii\db\Expression;
use yii\db\ExpressionInterface;
use yii\db\Query;
use yii\helpers\StringHelper;
/**
* QueryBuilder is the query builder for SQLite databases.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class QueryBuilder extends \yii\db\QueryBuilder
{
/**
* @var array mapping from abstract column types (keys) to physical column types (values).
*/
public $typeMap = [
Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
Schema::TYPE_UPK => 'integer UNSIGNED PRIMARY KEY AUTOINCREMENT NOT NULL',
Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
Schema::TYPE_UBIGPK => 'integer UNSIGNED PRIMARY KEY AUTOINCREMENT NOT NULL',
Schema::TYPE_CHAR => 'char(1)',
Schema::TYPE_STRING => 'varchar(255)',
Schema::TYPE_TEXT => 'text',
Schema::TYPE_TINYINT => 'tinyint',
Schema::TYPE_SMALLINT => 'smallint',
Schema::TYPE_INTEGER => 'integer',
Schema::TYPE_BIGINT => 'bigint',
Schema::TYPE_FLOAT => 'float',
Schema::TYPE_DOUBLE => 'double',
Schema::TYPE_DECIMAL => 'decimal(10,0)',
Schema::TYPE_DATETIME => 'datetime',
Schema::TYPE_TIMESTAMP => 'timestamp',
Schema::TYPE_TIME => 'time',
Schema::TYPE_DATE => 'date',
Schema::TYPE_BINARY => 'blob',
Schema::TYPE_BOOLEAN => 'boolean',
Schema::TYPE_MONEY => 'decimal(19,4)',
];
/**
* {@inheritdoc}
*/
protected function defaultExpressionBuilders()
{
return array_merge(parent::defaultExpressionBuilders(), [
'yii\db\conditions\LikeCondition' => 'yii\db\sqlite\conditions\LikeConditionBuilder',
'yii\db\conditions\InCondition' => 'yii\db\sqlite\conditions\InConditionBuilder',
]);
}
/**
* {@inheritdoc}
* @see https://stackoverflow.com/questions/15277373/sqlite-upsert-update-or-insert/15277374#15277374
*/
public function upsert($table, $insertColumns, $updateColumns, &$params)
{
/** @var Constraint[] $constraints */
list($uniqueNames, $insertNames, $updateNames) = $this->prepareUpsertColumns($table, $insertColumns, $updateColumns, $constraints);
if (empty($uniqueNames)) {
return $this->insert($table, $insertColumns, $params);
}
list(, $placeholders, $values, $params) = $this->prepareInsertValues($table, $insertColumns, $params);
$insertSql = 'INSERT OR IGNORE INTO ' . $this->db->quoteTableName($table)
. (!empty($insertNames) ? ' (' . implode(', ', $insertNames) . ')' : '')
. (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : $values);
if ($updateColumns === false) {
return $insertSql;
}
$updateCondition = ['or'];
$quotedTableName = $this->db->quoteTableName($table);
foreach ($constraints as $constraint) {
$constraintCondition = ['and'];
foreach ($constraint->columnNames as $name) {
$quotedName = $this->db->quoteColumnName($name);
$constraintCondition[] = "$quotedTableName.$quotedName=(SELECT $quotedName FROM `EXCLUDED`)";
}
$updateCondition[] = $constraintCondition;
}
if ($updateColumns === true) {
$updateColumns = [];
foreach ($updateNames as $name) {
$quotedName = $this->db->quoteColumnName($name);
if (strrpos($quotedName, '.') === false) {
$quotedName = "(SELECT $quotedName FROM `EXCLUDED`)";
}
$updateColumns[$name] = new Expression($quotedName);
}
}
$updateSql = 'WITH "EXCLUDED" (' . implode(', ', $insertNames)
. ') AS (' . (!empty($placeholders) ? 'VALUES (' . implode(', ', $placeholders) . ')' : ltrim($values, ' ')) . ') '
. $this->update($table, $updateColumns, $updateCondition, $params);
return "$updateSql; $insertSql;";
}
/**
* Generates a batch INSERT SQL statement.
*
* For example,
*
* ```php
* $connection->createCommand()->batchInsert('user', ['name', 'age'], [
* ['Tom', 30],
* ['Jane', 20],
* ['Linda', 25],
* ])->execute();
* ```
*
* Note that the values in each row must match the corresponding column names.
*
* @param string $table the table that new rows will be inserted into.
* @param array $columns the column names
* @param array|\Generator $rows the rows to be batch inserted into the table
* @return string the batch INSERT SQL statement
*/
public function batchInsert($table, $columns, $rows, &$params = [])
{
if (empty($rows)) {
return '';
}
// SQLite supports batch insert natively since 3.7.11
// http://www.sqlite.org/releaselog/3_7_11.html
$this->db->open(); // ensure pdo is not null
if (version_compare($this->db->getServerVersion(), '3.7.11', '>=')) {
return parent::batchInsert($table, $columns, $rows, $params);
}
$schema = $this->db->getSchema();
if (($tableSchema = $schema->getTableSchema($table)) !== null) {
$columnSchemas = $tableSchema->columns;
} else {
$columnSchemas = [];
}
$values = [];
foreach ($rows as $row) {
$vs = [];
foreach ($row as $i => $value) {
if (isset($columnSchemas[$columns[$i]])) {
$value = $columnSchemas[$columns[$i]]->dbTypecast($value);
}
if (is_string($value)) {
$value = $schema->quoteValue($value);
} elseif (is_float($value)) {
// ensure type cast always has . as decimal separator in all locales
$value = StringHelper::floatToString($value);
} elseif ($value === false) {
$value = 0;
} elseif ($value === null) {
$value = 'NULL';
} elseif ($value instanceof ExpressionInterface) {
$value = $this->buildExpression($value, $params);
}
$vs[] = $value;
}
$values[] = implode(', ', $vs);
}
if (empty($values)) {
return '';
}
foreach ($columns as $i => $name) {
$columns[$i] = $schema->quoteColumnName($name);
}
return 'INSERT INTO ' . $schema->quoteTableName($table)
. ' (' . implode(', ', $columns) . ') SELECT ' . implode(' UNION SELECT ', $values);
}
/**
* Creates a SQL statement for resetting the sequence value of a table's primary key.
* The sequence will be reset such that the primary key of the next new row inserted
* will have the specified value or 1.
* @param string $tableName the name of the table whose primary key sequence will be reset
* @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
* the next new row's primary key will have a value 1.
* @return string the SQL statement for resetting sequence
* @throws InvalidArgumentException if the table does not exist or there is no sequence associated with the table.
*/
public function resetSequence($tableName, $value = null)
{
$db = $this->db;
$table = $db->getTableSchema($tableName);
if ($table !== null && $table->sequenceName !== null) {
$tableName = $db->quoteTableName($tableName);
if ($value === null) {
$key = $this->db->quoteColumnName(reset($table->primaryKey));
$value = $this->db->useMaster(function (Connection $db) use ($key, $tableName) {
return $db->createCommand("SELECT MAX($key) FROM $tableName")->queryScalar();
});
} else {
$value = (int) $value - 1;
}
return "UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'";
} elseif ($table === null) {
throw new InvalidArgumentException("Table not found: $tableName");
}
throw new InvalidArgumentException("There is not sequence associated with table '$tableName'.'");
}
/**
* Enables or disables integrity check.
* @param bool $check whether to turn on or off the integrity check.
* @param string $schema the schema of the tables. Meaningless for SQLite.
* @param string $table the table name. Meaningless for SQLite.
* @return string the SQL statement for checking integrity
* @throws NotSupportedException this is not supported by SQLite
*/
public function checkIntegrity($check = true, $schema = '', $table = '')
{
return 'PRAGMA foreign_keys=' . (int) $check;
}
/**
* Builds a SQL statement for truncating a DB table.
* @param string $table the table to be truncated. The name will be properly quoted by the method.
* @return string the SQL statement for truncating a DB table.
*/
public function truncateTable($table)
{
return 'DELETE FROM ' . $this->db->quoteTableName($table);
}
/**
* Builds a SQL statement for dropping an index.
* @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
* @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
* @return string the SQL statement for dropping an index.
*/
public function dropIndex($name, $table)
{
return 'DROP INDEX ' . $this->db->quoteTableName($name);
}
/**
* Builds a SQL statement for dropping a DB column.
* @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
* @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
* @return string the SQL statement for dropping a DB column.
* @throws NotSupportedException this is not supported by SQLite
*/
public function dropColumn($table, $column)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* Builds a SQL statement for renaming a column.
* @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
* @param string $oldName the old name of the column. The name will be properly quoted by the method.
* @param string $newName the new name of the column. The name will be properly quoted by the method.
* @return string the SQL statement for renaming a DB column.
* @throws NotSupportedException this is not supported by SQLite
*/
public function renameColumn($table, $oldName, $newName)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* Builds a SQL statement for adding a foreign key constraint to an existing table.
* The method will properly quote the table and column names.
* @param string $name the name of the foreign key constraint.
* @param string $table the table that the foreign key constraint will be added to.
* @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 or use an array to represent them.
* @param string $refTable the table that the foreign key references to.
* @param string|array $refColumns the name of the column that the foreign key references to.
* If there are multiple columns, separate them with commas or use an array to represent them.
* @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
* @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
* @return string the SQL statement for adding a foreign key constraint to an existing table.
* @throws NotSupportedException this is not supported by SQLite
*/
public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* Builds a SQL statement for dropping a foreign key constraint.
* @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
* @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
* @return string the SQL statement for dropping a foreign key constraint.
* @throws NotSupportedException this is not supported by SQLite
*/
public function dropForeignKey($name, $table)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* Builds a SQL statement for renaming a DB table.
*
* @param string $table the table to be renamed. The name will be properly quoted by the method.
* @param string $newName the new table name. The name will be properly quoted by the method.
* @return string the SQL statement for renaming a DB table.
*/
public function renameTable($table, $newName)
{
return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' RENAME TO ' . $this->db->quoteTableName($newName);
}
/**
* Builds a SQL statement for changing the definition of a column.
* @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
* @param string $column the name of the column to be changed. The name will be properly quoted by the method.
* @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
* column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
* in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
* will become 'varchar(255) not null'.
* @return string the SQL statement for changing the definition of a column.
* @throws NotSupportedException this is not supported by SQLite
*/
public function alterColumn($table, $column, $type)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* Builds a SQL statement for adding a primary key constraint to an existing table.
* @param string $name the name of the primary key constraint.
* @param string $table the table that the primary key constraint will be added to.
* @param string|array $columns comma separated string or array of columns that the primary key will consist of.
* @return string the SQL statement for adding a primary key constraint to an existing table.
* @throws NotSupportedException this is not supported by SQLite
*/
public function addPrimaryKey($name, $table, $columns)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* Builds a SQL statement for removing a primary key constraint to an existing table.
* @param string $name the name of the primary key constraint to be removed.
* @param string $table the table that the primary key constraint will be removed from.
* @return string the SQL statement for removing a primary key constraint from an existing table.
* @throws NotSupportedException this is not supported by SQLite
*/
public function dropPrimaryKey($name, $table)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* {@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, $expression)
{
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, $value)
{
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
*/
public function addCommentOnColumn($table, $column, $comment)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* {@inheritdoc}
* @throws NotSupportedException
* @since 2.0.8
*/
public function addCommentOnTable($table, $comment)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* {@inheritdoc}
* @throws NotSupportedException
* @since 2.0.8
*/
public function dropCommentFromColumn($table, $column)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* {@inheritdoc}
* @throws NotSupportedException
* @since 2.0.8
*/
public function dropCommentFromTable($table)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
/**
* {@inheritdoc}
*/
public function buildLimit($limit, $offset)
{
$sql = '';
if ($this->hasLimit($limit)) {
$sql = 'LIMIT ' . $limit;
if ($this->hasOffset($offset)) {
$sql .= ' OFFSET ' . $offset;
}
} elseif ($this->hasOffset($offset)) {
// limit is not optional in SQLite
// http://www.sqlite.org/syntaxdiagrams.html#select-stmt
$sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1
}
return $sql;
}
/**
* {@inheritdoc}
*/
public function build($query, $params = [])
{
$query = $query->prepare($this);
$params = empty($params) ? $query->params : array_merge($params, $query->params);
$clauses = [
$this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
$this->buildFrom($query->from, $params),
$this->buildJoin($query->join, $params),
$this->buildWhere($query->where, $params),
$this->buildGroupBy($query->groupBy),
$this->buildHaving($query->having, $params),
];
$sql = implode($this->separator, array_filter($clauses));
$sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset);
if (!empty($query->orderBy)) {
foreach ($query->orderBy as $expression) {
if ($expression instanceof ExpressionInterface) {
$this->buildExpression($expression, $params);
}
}
}
if (!empty($query->groupBy)) {
foreach ($query->groupBy as $expression) {
if ($expression instanceof ExpressionInterface) {
$this->buildExpression($expression, $params);
}
}
}
$union = $this->buildUnion($query->union, $params);
if ($union !== '') {
$sql = "$sql{$this->separator}$union";
}
return [$sql, $params];
}
/**
* {@inheritdoc}
*/
public function buildUnion($unions, &$params)
{
if (empty($unions)) {
return '';
}
$result = '';
foreach ($unions as $i => $union) {
$query = $union['query'];
if ($query instanceof Query) {
list($unions[$i]['query'], $params) = $this->build($query, $params);
}
$result .= ' UNION ' . ($union['all'] ? 'ALL ' : '') . ' ' . $unions[$i]['query'];
}
return trim($result);
}
}

456
vendor/yiisoft/yii2/db/sqlite/Schema.php vendored Normal file
View File

@@ -0,0 +1,456 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\sqlite;
use yii\base\NotSupportedException;
use yii\db\CheckConstraint;
use yii\db\ColumnSchema;
use yii\db\Constraint;
use yii\db\ConstraintFinderInterface;
use yii\db\ConstraintFinderTrait;
use yii\db\Expression;
use yii\db\ForeignKeyConstraint;
use yii\db\IndexConstraint;
use yii\db\SqlToken;
use yii\db\TableSchema;
use yii\db\Transaction;
use yii\helpers\ArrayHelper;
/**
* Schema is the class for retrieving metadata from a SQLite (2/3) database.
*
* @property string $transactionIsolationLevel The transaction isolation level to use for this transaction.
* This can be either [[Transaction::READ_UNCOMMITTED]] or [[Transaction::SERIALIZABLE]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Schema extends \yii\db\Schema implements ConstraintFinderInterface
{
use ConstraintFinderTrait;
/**
* @var array mapping from physical column types (keys) to abstract column types (values)
*/
public $typeMap = [
'tinyint' => self::TYPE_TINYINT,
'bit' => self::TYPE_SMALLINT,
'boolean' => self::TYPE_BOOLEAN,
'bool' => self::TYPE_BOOLEAN,
'smallint' => self::TYPE_SMALLINT,
'mediumint' => self::TYPE_INTEGER,
'int' => self::TYPE_INTEGER,
'integer' => self::TYPE_INTEGER,
'bigint' => self::TYPE_BIGINT,
'float' => self::TYPE_FLOAT,
'double' => self::TYPE_DOUBLE,
'real' => self::TYPE_FLOAT,
'decimal' => self::TYPE_DECIMAL,
'numeric' => self::TYPE_DECIMAL,
'tinytext' => self::TYPE_TEXT,
'mediumtext' => self::TYPE_TEXT,
'longtext' => self::TYPE_TEXT,
'text' => self::TYPE_TEXT,
'varchar' => self::TYPE_STRING,
'string' => self::TYPE_STRING,
'char' => self::TYPE_CHAR,
'blob' => self::TYPE_BINARY,
'datetime' => self::TYPE_DATETIME,
'year' => self::TYPE_DATE,
'date' => self::TYPE_DATE,
'time' => self::TYPE_TIME,
'timestamp' => self::TYPE_TIMESTAMP,
'enum' => self::TYPE_STRING,
];
/**
* {@inheritdoc}
*/
protected $tableQuoteCharacter = '`';
/**
* {@inheritdoc}
*/
protected $columnQuoteCharacter = '`';
/**
* {@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;
}
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();
/** @var $code SqlToken[]|SqlToken[][]|SqlToken[][][] */
$code = (new SqlTokenizer($sql))->tokenize();
$pattern = (new SqlTokenizer('any CREATE any TABLE any()'))->tokenize();
if (!$code[0]->matches($pattern, 0, $firstMatchIndex, $lastMatchIndex)) {
return [];
}
$createTableToken = $code[0][$lastMatchIndex - 1];
$result = [];
$offset = 0;
while (true) {
$pattern = (new SqlTokenizer('any CHECK()'))->tokenize();
if (!$createTableToken->matches($pattern, $offset, $firstMatchIndex, $offset)) {
break;
}
$checkSql = $createTableToken[$offset - 1]->getSql();
$name = null;
$pattern = (new SqlTokenizer('CONSTRAINT any'))->tokenize();
if (isset($createTableToken[$firstMatchIndex - 2]) && $createTableToken->matches($pattern, $firstMatchIndex - 2)) {
$name = $createTableToken[$firstMatchIndex - 1]->content;
}
$result[] = new CheckConstraint([
'name' => $name,
'expression' => $checkSql,
]);
}
return $result;
}
/**
* {@inheritdoc}
* @throws NotSupportedException if this method is called.
*/
protected function loadTableDefaultValues($tableName)
{
throw new NotSupportedException('SQLite does not support default value constraints.');
}
/**
* Creates a query builder for the MySQL database.
* This method may be overridden by child classes to create a DBMS-specific query builder.
* @return QueryBuilder query builder instance
*/
public function createQueryBuilder()
{
return new QueryBuilder($this->db);
}
/**
* {@inheritdoc}
* @return ColumnSchemaBuilder column schema builder instance
*/
public function createColumnSchemaBuilder($type, $length = null)
{
return new ColumnSchemaBuilder($type, $length);
}
/**
* Collects the table column metadata.
* @param TableSchema $table the table metadata
* @return bool whether the table exists in the database
*/
protected function findColumns($table)
{
$sql = 'PRAGMA table_info(' . $this->quoteSimpleTableName($table->name) . ')';
$columns = $this->db->createCommand($sql)->queryAll();
if (empty($columns)) {
return false;
}
foreach ($columns as $info) {
$column = $this->loadColumnSchema($info);
$table->columns[$column->name] = $column;
if ($column->isPrimaryKey) {
$table->primaryKey[] = $column->name;
}
}
if (count($table->primaryKey) === 1 && !strncasecmp($table->columns[$table->primaryKey[0]]->dbType, 'int', 3)) {
$table->sequenceName = '';
$table->columns[$table->primaryKey[0]]->autoIncrement = true;
}
return true;
}
/**
* Collects the foreign key column details for the given table.
* @param TableSchema $table the table metadata
*/
protected function findConstraints($table)
{
$sql = 'PRAGMA foreign_key_list(' . $this->quoteSimpleTableName($table->name) . ')';
$keys = $this->db->createCommand($sql)->queryAll();
foreach ($keys as $key) {
$id = (int) $key['id'];
if (!isset($table->foreignKeys[$id])) {
$table->foreignKeys[$id] = [$key['table'], $key['from'] => $key['to']];
} else {
// composite FK
$table->foreignKeys[$id][$key['from']] = $key['to'];
}
}
}
/**
* Returns all unique indexes for the given table.
*
* Each array element is of the following structure:
*
* ```php
* [
* 'IndexName1' => ['col1' [, ...]],
* 'IndexName2' => ['col2' [, ...]],
* ]
* ```
*
* @param TableSchema $table the table metadata
* @return array all unique indexes for the given table.
*/
public function findUniqueIndexes($table)
{
$sql = 'PRAGMA index_list(' . $this->quoteSimpleTableName($table->name) . ')';
$indexes = $this->db->createCommand($sql)->queryAll();
$uniqueIndexes = [];
foreach ($indexes as $index) {
$indexName = $index['name'];
$indexInfo = $this->db->createCommand('PRAGMA index_info(' . $this->quoteValue($index['name']) . ')')->queryAll();
if ($index['unique']) {
$uniqueIndexes[$indexName] = [];
foreach ($indexInfo as $row) {
$uniqueIndexes[$indexName][] = $row['name'];
}
}
}
return $uniqueIndexes;
}
/**
* Loads the column information into a [[ColumnSchema]] object.
* @param array $info column information
* @return ColumnSchema the column schema object
*/
protected function loadColumnSchema($info)
{
$column = $this->createColumnSchema();
$column->name = $info['name'];
$column->allowNull = !$info['notnull'];
$column->isPrimaryKey = $info['pk'] != 0;
$column->dbType = strtolower($info['type']);
$column->unsigned = strpos($column->dbType, 'unsigned') !== false;
$column->type = self::TYPE_STRING;
if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
$type = strtolower($matches[1]);
if (isset($this->typeMap[$type])) {
$column->type = $this->typeMap[$type];
}
if (!empty($matches[2])) {
$values = explode(',', $matches[2]);
$column->size = $column->precision = (int) $values[0];
if (isset($values[1])) {
$column->scale = (int) $values[1];
}
if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
$column->type = 'boolean';
} elseif ($type === 'bit') {
if ($column->size > 32) {
$column->type = 'bigint';
} elseif ($column->size === 32) {
$column->type = 'integer';
}
}
}
}
$column->phpType = $this->getColumnPhpType($column);
if (!$column->isPrimaryKey) {
if ($info['dflt_value'] === 'null' || $info['dflt_value'] === '' || $info['dflt_value'] === null) {
$column->defaultValue = null;
} elseif ($column->type === 'timestamp' && $info['dflt_value'] === 'CURRENT_TIMESTAMP') {
$column->defaultValue = new Expression('CURRENT_TIMESTAMP');
} else {
$value = trim($info['dflt_value'], "'\"");
$column->defaultValue = $column->phpTypecast($value);
}
}
return $column;
}
/**
* Sets the isolation level of the current transaction.
* @param string $level The transaction isolation level to use for this transaction.
* This can be either [[Transaction::READ_UNCOMMITTED]] or [[Transaction::SERIALIZABLE]].
* @throws NotSupportedException when unsupported isolation levels are used.
* SQLite only supports SERIALIZABLE and READ UNCOMMITTED.
* @see http://www.sqlite.org/pragma.html#pragma_read_uncommitted
*/
public function setTransactionIsolationLevel($level)
{
switch ($level) {
case Transaction::SERIALIZABLE:
$this->db->createCommand('PRAGMA read_uncommitted = False;')->execute();
break;
case Transaction::READ_UNCOMMITTED:
$this->db->createCommand('PRAGMA read_uncommitted = True;')->execute();
break;
default:
throw new NotSupportedException(get_class($this) . ' only supports transaction isolation levels READ UNCOMMITTED and SERIALIZABLE.');
}
}
/**
* Loads multiple types of constraints and returns the specified ones.
* @param string $tableName table name.
* @param string $returnType return type:
* - primaryKey
* - indexes
* - uniques
* @return mixed constraints.
*/
private function loadTableConstraints($tableName, $returnType)
{
$indexes = $this->db->createCommand('PRAGMA INDEX_LIST (' . $this->quoteValue($tableName) . ')')->queryAll();
$indexes = $this->normalizePdoRowKeyCase($indexes, true);
$tableColumns = null;
if (!empty($indexes) && !isset($indexes[0]['origin'])) {
/*
* SQLite may not have an "origin" column in INDEX_LIST
* See https://www.sqlite.org/src/info/2743846cdba572f6
*/
$tableColumns = $this->db->createCommand('PRAGMA TABLE_INFO (' . $this->quoteValue($tableName) . ')')->queryAll();
$tableColumns = $this->normalizePdoRowKeyCase($tableColumns, true);
$tableColumns = ArrayHelper::index($tableColumns, 'cid');
}
$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);
if ($tableColumns !== null) {
// SQLite may not have an "origin" column in INDEX_LIST
$index['origin'] = 'c';
if (!empty($columns) && $tableColumns[$columns[0]['cid']]['pk'] > 0) {
$index['origin'] = 'pk';
} elseif ($index['unique'] && $this->isSystemIdentifier($index['name'])) {
$index['origin'] = 'u';
}
}
$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];
}
/**
* Return whether the specified identifier is a SQLite system identifier.
* @param string $identifier
* @return bool
* @see https://www.sqlite.org/src/artifact/74108007d286232f
*/
private function isSystemIdentifier($identifier)
{
return strncmp($identifier, 'sqlite_', 7) === 0;
}
}

View File

@@ -0,0 +1,290 @@
<?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;
}
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\sqlite\conditions;
use yii\base\NotSupportedException;
/**
* {@inheritdoc}
*
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
* @since 2.0.14
*/
class InConditionBuilder extends \yii\db\conditions\InConditionBuilder
{
/**
* {@inheritdoc}
* @throws NotSupportedException if `$columns` is an array
*/
protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
{
if (is_array($columns)) {
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
}
return parent::buildSubqueryInCondition($operator, $columns, $values, $params);
}
/**
* {@inheritdoc}
*/
protected function buildCompositeInCondition($operator, $columns, $values, &$params)
{
$quotedColumns = [];
foreach ($columns as $i => $column) {
$quotedColumns[$i] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column;
}
$vss = [];
foreach ($values as $value) {
$vs = [];
foreach ($columns as $i => $column) {
if (isset($value[$column])) {
$phName = $this->queryBuilder->bindParam($value[$column], $params);
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName;
} else {
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL';
}
}
$vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')';
}
return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')';
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\sqlite\conditions;
/**
* {@inheritdoc}
*/
class LikeConditionBuilder extends \yii\db\conditions\LikeConditionBuilder
{
/**
* {@inheritdoc}
*/
protected $escapeCharacter = '\\';
}