diff --git a/.github/workflows/bc.yml b/.github/workflows/bc.yml
new file mode 100644
index 000000000..5970206c9
--- /dev/null
+++ b/.github/workflows/bc.yml
@@ -0,0 +1,23 @@
+on:
+ pull_request:
+ paths:
+ - 'src/**'
+ - '.github/workflows/bc.yml'
+ - 'composer.json'
+ push:
+ branches: ['master']
+ paths:
+ - 'src/**'
+ - '.github/workflows/bc.yml'
+ - 'composer.json'
+
+name: backwards compatibility
+
+jobs:
+ roave_bc_check:
+ uses: yiisoft/actions/.github/workflows/bc.yml@master
+ with:
+ os: >-
+ ['ubuntu-latest']
+ php: >-
+ ['8.1']
diff --git a/.github/workflows/bc.yml_ b/.github/workflows/bc.yml_
deleted file mode 100644
index 35b3a8624..000000000
--- a/.github/workflows/bc.yml_
+++ /dev/null
@@ -1,15 +0,0 @@
-on:
- - pull_request
- - push
-
-name: backwards compatibility
-jobs:
- roave_bc_check:
- name: Roave BC Check
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@master
- - name: fetch tags
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- - name: Roave BC Check
- uses: docker://nyholm/roave-bc-check-ga
diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml
index cc226299f..c9d07e456 100644
--- a/.github/workflows/mutation.yml
+++ b/.github/workflows/mutation.yml
@@ -35,7 +35,6 @@ jobs:
- ubuntu-latest
php:
- - 8.0
- 8.1
steps:
@@ -71,6 +70,6 @@ jobs:
- name: Run infection.
run: |
- vendor/bin/roave-infection-static-analysis-plugin -j2 --ignore-msi-with-no-mutations --only-covered
+ vendor/bin/roave-infection-static-analysis-plugin --threads=2 --ignore-msi-with-no-mutations --only-covered
env:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml
index 0b1707659..b77787984 100644
--- a/.github/workflows/static.yml
+++ b/.github/workflows/static.yml
@@ -39,6 +39,7 @@ jobs:
- '8.0'
- '8.1'
- '8.2'
+ - '8.3'
steps:
- name: Checkout.
@@ -70,4 +71,9 @@ jobs:
run: composer update --no-interaction --no-progress --optimize-autoloader --ansi
- name: Static analysis.
+ if: ${{ matrix.php != '8.0' }}
run: vendor/bin/psalm --config=${{ inputs.psalm-config }} --shepherd --stats --output-format=github --php-version=${{ matrix.php }}
+
+ - name: Static analysis.
+ if: ${{ matrix.php == '8.0' }}
+ run: vendor/bin/psalm --config=psalm4.xml --shepherd --stats --output-format=github --php-version=${{ matrix.php }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 076a9d45f..30f7185b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,25 @@
# SQLite driver for Yii Database Change Log
-## 1.0.2 under development
+## 2.0.0 under development
+
+- Enh #289: Implement `SqlParser` and `ExpressionBuilder` driver classes (@Tigrov)
+
+## 1.2.0 March 21, 2024
+
+- Enh #281: Remove unused code in `Command` class (@vjik)
+- Enh #282: Change property `Schema::$typeMap` to constant `Schema::TYPE_MAP` (@Tigrov)
+- Enh #283: Remove unnecessary check for array type in `Schema::loadTableIndexes()` (@Tigrov)
+- Enh #287: Resolve deprecated methods (@Tigrov)
+- Enh #288: Minor refactoring of `DDLQueryBuilder` and `Schema` (@Tigrov)
+
+## 1.1.0 November 12, 2023
- Enh #263: Support json type (@Tigrov)
+- Enh #278: Move methods from `Command` to `AbstractPdoCommand` class (@Tigrov)
- Bug #268: Fix foreign keys: support multiple foreign keys referencing to one table and possible null columns for reference (@Tigrov)
- Enh #273: Implement `ColumnSchemaInterface` classes according to the data type of database table columns
for type casting performance. Related with yiisoft/db#752 (@Tigrov)
+- Bug #271: Refactor `DMLQueryBuilder`, related with yiisoft/db#746 (@Tigrov)
## 1.0.1 July 24, 2023
diff --git a/LICENSE.md b/LICENSE.md
index bc5674fe4..6a920d605 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,17 +1,17 @@
-Copyright © 2008 by Yii Software (https://www.yiiframework.com/)
+Copyright © 2008 by Yii Software ()
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- * Redistributions of source code must retain the above copyright
+* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
+* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
- * Neither the name of Yii Software nor the names of its
+* Neither the name of Yii Software nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
diff --git a/README.md b/README.md
index 8b66401ed..b1342beac 100644
--- a/README.md
+++ b/README.md
@@ -36,19 +36,30 @@ perform advanced database operations such as joins and aggregates.
The package could be installed via composer:
-```php
+```shell
composer require yiisoft/db-sqlite
```
-## Usage
+## Documentation
-For config connection to SQLite database check [Connecting SQLite](https://github.com/yiisoft/db/blob/master/docs/en/connection/sqlite.md).
+English:
-[Check the documentation docs](https://github.com/yiisoft/db/blob/master/docs/en/README.md) to learn about usage.
+- For config connection to SQLite database check [Connecting SQLite](https://github.com/yiisoft/db/blob/master/docs/en/connection/sqlite.md)
+- [Check the yiisoft/db docs](https://github.com/yiisoft/db/blob/master/docs/en/README.md) to learn about usage.
-## Testing
+Português - Brasil:
-[Check the documentation](/docs/en/testing.md) to learn about testing.
+- Para configurar a conexão com o SQLite leia [Connecting SQLite](https://github.com/yiisoft/db/blob/master/docs/pt-BR/connection/sqlite.md).
+- [Confira a documentação](https://github.com/yiisoft/db/blob/master/docs/pt-BR/README.md) para aprender como usar.
+
+Testing:
+
+- [Internals](docs/internals.md)
+
+## Support
+
+If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for that.
+You may also check out other [Yii Community Resources](https://www.yiiframework.com/community).
## Support the project
diff --git a/composer.json b/composer.json
index faaac7637..946228614 100644
--- a/composer.json
+++ b/composer.json
@@ -18,25 +18,25 @@
"forum": "https://www.yiiframework.com/forum/",
"wiki": "https://www.yiiframework.com/wiki/",
"chat": "https://t.me/yii3en",
- "irc": "irc://irc.freenode.net/yii"
+ "irc": "ircs://irc.libera.chat:6697/yii"
},
"require": {
"php": "^8.0",
"ext-mbstring": "*",
"ext-pdo": "*",
- "yiisoft/db": "^1.0",
+ "yiisoft/db": "^1.2",
"yiisoft/json": "^1.0"
},
"require-dev": {
"ext-json": "*",
"maglnet/composer-require-checker": "^4.2",
"phpunit/phpunit": "^9.6|^10.0",
- "rector/rector": "^0.18",
+ "rector/rector": "^1.0",
"roave/infection-static-analysis-plugin": "^1.16",
"spatie/phpunit-watcher": "^1.23",
- "vimeo/psalm": "^4.3|^5.6",
+ "vimeo/psalm": "^4.30|^5.20",
"yiisoft/aliases": "^2.0",
- "yiisoft/cache-file": "^2.0",
+ "yiisoft/cache-file": "^3.1",
"yiisoft/json": "^1.0",
"yiisoft/var-dumper": "^1.5"
},
diff --git a/docs/en/testing.md b/docs/internals.md
similarity index 51%
rename from docs/en/testing.md
rename to docs/internals.md
index 49d60d0e3..4966a62be 100644
--- a/docs/en/testing.md
+++ b/docs/internals.md
@@ -1,4 +1,4 @@
-# Testing
+# Internals
## Github actions
@@ -6,15 +6,42 @@ All our packages have github actions by default, so you can test your [contribut
> Note: We recommend pull requesting in draft mode until all tests pass.
+## Docker image
+
+For greater ease it is recommended to use docker containers, for this you can use the [docker-compose.yml](https://docs.docker.com/compose/compose-file/) file that is in the docs folder.
+
+1. [MySQL 8](../../../docker-compose.yml)
+2. [MariaDB 10.11](../../../docker-compose-mariadb.yml)
+
+For running the docker containers you can use the following command:
+
+MySQL 8.0.
+
+```dockerfile
+docker compose up -d
+```
+
+MariaDB 10.11.
+
+```dockerfile
+docker compose -f docker-compose-mariadb.yml up -d
+```
+
## Unit testing
The package is tested with [PHPUnit](https://phpunit.de/).
+The following steps are required to run the tests:
+
+1. Run the docker container for the dbms.
+2. Install the dependencies of the project with composer.
+3. Run the tests.
+
```shell
vendor/bin/phpunit
```
-### Mutation testing
+## Mutation testing
The package tests are checked with [Infection](https://infection.github.io/) mutation framework with
[Infection Static Analysis Plugin](https://github.com/Roave/infection-static-analysis-plugin). To run it:
@@ -33,16 +60,17 @@ The code is statically analyzed with [Psalm](https://psalm.dev/). To run static
## Rector
-Use [Rector](https://github.com/rectorphp/rector) to make codebase follow some specific rules or
-use either newest or any specific version of PHP:
+Use [Rector](https://github.com/rectorphp/rector) to make codebase follow some specific rules or
+use either newest or any specific version of PHP:
```shell
./vendor/bin/rector
```
-## Composer require checker
+## Dependencies
-This package uses [composer-require-checker](https://github.com/maglnet/ComposerRequireChecker) to check if all dependencies are correctly defined in `composer.json`.
+Use [ComposerRequireChecker](https://github.com/maglnet/ComposerRequireChecker) to detect transitive
+[Composer](https://getcomposer.org/) dependencies.
To run the checker, execute the following command:
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index c65978bba..368ef3ecc 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,5 +1,5 @@
-
+
diff --git a/psalm.xml b/psalm.xml
index 10d319ae3..906206a6b 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -15,5 +15,6 @@
+
diff --git a/psalm4.xml b/psalm4.xml
new file mode 100644
index 000000000..10d319ae3
--- /dev/null
+++ b/psalm4.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rector.php b/rector.php
index 6d4e4d49c..8cbdabbd1 100644
--- a/rector.php
+++ b/rector.php
@@ -11,7 +11,10 @@
$rectorConfig->paths([
__DIR__ . '/src',
- __DIR__ . '/tests',
+ /**
+ * Disabled ./tests directory due to different branches with main package when testing
+ */
+ // __DIR__ . '/tests',
]);
// register a single rule
diff --git a/src/AbstractTokenizer.php b/src/AbstractTokenizer.php
index 7febb90bd..effa5a310 100644
--- a/src/AbstractTokenizer.php
+++ b/src/AbstractTokenizer.php
@@ -101,7 +101,6 @@ public function tokenize(): SqlToken
$token[] = (new SqlToken())->type(SqlToken::TYPE_STATEMENT);
$this->tokenStack->push($token[0]);
- /** @psalm-var SqlToken */
$this->currentToken = $this->tokenStack->top();
$length = 0;
diff --git a/src/Builder/ExpressionBuilder.php b/src/Builder/ExpressionBuilder.php
new file mode 100644
index 000000000..96ac22ee4
--- /dev/null
+++ b/src/Builder/ExpressionBuilder.php
@@ -0,0 +1,16 @@
+getValue();
if ($value instanceof QueryInterface) {
diff --git a/src/Command.php b/src/Command.php
index f73ca1390..6996cd7f6 100644
--- a/src/Command.php
+++ b/src/Command.php
@@ -4,13 +4,10 @@
namespace Yiisoft\Db\Sqlite;
-use PDOException;
use Throwable;
use Yiisoft\Db\Driver\Pdo\AbstractPdoCommand;
-use Yiisoft\Db\Exception\ConvertException;
use Yiisoft\Db\Exception\Exception;
use Yiisoft\Db\Exception\InvalidArgumentException;
-use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
use function array_pop;
use function count;
@@ -44,7 +41,6 @@ public function insertWithReturningPks(string $table, array $columns): bool|arra
continue;
}
- /** @psalm-var mixed */
$result[$name] = $columns[$name] ?? $tableSchema?->getColumn($name)?->getDefaultValue();
}
@@ -60,11 +56,6 @@ public function showDatabases(): array
return $this->setSql($sql)->queryColumn();
}
- protected function getQueryBuilder(): QueryBuilderInterface
- {
- return $this->db->getQueryBuilder();
- }
-
/**
* Executes the SQL statement.
*
@@ -80,7 +71,6 @@ public function execute(): int
{
$sql = $this->getSql();
- /** @psalm-var array $params */
$params = $this->params;
$statements = $this->splitStatements($sql, $params);
@@ -91,11 +81,8 @@ public function execute(): int
$result = 0;
- /** @psalm-var array> $statements */
foreach ($statements as $statement) {
[$statementSql, $statementParams] = $statement;
- $statementSql = is_string($statementSql) ? $statementSql : '';
- $statementParams = is_array($statementParams) ? $statementParams : [];
$this->setSql($statementSql)->bindValues($statementParams);
$result = parent::execute();
}
@@ -105,43 +92,6 @@ public function execute(): int
return $result;
}
- /**
- * @psalm-suppress UnusedClosureParam
- *
- * @throws Throwable
- */
- protected function internalExecute(string|null $rawSql): void
- {
- $attempt = 0;
-
- while (true) {
- try {
- if (
- ++$attempt === 1
- && $this->isolationLevel !== null
- && $this->db->getTransaction() === null
- ) {
- $this->db->transaction(
- function () use ($rawSql): void {
- $this->internalExecute($rawSql);
- },
- $this->isolationLevel,
- );
- } else {
- $this->pdoStatement?->execute();
- }
- break;
- } catch (PDOException $e) {
- $rawSql = $rawSql ?: $this->getRawSql();
- $e = (new ConvertException($e, $rawSql))->run();
-
- if ($this->retryHandler === null || !($this->retryHandler)($e, $attempt)) {
- throw $e;
- }
- }
- }
- }
-
/**
* Performs the actual DB query of an SQL statement.
*
@@ -156,7 +106,6 @@ protected function queryInternal(int $queryMode): mixed
{
$sql = $this->getSql();
- /** @psalm-var array $params */
$params = $this->params;
$statements = $this->splitStatements($sql, $params);
@@ -167,14 +116,7 @@ protected function queryInternal(int $queryMode): mixed
[$lastStatementSql, $lastStatementParams] = array_pop($statements);
- /**
- * @psalm-var array $statements
- */
foreach ($statements as $statement) {
- /**
- * @psalm-var string $statementSql
- * @psalm-var array $statementParams
- */
[$statementSql, $statementParams] = $statement;
$this->setSql($statementSql)->bindValues($statementParams);
parent::execute();
@@ -182,7 +124,6 @@ protected function queryInternal(int $queryMode): mixed
$this->setSql($lastStatementSql)->bindValues($lastStatementParams);
- /** @psalm-var string $result */
$result = parent::queryInternal($queryMode);
$this->setSql($sql)->bindValues($params);
@@ -200,8 +141,6 @@ protected function queryInternal(int $queryMode): mixed
*
* @return array|bool List of SQL statements or `false` if there's a single statement.
*
- * @psalm-param array $params
- *
* @psalm-return false|list
*/
private function splitStatements(string $sql, array $params): bool|array
@@ -231,8 +170,6 @@ private function splitStatements(string $sql, array $params): bool|array
/**
* Returns named bindings used in the specified statement token.
- *
- * @psalm-param array $params
*/
private function extractUsedParams(SqlToken $statement, array $params): array
{
diff --git a/src/DDLQueryBuilder.php b/src/DDLQueryBuilder.php
index 84a9c89b8..15b8cd92a 100644
--- a/src/DDLQueryBuilder.php
+++ b/src/DDLQueryBuilder.php
@@ -105,8 +105,8 @@ public function createIndex(
[$schema, $table] = $tableParts;
}
- return 'CREATE ' . ($indexType ? ($indexType . ' ') : '') . 'INDEX '
- . $this->quoter->quoteTableName(($schema ? $schema . '.' : '') . $name)
+ return 'CREATE ' . (!empty($indexType) ? $indexType . ' ' : '') . 'INDEX '
+ . $this->quoter->quoteTableName((!empty($schema) ? $schema . '.' : '') . $name)
. ' ON '
. $this->quoter->quoteTableName($table)
. ' (' . $this->queryBuilder->buildColumns($columns) . ')';
diff --git a/src/DMLQueryBuilder.php b/src/DMLQueryBuilder.php
index a3ef422ed..e772a5985 100644
--- a/src/DMLQueryBuilder.php
+++ b/src/DMLQueryBuilder.php
@@ -8,13 +8,10 @@
use Yiisoft\Db\Exception\InvalidArgumentException;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\Expression;
-use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\AbstractDMLQueryBuilder;
use function implode;
-use function ltrim;
-use function reset;
/**
* Implements a DML (Data Manipulation Language) SQL statements for SQLite Server.
@@ -45,8 +42,8 @@ public function resetSequence(string $table, int|string $value = null): string
if ($value !== null) {
$value = "'" . ((int) $value - 1) . "'";
} else {
- $pk = $tableSchema->getPrimaryKey();
- $key = $this->quoter->quoteColumnName(reset($pk));
+ $key = $tableSchema->getPrimaryKey()[0];
+ $key = $this->quoter->quoteColumnName($key);
$value = '(SELECT MAX(' . $key . ') FROM ' . $tableName . ')';
}
@@ -59,14 +56,9 @@ public function upsert(
bool|array $updateColumns,
array &$params
): string {
- /** @psalm-var Constraint[] $constraints */
+ /** @var Constraint[] $constraints */
$constraints = [];
- /**
- * @psalm-var string[] $insertNames
- * @psalm-var string[] $updateNames
- * @psalm-var array|bool $updateColumns
- */
[$uniqueNames, $insertNames, $updateNames] = $this->prepareUpsertColumns(
$table,
$insertColumns,
@@ -78,20 +70,19 @@ public function upsert(
return $this->insert($table, $insertColumns, $params);
}
- /** @psalm-var string[] $placeholders */
[, $placeholders, $values, $params] = $this->prepareInsertValues($table, $insertColumns, $params);
- $insertSql = 'INSERT OR IGNORE INTO '
- . $this->quoter->quoteTableName($table)
+ $quotedTableName = $this->quoter->quoteTableName($table);
+
+ $insertSql = 'INSERT OR IGNORE INTO ' . $quotedTableName
. (!empty($insertNames) ? ' (' . implode(', ', $insertNames) . ')' : '')
- . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : "$values");
+ . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' ' . $values);
if ($updateColumns === false) {
return $insertSql;
}
$updateCondition = ['or'];
- $quotedTableName = $this->quoter->quoteTableName($table);
foreach ($constraints as $constraint) {
$constraintCondition = ['and'];
@@ -106,13 +97,9 @@ public function upsert(
if ($updateColumns === true) {
$updateColumns = [];
- foreach ($updateNames as $name) {
- $quotedName = $this->quoter->quoteColumnName($name);
-
- if (strrpos($quotedName, '.') === false) {
- $quotedName = "(SELECT $quotedName FROM `EXCLUDED`)";
- }
- $updateColumns[$name] = new Expression($quotedName);
+ /** @psalm-var string[] $updateNames */
+ foreach ($updateNames as $quotedName) {
+ $updateColumns[$quotedName] = new Expression("(SELECT $quotedName FROM `EXCLUDED`)");
}
}
@@ -120,13 +107,9 @@ public function upsert(
return $insertSql;
}
- /** @psalm-var array $params */
- $updateSql = 'WITH "EXCLUDED" ('
- . implode(', ', $insertNames)
- . ') AS (' . (!empty($placeholders)
- ? 'VALUES (' . implode(', ', $placeholders) . ')'
- : ltrim("$values", ' ')) . ') ' .
- $this->update($table, $updateColumns, $updateCondition, $params);
+ $updateSql = 'WITH "EXCLUDED" (' . implode(', ', $insertNames) . ') AS ('
+ . (!empty($placeholders) ? 'VALUES (' . implode(', ', $placeholders) . ')' : $values)
+ . ') ' . $this->update($table, $updateColumns, $updateCondition, $params);
return "$updateSql; $insertSql;";
}
diff --git a/src/DQLQueryBuilder.php b/src/DQLQueryBuilder.php
index 753e9504c..7723feb9f 100644
--- a/src/DQLQueryBuilder.php
+++ b/src/DQLQueryBuilder.php
@@ -4,6 +4,7 @@
namespace Yiisoft\Db\Sqlite;
+use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Expression\ExpressionBuilderInterface;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Expression\JsonExpression;
@@ -12,6 +13,7 @@
use Yiisoft\Db\QueryBuilder\AbstractDQLQueryBuilder;
use Yiisoft\Db\QueryBuilder\Condition\InCondition;
use Yiisoft\Db\QueryBuilder\Condition\LikeCondition;
+use Yiisoft\Db\Sqlite\Builder\ExpressionBuilder;
use Yiisoft\Db\Sqlite\Builder\InConditionBuilder;
use Yiisoft\Db\Sqlite\Builder\JsonExpressionBuilder;
use Yiisoft\Db\Sqlite\Builder\LikeConditionBuilder;
@@ -46,7 +48,6 @@ public function build(QueryInterface $query, array $params = []): array
$sql = $this->buildOrderByAndLimit($sql, $orderBy, $query->getLimit(), $query->getOffset());
if (!empty($orderBy)) {
- /** @psalm-var array $orderBy */
foreach ($orderBy as $expression) {
if ($expression instanceof ExpressionInterface) {
$this->buildExpression($expression, $params);
@@ -57,7 +58,6 @@ public function build(QueryInterface $query, array $params = []): array
$groupBy = $query->getGroupBy();
if (!empty($groupBy)) {
- /** @psalm-var array $groupBy */
foreach ($groupBy as $expression) {
if ($expression instanceof ExpressionInterface) {
$this->buildExpression($expression, $params);
@@ -94,7 +94,7 @@ public function buildLimit(ExpressionInterface|int|null $limit, ExpressionInterf
/**
* Limit isn't optional in SQLite.
*
- * {@see http://www.sqlite.org/syntaxdiagrams.html#select-stmt}
+ * {@see https://www.sqlite.org/syntaxdiagrams.html#select-stmt}
*/
$sql = 'LIMIT 9223372036854775807 OFFSET ' . // 2^63-1
($offset instanceof ExpressionInterface ? $this->buildExpression($offset) : (string)$offset);
@@ -138,6 +138,7 @@ protected function defaultExpressionBuilders(): array
LikeCondition::class => LikeConditionBuilder::class,
InCondition::class => InConditionBuilder::class,
JsonExpression::class => JsonExpressionBuilder::class,
+ Expression::class => ExpressionBuilder::class,
]);
}
}
diff --git a/src/Dsn.php b/src/Dsn.php
index 37f0c1cfa..8806397a8 100644
--- a/src/Dsn.php
+++ b/src/Dsn.php
@@ -24,7 +24,7 @@ public function __construct(private string $driver, private string|null $databas
/**
* @return string The Data Source Name, or DSN, has the information required to connect to the database.
*
- * Please refer to the [PHP manual](http://php.net/manual/en/pdo.construct.php) on the format of the DSN string.
+ * Please refer to the [PHP manual](https://php.net/manual/en/pdo.construct.php) on the format of the DSN string.
*
* The `driver` array key is used as the driver prefix of the DSN, all further key-value pairs are rendered as
* `key=value` and concatenated by `;`. For example:
diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php
index 3aa810b1f..4f867bd21 100644
--- a/src/QueryBuilder.php
+++ b/src/QueryBuilder.php
@@ -14,9 +14,7 @@
final class QueryBuilder extends AbstractQueryBuilder
{
/**
- * @var array Mapping from abstract column types (keys) to physical column types (values).
- *
- * @psalm-var string[] $typeMap
+ * @var string[] Mapping from abstract column types (keys) to physical column types (values).
*/
protected array $typeMap = [
SchemaInterface::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
diff --git a/src/Schema.php b/src/Schema.php
index d335a8bca..9badfad6d 100644
--- a/src/Schema.php
+++ b/src/Schema.php
@@ -21,6 +21,7 @@
use Yiisoft\Db\Schema\TableSchemaInterface;
use function array_column;
+use function array_map;
use function array_merge;
use function count;
use function explode;
@@ -75,11 +76,11 @@
final class Schema extends AbstractPdoSchema
{
/**
- * @var array Mapping from physical column types (keys) to abstract column types (values).
+ * Mapping from physical column types (keys) to abstract column types (values).
*
- * @psalm-var array $typeMap
+ * @var string[]
*/
- private array $typeMap = [
+ private const TYPE_MAP = [
'tinyint' => self::TYPE_TINYINT,
'bit' => self::TYPE_SMALLINT,
'boolean' => self::TYPE_BOOLEAN,
@@ -204,7 +205,7 @@ protected function loadTableForeignKeys(string $tableName): array
$foreignKeysList = $this->getPragmaForeignKeyList($tableName);
/** @psalm-var ForeignKeyInfo[] $foreignKeysList */
- $foreignKeysList = $this->normalizeRowKeyCase($foreignKeysList, true);
+ $foreignKeysList = array_map('array_change_key_case', $foreignKeysList);
$foreignKeysList = DbArrayHelper::index($foreignKeysList, null, ['table']);
DbArrayHelper::multisort($foreignKeysList, 'seq');
@@ -221,7 +222,6 @@ protected function loadTableForeignKeys(string $tableName): array
$primaryKey = $this->getTablePrimaryKey($table);
if ($primaryKey !== null) {
- /** @psalm-var string $primaryKeyColumnName */
foreach ((array) $primaryKey->getColumnNames() as $i => $primaryKeyColumnName) {
$foreignKey[$i]['to'] = $primaryKeyColumnName;
}
@@ -255,13 +255,12 @@ protected function loadTableForeignKeys(string $tableName): array
*
* @return array Indexes for the given table.
*
- * @psalm-return array|IndexConstraint[]
+ * @psalm-return IndexConstraint[]
*/
protected function loadTableIndexes(string $tableName): array
{
- $tableIndexes = $this->loadTableConstraints($tableName, self::INDEXES);
-
- return is_array($tableIndexes) ? $tableIndexes : [];
+ /** @var IndexConstraint[] */
+ return $this->loadTableConstraints($tableName, self::INDEXES);
}
/**
@@ -306,7 +305,6 @@ protected function loadTableChecks(string $tableName): array
$sql = ($sql === false || $sql === null) ? '' : (string) $sql;
- /** @psalm-var SqlToken[]|SqlToken[][]|SqlToken[][][] $code */
$code = (new SqlTokenizer($sql))->tokenize();
$pattern = (new SqlTokenizer('any CREATE any TABLE any()'))->tokenize();
$result = [];
@@ -366,7 +364,6 @@ protected function loadTableDefaultValues(string $tableName): array
*/
protected function findColumns(TableSchemaInterface $table): bool
{
- /** @psalm-var ColumnInfo[] $columns */
$columns = $this->getPragmaTableInfo($table->getName());
$jsonColumns = $this->getJsonColumns($table);
@@ -447,7 +444,6 @@ public function findUniqueIndexes(TableSchemaInterface $table): array
foreach ($indexList as $index) {
$indexName = $index['name'];
- /** @psalm-var IndexInfo[] $indexInfo */
$indexInfo = $this->getPragmaIndexInfo($index['name']);
if ($index['unique']) {
@@ -532,7 +528,7 @@ private function getColumnType(string $dbType, array &$info): string
}
}
- return $this->typeMap[$dbType] ?? self::TYPE_STRING;
+ return self::TYPE_MAP[$dbType] ?? self::TYPE_STRING;
}
/**
@@ -568,13 +564,16 @@ private function normalizeDefaultValue(string|null $defaultValue, ColumnSchemaIn
* @throws Throwable
*
* @return array The table columns info.
+ *
+ * @psalm-return ColumnInfo[] $tableColumns;
*/
private function loadTableColumnsInfo(string $tableName): array
{
$tableColumns = $this->getPragmaTableInfo($tableName);
/** @psalm-var ColumnInfo[] $tableColumns */
- $tableColumns = $this->normalizeRowKeyCase($tableColumns, true);
+ $tableColumns = array_map('array_change_key_case', $tableColumns);
+ /** @psalm-var ColumnInfo[] */
return DbArrayHelper::index($tableColumns, 'cid');
}
@@ -594,7 +593,7 @@ private function loadTableConstraints(string $tableName, string $returnType): Co
{
$indexList = $this->getPragmaIndexList($tableName);
/** @psalm-var IndexListInfo[] $indexes */
- $indexes = $this->normalizeRowKeyCase($indexList, true);
+ $indexes = array_map('array_change_key_case', $indexList);
$result = [
self::PRIMARY_KEY => null,
self::INDEXES => [],
@@ -602,7 +601,6 @@ private function loadTableConstraints(string $tableName, string $returnType): Co
];
foreach ($indexes as $index) {
- /** @psalm-var IndexInfo[] $columns */
$columns = $this->getPragmaIndexInfo($index['name']);
if ($index['origin'] === 'pk') {
@@ -628,8 +626,6 @@ private function loadTableConstraints(string $tableName, string $returnType): Co
* Extra check for PK in case of `INTEGER PRIMARY KEY` with ROWID.
*
* @link https://www.sqlite.org/lang_createtable.html#primkeyconst
- *
- * @psalm-var ColumnInfo[] $tableColumns
*/
$tableColumns = $this->loadTableColumnsInfo($tableName);
@@ -652,9 +648,12 @@ private function loadTableConstraints(string $tableName, string $returnType): Co
* @throws Exception
* @throws InvalidConfigException
* @throws Throwable
+ *
+ * @psalm-return ForeignKeyInfo[]
*/
private function getPragmaForeignKeyList(string $tableName): array
{
+ /** @psalm-var ForeignKeyInfo[] */
return $this->db->createCommand(
'PRAGMA FOREIGN_KEY_LIST(' . $this->db->getQuoter()->quoteSimpleTableName($tableName) . ')'
)->queryAll();
@@ -664,16 +663,18 @@ private function getPragmaForeignKeyList(string $tableName): array
* @throws Exception
* @throws InvalidConfigException
* @throws Throwable
+ *
+ * @psalm-return IndexInfo[]
*/
private function getPragmaIndexInfo(string $name): array
{
$column = $this->db
->createCommand('PRAGMA INDEX_INFO(' . (string) $this->db->getQuoter()->quoteValue($name) . ')')
->queryAll();
- /** @psalm-var IndexInfo[] $column */
- $column = $this->normalizeRowKeyCase($column, true);
+ $column = array_map('array_change_key_case', $column);
DbArrayHelper::multisort($column, 'seqno');
+ /** @psalm-var IndexInfo[] $column */
return $column;
}
@@ -681,9 +682,12 @@ private function getPragmaIndexInfo(string $name): array
* @throws Exception
* @throws InvalidConfigException
* @throws Throwable
+ *
+ * @psalm-return IndexListInfo[]
*/
private function getPragmaIndexList(string $tableName): array
{
+ /** @psalm-var IndexListInfo[] */
return $this->db
->createCommand('PRAGMA INDEX_LIST(' . (string) $this->db->getQuoter()->quoteValue($tableName) . ')')
->queryAll();
@@ -693,9 +697,12 @@ private function getPragmaIndexList(string $tableName): array
* @throws Exception
* @throws InvalidConfigException
* @throws Throwable
+ *
+ * @psalm-return ColumnInfo[]
*/
private function getPragmaTableInfo(string $tableName): array
{
+ /** @psalm-var ColumnInfo[] */
return $this->db->createCommand(
'PRAGMA TABLE_INFO(' . $this->db->getQuoter()->quoteSimpleTableName($tableName) . ')'
)->queryAll();
@@ -708,7 +715,7 @@ private function getPragmaTableInfo(string $tableName): array
*/
protected function findViewNames(string $schema = ''): array
{
- /** @psalm-var string[][] $views */
+ /** @var string[][] $views */
$views = $this->db->createCommand(
<<getExpression(), $matches, PREG_SET_ORDER)) {
+ if (preg_match_all($regexp, $check->getExpression(), $matches, PREG_SET_ORDER) > 0) {
foreach ($matches as $match) {
$result[] = $match[1];
}
diff --git a/src/SqlParser.php b/src/SqlParser.php
new file mode 100644
index 000000000..9698d6d68
--- /dev/null
+++ b/src/SqlParser.php
@@ -0,0 +1,45 @@
+length - 1;
+
+ while ($this->position < $length) {
+ $pos = $this->position++;
+
+ match ($this->sql[$pos]) {
+ ':' => ($word = $this->parseWord()) === ''
+ ? $this->skipChars(':')
+ : $result = ':' . $word,
+ '"', "'", '`' => $this->skipQuotedWithoutEscape($this->sql[$pos]),
+ '[' => $this->sql[$this->position] === '['
+ ? $this->skipToAfterString(']]')
+ : $this->skipQuotedWithoutEscape(']'),
+ '-' => $this->sql[$this->position] === '-'
+ ? ++$this->position && $this->skipToAfterChar("\n")
+ : null,
+ '/' => $this->sql[$this->position] === '*'
+ ? ++$this->position && $this->skipToAfterString('*/')
+ : null,
+ default => null,
+ };
+
+ if ($result !== null) {
+ $position = $pos;
+
+ return $result;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/SqlTokenizer.php b/src/SqlTokenizer.php
index 54a4154a7..7db891d25 100644
--- a/src/SqlTokenizer.php
+++ b/src/SqlTokenizer.php
@@ -12,7 +12,7 @@
*
* It's used to obtain `CHECK` constraint information from a `CREATE TABLE` SQL code.
*
- * @link http://www.sqlite.org/draft/tokenreq.html
+ * @link https://www.sqlite.org/draft/tokenreq.html
* @link https://sqlite.org/lang.html
*/
final class SqlTokenizer extends AbstractTokenizer
diff --git a/src/Transaction.php b/src/Transaction.php
index 2256e4732..15731a299 100644
--- a/src/Transaction.php
+++ b/src/Transaction.php
@@ -29,7 +29,7 @@ final class Transaction extends AbstractPdoTransaction
* @throws Throwable When unsupported isolation levels are used. SQLite only supports `SERIALIZABLE`
* and `READ UNCOMMITTED`.
*
- * @link http://www.sqlite.org/pragma.html#pragma_read_uncommitted
+ * @link https://www.sqlite.org/pragma.html#pragma_read_uncommitted
*/
protected function setTransactionIsolationLevel(string $level): void
{
diff --git a/tests/CommandTest.php b/tests/CommandTest.php
index 7f3dbd4d5..32486c272 100644
--- a/tests/CommandTest.php
+++ b/tests/CommandTest.php
@@ -465,9 +465,10 @@ public function testUpdate(
array $columns,
array|string $conditions,
array $params,
- string $expected
+ array $expectedValues,
+ int $expectedCount,
): void {
- parent::testUpdate($table, $columns, $conditions, $params, $expected);
+ parent::testUpdate($table, $columns, $conditions, $params, $expectedValues, $expectedCount);
}
/**
diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php
index 6761ba429..f4d4e5d2e 100644
--- a/tests/Provider/QueryBuilderProvider.php
+++ b/tests/Provider/QueryBuilderProvider.php
@@ -176,6 +176,11 @@ public static function upsert(): array
WITH "EXCLUDED" (`email`, `address`, `status`, `profile_id`) AS (VALUES (:qp0, :qp1, :qp2, :qp3)) UPDATE `T_upsert` SET `address`=(SELECT `address` FROM `EXCLUDED`), `status`=(SELECT `status` FROM `EXCLUDED`), `profile_id`=(SELECT `profile_id` FROM `EXCLUDED`) WHERE `T_upsert`.`email`=(SELECT `email` FROM `EXCLUDED`); INSERT OR IGNORE INTO `T_upsert` (`email`, `address`, `status`, `profile_id`) VALUES (:qp0, :qp1, :qp2, :qp3);
SQL,
],
+ 'regular values with unique at not the first position' => [
+ 3 => << [
3 => <<assertSame(
<<insert('json_table', ['json_col' => new JsonExpression(['a' => 1, 'b' => 2])]),
);
}
+
+ /** @dataProvider \Yiisoft\Db\Sqlite\Tests\Provider\QueryBuilderProvider::selectScalar */
+ public function testSelectScalar(array|bool|float|int|string $columns, string $expected): void
+ {
+ parent::testSelectScalar($columns, $expected);
+ }
}
diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php
index e15c3f1ba..2a1711279 100644
--- a/tests/SchemaTest.php
+++ b/tests/SchemaTest.php
@@ -352,11 +352,11 @@ public function testWorkWithPrimaryKeyConstraint(): void
public function testNotConnectionPDO(): void
{
$db = $this->createMock(ConnectionInterface::class);
- $schema = new Schema($db, DbHelper::getSchemaCache(), 'system');
+ $schema = new Schema($db, DbHelper::getSchemaCache());
$this->expectException(NotSupportedException::class);
$this->expectExceptionMessage('Only PDO connections are supported.');
- $schema->refreshTableSchema('customer');
+ $schema->refresh();
}
}
diff --git a/tests/SqlParserTest.php b/tests/SqlParserTest.php
new file mode 100644
index 000000000..512b770d6
--- /dev/null
+++ b/tests/SqlParserTest.php
@@ -0,0 +1,25 @@
+