diff --git a/.github/workflows/db-mssql.yml b/.github/workflows/db-mssql.yml index c2d164202..1354fcdc2 100644 --- a/.github/workflows/db-mssql.yml +++ b/.github/workflows/db-mssql.yml @@ -27,7 +27,6 @@ jobs: env: COMPOSER_ROOT_VERSION: dev-master - composer_flag: "${{ matrix.php == '8.2' && '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi --ignore-platform-reqs' || '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi' }}" extensions: pdo, pdo_sqlsrv-5.10.1 runs-on: ${{ matrix.os }} @@ -45,6 +44,7 @@ jobs: mssql: - server:2017-latest - server:2019-latest + - server:2022-latest services: mssql: @@ -58,13 +58,13 @@ jobs: options: --name=mssql --health-cmd="/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - - name: Checkout + - name: Checkout. uses: actions/checkout@v3 - - name: Create MS SQL Database + - name: Create MS SQL Database. run: docker exec -i mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE yiitest' - - name: Install PHP with extensions + - name: Install PHP with extensions. uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -75,16 +75,16 @@ jobs: env: update: true - - name: Update composer + - name: Update composer. run: composer self-update - - name: Install db-mssql - run: composer require yiisoft/db-mssql ${{ env.composer_flag }} + - name: Install db-mssql. + run: composer config preferred-install.yiisoft/db-mssql source && composer require yiisoft/db-mssql --no-interaction --no-progress --optimize-autoloader --ansi - - name: Run mssql tests with phpunit + - name: Run mssql tests with phpunit and code coverage. run: vendor/bin/phpunit --testsuite Mssql --coverage-clover=coverage.xml --colors=always - - name: Upload coverage to Codecov + - name: Upload coverage to Codecov. if: matrix.php == '8.0' uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/db-mysql.yml b/.github/workflows/db-mysql.yml index 07de7ac1f..091a33228 100644 --- a/.github/workflows/db-mysql.yml +++ b/.github/workflows/db-mysql.yml @@ -27,7 +27,6 @@ jobs: env: COMPOSER_ROOT_VERSION: dev-master - composer_flag: "${{ matrix.php == '8.2' && '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi --ignore-platform-reqs' || '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi' }}" extensions: pdo, pdo_mysql runs-on: ${{ matrix.os }} @@ -58,10 +57,10 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - - name: Checkout + - name: Checkout. uses: actions/checkout@v3 - - name: Install PHP with extensions + - name: Install PHP with extensions. uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -69,16 +68,16 @@ jobs: ini-values: date.timezone='UTC' coverage: pcov - - name: Update composer + - name: Update composer. run: composer self-update - - name: Install db-mysql - run: composer require yiisoft/db-mysql ${{ env.composer_flag }} + - name: Install db-mysql. + run: composer config preferred-install.yiisoft/db-mysql source && composer require yiisoft/db-mysql --no-interaction --no-progress --optimize-autoloader --ansi - - name: Run tests with phpunit + - name: Run Mssql tests with phpunit and code coverage. run: vendor/bin/phpunit --testsuite Mysql --coverage-clover=coverage.xml --colors=always - - name: Upload coverage to Codecov + - name: Upload coverage to Codecov. if: matrix.php == '8.0' uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/db-oracle.yml b/.github/workflows/db-oracle.yml index 7bf570540..bdca6e936 100644 --- a/.github/workflows/db-oracle.yml +++ b/.github/workflows/db-oracle.yml @@ -27,7 +27,6 @@ jobs: env: COMPOSER_ROOT_VERSION: dev-master - composer_flag: "${{ matrix.php == '8.2' && '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi --ignore-platform-reqs' || '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi' }}" extensions: pdo, pdo_oci runs-on: ${{ matrix.os }} @@ -53,10 +52,10 @@ jobs: options: --name=oci steps: - - name: Checkout + - name: Checkout. uses: actions/checkout@v3 - - name: Install PHP with extensions + - name: Install PHP with extensions. uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -65,16 +64,16 @@ jobs: coverage: pcov tools: composer:v2, pecl - - name: Update composer + - name: Update composer. run: composer self-update - - name: Install db-oracle - run: composer require yiisoft/db-oracle ${{ env.composer_flag }} + - name: Install db-oracle. + run: composer config preferred-install.yiisoft/db-oracle source && composer require yiisoft/db-oracle --no-interaction --no-progress --optimize-autoloader --ansi - - name: Run oracle tests with phpunit + - name: Run oracle tests with phpunit and code coverage. run: vendor/bin/phpunit --testsuite Oracle --coverage-clover=coverage.xml --colors=always - - name: Upload coverage to Codecov + - name: Upload coverage to Codecov. if: matrix.php == '8.0' uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/db-pgsql.yml b/.github/workflows/db-pgsql.yml index 3cb90488d..31a86ce6b 100644 --- a/.github/workflows/db-pgsql.yml +++ b/.github/workflows/db-pgsql.yml @@ -27,7 +27,6 @@ jobs: env: COMPOSER_ROOT_VERSION: dev-master - composer_flag: "${{ matrix.php == '8.2' && '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi --ignore-platform-reqs' || '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi' }}" extensions: pdo, pdo_pgsql runs-on: ${{ matrix.os }} @@ -62,10 +61,10 @@ jobs: options: --name=postgres --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - - name: Checkout + - name: Checkout. uses: actions/checkout@v3 - - name: Install PHP with extensions + - name: Install PHP with extensions. uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -73,16 +72,16 @@ jobs: ini-values: date.timezone='UTC' coverage: pcov - - name: Update composer + - name: Update composer. run: composer self-update - - name: Install db-pgsql - run: composer require yiisoft/db-pgsql ${{ env.composer_flag }} + - name: Install db-pgsql. + run: composer config preferred-install.yiisoft/db-pgsql source && composer require yiisoft/db-pgsql --no-interaction --no-progress --optimize-autoloader --ansi - - name: Run pgsql tests with phpunit + - name: Run pgsql tests with phpunit and code coverage. run: vendor/bin/phpunit --testsuite Pgsql --coverage-clover=coverage.xml --colors=always - - name: Upload coverage to Codecov + - name: Upload coverage to Codecov. if: matrix.php == '8.0' uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/db-sqlite.yml b/.github/workflows/db-sqlite.yml index adef9a0a7..5e2adf47a 100644 --- a/.github/workflows/db-sqlite.yml +++ b/.github/workflows/db-sqlite.yml @@ -27,7 +27,6 @@ jobs: env: COMPOSER_ROOT_VERSION: dev-master - composer_flag: "${{ matrix.php == '8.2' && '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi --ignore-platform-reqs' || '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi' }}" extensions: pdo, pdo_sqlite runs-on: ${{ matrix.os }} @@ -44,10 +43,10 @@ jobs: - 8.2 steps: - - name: Checkout + - name: Checkout. uses: actions/checkout@v3 - - name: Install PHP with extensions + - name: Install PHP with extensions. uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -56,16 +55,16 @@ jobs: coverage: pcov tools: composer:v2, pecl - - name: Update composer + - name: Update composer. run: composer self-update - - name: Install db-sqlite - run: composer require yiisoft/db-sqlite ${{ env.composer_flag }} + - name: Install db-sqlite. + run: composer config preferred-install.yiisoft/db-sqlite source && composer require yiisoft/db-sqlite --no-interaction --no-progress --optimize-autoloader --ansi - - name: Run sqlite tests with phpunit + - name: Run sqlite tests with phpunit and code coverage. run: vendor/bin/phpunit --testsuite Sqlite --coverage-clover=coverage.xml --colors=always - - name: Upload coverage to Codecov + - name: Upload coverage to Codecov. if: matrix.os == 'ubuntu-latest' && matrix.php == '8.0' uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index b7f131daf..fb7fc77dd 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -22,45 +22,10 @@ on: name: static analysis jobs: - mutation: - name: PHP ${{ matrix.php }}-${{ matrix.os }} - - env: - COMPOSER_ROOT_VERSION: dev-master - composer_flag: "${{ matrix.php == '8.2' && '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi --ignore-platform-reqs' || '--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi' }}" - extensions: dom, json, libxml, mbstring, pdo, pdo_mysql, pdo_pgsql, pdo_sqlite, pdo_sqlsrv-5.10.1, tokenizer - - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: - - ubuntu-latest - - php: - - 8.0 - - 8.1 - - 8.2 - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "${{ matrix.php }}" - extensions: ${{ env.extensions }} - tools: composer:v2, cs2pr - coverage: pcov - env: - update: true - - - name: Update composer - run: composer self-update - - - name: Install dependencies with composer - run: composer update ${{ env.composer_flag }} - - - name: Static analysis with psalm - run: vendor/bin/psalm --shepherd --stats --output-format=checkstyle | cs2pr --graceful-warnings --colorize + psalm: + uses: yiisoft/actions/.github/workflows/psalm.yml@master + with: + os: >- + ['ubuntu-latest'] + php: >- + ['8.0', '8.1', '8.2'] diff --git a/README.md b/README.md index 7b8291b3e..a10b3f2d5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ It is used in [Yii Framework] but is supposed to be usable separately. [![Latest Stable Version](https://poser.pugx.org/yiisoft/db/v/stable.png)](https://packagist.org/packages/yiisoft/db) [![Total Downloads](https://poser.pugx.org/yiisoft/db/downloads.png)](https://packagist.org/packages/yiisoft/db) [![Build status](https://github.com/yiisoft/db/workflows/build/badge.svg)](https://github.com/yiisoft/db/actions?query=workflow%3Abuild) -[![codecov](https://codecov.io/gh/yiisoft/db/branch/dev/graph/badge.svg?token=pRr4gci2qj)](https://codecov.io/gh/yiisoft/db) +[![codecov](https://codecov.io/gh/yiisoft/db/branch/master/graph/badge.svg?token=pRr4gci2qj)](https://codecov.io/gh/yiisoft/db) [![static analysis](https://github.com/yiisoft/db/actions/workflows/static.yml/badge.svg?branch=dev)](https://github.com/yiisoft/db/actions/workflows/static.yml) [![type-coverage](https://shepherd.dev/github/yiisoft/db/coverage.svg)](https://shepherd.dev/github/yiisoft/db) @@ -26,6 +26,14 @@ The package is tested with [PHPUnit](https://phpunit.de/). To run tests: ./vendor/bin/phpunit ``` +### Mutation testing + +The package tests are checked with [Infection](https://infection.github.io/) mutation framework. To run it: + +```shell +./vendor/bin/infection +``` + ### Static analysis The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis: @@ -34,6 +42,25 @@ The code is statically analyzed with [Psalm](https://psalm.dev/). To run static ./vendor/bin/psalm ``` +### 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: + +```shell +./vendor/bin/rector +``` + +### Composer require checker + +This package uses [composer-require-checker](https://github.com/maglnet/ComposerRequireChecker) to check if all dependencies are correctly defined in `composer.json`. + +To run the checker, execute the following command: + +```shell +./vendor/bin/composer-require-checker +``` + ### Support the project [![Open Collective](https://img.shields.io/badge/Open%20Collective-sponsor-7eadf1?logo=open%20collective&logoColor=7eadf1&labelColor=555555)](https://opencollective.com/yiisoft) diff --git a/composer.dev.json b/composer.dev.json new file mode 100644 index 000000000..8668d41bf --- /dev/null +++ b/composer.dev.json @@ -0,0 +1,12 @@ +{ + "autoload-dev": { + "psr-4": { + "Yiisoft\\ActiveRecord\\Tests\\": "vendor/yiisoft/active-record/tests", + "Yiisoft\\Db\\Mssql\\Tests\\": "vendor/yiisoft/db-mssql/tests", + "Yiisoft\\Db\\Mysql\\Tests\\": "vendor/yiisoft/db-mysql/tests", + "Yiisoft\\Db\\Oracle\\Tests\\": "vendor/yiisoft/db-oracle/tests", + "Yiisoft\\Db\\Pgsql\\Tests\\": "vendor/yiisoft/db-pgsql/tests", + "Yiisoft\\Db\\Sqlite\\Tests\\": "vendor/yiisoft/db-sqlite/tests" + } + } +} diff --git a/composer.json b/composer.json index 14fe1c7d1..108a5de60 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,7 @@ "roave/infection-static-analysis-plugin": "^1.16", "spatie/phpunit-watcher": "^1.23", "vimeo/psalm": "^4.18", + "wikimedia/composer-merge-plugin": "^2.0", "yiisoft/aliases": "^1.1|^2.0", "yiisoft/di": "^1.0", "yiisoft/event-dispatcher": "^1.0", @@ -48,25 +49,26 @@ }, "autoload-dev": { "psr-4": { - "Yiisoft\\Db\\Tests\\": "tests", - "Yiisoft\\ActiveRecord\\Tests\\": "vendor/yiisoft/active-record/tests", - "Yiisoft\\Db\\Mssql\\Tests\\": "vendor/yiisoft/db-mssql/tests", - "Yiisoft\\Db\\Mysql\\Tests\\": "vendor/yiisoft/db-mysql/tests", - "Yiisoft\\Db\\Oracle\\Tests\\": "vendor/yiisoft/db-oracle/tests", - "Yiisoft\\Db\\Pgsql\\Tests\\": "vendor/yiisoft/db-pgsql/tests", - "Yiisoft\\Db\\Sqlite\\Tests\\": "vendor/yiisoft/db-sqlite/tests" + "Yiisoft\\Db\\Tests\\": "tests" } }, "extra": { "branch-alias": { "dev-master": "3.0.x-dev" + }, + "merge-plugin": { + "include": [ + "composer.dev.json" + ], + "merge-dev": true } }, "config": { "sort-packages": true, "allow-plugins": { "infection/extension-installer": true, - "composer/package-versions-deprecated": true + "composer/package-versions-deprecated": true, + "wikimedia/composer-merge-plugin": true } }, "scripts": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 780d0e9b7..dac1dc8e7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,8 +1,20 @@ - + + + - ./ + ./src ./src/TestSupport @@ -10,9 +22,11 @@ ./vendor + + ./tests diff --git a/src/QueryBuilder/DDLQueryBuilder.php b/src/QueryBuilder/DDLQueryBuilder.php index d9bd3629a..3d8814435 100644 --- a/src/QueryBuilder/DDLQueryBuilder.php +++ b/src/QueryBuilder/DDLQueryBuilder.php @@ -68,11 +68,12 @@ public function addCommentOnTable(string $table, string $comment): string */ public function addDefaultValue(string $name, string $table, string $column, mixed $value): string { - throw new NotSupportedException(static::class . ' does not support adding default value constraints.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } /** - * @throws Exception|InvalidArgumentException + * @throws Exception + * @throws InvalidArgumentException */ public function addForeignKey( string $name, @@ -151,11 +152,12 @@ public function alterColumn(string $table, string $column, ColumnSchemaBuilder|s */ public function checkIntegrity(string $schema = '', string $table = '', bool $check = true): string { - throw new NotSupportedException(static::class . ' does not support enabling/disabling integrity check.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } /** - * @throws Exception|InvalidArgumentException + * @throws Exception + * @throws InvalidArgumentException */ public function createIndex( string $name, @@ -193,16 +195,19 @@ public function createTable(string $table, array $columns, string $options = nul } /** - * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException + * @throws Exception + * @throws InvalidArgumentException + * @throws InvalidConfigException + * @throws NotSupportedException */ public function createView(string $viewName, QueryInterface|string $subQuery): string { if ($subQuery instanceof QueryInterface) { [$rawQuery, $params] = $this->queryBuilder->build($subQuery); - /** @var mixed $value */ + /** @psalm-var mixed $value */ foreach ($params as $key => $value) { - /** @var mixed */ + /** @psalm-var mixed */ $params[$key] = $this->quoter->quoteValue($value); } @@ -249,7 +254,7 @@ public function dropCommentFromTable(string $table): string */ public function dropDefaultValue(string $name, string $table): string { - throw new NotSupportedException(static::class . ' does not support dropping default value constraints.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } public function dropForeignKey(string $name, string $table): string diff --git a/src/QueryBuilder/DMLQueryBuilder.php b/src/QueryBuilder/DMLQueryBuilder.php index 6ac7dcc9d..e112c645c 100644 --- a/src/QueryBuilder/DMLQueryBuilder.php +++ b/src/QueryBuilder/DMLQueryBuilder.php @@ -101,9 +101,6 @@ public function delete(string $table, array|string $condition, array &$params): return $where === '' ? $sql : $sql . ' ' . $where; } - /** - * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException - */ public function insert(string $table, QueryInterface|array $columns, array &$params = []): string { /** @@ -132,7 +129,7 @@ public function insertEx(string $table, QueryInterface|array $columns, array &$p */ public function resetSequence(string $tableName, int|string|null $value = null): string { - throw new NotSupportedException(static::class . ' does not support resetting sequence.'); + throw new NotSupportedException(__METHOD__ . '() is not supported by this DBMS.'); } /** @@ -181,7 +178,7 @@ protected function prepareInsertSelectSubQuery(QueryInterface $columns, array $p $names = []; $values = ' ' . $values; - /** @psalm-var string[] */ + /** @psalm-var string[] $select */ $select = $columns->getSelect(); foreach ($select as $title => $field) { @@ -244,7 +241,7 @@ protected function prepareUpdateSets(string $table, array $columns, array $param * @psalm-var mixed $value */ foreach ($columns as $name => $value) { - /** @var mixed */ + /** @psalm-var mixed $value */ $value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value; if ($value instanceof ExpressionInterface) { $placeholder = $this->queryBuilder->buildExpression($value, $params); @@ -281,7 +278,7 @@ protected function prepareUpsertColumns( } } - /** @psalm-var string[] */ + /** @psalm-var string[] $uniqueNames */ $uniqueNames = $this->getTableUniqueColumnNames($table, $insertNames, $constraints); foreach ($uniqueNames as $key => $name) { @@ -320,7 +317,7 @@ private function getTableUniqueColumnNames(string $name, array $columns, array & $constraints[] = $primaryKey; } - /** @psalm-var IndexConstraint[] */ + /** @psalm-var IndexConstraint[] $tableIndexes */ $tableIndexes = $this->schema->getTableIndexes($name); foreach ($tableIndexes as $constraint) { @@ -353,7 +350,7 @@ static function (Constraint $constraint) { array_filter( $constraints, static function (Constraint $constraint) use ($quoter, $columns, &$columnNames) { - /** @psalm-var string[]|string */ + /** @psalm-var string[]|string $getColumnNames */ $getColumnNames = $constraint->getColumnNames() ?? []; $constraintColumnNames = []; diff --git a/src/TestSupport/TestCommandTrait.php b/src/TestSupport/TestCommandTrait.php deleted file mode 100644 index 2476e65cb..000000000 --- a/src/TestSupport/TestCommandTrait.php +++ /dev/null @@ -1,1574 +0,0 @@ -getConnection(); - - /* null */ - $command = $db->createCommand(); - $this->assertEmpty($command->getSql()); - - /* string */ - $sql = 'SELECT * FROM customer'; - $command = $db->createCommand($sql); - $this->assertEquals($sql, $command->getSql()); - } - - /** - * @throws Exception|InvalidConfigException - */ - public function testGetSetSql(): void - { - $db = $this->getConnection(); - - $sql = 'SELECT * FROM customer'; - $command = $db->createCommand($sql); - $this->assertEquals($sql, $command->getSql()); - - $sql2 = 'SELECT * FROM order'; - $command->setSql($sql2); - $this->assertEquals($sql2, $command->getSql()); - } - - /** - * @throws Exception|InvalidConfigException - */ - public function testPrepareCancel(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand('SELECT * FROM {{customer}}'); - $this->assertNull($command->getPdoStatement()); - - $command->prepare(); - $this->assertNotNull($command->getPdoStatement()); - - $command->cancel(); - $this->assertNull($command->getPdoStatement()); - } - - /** - * @throws Exception|InvalidConfigException|Throwable - */ - public function testExecute(): void - { - $db = $this->getConnection(true); - - $sql = 'INSERT INTO {{customer}}([[email]], [[name]], [[address]])' - . ' VALUES (\'user4@example.com\', \'user4\', \'address4\')'; - - $command = $db->createCommand($sql); - $this->assertEquals(1, $command->execute()); - - $sql = 'SELECT COUNT(*) FROM {{customer}} WHERE [[name]] = \'user4\''; - $command = $db->createCommand($sql); - $this->assertEquals(1, $command->queryScalar()); - - $command = $db->createCommand('bad SQL'); - $this->expectException(Exception::class); - $command->execute(); - } - - public function testDataReaderCreationException(): void - { - $db = $this->getConnection(true); - - $this->expectException(InvalidParamException::class); - $this->expectExceptionMessage('The PDOStatement cannot be null.'); - - $sql = 'SELECT * FROM {{customer}}'; - new DataReader($db->createCommand($sql)); - } - - public function testDataReaderRewindException(): void - { - $db = $this->getConnection(true); - - $this->expectException(InvalidCallException::class); - $this->expectExceptionMessage('DataReader cannot rewind. It is a forward-only reader.'); - - $sql = 'SELECT * FROM {{customer}}'; - $reader = $db->createCommand($sql)->query(); - $reader->next(); - $reader->rewind(); - } - - /** - * @throws Exception|InvalidConfigException|Throwable - */ - public function testQuery(): void - { - $db = $this->getConnection(true); - - $sql = 'SELECT * FROM {{customer}}'; - $reader = $db->createCommand($sql)->query(); - $this->assertInstanceOf(DataReaderInterface::class, $reader); - - // Next line is commented by reason:: For sqlite & pgsql result may be incorrect - // $this->assertEquals(3, $reader->count()); - $this->assertIsInt($reader->count()); - foreach ($reader as $row) { - $this->assertIsArray($row); - $this->assertTrue(count($row) >= 6); - } - - $command = $db->createCommand('bad SQL'); - $this->expectException(Exception::class); - $command->query(); - } - - public function testQyeryScalar(): void - { - $db = $this->getConnection(); - - $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]'; - $this->assertEquals($db->createCommand($sql)->queryScalar(), 1); - - $sql = 'SELECT [[id]] FROM {{customer}} ORDER BY [[id]]'; - $command = $db->createCommand($sql); - - $command->prepare(); - $this->assertEquals(1, $command->queryScalar()); - - $command = $db->createCommand('SELECT [[id]] FROM {{customer}} WHERE [[id]] = 10'); - $this->assertFalse($command->queryScalar()); - } - - public function testQueryOne(): void - { - $db = $this->getConnection(); - - $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]'; - $row = $db->createCommand($sql)->queryOne(); - $this->assertIsArray($row); - $this->assertEquals(1, $row['id']); - $this->assertEquals('user1', $row['name']); - - $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]'; - $command = $db->createCommand($sql); - $command->prepare(); - $row = $command->queryOne(); - - $this->assertIsArray($row); - $this->assertEquals(1, $row['id']); - $this->assertEquals('user1', $row['name']); - - $sql = 'SELECT * FROM {{customer}} WHERE [[id]] = 10'; - $command = $db->createCommand($sql); - $this->assertNull($command->queryOne()); - } - - public function testQueryColumn(): void - { - $db = $this->getConnection(); - - $sql = 'SELECT * FROM {{customer}}'; - $column = $db->createCommand($sql)->queryColumn(); - $this->assertEquals(range(1, 3), $column); - $this->assertIsArray($column); - - $command = $db->createCommand('SELECT [[id]] FROM {{customer}} WHERE [[id]] = 10'); - $this->assertEmpty($command->queryColumn()); - } - - public function testQueryAll(): void - { - $db = $this->getConnection(); - - $rows = $db->createCommand('SELECT [[id]],[[name]] FROM {{customer}}')->queryAll(); - /** @psalm-suppress RedundantCondition */ - $this->assertIsArray($rows); - $this->assertCount(3, $rows); - - $row = $rows[2]; - $this->assertEquals(3, $row['id']); - $this->assertEquals('user3', $row['name']); - $this->assertTrue(is_array($rows) && count($rows)>1 && count($rows[0]) === 2); - - $rows = $db->createCommand('SELECT * FROM {{customer}} WHERE [[id]] = 10')->queryAll(); - $this->assertEquals([], $rows); - } - - /** - * @throws Exception|InvalidConfigException|Throwable - */ - public function testBatchInsert(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $command->batchInsert( - '{{customer}}', - ['email', 'name', 'address'], - [ - ['t1@example.com', 't1', 't1 address'], - ['t2@example.com', null, false], - ] - ); - $this->assertEquals(2, $command->execute()); - $result = (new Query($db)) - ->select(['email', 'name', 'address']) - ->from('{{customer}}') - ->where(['=', '[[email]]', 't1@example.com']) - ->one(); - $this->assertCount(3, $result); - $this->assertSame( - [ - 'email' => 't1@example.com', - 'name' => 't1', - 'address' => 't1 address', - ], - $result, - ); - $result = (new Query($db)) - ->select(['email', 'name', 'address']) - ->from('{{customer}}') - ->where(['=', '[[email]]', 't2@example.com']) - ->one(); - $this->assertCount(3, $result); - $this->assertSame( - [ - 'email' => 't2@example.com', - 'name' => null, - 'address' => '0', - ], - $result, - ); - - /** - * @link https://github.com/yiisoft/yii2/issues/11693 - */ - $command = $db->createCommand(); - $command->batchInsert( - '{{customer}}', - ['email', 'name', 'address'], - [] - ); - $this->assertEquals(0, $command->execute()); - } - - public function testBatchInsertWithManyData(): void - { - $values = []; - $attemptsInsertRows = 200; - $db = $this->getConnection(true); - - $command = $db->createCommand(); - for ($i = 0; $i < $attemptsInsertRows; $i++) { - $values[$i] = ['t' . $i . '@any.com', 't' . $i, 't' . $i . ' address']; - } - - $command->batchInsert('{{customer}}', ['email', 'name', 'address'], $values); - - $this->assertEquals($attemptsInsertRows, $command->execute()); - - $insertedRowsCount = (new Query($db))->from('{{customer}}')->count(); - $this->assertGreaterThanOrEqual($attemptsInsertRows, $insertedRowsCount); - } - - /** - * @throws Exception|InvalidConfigException|Throwable - */ - public function testBatchInsertFailsOld(): void - { - $db = $this->getConnection(true); - - $command = $db->createCommand(); - $command->batchInsert( - '{{customer}}', - ['email', 'name', 'address'], - [ - ['t1@example.com', 'test_name', 'test_address'], - ] - ); - $this->assertEquals(1, $command->execute()); - - $result = (new Query($db)) - ->select(['email', 'name', 'address']) - ->from('{{customer}}') - ->where(['=', '[[email]]', 't1@example.com']) - ->one(); - - $this->assertCount(3, $result); - $this->assertSame( - [ - 'email' => 't1@example.com', - 'name' => 'test_name', - 'address' => 'test_address', - ], - $result, - ); - } - - /** - * @throws Exception|InvalidConfigException|Throwable - */ - public function testBatchInsertWithYield(): void - { - $rows = (static function () { - foreach ([['test@email.com', 'test name', 'test address']] as $row) { - yield $row; - } - })(); - - $command = $this->getConnection()->createCommand(); - $command->batchInsert( - '{{customer}}', - ['email', 'name', 'address'], - $rows - ); - $this->assertEquals(1, $command->execute()); - } - - /** - * Test batch insert with different data types. - * - * Ensure double is inserted with `.` decimal separator. - * - * @link https://github.com/yiisoft/yii2/issues/6526 - */ - public function testBatchInsertDataTypesLocale(): void - { - $locale = setlocale(LC_NUMERIC, 0); - - if (false === $locale) { - $this->markTestSkipped('Your platform does not support locales.'); - } - - $db = $this->getConnection(true); - - try { - /* This one sets decimal mark to comma sign */ - setlocale(LC_NUMERIC, 'ru_RU.utf8'); - $cols = ['int_col', 'char_col', 'float_col', 'bool_col']; - $data = [ - [1, 'A', 9.735, true], - [2, 'B', -2.123, false], - [3, 'C', 2.123, false], - ]; - - /* clear data in "type" table */ - $db->createCommand()->delete('type')->execute(); - - /* change, for point oracle. */ - if ($db->getDriver()->getDriverName() === 'oci') { - $db->createCommand("ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,'")->execute(); - } - - /* batch insert on "type" table */ - $db->createCommand()->batchInsert('type', $cols, $data)->execute(); - $data = $db->createCommand( - 'SELECT [[int_col]], [[char_col]], [[float_col]], [[bool_col]] ' . - 'FROM {{type}} WHERE [[int_col]] IN (1,2,3) ORDER BY [[int_col]]' - )->queryAll(); - $this->assertCount(3, $data); - $this->assertEquals(1, $data[0]['int_col']); - $this->assertEquals(2, $data[1]['int_col']); - $this->assertEquals(3, $data[2]['int_col']); - - /* rtrim because Postgres padds the column with whitespace */ - $this->assertEquals('A', rtrim($data[0]['char_col'])); - $this->assertEquals('B', rtrim($data[1]['char_col'])); - $this->assertEquals('C', rtrim($data[2]['char_col'])); - $this->assertEquals('9.735', $data[0]['float_col']); - $this->assertEquals('-2.123', $data[1]['float_col']); - $this->assertEquals('2.123', $data[2]['float_col']); - $this->assertEquals('1', $data[0]['bool_col']); - $this->assertIsOneOf($data[1]['bool_col'], ['0', false]); - $this->assertIsOneOf($data[2]['bool_col'], ['0', false]); - } catch (Exception|Throwable $e) { - setlocale(LC_NUMERIC, $locale); - throw $e; - } - - setlocale(LC_NUMERIC, $locale); - } - - /** - * @throws Exception|InvalidConfigException|Throwable - */ - public function testInsert(): void - { - $db = $this->getConnection(); - - $db->createCommand('DELETE FROM {{customer}}')->execute(); - $command = $db->createCommand(); - $command->insert( - '{{customer}}', - [ - 'email' => 't1@example.com', - 'name' => 'test', - 'address' => 'test address', - ] - )->execute(); - $this->assertEquals(1, $db->createCommand('SELECT COUNT(*) FROM {{customer}};')->queryScalar()); - - $record = $db->createCommand('SELECT [[email]], [[name]], [[address]] FROM {{customer}}')->queryOne(); - $this->assertEquals([ - 'email' => 't1@example.com', - 'name' => 'test', - 'address' => 'test address', - ], $record); - } - - public function testInsertEx(): void - { - $db = $this->getConnection(); - - $result = $db->createCommand()->insertEx( - 'customer', - [ - 'name' => 'testParams', - 'email' => 'testParams@example.com', - 'address' => '1', - ] - ); - - $this->assertIsArray($result); - $this->assertNotNull($result['id']); - } - - /** - * Verify that {{}} are not going to be replaced in parameters. - */ - public function testNoTablenameReplacement(): void - { - $db = $this->getConnection(true); - - $db->createCommand()->insert( - '{{customer}}', - [ - 'name' => 'Some {{weird}} name', - 'email' => 'test@example.com', - 'address' => 'Some {{%weird}} address', - ] - )->execute(); - - if ($db->getDriver()->getDriverName() === 'pgsql') { - $customerId = $db->getLastInsertID('public.customer_id_seq'); - } else { - $customerId = $db->getLastInsertID(); - } - - $customer = $db->createCommand('SELECT * FROM {{customer}} WHERE id=' . $customerId)->queryOne(); - $this->assertIsArray($customer); - $this->assertEquals('Some {{weird}} name', $customer['name']); - $this->assertEquals('Some {{%weird}} address', $customer['address']); - - $db->createCommand()->update( - '{{customer}}', - [ - 'name' => 'Some {{updated}} name', - 'address' => 'Some {{%updated}} address', - ], - ['id' => $customerId] - )->execute(); - $customer = $db->createCommand('SELECT * FROM {{customer}} WHERE id=' . $customerId)->queryOne(); - $this->assertIsArray($customer); - $this->assertEquals('Some {{updated}} name', $customer['name']); - $this->assertEquals('Some {{%updated}} address', $customer['address']); - } - - /** - * Test INSERT INTO ... SELECT SQL statement. - */ - public function testInsertSelect(): void - { - $db = $this->getConnection(); - - $db->createCommand('DELETE FROM {{customer}}')->execute(); - $command = $db->createCommand(); - $command->insert( - '{{customer}}', - [ - 'email' => 't1@example.com', - 'name' => 'test', - 'address' => 'test address', - ] - )->execute(); - $query = new Query($db); - $query->select( - [ - '{{customer}}.[[email]] as name', - '[[name]] as email', - '[[address]]', - ] - ) - ->from('{{customer}}') - ->where([ - 'and', - ['<>', 'name', 'foo'], - ['status' => [0, 1, 2, 3]], - ]); - $command = $db->createCommand(); - $command->insert( - '{{customer}}', - $query - )->execute(); - $this->assertEquals(2, $db->createCommand('SELECT COUNT(*) FROM {{customer}}')->queryScalar()); - - $record = $db->createCommand('SELECT [[email]], [[name]], [[address]] FROM {{customer}}')->queryAll(); - $this->assertEquals([ - [ - 'email' => 't1@example.com', - 'name' => 'test', - 'address' => 'test address', - ], - [ - 'email' => 'test', - 'name' => 't1@example.com', - 'address' => 'test address', - ], - ], $record); - } - - /** - * Test INSERT INTO ... SELECT SQL statement with alias syntax. - */ - public function testInsertSelectAlias(): void - { - $db = $this->getConnection(); - - $db->createCommand('DELETE FROM {{customer}}')->execute(); - $command = $db->createCommand(); - $command->insert( - '{{customer}}', - [ - 'email' => 't1@example.com', - 'name' => 'test', - 'address' => 'test address', - ] - )->execute(); - $query = new Query($db); - $query->select( - [ - 'email' => '{{customer}}.[[email]]', - 'address' => 'name', - 'name' => 'address', - ] - ) - ->from('{{customer}}') - ->where([ - 'and', - ['<>', 'name', 'foo'], - ['status' => [0, 1, 2, 3]], - ]); - $command = $db->createCommand(); - $command->insert( - '{{customer}}', - $query - )->execute(); - $this->assertEquals(2, $db->createCommand('SELECT COUNT(*) FROM {{customer}}')->queryScalar()); - - $record = $db->createCommand('SELECT [[email]], [[name]], [[address]] FROM {{customer}}')->queryAll(); - $this->assertEquals([ - [ - 'email' => 't1@example.com', - 'name' => 'test', - 'address' => 'test address', - ], - [ - 'email' => 't1@example.com', - 'name' => 'test address', - 'address' => 'test', - ], - ], $record); - } - - public function testInsertExpression(): void - { - $expression = ''; - $db = $this->getConnection(); - - $db->createCommand('DELETE FROM {{order_with_null_fk}}')->execute(); - switch ($db->getDriver()->getDriverName()) { - case 'pgsql': - $expression = "EXTRACT(YEAR FROM TIMESTAMP 'now')"; - break; - case 'mysql': - $expression = 'YEAR(NOW())'; - break; - case 'sqlite': - $expression = "strftime('%Y')"; - break; - case 'sqlsrv': - $expression = 'YEAR(GETDATE())'; - } - $command = $db->createCommand(); - $command->insert( - '{{order_with_null_fk}}', - [ - 'created_at' => new Expression($expression), - 'total' => 1, - ] - )->execute(); - $this->assertEquals(1, $db->createCommand('SELECT COUNT(*) FROM {{order_with_null_fk}}')->queryScalar()); - - $record = $db->createCommand('SELECT [[created_at]] FROM {{order_with_null_fk}}')->queryOne(); - $this->assertEquals(['created_at' => date('Y')], $record); - } - - public function testsInsertQueryAsColumnValue(): void - { - $time = time(); - $db = $this->getConnection(true); - - $db->createCommand('DELETE FROM {{order_with_null_fk}}')->execute(); - $command = $db->createCommand(); - $command->insert('{{order}}', [ - 'customer_id' => 1, - 'created_at' => $time, - 'total' => 42, - ])->execute(); - - if ($db->getDriver()->getDriverName() === 'pgsql') { - $orderId = $db->getLastInsertID('public.order_id_seq'); - } else { - $orderId = $db->getLastInsertID(); - } - - $columnValueQuery = new Query($db); - $columnValueQuery->select('created_at')->from('{{order}}')->where(['id' => $orderId]); - $command = $db->createCommand(); - $command->insert( - '{{order_with_null_fk}}', - [ - 'customer_id' => $orderId, - 'created_at' => $columnValueQuery, - 'total' => 42, - ] - )->execute(); - $this->assertEquals( - $time, - $db->createCommand( - 'SELECT [[created_at]] FROM {{order_with_null_fk}} WHERE [[customer_id]] = ' . $orderId - )->queryScalar() - ); - - $db->createCommand('DELETE FROM {{order_with_null_fk}}')->execute(); - $db->createCommand('DELETE FROM {{order}} WHERE [[id]] = ' . $orderId)->execute(); - } - - public function testCreateTable(): void - { - $db = $this->getConnection(); - - if ($db->getSchema()->getTableSchema('testCreateTable') !== null) { - $db->createCommand()->dropTable('testCreateTable')->execute(); - } - - $db->createCommand()->createTable( - 'testCreateTable', - ['id' => Schema::TYPE_PK, 'bar' => Schema::TYPE_INTEGER] - )->execute(); - $db->createCommand()->insert('testCreateTable', ['bar' => 1])->execute(); - $records = $db->createCommand('SELECT [[id]], [[bar]] FROM {{testCreateTable}};')->queryAll(); - $this->assertEquals([['id' => 1, 'bar' => 1]], $records); - } - - public function testDropTable(): void - { - $db = $this->getConnection(); - - $tableName = 'type'; - $this->assertNotNull($db->getSchema()->getTableSchema($tableName)); - - $db->createCommand()->dropTable($tableName)->execute(); - $this->assertNull($db->getSchema()->getTableSchema($tableName)); - } - - public function testTruncateTable(): void - { - $db = $this->getConnection(); - - $rows = $db->createCommand('SELECT * FROM {{animal}}')->queryAll(); - $this->assertCount(2, $rows); - - $db->createCommand()->truncateTable('animal')->execute(); - $rows = $db->createCommand('SELECT * FROM {{animal}}')->queryAll(); - $this->assertCount(0, $rows); - } - - public function testRenameTable(): void - { - $db = $this->getConnection(true); - - $fromTableName = 'type'; - $toTableName = 'new_type'; - - if ($db->getSchema()->getTableSchema($toTableName) !== null) { - $db->createCommand()->dropTable($toTableName)->execute(); - } - - $this->assertNotNull($db->getSchema()->getTableSchema($fromTableName)); - $this->assertNull($db->getSchema()->getTableSchema($toTableName)); - - $db->createCommand()->renameTable($fromTableName, $toTableName)->execute(); - $this->assertNull($db->getSchema()->getTableSchema($fromTableName, true)); - $this->assertNotNull($db->getSchema()->getTableSchema($toTableName, true)); - } - - protected function performAndCompareUpsertResult(ConnectionInterface $db, array $data): void - { - $params = $data['params']; - $expected = $data['expected'] ?? $params[1]; - - $command = $db->createCommand(); - call_user_func_array([$command, 'upsert'], $params); - $command->execute(); - - $actual = (new Query($db)) - ->select([ - 'email', - 'address' => new Expression($this->upsertTestCharCast), - 'status', - ]) - ->from('T_upsert') - ->one(); - $this->assertEquals($expected, $actual, $this->upsertTestCharCast); - } - - public function testAddDropForeignKey(): void - { - $db = $this->getConnection(); - - $tableName = 'test_fk'; - $name = 'test_fk_constraint'; - - $schema = $db->getSchema(); - - if ($schema->getTableSchema($tableName) !== null) { - $db->createCommand()->dropTable($tableName)->execute(); - } - - $db->createCommand()->createTable($tableName, [ - 'int1' => 'integer not null unique', - 'int2' => 'integer not null unique', - 'int3' => 'integer not null unique', - 'int4' => 'integer not null unique', - 'unique ([[int1]], [[int2]])', - 'unique ([[int3]], [[int4]])', - ])->execute(); - $this->assertEmpty($schema->getTableForeignKeys($tableName, true)); - - $db->createCommand()->addForeignKey($name, $tableName, ['int1'], $tableName, ['int3'])->execute(); - $this->assertEquals(['int1'], $schema->getTableForeignKeys($tableName, true)[0]->getColumnNames()); - $this->assertEquals(['int3'], $schema->getTableForeignKeys($tableName, true)[0]->getForeignColumnNames()); - - $db->createCommand()->dropForeignKey($name, $tableName)->execute(); - $this->assertEmpty($schema->getTableForeignKeys($tableName, true)); - - $db->createCommand()->addForeignKey( - $name, - $tableName, - ['int1', 'int2'], - $tableName, - ['int3', 'int4'] - )->execute(); - $this->assertEquals( - ['int1', 'int2'], - $schema->getTableForeignKeys($tableName, true)[0]->getColumnNames() - ); - $this->assertEquals( - ['int3', 'int4'], - $schema->getTableForeignKeys($tableName, true)[0]->getForeignColumnNames() - ); - } - - public function testCreateDropIndex(): void - { - $db = $this->getConnection(); - - $tableName = 'test_idx'; - $name = 'test_idx_constraint'; - - $schema = $db->getSchema(); - - if ($schema->getTableSchema($tableName) !== null) { - $db->createCommand()->dropTable($tableName)->execute(); - } - - $db->createCommand()->createTable($tableName, [ - 'int1' => 'integer not null', - 'int2' => 'integer not null', - ])->execute(); - $this->assertEmpty($schema->getTableIndexes($tableName, true)); - - $db->createCommand()->createIndex($name, $tableName, ['int1'])->execute(); - $this->assertEquals(['int1'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames()); - $this->assertFalse($schema->getTableIndexes($tableName, true)[0]->isUnique()); - - $db->createCommand()->dropIndex($name, $tableName)->execute(); - $this->assertEmpty($schema->getTableIndexes($tableName, true)); - - $db->createCommand()->createIndex($name, $tableName, ['int1', 'int2'])->execute(); - $this->assertEquals(['int1', 'int2'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames()); - $this->assertFalse($schema->getTableIndexes($tableName, true)[0]->isUnique()); - - $db->createCommand()->dropIndex($name, $tableName)->execute(); - $this->assertEmpty($schema->getTableIndexes($tableName, true)); - $this->assertEmpty($schema->getTableIndexes($tableName, true)); - - $db->createCommand()->createIndex($name, $tableName, ['int1'], QueryBuilder::INDEX_UNIQUE)->execute(); - $this->assertEquals(['int1'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames()); - $this->assertTrue($schema->getTableIndexes($tableName, true)[0]->isUnique()); - - $db->createCommand()->dropIndex($name, $tableName)->execute(); - $this->assertEmpty($schema->getTableIndexes($tableName, true)); - - $db->createCommand()->createIndex($name, $tableName, ['int1', 'int2'], QueryBuilder::INDEX_UNIQUE)->execute(); - $this->assertEquals(['int1', 'int2'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames()); - $this->assertTrue($schema->getTableIndexes($tableName, true)[0]->isUnique()); - } - - public function testAddDropUnique(): void - { - $db = $this->getConnection(); - - $tableName = 'test_uq'; - $name = 'test_uq_constraint'; - - $schema = $db->getSchema(); - - if ($schema->getTableSchema($tableName) !== null) { - $db->createCommand()->dropTable($tableName)->execute(); - } - - $db->createCommand()->createTable($tableName, [ - 'int1' => 'integer not null', - 'int2' => 'integer not null', - ])->execute(); - $this->assertEmpty($schema->getTableUniques($tableName, true)); - - $db->createCommand()->addUnique($name, $tableName, ['int1'])->execute(); - $this->assertEquals(['int1'], $schema->getTableUniques($tableName, true)[0]->getColumnNames()); - - $db->createCommand()->dropUnique($name, $tableName)->execute(); - $this->assertEmpty($schema->getTableUniques($tableName, true)); - - $db->createCommand()->addUnique($name, $tableName, ['int1', 'int2'])->execute(); - $this->assertEquals(['int1', 'int2'], $schema->getTableUniques($tableName, true)[0]->getColumnNames()); - } - - public function testBindValues(): void - { - $command = $this->getConnection()->createCommand(); - - $values = [ - 'int' => 1, - 'string' => 'str', - ]; - $command->bindValues($values); - $bindedValues = $command->getParams(false); - - $this->assertIsArray($bindedValues); - $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); - $this->assertCount(2, $bindedValues); - - $param = new Param('str', 99); - $command->bindValues(['param' => $param]); - $bindedValues = $command->getParams(false); - - $this->assertIsArray($bindedValues); - $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); - $this->assertCount(3, $bindedValues); - $this->assertEquals($param, $bindedValues['param']); - $this->assertNotEquals($param, $bindedValues['int']); - - // Replace test - $command->bindValues(['int' => $param]); - $bindedValues = $command->getParams(false); - - $this->assertIsArray($bindedValues); - $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); - $this->assertCount(3, $bindedValues); - $this->assertEquals($param, $bindedValues['int']); - } - - public function testIntegrityViolation(): void - { - $this->expectException(IntegrityException::class); - - $db = $this->getConnection(); - - $sql = 'INSERT INTO {{profile}}([[id]], [[description]]) VALUES (123, \'duplicate\')'; - $command = $db->createCommand($sql); - $command->execute(); - $command->execute(); - } - - public function testLastInsertId(): void - { - $db = $this->getConnection(true); - - $sql = 'INSERT INTO {{profile}}([[description]]) VALUES (\'non duplicate\')'; - $command = $db->createCommand($sql); - $command->execute(); - $this->assertEquals(3, $db->getLastInsertID()); - } - - public function testLastInsertIdException(): void - { - $db = $this->getConnection(); - $db->close(); - - $this->expectException(InvalidCallException::class); - $db->getLastInsertID(); - } - - public function testQueryCache(): void - { - $db = $this->getConnection(true); - - /** @psalm-suppress PossiblyNullReference */ - $db->queryCacheEnable(true); - $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id'); - - $this->assertEquals('user1', $command->bindValue(':id', 1)->queryScalar()); - - $update = $db->createCommand('UPDATE {{customer}} SET [[name]] = :name WHERE [[id]] = :id'); - $update->bindValues([':id' => 1, ':name' => 'user11'])->execute(); - - $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar()); - - $db->cache(function (ConnectionInterface $db) use ($command, $update) { - $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar()); - - $update->bindValues([':id' => 2, ':name' => 'user22'])->execute(); - - $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar()); - - $db->noCache(function () use ($command) { - $this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar()); - }); - - $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar()); - }, 10); - - /** @psalm-suppress PossiblyNullReference */ - $db->queryCacheEnable(false); - - $db->cache(function () use ($command, $update) { - $this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar()); - $update->bindValues([':id' => 2, ':name' => 'user2'])->execute(); - $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar()); - }, 10); - - /** @psalm-suppress PossiblyNullReference */ - $db->queryCacheEnable(true); - $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id')->cache(); - - $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar()); - - $update->bindValues([':id' => 1, ':name' => 'user1'])->execute(); - - $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar()); - $this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar()); - - $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id'); - - $db->cache(function () use ($command) { - $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar()); - $this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar()); - }, 10); - } - - public function testColumnCase(): void - { - $db = $this->getConnection(); - - $this->assertEquals(PDO::CASE_NATURAL, $db->getActivePDO()->getAttribute(PDO::ATTR_CASE)); - - $sql = 'SELECT [[customer_id]], [[total]] FROM {{order}}'; - $rows = $db->createCommand($sql)->queryAll(); - $this->assertTrue(isset($rows[0])); - $this->assertTrue(isset($rows[0]['customer_id'])); - $this->assertTrue(isset($rows[0]['total'])); - - $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); - $rows = $db->createCommand($sql)->queryAll(); - $this->assertTrue(isset($rows[0])); - $this->assertTrue(isset($rows[0]['customer_id'])); - $this->assertTrue(isset($rows[0]['total'])); - - $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); - $rows = $db->createCommand($sql)->queryAll(); - $this->assertTrue(isset($rows[0])); - $this->assertTrue(isset($rows[0]['CUSTOMER_ID'])); - $this->assertTrue(isset($rows[0]['TOTAL'])); - } - - public function testTransaction(): void - { - $db = $this->getConnection(); - - $this->assertNull($db->getTransaction()); - - $command = $db->createCommand("INSERT INTO {{profile}}([[description]]) VALUES('command transaction')"); - $this->invokeMethod($command, 'requireTransaction'); - $command->execute(); - $this->assertNull($db->getTransaction()); - $this->assertEquals( - 1, - $db->createCommand( - "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command transaction'" - )->queryScalar() - ); - } - - public function testRetryHandler(): void - { - $db = $this->getConnection(); - - $this->assertNull($db->getTransaction()); - - $db->createCommand("INSERT INTO {{profile}}([[description]]) VALUES('command retry')")->execute(); - $this->assertNull($db->getTransaction()); - $this->assertEquals( - 1, - $db->createCommand( - "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command retry'" - )->queryScalar() - ); - - $attempts = null; - $hitHandler = false; - $hitCatch = false; - - $command = $db->createCommand( - "INSERT INTO {{profile}}([[id]], [[description]]) VALUES(1, 'command retry')" - ); - $this->invokeMethod( - $command, - 'setRetryHandler', - [static function ($exception, $attempt) use (&$attempts, &$hitHandler) { - $attempts = $attempt; - $hitHandler = true; - - return $attempt <= 2; - }] - ); - - try { - $command->execute(); - } catch (Exception $e) { - $hitCatch = true; - $this->assertInstanceOf(IntegrityException::class, $e); - } - - $this->assertNull($db->getTransaction()); - $this->assertSame(3, $attempts); - $this->assertTrue($hitHandler); - $this->assertTrue($hitCatch); - } - - public function testCreateView(): void - { - $db = $this->getConnection(); - - $subquery = (new Query($db)) - ->select('bar') - ->from('testCreateViewTable') - ->where(['>', 'bar', '5']); - - if ($db->getSchema()->getTableSchema('testCreateView') !== null) { - $db->createCommand()->dropView('testCreateView')->execute(); - } - - if ($db->getSchema()->getTableSchema('testCreateViewTable')) { - $db->createCommand()->dropTable('testCreateViewTable')->execute(); - } - - $db->createCommand()->createTable('testCreateViewTable', [ - 'id' => Schema::TYPE_PK, - 'bar' => Schema::TYPE_INTEGER, - ])->execute(); - $db->createCommand()->insert('testCreateViewTable', ['bar' => 1])->execute(); - $db->createCommand()->insert('testCreateViewTable', ['bar' => 6])->execute(); - $db->createCommand()->createView('testCreateView', $subquery)->execute(); - $records = $db->createCommand('SELECT [[bar]] FROM {{testCreateView}};')->queryAll(); - $this->assertEquals([['bar' => 6]], $records); - } - - public function testDropView(): void - { - $db = $this->getConnection(); - - /* since it already exists in the fixtures */ - $viewName = 'animal_view'; - - $this->assertNotNull($db->getSchema()->getTableSchema($viewName)); - - $db->createCommand()->dropView($viewName)->execute(); - $this->assertNull($db->getSchema()->getTableSchema($viewName)); - } - - public function batchInsertSqlProviderTrait(): array - { - $db = $this->getConnection(); - - return [ - 'multirow' => [ - 'type', - ['int_col', 'float_col', 'char_col', 'bool_col'], - 'values' => [ - ['0', '0.0', 'test string', true,], - [false, 0, 'test string2', false,], - ], - 'expected' => DbHelper::replaceQuotes( - 'INSERT INTO [[type]] ([[int_col]], [[float_col]], [[char_col]], [[bool_col]])' - . ' VALUES (:qp0, :qp1, :qp2, :qp3), (:qp4, :qp5, :qp6, :qp7)', - $db->getDriver()->getDriverName(), - ), - 'expectedParams' => [ - ':qp0' => 0, - ':qp1' => 0.0, - ':qp2' => 'test string', - ':qp3' => true, - ':qp4' => 0, - ':qp5' => 0.0, - ':qp6' => 'test string2', - ':qp7' => false, - ], - 2, - ], - 'issue11242' => [ - 'type', - ['int_col', 'float_col', 'char_col', 'bool_col'], - 'values' => [[1.0, 1.1, 'Kyiv {{city}}, Ukraine', true]], - /** - * {@see https://github.com/yiisoft/yii2/issues/11242} - * - * Make sure curly bracelets (`{{..}}`) in values will not be escaped - */ - 'expected' => DbHelper::replaceQuotes( - 'INSERT INTO [[type]] ([[int_col]], [[float_col]], [[char_col]], [[bool_col]])' - . ' VALUES (:qp0, :qp1, :qp2, :qp3)', - $db->getDriver()->getDriverName(), - ), - 'expectedParams' => [ - ':qp0' => 1, - ':qp1' => 1.1, - ':qp2' => 'Kyiv {{city}}, Ukraine', - ':qp3' => true, - ], - ], - 'wrongBehavior' => [ - '{{%type}}', - ['{{%type}}.[[int_col]]', '[[float_col]]', 'char_col', 'bool_col'], - 'values' => [['0', '0.0', 'Kyiv {{city}}, Ukraine', false]], - /** - * Test covers potentially wrong behavior and marks it as expected!. - * - * In case table name or table column is passed with curly or square bracelets, QueryBuilder can not - * determine the table schema and typecast values properly. - * TODO: make it work. Impossible without BC breaking for public methods. - */ - 'expected' => DbHelper::replaceQuotes( - 'INSERT INTO [[type]] ([[type]].[[int_col]], [[float_col]], [[char_col]], [[bool_col]])' - . ' VALUES (:qp0, :qp1, :qp2, :qp3)', - $db->getDriver()->getDriverName(), - ), - 'expectedParams' => [ - ':qp0' => '0', - ':qp1' => '0.0', - ':qp2' => 'Kyiv {{city}}, Ukraine', - ':qp3' => false, - ], - ], - 'batchInsert binds params from expression' => [ - '{{%type}}', - ['int_col', 'float_col', 'char_col', 'bool_col'], - /** - * This example is completely useless. This feature of batchInsert is intended to be used with complex - * expression objects, such as JsonExpression. - */ - 'values' => [[new Expression(':exp1', [':exp1' => 42]), 1, 'test', false]], - 'expected' => DbHelper::replaceQuotes( - 'INSERT INTO [[type]] ([[int_col]], [[float_col]], [[char_col]], [[bool_col]])' - . ' VALUES (:exp1, :qp1, :qp2, :qp3)', - $db->getDriver()->getDriverName(), - ), - 'expectedParams' => [ - ':exp1' => 42, - ':qp1' => 1.0, - ':qp2' => 'test', - ':qp3' => false, - ], - ], - ]; - } - - public function bindParamsNonWhereProviderTrait(): array - { - return [ - ['SELECT SUBSTR(name, :len) FROM {{customer}} WHERE [[email]] = :email GROUP BY SUBSTR(name, :len)'], - ['SELECT SUBSTR(name, :len) FROM {{customer}} WHERE [[email]] = :email ORDER BY SUBSTR(name, :len)'], - ['SELECT SUBSTR(name, :len) FROM {{customer}} WHERE [[email]] = :email'], - ]; - } - - public function getRawSqlProviderTrait(): array - { - return [ - [ - 'SELECT * FROM customer WHERE id = :id', - [':id' => 1], - 'SELECT * FROM customer WHERE id = 1', - ], - [ - 'SELECT * FROM customer WHERE id = :id', - ['id' => 1], - 'SELECT * FROM customer WHERE id = 1', - ], - [ - 'SELECT * FROM customer WHERE id = :id', - ['id' => null], - 'SELECT * FROM customer WHERE id = NULL', - ], - [ - 'SELECT * FROM customer WHERE id = :base OR id = :basePrefix', - [ - 'base' => 1, - 'basePrefix' => 2, - ], - 'SELECT * FROM customer WHERE id = 1 OR id = 2', - ], - /** - * {@see https://github.com/yiisoft/yii2/issues/9268} - */ - [ - 'SELECT * FROM customer WHERE active = :active', - [':active' => false], - 'SELECT * FROM customer WHERE active = FALSE', - ], - /** - * {@see https://github.com/yiisoft/yii2/issues/15122} - */ - [ - 'SELECT * FROM customer WHERE id IN (:ids)', - [':ids' => new Expression(implode(', ', [1, 2]))], - 'SELECT * FROM customer WHERE id IN (1, 2)', - ], - ]; - } - - public function invalidSelectColumnsProviderTrait(): array - { - return [ - [[]], - ['*'], - [['*']], - ]; - } - - public function upsertProviderTrait(): array - { - return [ - 'regular values' => [ - [ - 'params' => [ - 'T_upsert', - [ - 'email' => 'foo@example.com', - 'address' => 'Earth', - 'status' => 3, - ], - ], - ], - [ - 'params' => [ - 'T_upsert', - [ - 'email' => 'foo@example.com', - 'address' => 'Universe', - 'status' => 1, - ], - ], - ], - ], - 'regular values with update part' => [ - [ - 'params' => [ - 'T_upsert', - [ - 'email' => 'foo@example.com', - 'address' => 'Earth', - 'status' => 3, - ], - [ - 'address' => 'Moon', - 'status' => 2, - ], - ], - ], - [ - 'params' => [ - 'T_upsert', - [ - 'email' => 'foo@example.com', - 'address' => 'Universe', - 'status' => 1, - ], - [ - 'address' => 'Moon', - 'status' => 2, - ], - ], - 'expected' => [ - 'email' => 'foo@example.com', - 'address' => 'Moon', - 'status' => 2, - ], - ], - ], - 'regular values without update part' => [ - [ - 'params' => [ - 'T_upsert', - [ - 'email' => 'foo@example.com', - 'address' => 'Earth', - 'status' => 3, - ], - false, - ], - ], - [ - 'params' => [ - 'T_upsert', - [ - 'email' => 'foo@example.com', - 'address' => 'Universe', - 'status' => 1, - ], - false, - ], - 'expected' => [ - 'email' => 'foo@example.com', - 'address' => 'Earth', - 'status' => 3, - ], - ], - ], - 'query' => [ - [ - 'params' => [ - 'T_upsert', - (new Query($this->getConnection())) - ->select([ - 'email', - 'address', - 'status' => new Expression('1'), - ]) - ->from('customer') - ->where(['name' => 'user1']) - ->limit(1), - ], - 'expected' => [ - 'email' => 'user1@example.com', - 'address' => 'address1', - 'status' => 1, - ], - ], - [ - 'params' => [ - 'T_upsert', - (new Query($this->getConnection())) - ->select([ - 'email', - 'address', - 'status' => new Expression('2'), - ]) - ->from('customer') - ->where(['name' => 'user1']) - ->limit(1), - ], - 'expected' => [ - 'email' => 'user1@example.com', - 'address' => 'address1', - 'status' => 2, - ], - ], - ], - 'query with update part' => [ - [ - 'params' => [ - 'T_upsert', - (new Query($this->getConnection())) - ->select([ - 'email', - 'address', - 'status' => new Expression('1'), - ]) - ->from('customer') - ->where(['name' => 'user1']) - ->limit(1), - [ - 'address' => 'Moon', - 'status' => 2, - ], - ], - 'expected' => [ - 'email' => 'user1@example.com', - 'address' => 'address1', - 'status' => 1, - ], - ], - [ - 'params' => [ - 'T_upsert', - (new Query($this->getConnection())) - ->select([ - 'email', - 'address', - 'status' => new Expression('3'), - ]) - ->from('customer') - ->where(['name' => 'user1']) - ->limit(1), - [ - 'address' => 'Moon', - 'status' => 2, - ], - ], - 'expected' => [ - 'email' => 'user1@example.com', - 'address' => 'Moon', - 'status' => 2, - ], - ], - ], - 'query without update part' => [ - [ - 'params' => [ - 'T_upsert', - (new Query($this->getConnection())) - ->select([ - 'email', - 'address', - 'status' => new Expression('1'), - ]) - ->from('customer') - ->where(['name' => 'user1']) - ->limit(1), - false, - ], - 'expected' => [ - 'email' => 'user1@example.com', - 'address' => 'address1', - 'status' => 1, - ], - ], - [ - 'params' => [ - 'T_upsert', - (new Query($this->getConnection())) - ->select([ - 'email', - 'address', - 'status' => new Expression('2'), - ]) - ->from('customer') - ->where(['name' => 'user1']) - ->limit(1), - false, - ], - 'expected' => [ - 'email' => 'user1@example.com', - 'address' => 'address1', - 'status' => 1, - ], - ], - ], - ]; - } - - public function testAlterTable(): void - { - $db = $this->getConnection(); - - if ($db->getDriver()->getDriverName() === 'sqlite') { - $this->markTestSkipped('Sqlite does not support alterTable'); - } - - if ($db->getSchema()->getTableSchema('testAlterTable', true) !== null) { - $db->createCommand()->dropTable('testAlterTable')->execute(); - } - - $db->createCommand()->createTable( - 'testAlterTable', - [ - 'id' => Schema::TYPE_PK, - 'bar' => Schema::TYPE_INTEGER, - ] - )->execute(); - - $db->createCommand()->insert('testAlterTable', ['bar' => 1])->execute(); - $db->createCommand()->alterColumn('testAlterTable', 'bar', Schema::TYPE_STRING)->execute(); - $db->createCommand()->insert('testAlterTable', ['bar' => 'hello'])->execute(); - - $records = $db->createCommand('SELECT [[id]], [[bar]] FROM {{testAlterTable}}')->queryAll(); - $this->assertEquals([ - ['id' => 1, 'bar' => 1], - ['id' => 2, 'bar' => 'hello'], - ], $records); - } - - public function testInsertToBlob(): void - { - $db = $this->getConnection(true); - - $db->createCommand()->delete('type')->execute(); - - $columns = [ - 'int_col' => 1, - 'char_col' => 'test', - 'float_col' => 3.14, - 'bool_col' => true, - 'blob_col' => serialize(['test' => 'data', 'num' => 222]), - ]; - $db->createCommand()->insert('type', $columns)->execute(); - $result = $db->createCommand('SELECT [[blob_col]] FROM {{type}}')->queryOne(); - - $this->assertIsArray($result); - $resultBlob = is_resource($result['blob_col']) ? stream_get_contents($result['blob_col']) : $result['blob_col']; - - $this->assertEquals($columns['blob_col'], $resultBlob); - } -} diff --git a/tests/AbstractCommandTest.php b/tests/AbstractCommandTest.php index f7920d40b..65dda4bb1 100644 --- a/tests/AbstractCommandTest.php +++ b/tests/AbstractCommandTest.php @@ -5,279 +5,42 @@ namespace Yiisoft\Db\Tests; use PHPUnit\Framework\TestCase; -use Yiisoft\Cache\Dependency\TagDependency; use Yiisoft\Db\Command\CommandInterface; use Yiisoft\Db\Command\Param; use Yiisoft\Db\Command\ParamInterface; -use Yiisoft\Db\Driver\PDO\ConnectionPDOInterface; use Yiisoft\Db\Exception\Exception; -use Yiisoft\Db\Exception\InvalidCallException; use Yiisoft\Db\Exception\InvalidConfigException; -use Yiisoft\Db\Exception\InvalidParamException; use Yiisoft\Db\Exception\NotSupportedException; -use Yiisoft\Db\Expression\Expression; -use Yiisoft\Db\Query\Data\DataReader; -use Yiisoft\Db\Query\Query; -use Yiisoft\Db\Schema\Schema; -use Yiisoft\Db\Schema\SchemaBuilderTrait; use Yiisoft\Db\Tests\Support\Assert; use Yiisoft\Db\Tests\Support\DbHelper; use Yiisoft\Db\Tests\Support\TestTrait; -use function call_user_func_array; - abstract class AbstractCommandTest extends TestCase { - use SchemaBuilderTrait; use TestTrait; - protected ConnectionPDOInterface $db; protected string $upsertTestCharCast = ''; - public function testAddCheck(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->addCheck('name', 'table', 'id > 0')->getSql(); - - - $this->assertSame( - DbHelper::replaceQuotes( - << 0) - SQL, - $db->getName(), - ), - $sql, - ); - } - - public function testAddColumn(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->addColumn('table', 'column', Schema::TYPE_INTEGER)->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testAddCommentOnColumn(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->addCommentOnColumn('customer', 'id', 'Primary key.')->getSql(); - - $this->assertStringContainsString( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testAddCommentOnTable(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->addCommentOnTable('table', 'comment')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testAddForeignKey(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->addForeignKey('name', 'table', 'column', 'ref_table', 'ref_column')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - - $sql = $command->addForeignKey('name', 'table', 'column', 'ref_table', 'ref_column', 'CASCADE')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - - $sql = $command - ->addForeignKey('name', 'table', 'column', 'ref_table', 'ref_column', 'CASCADE', 'CASCADE') - ->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testAddPrimaryKey(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->addPrimaryKey('name', 'table', 'column')->getSql(); - - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - - $sql = $command->addPrimaryKey('name', 'table', ['column1', 'column2'])->getSql(); - - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testAddUnique(): void + public function testAutoQuoting(): void { $db = $this->getConnection(); - $command = $db->createCommand(); - $sql = $command->addUnique('name', 'table', 'column')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - - $sql = $command->addUnique('name', 'table', ['column1', 'column2'])->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testAlterColumn(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->alterColumn('table', 'column', Schema::TYPE_INTEGER)->getSql(); + $sql = <<createCommand($sql); $this->assertSame( DbHelper::replaceQuotes( <<getName(), ), - $sql, + $command->getSql(), ); } - public function testBindValues(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - - $values = ['int' => 1, 'string' => 'str']; - $command->bindValues($values); - $bindedValues = $command->getParams(false); - - $this->assertIsArray($bindedValues); - $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); - $this->assertCount(2, $bindedValues); - - $param = new Param('str', 99); - $command->bindValues(['param' => $param]); - $bindedValues = $command->getParams(false); - - $this->assertIsArray($bindedValues); - $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); - $this->assertCount(3, $bindedValues); - $this->assertSame($param, $bindedValues['param']); - $this->assertNotEquals($param, $bindedValues['int']); - - /* Replace test */ - $command->bindValues(['int' => $param]); - $bindedValues = $command->getParams(false); - - $this->assertIsArray($bindedValues); - $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); - $this->assertCount(3, $bindedValues); - $this->assertSame($param, $bindedValues['int']); - } - - public function testCache(): void - { - $db = $this->getConnection(); - - $tagDependency = new TagDependency('tag'); - $command = $db->createCommand(); - $command->cache(100, $tagDependency); - - $this->assertInstanceOf(CommandInterface::class, $command); - $this->assertSame(100, Assert::getInaccessibleProperty($command, 'queryCacheDuration')); - $this->assertSame($tagDependency, Assert::getInaccessibleProperty($command, 'queryCacheDependency')); - } - public function testConstruct(): void { $db = $this->getConnection(); @@ -295,277 +58,12 @@ public function testConstruct(): void $this->assertSame([':name' => 'John'], $command->getParams()); } - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::createIndex() - */ - public function testCreateIndex( - string $name, - string $table, - array|string $column, - string $indexType, - string $indexMethod, - string $expected, - ): void { - $db = $this->getConnection(); - - $command = $db->createCommand(); - - $sql = $command->createIndex($name, $table, $column, $indexType, $indexMethod)->getSql(); - - $this->assertSame($expected, $sql); - } - - public function testCreateView(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - - $sql = $command->createView( - 'view', - <<getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testDataReaderCreationException(): void - { - $db = $this->getConnection(); - - $this->expectException(InvalidParamException::class); - $this->expectExceptionMessage('The PDOStatement cannot be null.'); - - $sql = 'SELECT * FROM {{customer}}'; - new DataReader($db->createCommand($sql)); - } - - public function testDelete(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->delete('table', ['column' => 'value'])->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testDropCheck(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->dropCheck('name', 'table')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testDropColumn(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->dropColumn('table', 'column')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testDropCommentFromColumn(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->dropCommentFromColumn('table', 'column')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testDropCommentFromTable(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->dropCommentFromTable('table')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testDropForeingKey(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->dropForeignKey('name', 'table')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testDropIndex(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->dropIndex('name', 'table')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testDropPrimaryKey(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->dropPrimaryKey('name', 'table')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testDropTable(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->dropTable('table')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testDropView(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->dropView('view')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testDropUnique(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - $sql = $command->dropUnique('name', 'table')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - - public function testExecuteWithSqlEmtpy(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - - $this->assertSame(0, $command->execute()); - } - public function testGetParams(): void { $db = $this->getConnection(); $command = $db->createCommand(); - $values = [ - 'int' => 1, - 'string' => 'str', - ]; + $values = ['int' => 1, 'string' => 'str']; $command->bindValues($values); $bindedValues = $command->getParams(false); @@ -630,17 +128,6 @@ public function testGetSetSql(): void $this->assertSame($sql2, $command->getSql()); } - public function testLastInsertIdException(): void - { - $db = $this->getConnection(); - - $db->close(); - - $this->expectException(InvalidCallException::class); - - $db->getLastInsertID(); - } - public function testNoCache(): void { $db = $this->getConnection(); @@ -653,12 +140,12 @@ public function testNoCache(): void public function testPrepareCancel(): void { - $db = $this->getConnection('customer'); + $db = $this->getConnection(true); $command = $db->createCommand(); $command->setSql( <<assertNull($command->getPdoStatement()); } - public function testRenameColumn(): void - { - $db = $this->getConnection(); - - $sql = $db->createCommand()->renameColumn('table', 'oldname', 'newname')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, - ); - } - public function testSetRawSql(): void { $db = $this->getConnection(); @@ -704,52 +174,17 @@ public function testSetRawSql(): void $this->assertSame('SELECT 123', $command->getRawSql()); } - public function testSetRetryHandler(): void - { - $db = $this->getConnection(); - - $command = $db->createCommand(); - - $handler = static fn (): bool => true; - - Assert::invokeMethod($command, 'setRetryHandler', [$handler]); - - $this->assertSame($handler, Assert::getInaccessibleProperty($command, 'retryHandler')); - } - - public function testTruncateTable(): void + public function testSetSql(): void { $db = $this->getConnection(); $command = $db->createCommand(); - $sql = $command->truncateTable('table')->getSql(); - - $this->assertSame( - DbHelper::replaceQuotes( - <<getName(), - ), - $sql, + $command->setSql( + <<createCommand(); - - call_user_func_array([$command, 'upsert'], $params); - - $command->execute(); - $actual = (new Query($db)) - ->select(['email', 'address' => new Expression($this->upsertTestCharCast), 'status']) - ->from('T_upsert') - ->one(); - $this->assertEquals($expected, $actual, $this->upsertTestCharCast); + $this->assertSame('SELECT 123', $command->getSql()); } } diff --git a/tests/AbstractQuoterTest.php b/tests/AbstractQuoterTest.php new file mode 100644 index 000000000..2a88cd867 --- /dev/null +++ b/tests/AbstractQuoterTest.php @@ -0,0 +1,103 @@ +getConnection(); + + $this->assertSame($expected, $db->getQuoter()->ensureColumnName($columnName)); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::ensureNameQuoted() + */ + public function testEnsureNameQuoted(string $name, string $expected): void + { + $db = $this->getConnection(); + + $this->assertSame($expected, $db->getQuoter()->ensureNameQuoted($name)); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::tableNameParts() + */ + public function testGetTableNameParts(string $tableName, string ...$expected): void + { + $db = $this->getConnection(); + + $this->assertSame($expected, array_reverse($db->getQuoter()->getTableNameParts($tableName))); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::columnName() + */ + public function testQuoteColumnName(string $columnName, string $expected): void + { + $db = $this->getConnection(); + + $this->assertSame($expected, $db->getQuoter()->quoteColumnName($columnName)); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::columnName() + */ + public function testQuoteSimpleColumnName(string $columnName, string $expected): void + { + $db = $this->getConnection(); + + $this->assertSame($expected, $db->getQuoter()->quoteSimpleColumnName($columnName)); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::simpleTableName() + */ + public function testQuoteSimpleTableName(string $columnName, string $expected): void + { + $db = $this->getConnection(); + + $this->assertSame($expected, $db->getQuoter()->quoteSimpleTableName($columnName)); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::tableName() + */ + public function testQuoteTableName(string $tableName, string $expected): void + { + $db = $this->getConnection(); + + $this->assertSame($expected, $db->getQuoter()->quoteTableName($tableName)); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::unquoteSimpleColumnName() + */ + public function testUnquoteSimpleColumnName(string $columnName, string $expected): void + { + $db = $this->getConnection(); + + $this->assertSame($expected, $db->getQuoter()->unquoteSimpleColumnName($columnName)); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::unquoteSimpleTableName() + */ + public function testUnquoteSimpleTableName(string $tableName, string $expected): void + { + $db = $this->getConnection(); + + $this->assertSame($expected, $db->getQuoter()->unquoteSimpleTableName($tableName)); + } +} diff --git a/tests/Cache/QueryCacheTest.php b/tests/Cache/QueryCacheTest.php new file mode 100644 index 000000000..51c0f1bae --- /dev/null +++ b/tests/Cache/QueryCacheTest.php @@ -0,0 +1,93 @@ +assertInstanceOf(CacheInterface::class, Assert::getInaccessibleProperty($queryCache, 'cache')); + } + + public function testInfo(): void + { + $queryCache = new QueryCache(DbHelper::getCache()); + + $tagDependency = new TagDependency('tag'); + $queryCache->setInfo([3600, $tagDependency]); + + $this->assertIsArray($queryCache->info(null)); + $this->assertIsArray($queryCache->info(3600)); + } + + public function testIsEnabled(): void + { + $queryCache = new QueryCache(DbHelper::getCache()); + + $this->assertTrue($queryCache->isEnabled()); + } + + public function testRemoveLastInfo(): void + { + $queryCache = new QueryCache(DbHelper::getCache()); + + $tagDependency = new TagDependency('tag'); + $queryCache->setInfo([3600, $tagDependency]); + + $this->assertIsArray($queryCache->info(null)); + $this->assertIsArray($queryCache->info(3600)); + + $queryCache->removeLastInfo(); + + $this->assertNull($queryCache->info(null)); + $this->assertIsArray($queryCache->info(3600)); + } + + public function testSetDuration(): void + { + $queryCache = new QueryCache(DbHelper::getCache()); + + $queryCache->setDuration(10); + + $this->assertSame(10, $queryCache->getDuration()); + } + + public function testSetEnable(): void + { + $queryCache = new QueryCache(DbHelper::getCache()); + + $queryCache->setEnable(false); + + $this->assertFalse($queryCache->isEnabled()); + } + + public function testSetInfo(): void + { + $queryCache = new QueryCache(DbHelper::getCache()); + + $queryCache->setInfo('test'); + + $this->assertSame(['test'], Assert::getInaccessibleProperty($queryCache, 'info')); + + $queryCache->setInfo(['test2']); + + $this->assertSame(['test', ['test2']], Assert::getInaccessibleProperty($queryCache, 'info')); + } +} diff --git a/tests/Cache/SchemaCacheTest.php b/tests/Cache/SchemaCacheTest.php new file mode 100644 index 000000000..e3f717d49 --- /dev/null +++ b/tests/Cache/SchemaCacheTest.php @@ -0,0 +1,72 @@ +assertInstanceOf(CacheInterface::class, Assert::getInaccessibleProperty($schemaCache, 'cache')); + } + + public function testInvalidate(): void + { + $schemaCache = new SchemaCache(DbHelper::getCache()); + + $schemaCache->set('key', 'value', 3600, new TagDependency('tag')); + + $this->assertSame('value', $schemaCache->getOrSet('key')); + + $schemaCache->invalidate('tag'); + + $this->assertNull($schemaCache->getOrSet('key')); + } + + public function testSetDuration(): void + { + $schemaCache = new SchemaCache(DbHelper::getCache()); + + $schemaCache->setDuration(3600); + + $this->assertSame(3600, $schemaCache->getDuration()); + } + + public function testSetEnabled(): void + { + $schemaCache = new SchemaCache(DbHelper::getCache()); + + $schemaCache->setEnable(false); + + $this->assertFalse($schemaCache->isEnabled()); + + $schemaCache->setEnable(true); + + $this->assertTrue($schemaCache->isEnabled()); + } + + public function testSetExclude(): void + { + $schemaCache = new SchemaCache(DbHelper::getCache()); + + $schemaCache->setExclude(['table1', 'table2']); + + $this->assertSame(['table1', 'table2'], Assert::getInaccessibleProperty($schemaCache, 'exclude')); + } +} diff --git a/tests/Command/CommandTest.php b/tests/Command/CommandTest.php index 3a4824859..85c56a90d 100644 --- a/tests/Command/CommandTest.php +++ b/tests/Command/CommandTest.php @@ -4,9 +4,12 @@ namespace Yiisoft\Db\Tests\Command; -use Yiisoft\Db\Exception\Exception; -use Yiisoft\Db\Exception\InvalidConfigException; +use Yiisoft\Cache\Dependency\TagDependency; +use Yiisoft\Db\Command\CommandInterface; +use Yiisoft\Db\Driver\PDO\ConnectionPDOInterface; use Yiisoft\Db\Exception\NotSupportedException; +use Yiisoft\Db\Schema\Schema; +use Yiisoft\Db\Schema\SchemaBuilderTrait; use Yiisoft\Db\Tests\AbstractCommandTest; use Yiisoft\Db\Tests\Support\Assert; use Yiisoft\Db\Tests\Support\DbHelper; @@ -19,8 +22,84 @@ */ final class CommandTest extends AbstractCommandTest { + use SchemaBuilderTrait; use TestTrait; + protected ConnectionPDOInterface $db; + + public function testAddCheck(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addCheck('name', 'table', 'id > 0')->getSql(); + + + $this->assertSame( + DbHelper::replaceQuotes( + << 0) + SQL, + $db->getName(), + ), + $sql, + ); + } + + public function testAddColumn(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addColumn('table', 'column', Schema::TYPE_INTEGER)->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testAddCommentOnColumn(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addCommentOnColumn('customer', 'id', 'Primary key.')->getSql(); + + $this->assertStringContainsString( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testAddCommentOnTable(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addCommentOnTable('table', 'comment')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + public function testAddDefaultValue(): void { $db = $this->getConnection(); @@ -29,12 +108,77 @@ public function testAddDefaultValue(): void $this->expectException(NotSupportedException::class); $this->expectExceptionMessage( - 'Yiisoft\Db\Tests\Support\Stub\DDLQueryBuilder does not support adding default value constraints.' + 'Yiisoft\Db\QueryBuilder\DDLQueryBuilder::addDefaultValue is not supported by this DBMS.' ); $command->addDefaultValue('name', 'table', 'column', 'value'); } + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::addForeignKeySql() + */ + public function testAddForeignKeySql( + string $name, + string $tableName, + array|string $column1, + array|string $column2, + string|null $delete, + string|null $update, + string $expected + ): void { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addForeignKey($name, $tableName, $column1, $tableName, $column2, $delete, $update)->getSql(); + + $this->assertSame($expected, $sql); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::addPrimaryKeySql() + */ + public function testAddPrimaryKeySql(string $name, string $tableName, array|string $column, string $expected): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addPrimaryKey($name, $tableName, $column)->getSql(); + + + $this->assertSame($expected, $sql); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::addUniqueSql() + */ + public function testAddUniqueSql(string $name, string $tableName, array|string $column, string $expected): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addUnique($name, $tableName, $column)->getSql(); + + $this->assertSame($expected, $sql); + } + + public function testAlterColumn(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->alterColumn('table', 'column', Schema::TYPE_INTEGER)->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + public function testBatchInsert(): void { $db = $this->getConnection(); @@ -43,12 +187,45 @@ public function testBatchInsert(): void $this->expectException(NotSupportedException::class); $this->expectExceptionMessage( - 'Yiisoft\Db\Tests\Support\Stub\Schema::loadTableSchema() is not supported by core-db.' + 'Yiisoft\Db\Tests\Support\Stub\Schema::loadTableSchema is not supported by this DBMS.' ); $command->batchInsert('table', ['column1', 'column2'], [['value1', 'value2'], ['value3', 'value4']]); } + public function testCache(): void + { + $db = $this->getConnection(); + + $tagDependency = new TagDependency('tag'); + $command = $db->createCommand(); + $command->cache(100, $tagDependency); + + $this->assertInstanceOf(CommandInterface::class, $command); + $this->assertSame(100, Assert::getInaccessibleProperty($command, 'queryCacheDuration')); + $this->assertSame($tagDependency, Assert::getInaccessibleProperty($command, 'queryCacheDependency')); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::createIndexSql() + */ + public function testCreateIndexSql( + string $name, + string $table, + array|string $column, + string $indexType, + string $indexMethod, + string $expected, + ): void { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $sql = $command->createIndex($name, $table, $column, $indexType, $indexMethod)->getSql(); + + $this->assertSame($expected, $sql); + } + public function testCheckIntegrity(): void { $db = $this->getConnection(); @@ -57,7 +234,7 @@ public function testCheckIntegrity(): void $this->expectException(NotSupportedException::class); $this->expectExceptionMessage( - 'Yiisoft\Db\Tests\Support\Stub\DDLQueryBuilder does not support enabling/disabling integrity check.' + 'Yiisoft\Db\QueryBuilder\DDLQueryBuilder::checkIntegrity is not supported by this DBMS.' ); $command->checkIntegrity('schema', 'table')->execute(); @@ -97,6 +274,120 @@ public function testCreateTable(): void Assert::equalsWithoutLE($expected, $sql); } + public function testCreateView(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $sql = $command->createView( + 'view', + <<getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDelete(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->delete('table', ['column' => 'value'])->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropCheck(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropCheck('name', 'table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropColumn(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropColumn('table', 'column')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropCommentFromColumn(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropCommentFromColumn('table', 'column')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropCommentFromTable(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropCommentFromTable('table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + public function testDropDefaultValue(): void { $db = $this->getConnection(); @@ -105,12 +396,120 @@ public function testDropDefaultValue(): void $this->expectException(NotSupportedException::class); $this->expectExceptionMessage( - 'Yiisoft\Db\Tests\Support\Stub\DDLQueryBuilder does not support dropping default value constraints.' + 'Yiisoft\Db\QueryBuilder\DDLQueryBuilder::dropDefaultValue is not supported by this DBMS.' ); $command->dropDefaultValue('column', 'table'); } + public function testDropForeingKey(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropForeignKey('name', 'table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropIndex(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropIndex('name', 'table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropPrimaryKey(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropPrimaryKey('name', 'table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropView(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropView('view')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropTable(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropTable('table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropUnique(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropUnique('name', 'table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + public function testExecute(): void { $db = $this->getConnection(); @@ -119,7 +518,7 @@ public function testExecute(): void $this->expectException(NotSupportedException::class); $this->expectExceptionMessage( - 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute() is not supported by core-db.' + 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute is not supported by this DBMS.' ); $command->createTable('customer', ['id' => 'pk'])->execute(); @@ -133,7 +532,7 @@ public function testInsert(): void $this->expectException(NotSupportedException::class); $this->expectExceptionMessage( - 'Yiisoft\Db\Tests\Support\Stub\Schema::loadTableSchema() is not supported by core-db.' + 'Yiisoft\Db\Tests\Support\Stub\Schema::loadTableSchema is not supported by this DBMS.' ); $command->insert('customer', ['email' => 't1@example.com', 'name' => 'test', 'address' => 'test address']); @@ -141,18 +540,18 @@ public function testInsert(): void public function testQuery(): void { - $db = $this->getConnection('customer'); + $db = $this->getConnection(true); $command = $db->createCommand(); $command->setSql( <<expectException(NotSupportedException::class); $this->expectExceptionMessage( - 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute() is not supported by core-db.' + 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute is not supported by this DBMS.' ); $command->query(); @@ -160,23 +559,93 @@ public function testQuery(): void public function testQueryAll(): void { - $db = $this->getConnection('customer'); + $db = $this->getConnection(true); $command = $db->createCommand(); $command->setSql( <<expectException(NotSupportedException::class); $this->expectExceptionMessage( - 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute() is not supported by core-db.' + 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute is not supported by this DBMS.' ); $command->queryAll(); } + public function testQueryColumn(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->setSql( + <<expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute is not supported by this DBMS.' + ); + + $command->queryColumn(); + } + + public function testQueryOne(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $sql = <<expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute is not supported by this DBMS.' + ); + + $command->setSql($sql)->queryOne(); + } + + public function testQueryScalar(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $sql = <<expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute is not supported by this DBMS.' + ); + + $this->assertEquals(1, $command->setSql($sql)->queryScalar()); + } + + public function testRenameColumn(): void + { + $db = $this->getConnection(); + + $sql = $db->createCommand()->renameColumn('table', 'oldname', 'newname')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + public function testRenameTable(): void { $db = $this->getConnection(); @@ -200,20 +669,58 @@ public function testResetSequence(): void $this->expectException(NotSupportedException::class); $this->expectExceptionMessage( - 'Yiisoft\Db\Tests\Support\Stub\DMLQueryBuilder does not support resetting sequence.' + 'Yiisoft\Db\QueryBuilder\DMLQueryBuilder::resetSequence() is not supported by this DBMS.' ); $db->createCommand()->resetSequence('table', 5); } - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::upsert() - * - * @throws Exception - * @throws InvalidConfigException - * @throws NotSupportedException - */ - public function testUpsert(array $firstData, array $secondData): void + public function testSetRetryHandler(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $handler = static fn (): bool => true; + + Assert::invokeMethod($command, 'setRetryHandler', [$handler]); + + $this->assertSame($handler, Assert::getInaccessibleProperty($command, 'retryHandler')); + } + + public function testTruncateTable(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->truncateTable('{{table}}')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testUpdate(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\Schema::loadTableSchema is not supported by this DBMS.' + ); + + $command->update('{{table}}', [], [], []); + } + + public function testUpsert(): void { $db = $this->getConnection(); @@ -224,6 +731,6 @@ public function testUpsert(array $firstData, array $secondData): void 'Yiisoft\Db\Tests\Support\Stub\DMLQueryBuilder does not support upsert.' ); - $command->upsert('table', $firstData); + $command->upsert('{{table}}', []); } } diff --git a/tests/Common/CommonCommandPDOTest.php b/tests/Common/CommonCommandPDOTest.php new file mode 100644 index 000000000..26f88b0c8 --- /dev/null +++ b/tests/Common/CommonCommandPDOTest.php @@ -0,0 +1,186 @@ +getConnection(true); + + /** @psalm-var $sql */ + $sql = DbHelper::replaceQuotes( + <<getName(), + ); + $command = $db->createCommand(); + $command->setSql($sql); + $command->bindParam($name, $value, $dataType, $length, $driverOptions); + + $this->assertSame($sql, $command->getSql()); + $this->assertSame($expected, $command->queryOne()); + } + + /** + * Test whether param binding works in other places than WHERE. + * + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandPDOProvider::bindParamsNonWhere() + */ + public function testBindParamsNonWhere(string $sql): void + { + $db = $this->getConnection(true); + + $db->createCommand()->insert( + '{{customer}}', + [ + 'name' => 'testParams', + 'email' => 'testParams@example.com', + 'address' => '1', + ] + )->execute(); + $params = [':email' => 'testParams@example.com', ':len' => 5]; + $command = $db->createCommand($sql, $params); + + $this->assertSame('Params', $command->queryScalar()); + } + + public function testBindParamValue(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + + // bindParam + $command->setSql( + <<bindParam(':email', $email); + $command->bindParam(':name', $name); + $command->bindParam(':address', $address); + $command->execute(); + $command = $command->setSql( + <<bindParam(':email', $email); + + $this->assertSame($name, $command->queryScalar()); + + // bindValue + $command->setSql( + <<bindValue(':email', 'user5@example.com'); + $command->execute(); + $command->setSql( + <<bindValue(':name', 'user5'); + + $this->assertSame('user5@example.com', $command->queryScalar()); + } + + public function testBindValues(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $values = ['int' => 1, 'string' => 'str']; + $command->bindValues($values); + $bindedValues = $command->getParams(false); + + $this->assertIsArray($bindedValues); + $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); + $this->assertCount(2, $bindedValues); + + $param = new Param('str', 99); + $command->bindValues(['param' => $param]); + $bindedValues = $command->getParams(false); + + $this->assertIsArray($bindedValues); + $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); + $this->assertCount(3, $bindedValues); + $this->assertSame($param, $bindedValues['param']); + $this->assertNotEquals($param, $bindedValues['int']); + + /* Replace test */ + $command->bindValues(['int' => $param]); + $bindedValues = $command->getParams(false); + + $this->assertIsArray($bindedValues); + $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); + $this->assertCount(3, $bindedValues); + $this->assertSame($param, $bindedValues['int']); + } + + public function testColumnCase(): void + { + $db = $this->getConnection(true); + + $this->assertSame(PDO::CASE_NATURAL, $db->getActivePDO()?->getAttribute(PDO::ATTR_CASE)); + + $command = $db->createCommand(); + $sql = <<setSql($sql)->queryAll(); + + $this->assertTrue(isset($rows[0])); + $this->assertTrue(isset($rows[0]['customer_id'])); + $this->assertTrue(isset($rows[0]['total'])); + + $db->getActivePDO()?->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + + $this->assertSame(PDO::CASE_LOWER, $db->getActivePDO()?->getAttribute(PDO::ATTR_CASE)); + + $rows = $command->setSql($sql)->queryAll(); + + $this->assertTrue(isset($rows[0])); + $this->assertTrue(isset($rows[0]['customer_id'])); + $this->assertTrue(isset($rows[0]['total'])); + + $db->getActivePDO()?->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); + + $this->assertSame(PDO::CASE_UPPER, $db->getActivePDO()?->getAttribute(PDO::ATTR_CASE)); + + $rows = $command->setSql($sql)->queryAll(); + + $this->assertTrue(isset($rows[0])); + $this->assertTrue(isset($rows[0]['CUSTOMER_ID'])); + $this->assertTrue(isset($rows[0]['TOTAL'])); + } +} diff --git a/tests/Common/CommonCommandTest.php b/tests/Common/CommonCommandTest.php new file mode 100644 index 000000000..1cab7ee6e --- /dev/null +++ b/tests/Common/CommonCommandTest.php @@ -0,0 +1,1645 @@ +getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{test_ck}}') !== null) { + $command->dropTable('{{test_ck}}')->execute(); + } + + $command->createTable('{{test_ck}}', ['int1' => 'integer'])->execute(); + + $this->assertEmpty($schema->getTableChecks('{{test_ck}}', true)); + + $command->addCheck('{{test_ck_constraint}}', '{{test_ck}}', '{{int1}} > 1')->execute(); + + $this->assertMatchesRegularExpression( + '/^.*int1.*>.*1.*$/', + $schema->getTableChecks('{{test_ck}}', true)[0]->getExpression() + ); + } + + public function testAddColumn(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->addColumn('{{customer}}', '{{city}}', Schema::TYPE_STRING)->execute(); + + $this->assertTrue($db->getTableSchema('{{customer}}')->getColumn('city') !== null); + $this->assertSame(Schema::TYPE_STRING, $db->getTableSchema('{{customer}}')->getColumn('city')->getType()); + } + + public function testAddCommentOnColumn(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + $command->addCommentOnColumn('{{customer}}', 'id', 'Primary key.')->execute(); + + $commentOnColumn = match ($db->getName()) { + 'mysql', 'pgsql', 'oci' => [ + 'comment' => $schema->getTableSchema('{{customer}}')->getColumn('id')->getComment(), + ], + 'sqlsrv' => DbHelper::getCommmentsFromColumn('customer', 'id', $db), + }; + + $this->assertSame(['comment' => 'Primary key.'], $commentOnColumn); + } + + public function testAddCommentOnTable(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->addCommentOnTable('{{customer}}', 'Customer table.')->execute(); + $commentOnTable = DbHelper::getCommmentsFromTable('customer', $db); + + $this->assertSame(['comment' => 'Customer table.'], $commentOnTable); + } + + public function testAddDefaultValue() + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{test_def}}') !== null) { + $command->dropTable('{{test_def}}')->execute(); + } + + $command->createTable('{{test_def}}', ['int1' => Schema::TYPE_INTEGER])->execute(); + + $this->assertEmpty($schema->getTableDefaultValues('{{test_def}}', true)); + + $command->addDefaultValue('{{test_def_constraint}}', '{{test_def}}', 'int1', 41)->execute(); + + $this->assertMatchesRegularExpression( + '/^.*41.*$/', + $schema->getTableDefaultValues('{{test_def}}', true)[0]->getValue(), + ); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::addForeignKey() + */ + public function testAddForeignKey( + string $name, + string $tableName, + array|string $column1, + array|string $column2, + string $expectedName, + ): void { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema($tableName) !== null) { + $command->dropTable($tableName)->execute(); + } + + $command->createTable( + $tableName, + [ + 'int1' => 'integer not null unique', + 'int2' => 'integer not null unique', + 'int3' => 'integer not null unique', + 'int4' => 'integer not null unique', + 'unique ([[int1]], [[int2]])', + 'unique ([[int3]], [[int4]])', + ], + )->execute(); + + $this->assertEmpty($schema->getTableForeignKeys($tableName, true)); + + $command->addForeignKey($name, $tableName, $column1, $tableName, $column2)->execute(); + + $this->assertSame($expectedName, $schema->getTableForeignKeys($tableName, true)[0]->getName()); + + if (is_string($column1)) { + $column1 = [$column1]; + } + + $this->assertSame($column1, $schema->getTableForeignKeys($tableName, true)[0]->getColumnNames()); + + if (is_string($column2)) { + $column2 = [$column2]; + } + + $this->assertSame($column2, $schema->getTableForeignKeys($tableName, true)[0]->getForeignColumnNames()); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::addPrimaryKey() + */ + public function testAddPrimaryKey(string $name, string $tableName, array|string $column): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema($tableName) !== null) { + $command->dropTable($tableName)->execute(); + } + + $command->createTable($tableName, ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute(); + + $this->assertNull($schema->getTablePrimaryKey($tableName, true)); + + $db->createCommand()->addPrimaryKey($name, $tableName, $column)->execute(); + + if (is_string($column)) { + $column = [$column]; + } + + $this->assertSame($column, $schema->getTablePrimaryKey($tableName, true)->getColumnNames()); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::addUnique() + */ + public function testAddUnique(string $name, string $tableName, array|string $column): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema($tableName) !== null) { + $command->dropTable($tableName)->execute(); + } + + $command->createTable($tableName, ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute(); + + $this->assertEmpty($schema->getTableUniques($tableName, true)); + + $command->addUnique($name, $tableName, $column)->execute(); + + if (is_string($column)) { + $column = [$column]; + } + + $this->assertSame($column, $schema->getTableUniques($tableName, true)[0]->getColumnNames()); + } + + /** + * Make sure that `{{something}}` in values will not be encoded. + * + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::batchInsert() + * + * {@see https://github.com/yiisoft/yii2/issues/11242} + */ + public function testBatchInsert( + string $table, + array $columns, + array $values, + string $expected, + array $expectedParams = [], + int $insertedRow = 1 + ): void { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->batchInsert($table, $columns, $values); + $command->prepare(false); + + $this->assertSame($expected, $command->getSql()); + $this->assertSame($expectedParams, $command->getParams()); + + $command->execute(); + + $this->assertEquals($insertedRow, (new Query($db))->from($table)->count()); + } + + /** + * Test batch insert with different data types. + * + * Ensure double is inserted with `.` decimal separator. + * + * @link https://github.com/yiisoft/yii2/issues/6526 + */ + public function testBatchInsertDataTypesLocale(): void + { + $locale = setlocale(LC_NUMERIC, 0); + + if ($locale === false) { + $this->markTestSkipped('Your platform does not support locales.'); + } + + $db = $this->getConnection(true); + + $command = $db->createCommand(); + + try { + /* This one sets decimal mark to comma sign */ + setlocale(LC_NUMERIC, 'ru_RU.utf8'); + + $cols = ['int_col', 'char_col', 'float_col', 'bool_col']; + $data = [[1, 'A', 9.735, true], [2, 'B', -2.123, false], [3, 'C', 2.123, false]]; + + /* clear data in "type" table */ + $command->delete('{{type}}')->execute(); + + /* change, for point oracle. */ + if ($db->getName() === 'oci') { + $command->setSql( + <<execute(); + } + + /* batch insert on "type" table */ + $command->batchInsert('{{type}}', $cols, $data)->execute(); + $data = $command->setSql( + <<queryAll(); + + $this->assertCount(3, $data); + $this->assertEquals(1, $data[0]['int_col']); + $this->assertEquals(2, $data[1]['int_col']); + $this->assertEquals(3, $data[2]['int_col']); + + /* rtrim because Postgres padds the column with whitespace */ + $this->assertSame('A', rtrim($data[0]['char_col'])); + $this->assertSame('B', rtrim($data[1]['char_col'])); + $this->assertSame('C', rtrim($data[2]['char_col'])); + $this->assertEquals(9.735, $data[0]['float_col']); + $this->assertEquals(-2.123, $data[1]['float_col']); + $this->assertEquals(2.123, $data[2]['float_col']); + $this->assertEquals(1, $data[0]['bool_col']); + Assert::isOneOf($data[1]['bool_col'], ['0', false]); + Assert::isOneOf($data[2]['bool_col'], ['0', false]); + } catch (Exception | Throwable $e) { + setlocale(LC_NUMERIC, $locale); + + throw $e; + } + + setlocale(LC_NUMERIC, $locale); + } + + public function testBatchInsertFailsOld(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->batchInsert( + '{{customer}}', + ['email', 'name', 'address'], + [['t1@example.com', 'test_name', 'test_address']], + ); + + $this->assertSame(1, $command->execute()); + + $result = (new Query($db)) + ->select(['email', 'name', 'address']) + ->from('{{customer}}') + ->where(['=', '{{email}}', 't1@example.com']) + ->one(); + + $this->assertCount(3, $result); + $this->assertSame(['email' => 't1@example.com', 'name' => 'test_name', 'address' => 'test_address'], $result); + } + + public function testBatchInsertWithManyData(): void + { + $db = $this->getConnection(true); + + $values = []; + $attemptsInsertRows = 200; + $command = $db->createCommand(); + + for ($i = 0; $i < $attemptsInsertRows; $i++) { + $values[$i] = ['t' . $i . '@any.com', 't' . $i, 't' . $i . ' address']; + } + + $command->batchInsert('{{customer}}', ['email', 'name', 'address'], $values); + + $this->assertSame($attemptsInsertRows, $command->execute()); + + $insertedRowsCount = (new Query($db))->from('{{customer}}')->count(); + + $this->assertGreaterThanOrEqual($attemptsInsertRows, $insertedRowsCount); + } + + public function testBatchInsertWithYield(): void + { + $db = $this->getConnection(true); + + $rows = ( + static function () { + yield ['test@email.com', 'test name', 'test address']; + } + )(); + $command = $db->createCommand(); + $command->batchInsert('{{customer}}', ['email', 'name', 'address'], $rows); + + $this->assertSame(1, $command->execute()); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::createIndex() + */ + public function testCreateIndex( + string $name, + string $tableName, + array|string $column, + string|null $indexType, + string|null $indexMethod, + ): void { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema($tableName) !== null) { + $command->dropTable($tableName)->execute(); + } + + $command->createTable($tableName, ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute(); + + $this->assertEmpty($schema->getTableIndexes($tableName, true)); + + $command->createIndex($name, $tableName, $column, $indexType, $indexMethod)->execute(); + + if (is_string($column)) { + $column = [$column]; + } + + $this->assertSame($column, $schema->getTableIndexes($tableName, true)[0]->getColumnNames()); + + if ($indexType === 'UNIQUE') { + $this->assertTrue($schema->getTableIndexes($tableName, true)[0]->isUnique()); + } else { + $this->assertFalse($schema->getTableIndexes($tableName, true)[0]->isUnique()); + } + } + + public function testCreateTable(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{testCreateTable}}', true) !== null) { + $command->dropTable('{{testCreateTable}}')->execute(); + } + + $command->createTable( + '{{testCreateTable}}', + ['[[id]]' => Schema::TYPE_PK, '[[bar]]' => Schema::TYPE_INTEGER], + )->execute(); + $command->insert('{{testCreateTable}}', ['[[bar]]' => 1])->execute(); + $records = $command->setSql( + <<queryAll(); + + $this->assertEquals([['id' => 1, 'bar' => 1]], $records); + } + + public function testCreateView(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + $subQuery = (new Query($db))->select('{{bar}}')->from('{{testCreateViewTable}}')->where(['>', 'bar', '5']); + + if ($schema->getTableSchema('{{testCreateView}}') !== null) { + $command->dropView('{{testCreateView}}')->execute(); + } + + if ($schema->getTableSchema('{{testCreateViewTable}}')) { + $command->dropTable('{{testCreateViewTable}}')->execute(); + } + + $command->createTable( + '{{testCreateViewTable}}', + ['id' => Schema::TYPE_PK, 'bar' => Schema::TYPE_INTEGER], + )->execute(); + $command->insert('{{testCreateViewTable}}', ['bar' => 1])->execute(); + $command->insert('{{testCreateViewTable}}', ['bar' => 6])->execute(); + $command->createView('{{testCreateView}}', $subQuery)->execute(); + $records = $command->setSql( + <<queryAll(); + + $this->assertEquals([['bar' => 6]], $records); + } + + public function testDataReaderRewindException(): void + { + $db = $this->getConnection(true); + + $this->expectException(InvalidCallException::class); + $this->expectExceptionMessage('DataReader cannot rewind. It is a forward-only reader.'); + + $command = $db->createCommand(); + $reader = $command->setSql( + <<query(); + $reader->next(); + $reader->rewind(); + } + + public function testDelete(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->delete('{{customer}}', ['id' => 2])->execute(); + $chekSql = <<setSql($chekSql); + + $this->assertSame('2', $command->queryScalar()); + + $command->delete('{{customer}}', ['id' => 3])->execute(); + $command->setSql($chekSql); + + $this->assertSame('1', $command->queryScalar()); + } + + public function testDropCheck() + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{test_ck}}') !== null) { + $command->dropTable('{{test_ck}}')->execute(); + } + + $command->createTable('{{test_ck}}', ['int1' => 'integer'])->execute(); + + $this->assertEmpty($schema->getTableChecks('{{test_ck}}', true)); + + $command->addCheck('{{test_ck_constraint}}', '{{test_ck}}', '[[int1]] > 1')->execute(); + + $this->assertMatchesRegularExpression( + '/^.*int1.*>.*1.*$/', + $schema->getTableChecks('{{test_ck}}', true)[0]->getExpression(), + ); + + $command->dropCheck('{{test_ck_constraint}}', '{{test_ck}}')->execute(); + + $this->assertEmpty($schema->getTableChecks('{{test_ck}}', true)); + } + + public function testDropColumn(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{testDropColumn}}', true) !== null) { + $command->dropTable('{{testDropColumn}}')->execute(); + } + + $command->createTable( + '{{testDropColumn}}', + ['id' => Schema::TYPE_PK, 'bar' => Schema::TYPE_INTEGER, 'baz' => Schema::TYPE_INTEGER], + )->execute(); + $command->dropColumn('{{testDropColumn}}', 'bar')->execute(); + + $this->assertArrayNotHasKey('bar', $schema->getTableSchema('{{testDropColumn}}')->getColumns()); + $this->assertArrayHasKey('baz', $schema->getTableSchema('{{testDropColumn}}')->getColumns()); + } + + public function testDropCommentFromColumn(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + $command->addCommentOnColumn('{{customer}}', 'id', 'Primary key.')->execute(); + $commentOnColumn = match ($db->getName()) { + 'mysql', 'pgsql', 'oci' => [ + 'comment' => $schema->getTableSchema('{{customer}}')->getColumn('id')->getComment(), + ], + 'sqlsrv' => DbHelper::getCommmentsFromColumn('customer', 'id', $db), + }; + + $this->assertSame(['comment' => 'Primary key.'], $commentOnColumn); + + $command->dropCommentFromColumn('{{customer}}', 'id')->execute(); + $commentOnColumn = match ($db->getName()) { + 'mysql', 'pgsql', 'oci' => $schema->getTableSchema('{{customer}}')->getColumn('id')->getComment(), + 'sqlsrv' => DbHelper::getCommmentsFromColumn('customer', 'id', $db), + }; + + $this->assertEmpty($commentOnColumn); + } + + public function testDropCommentFromTable(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->addCommentOnTable('{{customer}}', 'Customer table.')->execute(); + $commentOnTable = DbHelper::getCommmentsFromTable('customer', $db); + + $this->assertSame(['comment' => 'Customer table.'], $commentOnTable); + + $command->dropCommentFromTable('{{customer}}')->execute(); + $commentOnTable = DbHelper::getCommmentsFromTable('customer', $db); + + $this->assertNull($commentOnTable); + } + + public function testDropDefaultValue(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{test_def}}') !== null) { + $command->dropTable('{{test_def}}')->execute(); + } + + $command->createTable('{{test_def}}', ['int1' => 'integer'])->execute(); + + $this->assertEmpty($schema->getTableDefaultValues('{{test_def}}', true)); + + $command->addDefaultValue('{{test_def_constraint}}', '{{test_def}}', 'int1', 41)->execute(); + + $this->assertMatchesRegularExpression( + '/^.*41.*$/', + $schema->getTableDefaultValues('{{test_def}}', true)[0]->getValue(), + ); + + $command->dropDefaultValue('{{test_def_constraint}}', '{{test_def}}')->execute(); + + $this->assertEmpty($schema->getTableDefaultValues('{{test_def}}', true)); + } + + public function testDropForeignKey(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{test_fk}}') !== null) { + $command->dropTable('{{test_fk}}')->execute(); + } + + $command->createTable('{{test_fk}}', ['id' => Schema::TYPE_PK, 'int1' => 'integer'])->execute(); + + $this->assertEmpty($schema->getTableForeignKeys('{{test_fk}}', true)); + + $command->addForeignKey('{{test_fk_constraint}}', '{{test_fk}}', 'int1', '{{test_fk}}', 'id')->execute(); + + $this->assertNotEmpty($schema->getTableForeignKeys('{{test_fk}}', true)); + + $command->dropForeignKey('{{test_fk_constraint}}', '{{test_fk}}')->execute(); + + $this->assertEmpty($schema->getTableForeignKeys('{{test_fk}}', true)); + } + + public function testDropIndex(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{test_idx}}') !== null) { + $command->dropTable('{{test_idx}}')->execute(); + } + + $command->createTable('{{test_idx}}', ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute(); + + $this->assertEmpty($schema->getTableIndexes('{[test_idx}}', true)); + + $command->createIndex('{{test_idx_constraint}}', '{{test_idx}}', ['int1', 'int2'], 'UNIQUE')->execute(); + + $this->assertSame(['int1', 'int2'], $schema->getTableIndexes('{{test_idx}}', true)[0]->getColumnNames()); + $this->assertTrue($schema->getTableIndexes('{{test_idx}}', true)[0]->isUnique()); + + $command->dropIndex('{{test_idx_constraint}}', '{{test_idx}}')->execute(); + + $this->assertEmpty($schema->getTableIndexes('{{test_idx}}', true)); + } + + public function testDropPrimaryKey(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{test_pk}}') !== null) { + $command->dropTable('{{test_pk}}')->execute(); + } + + $command->createTable('{{test_pk}}', ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute(); + + $this->assertEmpty($schema->getTableSchema('{{test_pk}}', true)->getPrimaryKey()); + + $command->addPrimaryKey('{{test_pk_constraint}}', '{{test_pk}}', ['int1', 'int2'])->execute(); + + $this->assertSame(['int1', 'int2'], $schema->getTableSchema('{{test_pk}}', true)->getColumnNames()); + + $command->dropPrimaryKey('{{test_pk_constraint}}', '{{test_pk}}')->execute(); + + $this->assertEmpty($schema->getTableSchema('{{test_pk}}', true)->getPrimaryKey()); + } + + public function testDropTable(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{testDropTable}}') !== null) { + $command->dropTable('{{testDropTable}}')->execute(); + } + + $command->createTable('{{testDropTable}}', ['id' => Schema::TYPE_PK, 'foo' => 'integer'])->execute(); + + $this->assertNotNull($schema->getTableSchema('{{testDropTable}}', true)); + + $command->dropTable('{{testDropTable}}')->execute(); + + $this->assertNull($schema->getTableSchema('{{testDropTable}}', true)); + } + + public function testDropUnique(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{test_uq}}') !== null) { + $command->dropTable('{{test_uq}}')->execute(); + } + + $command->createTable('{{test_uq}}', ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute(); + + $this->assertEmpty($schema->getTableUniques('{{test_uq}}', true)); + + $command->addUnique('{{test_uq_constraint}}', '{{test_uq}}', ['int1'])->execute(); + + $this->assertSame(['int1'], $schema->getTableUniques('{{test_uq}}', true)[0]->getColumnNames()); + + $command->dropUnique('{{test_uq_constraint}}', '{{test_uq}}')->execute(); + + $this->assertEmpty($schema->getTableUniques('{{test_uq}}', true)); + } + + public function testDropView(): void + { + $db = $this->getConnection(true); + + /* since it already exists in the fixtures */ + $viewName = '{{animal_view}}'; + + $schema = $db->getSchema(); + + $this->assertNotNull($schema->getTableSchema($viewName)); + + $db->createCommand()->dropView($viewName)->execute(); + + $this->assertNull($schema->getTableSchema($viewName)); + } + + public function testExecute(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->setSql( + <<assertSame(1, $command->execute()); + + $command = $command->setSql( + <<assertEquals(1, $command->queryScalar()); + + $command->setSql('bad SQL'); + $message = match ($db->getName()) { + 'pgsql' => 'SQLSTATE[42601]', + 'sqlite', 'oci' => 'SQLSTATE[HY000]', + default => 'SQLSTATE[42000]', + }; + + $this->expectException(Exception::class); + $this->expectExceptionMessage($message); + + $command->execute(); + } + + public function testExecuteWithoutSql(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $result = $command->setSql('')->execute(); + + $this->assertSame(0, $result); + } + + public function testExecuteWithTransaction(): void + { + $db = $this->getConnection(true); + + $this->assertNull($db->getTransaction()); + + $command = $db->createCommand( + <<execute(); + + $this->assertNull($db->getTransaction()); + + $this->assertEquals( + 1, + $db->createCommand( + <<queryScalar(), + ); + + $command = $db->createCommand( + <<execute(); + + $this->assertNull($db->getTransaction()); + + $this->assertEquals( + 1, + $db->createCommand( + <<queryScalar(), + ); + } + + public function testInsert(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->delete('{{customer}}')->execute(); + $command->insert( + '{{customer}}', + ['[[email]]' => 't1@example.com', '[[name]]' => 'test', '[[address]]' => 'test address'] + )->execute(); + + $this->assertEquals( + 1, + $command->setSql( + <<queryScalar(), + ); + + $record = $command->setSql( + <<queryOne(); + + $this->assertSame(['email' => 't1@example.com', 'name' => 'test', 'address' => 'test address'], $record); + } + + public function testInsertEx(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + + $expected = match ($db->getName()) { + 'pgsql' => ['id' => 4], + default => ['id' => '4'], + }; + + $this->assertSame( + $expected, + $command->insertEx('{{customer}}', ['name' => 'test_1', 'email' => 'test_1@example.com']), + ); + } + + public function testInsertExpression(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->delete('{{order_with_null_fk}}')->execute(); + $expression = match ($db->getName()) { + 'mysql' => 'YEAR(NOW())', + 'oci' => "TO_CHAR(SYSDATE, 'YYYY')", + 'pgsql' => "EXTRACT(YEAR FROM TIMESTAMP 'now')", + 'sqlite' => "strftime('%Y')", + 'sqlsrv' => 'YEAR(GETDATE())', + }; + $command->insert( + '{{order_with_null_fk}}', + ['created_at' => new Expression($expression), 'total' => 1], + )->execute(); + + $this->assertEquals( + 1, + $command->setSql( + <<queryScalar(), + ); + + $record = $command->setSql( + <<queryOne(); + + $this->assertEquals(['created_at' => date('Y')], $record); + } + + public function testsInsertQueryAsColumnValue(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $time = time(); + $command->setSql( + <<execute(); + $command->insert('{{order}}', ['customer_id' => 1, 'created_at' => $time, 'total' => 42])->execute(); + + if ($db->getName() === 'pgsql') { + $orderId = $db->getLastInsertID('public.order_id_seq'); + } else { + $orderId = $db->getLastInsertID(); + } + + $columnValueQuery = (new Query($db))->select('{{created_at}}')->from('{{order}}')->where(['id' => $orderId]); + $command->insert( + '{{order_with_null_fk}}', + ['customer_id' => $orderId, 'created_at' => $columnValueQuery, 'total' => 42], + )->execute(); + + $this->assertEquals( + $time, + $command->setSql( + <<bindValues([':id' => $orderId])->queryScalar(), + ); + + $command->setSql( + <<execute(); + $command->setSql( + <<execute(); + } + + public function testInsertSelect(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->setSql( + <<execute(); + $command->insert( + '{{customer}}', + ['email' => 't1@example.com', 'name' => 'test', 'address' => 'test address'] + )->execute(); + $query = (new Query($db)) + ->select(['{{customer}}.{{email}} as name', '{{name}} as email', '{{address}}']) + ->from('{{customer}}') + ->where(['and', ['<>', 'name', 'foo'], ['status' => [0, 1, 2, 3]]]); + $command->insert('{{customer}}', $query)->execute(); + + $this->assertEquals( + 2, + $command->setSql( + <<queryScalar(), + ); + + $record = $command->setSql( + <<queryAll(); + + $this->assertSame( + [ + ['email' => 't1@example.com', 'name' => 'test', 'address' => 'test address'], + ['email' => 'test', 'name' => 't1@example.com', 'address' => 'test address'], + ], + $record, + ); + } + + public function testInsertSelectAlias(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->delete('{{customer}}')->execute(); + $command->insert( + '{{customer}}', + [ + 'email' => 't1@example.com', + 'name' => 'test', + 'address' => 'test address', + ] + )->execute(); + $query = (new Query($db)) + ->select(['email' => '{{customer}}.{{email}}', 'address' => 'name', 'name' => 'address']) + ->from('{{customer}}') + ->where(['and', ['<>', 'name', 'foo'], ['status' => [0, 1, 2, 3]]]); + $command->insert('{{customer}}', $query)->execute(); + + $this->assertEquals( + 2, + $command->setSql( + <<queryScalar(), + ); + + $record = $command->setSql( + <<queryAll(); + + $this->assertSame( + [ + ['email' => 't1@example.com', 'name' => 'test', 'address' => 'test address'], + ['email' => 't1@example.com', 'name' => 'test address', 'address' => 'test'], + ], + $record, + ); + } + + /** + * Test INSERT INTO ... SELECT SQL statement with wrong query object. + * + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::invalidSelectColumns() + * + * @throws Exception + * @throws Throwable + */ + public function testInsertSelectFailed(array|ExpressionInterface|string $invalidSelectColumns): void + { + $db = $this->getConnection(); + + $query = new Query($db); + $query->select($invalidSelectColumns)->from('{{customer}}'); + $command = $db->createCommand(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expected select query object with enumerated (named) parameters'); + + $command->insert('{{customer}}', $query)->execute(); + } + + public function testInsertToBlob(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->delete('{{type}}')->execute(); + $columns = [ + 'int_col' => 1, + 'char_col' => 'test', + 'float_col' => 3.14, + 'bool_col' => true, + 'blob_col' => serialize(['test' => 'data', 'num' => 222]), + ]; + $command->insert('{{type}}', $columns)->execute(); + $result = $command->setSql( + <<queryOne(); + + $this->assertIsArray($result); + + $resultBlob = is_resource($result['blob_col']) ? stream_get_contents($result['blob_col']) : $result['blob_col']; + + $this->assertSame($columns['blob_col'], $resultBlob); + } + + public function testIntegrityViolation(): void + { + $db = $this->getConnection(true); + + $this->expectException(IntegrityException::class); + + $command = $db->createCommand( + <<execute(); + $command->execute(); + } + + public function testNoTablenameReplacement(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->insert( + '{{customer}}', + ['name' => 'Some {{weird}} name', 'email' => 'test@example.com', 'address' => 'Some {{%weird}} address'] + )->execute(); + + if ($db->getName() === 'pgsql') { + $customerId = $db->getLastInsertID('public.customer_id_seq'); + } else { + $customerId = $db->getLastInsertID(); + } + + $customer = $command->setSql( + <<bindValues([':id' => $customerId])->queryOne(); + + $this->assertIsArray($customer); + $this->assertSame('Some {{weird}} name', $customer['name']); + $this->assertSame('Some {{%weird}} address', $customer['address']); + + $command->update( + '{{customer}}', + ['name' => 'Some {{updated}} name', 'address' => 'Some {{%updated}} address'], + ['id' => $customerId] + )->execute(); + $customer = $command->setSql( + <<bindValues([':id' => $customerId])->queryOne(); + + $this->assertIsArray($customer); + $this->assertSame('Some {{updated}} name', $customer['name']); + $this->assertSame('Some {{%updated}} address', $customer['address']); + } + + public function testQuery(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->setSql( + <<assertNull($command->getPdoStatement()); + + $reader = $command->query(); + + $this->assertNotNull($command->getPdoStatement()); + $this->assertInstanceOf(DataReaderInterface::class, $reader); + $this->assertIsInt($reader->count()); + + $expectedRow = 6; + + if ($db->getName() === 'oci' || $db->getName() === 'pgsql') { + $expectedRow = 7; + } + + foreach ($reader as $row) { + $this->assertIsArray($row); + $this->assertCount($expectedRow, $row); + } + + $command = $db->createCommand('bad SQL'); + + $this->expectException(Exception::class); + + $command->query(); + } + + public function testQueryAll(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->setSql( + <<queryAll(); + $expectedRow = 6; + + if ($db->getName() === 'oci' || $db->getName() === 'pgsql') { + $expectedRow = 7; + } + + $this->assertIsArray($rows); + $this->assertCount(3, $rows); + $this->assertIsArray($rows[0]); + $this->assertCount($expectedRow, $rows[0]); + + $command->setSql('bad SQL'); + + $this->expectException(Exception::class); + + $command->queryAll(); + $command->setSql( + <<queryAll(); + + $this->assertIsArray($rows); + $this->assertCount(0, $rows); + $this->assertSame([], $rows); + } + + public function testQueryCache(): void + { + $db = $this->getConnection(true); + + $query = (new Query($db))->select(['{{name}}'])->from('{{customer}}'); + $command = $db->createCommand(); + $update = $command->setSql( + <<assertSame('user1', $query->where(['id' => 1])->scalar(), 'Asserting initial value'); + + /* No cache */ + $update->bindValues([':id' => 1, ':name' => 'user11'])->execute(); + + $this->assertSame( + 'user11', + $query->where(['id' => 1])->scalar(), + 'Query reflects DB changes when caching is disabled', + ); + + /* Connection cache */ + $db->cache( + static function (ConnectionPDOInterface $db) use ($query, $update) { + self::assertSame('user2', $query->where(['id' => 2])->scalar(), 'Asserting initial value for user #2'); + + $update->bindValues([':id' => 2, ':name' => 'user22'])->execute(); + + self::assertSame( + 'user2', + $query->where(['id' => 2])->scalar(), + 'Query does NOT reflect DB changes when wrapped in connection caching', + ); + + $db->noCache( + static function () use ($query) { + self::assertSame( + 'user22', + $query->where(['id' => 2])->scalar(), + 'Query reflects DB changes when wrapped in connection caching and noCache simultaneously', + ); + } + ); + + self::assertSame( + 'user2', + $query->where(['id' => 2])->scalar(), + 'Cache does not get changes after getting newer data from DB in noCache block.', + ); + }, + 10, + ); + + $db->queryCacheEnable(false); + + $db->cache( + static function () use ($query, $update) { + self::assertSame( + 'user22', + $query->where(['id' => 2])->scalar(), + 'When cache is disabled for the whole connection, Query inside cache block does not get cached', + ); + + $update->bindValues([':id' => 2, ':name' => 'user2'])->execute(); + + self::assertSame('user2', $query->where(['id' => 2])->scalar()); + }, + 10, + ); + + $db->queryCacheEnable(true); + $query->cache(); + + $this->assertSame('user11', $query->where(['id' => 1])->scalar()); + + $update->bindValues([':id' => 1, ':name' => 'user1'])->execute(); + + $this->assertSame( + 'user11', + $query->where(['id' => 1])->scalar(), + 'When both Connection and Query have cache enabled, we get cached value', + ); + $this->assertSame( + 'user1', + $query->noCache()->where(['id' => 1])->scalar(), + 'When Query has disabled cache, we get actual data', + ); + + $db->cache( + static function () use ($query) { + self::assertSame('user1', $query->noCache()->where(['id' => 1])->scalar()); + self::assertSame('user11', $query->cache()->where(['id' => 1])->scalar()); + }, + 10, + ); + } + + public function testQueryColumn(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $command->setSql( + <<queryColumn(); + + $this->assertIsArray($rows); + $this->assertCount(3, $rows); + $this->assertEquals('1', $rows[0]); + + $command->setSql('bad SQL'); + + $this->expectException(Exception::class); + + $command->queryColumn(); + $command->setSql( + <<queryColumn(); + + $this->assertIsArray($rows); + $this->assertCount(0, $rows); + $this->assertSame([], $rows); + } + + public function testQueryOne(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $sql = <<setSql($sql)->queryOne(); + + $this->assertIsArray($row); + $this->assertEquals(1, $row['id']); + $this->assertEquals('user1', $row['name']); + + $command->setSql($sql)->prepare(); + $row = $command->queryOne(); + + $this->assertIsArray($row); + $this->assertEquals(1, $row['id']); + $this->assertEquals('user1', $row['name']); + + $sql = <<setSql($sql); + + $this->assertNull($command->queryOne()); + } + + public function testQueryScalar(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $sql = <<assertEquals(1, $command->setSql($sql)->queryScalar()); + + $sql = <<setSql($sql)->prepare(); + + $this->assertEquals(1, $command->queryScalar()); + + $command = $command->setSql( + <<assertFalse($command->queryScalar()); + } + + public function testRenameColumn(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + $command->renameColumn('{{customer}}', 'address', 'address_city')->execute(); + + $this->assertContains('address_city', $schema->getTableSchema('{{customer}}')->getColumnNames()); + $this->assertNotContains('address', $schema->getTableSchema('{{customer}}')->getColumnNames()); + } + + public function testRenameTable(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + + if ($schema->getTableSchema('{{new_type}}') !== null) { + $command->dropTable('{{new_type}}')->execute(); + } + + $this->assertNotNull($schema->getTableSchema('{{type}}')); + $this->assertNull($schema->getTableSchema('{{new_type}}')); + + $command->renameTable('{{type}}', '{{new_type}}')->execute(); + + $this->assertNull($schema->getTableSchema('{{type}}', true)); + $this->assertNotNull($schema->getTableSchema('{{new_type}}', true)); + } + + public function testSetRetryHandler(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + + $this->assertNull($db->getTransaction()); + + $command->setSql( + <<execute(); + + $this->assertNull($db->getTransaction()); + $this->assertEquals( + 1, + $command->setSql( + <<queryScalar() + ); + + $attempts = null; + $hitHandler = false; + $hitCatch = false; + $command->setSql( + <<execute(); + } catch (Exception $e) { + $hitCatch = true; + + $this->assertInstanceOf(IntegrityException::class, $e); + } + + $this->assertNull($db->getTransaction()); + $this->assertSame(3, $attempts); + $this->assertTrue($hitHandler); + $this->assertTrue($hitCatch); + } + + public function testTransaction(): void + { + $db = $this->getConnection(true); + + $this->assertNull($db->getTransaction()); + + $command = $db->createCommand(); + $command = $command->setSql( + <<execute(); + + $this->assertNull($db->getTransaction()); + $this->assertEquals( + 1, + $command->setSql( + <<queryScalar(), + ); + } + + public function testTruncateTable(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $rows = $command->setSql( + <<queryAll(); + + $this->assertCount(2, $rows); + + $command->truncateTable('{{animal}}')->execute(); + $rows = $command->setSql( + <<queryAll(); + + $this->assertCount(0, $rows); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::update() + */ + public function testUpdate( + string $table, + array $columns, + array|string $conditions, + array $params, + string $expected + ): void { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->update($table, $columns, $conditions, $params)->getSql(); + + $this->assertSame($expected, $sql); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::upsert() + */ + public function testUpsert(array $firstData, array $secondData): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + + $this->assertEquals( + 0, + $command->setSql( + <<queryScalar() + ); + + $this->performAndCompareUpsertResult($db, $firstData); + + $this->assertEquals( + 1, + $command->setSql( + <<queryScalar() + ); + + $this->performAndCompareUpsertResult($db, $secondData); + } + + protected function performAndCompareUpsertResult(ConnectionPDOInterface $db, array $data): void + { + $params = $data['params']; + $expected = $data['expected'] ?? $params[1]; + + $command = $db->createCommand(); + + call_user_func_array([$command, 'upsert'], $params); + + $command->execute(); + + $actual = (new Query($db)) + ->select(['email', 'address' => new Expression($this->upsertTestCharCast), 'status']) + ->from('{{T_upsert}}') + ->one(); + $this->assertEquals($expected, $actual, $this->upsertTestCharCast); + } +} diff --git a/tests/Provider/BaseCommandPDOProvider.php b/tests/Provider/BaseCommandPDOProvider.php new file mode 100644 index 000000000..45ee6bec8 --- /dev/null +++ b/tests/Provider/BaseCommandPDOProvider.php @@ -0,0 +1,53 @@ + '1', + 'email' => 'user1@example.com', + 'name' => 'user1', + 'address' => 'address1', + 'status' => '1', + 'profile_id' => '1', + ], + ], + ]; + } + + public function bindParamsNonWhere(): array + { + return [ + [ + <<getName(), + ), + ], + [ + '{{test_fk_constraint_2}}', + '{{test_fk}}', + ['int1'], + 'int3', + null, + null, + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + '{{test_fk_constraint_3}}', + '{{test_fk}}', + ['int1'], + ['int3'], + null, + null, + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + '{{test_fk_constraint_4}}', + '{{test_fk}}', + ['int1'], + ['int3'], + 'CASCADE', + null, + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + '{{test_fk_constraint_5}}', + '{{test_fk}}', + ['int1'], + ['int3'], + 'CASCADE', + 'CASCADE', + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + '{{test_fk_constraint_6}}', + '{{test_fk}}', + ['int1', 'int2'], + ['int3', 'int4'], + null, + null, + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + '{{test_fk_constraint_7}}', + '{{test_fk}}', + ['int1', 'int2'], + ['int3', 'int4'], + 'CASCADE', + null, + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + '{{test_fk_constraint_8}}', + '{{test_fk}}', + ['int1', 'int2'], + ['int3', 'int4'], + 'CASCADE', + 'CASCADE', + DbHelper::replaceQuotes( + <<getName(), + ), + ], + ]; + } + + public function addPrimaryKey(): array + { + return [ + ['{{test_pk_constraint_1}}', '{{test_pk}}', 'int1'], + ['{{test_pk_constraint_2}}', '{{test_pk}}', ['int1']], + ['{{test_pk_constraint_3}}', 'test_pk', ['int1', 'int2']], + ]; + } + + public function addPrimaryKeySql(ConnectionPDOInterface $db): array + { + return [ + [ + '{{test_fk_constraint_1}}', + '{{test_fk}}', + 'int1', + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + '{{test_fk_constraint_2}}', + '{{test_fk}}', + ['int1'], + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + '{{test_fk_constraint_3}}', + '{{test_fk}}', + ['int3', 'int4'], + DbHelper::replaceQuotes( + <<getName(), + ), + ], + ]; + } + + public function addUnique(): array + { + return [ + ['{{test_unique_constraint_1}}', '{{test_unique}}', 'int1'], + ['{{test_unique_constraint_2}}', '{{test_unique}}', ['int1']], + ['{{test_unique_constraint_3}}', '{{test_unique}}', ['int1', 'int2']], + ]; + } + + public function addUniqueSql(ConnectionPDOInterface $db): array + { + return [ + [ + '{{test_fk_constraint_1}}', + '{{test_fk}}', + 'int1', + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + '{{test_fk_constraint_2}}', + '{{test_fk}}', + ['int1'], + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + '{{test_fk_constraint_3}}', + '{{test_fk}}', + ['int3', 'int4'], + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + '{{test_fk_constraint_3}}', + '{{test_fk}}', + ['int1', 'int2'], + DbHelper::replaceQuotes( + <<getName(), + ), + ], + ]; + } + + public function batchInsert(ConnectionPDOInterface $db): array { return [ 'multirow' => [ @@ -110,12 +345,21 @@ public function batchInsertSql(ConnectionPDOInterface $db): array ]; } - public function createIndex(ConnectionPDOInterface $db): array + public function createIndex(): array + { + return [ + ['{{test_idx_constraint_1}}', '{{test_idx}}', 'int1', null, null], + ['{{test_idx_constraint_2}}', '{{test_idx}}', ['int1'], QueryBuilder::INDEX_UNIQUE, null], + ['{{test_idx_constraint_3}}', '{{test_idx}}', ['int1', 'int2'], null, null], + ]; + } + + public function createIndexSql(ConnectionPDOInterface $db): array { return [ [ - 'name', - 'table', + '{{name}}', + '{{table}}', 'column', '', '', @@ -127,8 +371,8 @@ public function createIndex(ConnectionPDOInterface $db): array ), ], [ - 'name', - 'table', + '{{name}}', + '{{table}}', ['column1', 'column2'], '', '', @@ -140,8 +384,8 @@ public function createIndex(ConnectionPDOInterface $db): array ), ], [ - 'name', - 'table', + '{{name}}', + '{{table}}', ['column1', 'column2'], QueryBuilder::INDEX_UNIQUE, '', @@ -153,8 +397,8 @@ public function createIndex(ConnectionPDOInterface $db): array ), ], [ - 'name', - 'table', + '{{name}}', + '{{table}}', ['column1', 'column2'], 'FULLTEXT', '', @@ -166,8 +410,8 @@ public function createIndex(ConnectionPDOInterface $db): array ), ], [ - 'name', - 'table', + '{{name}}', + '{{table}}', ['column1', 'column2'], 'SPATIAL', '', @@ -179,8 +423,8 @@ public function createIndex(ConnectionPDOInterface $db): array ), ], [ - 'name', - 'table', + '{{name}}', + '{{table}}', ['column1', 'column2'], 'BITMAP', '', @@ -194,68 +438,91 @@ public function createIndex(ConnectionPDOInterface $db): array ]; } - public function rawSql(): array + public function invalidSelectColumns(): array + { + return [[[]], ['*'], [['*']]]; + } + + public function rawSql(ConnectionPDOInterface $db): array { return [ [ << 1], - <<getName() + ), ], [ << 1], - <<getName(), + ), ], [ << null], - <<getName(), + ), ], [ << 1, 'basePrefix' => 2], - <<getName(), + ), ], /** * {@see https://github.com/yiisoft/yii2/issues/9268} */ [ << false], - <<getName(), + ), ], /** * {@see https://github.com/yiisoft/yii2/issues/15122} */ [ << new Expression(implode(', ', [1, 2]))], - <<getName(), + ), ], ]; } @@ -264,8 +531,8 @@ public function update(ConnectionPDOInterface $db): array { return [ [ - 'table', - ['name' => 'test'], + '{{table}}', + ['name' => '{{test}}'], [], [], DbHelper::replaceQuotes( @@ -276,8 +543,8 @@ public function update(ConnectionPDOInterface $db): array ), ], [ - 'table', - ['name' => 'test'], + '{{table}}', + ['name' => '{{test}}'], ['id' => 1], [], DbHelper::replaceQuotes( @@ -288,8 +555,8 @@ public function update(ConnectionPDOInterface $db): array ), ], [ - 'table', - ['name' => 'test'], + '{{table}}', + ['name' => '{{test}}'], ['id' => 1], ['id' => 'integer'], DbHelper::replaceQuotes( @@ -300,8 +567,8 @@ public function update(ConnectionPDOInterface $db): array ), ], [ - 'table', - ['name' => 'test'], + '{{table}}', + ['name' => '{{test}}'], ['id' => 1], ['id' => 'string'], DbHelper::replaceQuotes( @@ -312,8 +579,8 @@ public function update(ConnectionPDOInterface $db): array ), ], [ - 'table', - ['name' => 'test'], + '{{table}}', + ['name' => '{{test}}'], ['id' => 1], ['id' => 'boolean'], DbHelper::replaceQuotes( @@ -324,8 +591,8 @@ public function update(ConnectionPDOInterface $db): array ), ], [ - 'table', - ['name' => 'test'], + '{{table}}', + ['name' => '{{test}}'], ['id' => 1], ['id' => 'float'], DbHelper::replaceQuotes( @@ -378,7 +645,7 @@ public function upsert(ConnectionPDOInterface $db): array 'T_upsert', (new query($db)) ->select(['email', 'address', 'status' => new Expression('1')]) - ->from('customer') + ->from('{{customer}}') ->where(['name' => 'user1']) ->limit(1), ], @@ -389,7 +656,7 @@ public function upsert(ConnectionPDOInterface $db): array 'T_upsert', (new query($db)) ->select(['email', 'address', 'status' => new Expression('2')]) - ->from('customer') + ->from('{{customer}}') ->where(['name' => 'user1']) ->limit(1), ], @@ -402,7 +669,7 @@ public function upsert(ConnectionPDOInterface $db): array 'T_upsert', (new query($db)) ->select(['email', 'address', 'status' => new Expression('1')]) - ->from('customer') + ->from('{{customer}}') ->where(['name' => 'user1']) ->limit(1), ['address' => 'Moon', 'status' => 2], @@ -414,7 +681,7 @@ public function upsert(ConnectionPDOInterface $db): array 'T_upsert', (new query($db)) ->select(['email', 'address', 'status' => new Expression('3')]) - ->from('customer') + ->from('{{customer}}') ->where(['name' => 'user1']) ->limit(1), ['address' => 'Moon', 'status' => 2], @@ -428,7 +695,7 @@ public function upsert(ConnectionPDOInterface $db): array 'T_upsert', (new query($db)) ->select(['email', 'address', 'status' => new Expression('1')]) - ->from('customer') + ->from('{{customer}}') ->where(['name' => 'user1']) ->limit(1), false, @@ -440,7 +707,7 @@ public function upsert(ConnectionPDOInterface $db): array 'T_upsert', (new query($db)) ->select(['email', 'address', 'status' => new Expression('2')]) - ->from('customer') + ->from('{{customer}}') ->where(['name' => 'user1']) ->limit(1), false, diff --git a/tests/Provider/CommandPDOProvider.php b/tests/Provider/CommandPDOProvider.php new file mode 100644 index 000000000..84a37e8fb --- /dev/null +++ b/tests/Provider/CommandPDOProvider.php @@ -0,0 +1,22 @@ +bindParam(); + } + + public function bindParamsNonWhere(): array + { + $baseCommandPDOProvider = new BaseCommandPDOProvider(); + + return $baseCommandPDOProvider->bindParamsNonWhere(); + } +} diff --git a/tests/Provider/CommandProvider.php b/tests/Provider/CommandProvider.php index 8b6ea1610..ec838fdb6 100644 --- a/tests/Provider/CommandProvider.php +++ b/tests/Provider/CommandProvider.php @@ -10,18 +10,81 @@ final class CommandProvider { use TestTrait; + public function addForeignKey(): array + { + $baseCommandProvider = new BaseCommandProvider(); + + return $baseCommandProvider->addForeignKey(); + } + + public function addForeignKeySql(): array + { + $baseCommandProvider = new BaseCommandProvider(); + + return $baseCommandProvider->addForeignKeySql($this->getConnection()); + } + + public function addPrimaryKey(): array + { + $baseCommandProvider = new BaseCommandProvider(); + + return $baseCommandProvider->addPrimaryKey(); + } + + public function addPrimaryKeySql(): array + { + $baseCommandProvider = new BaseCommandProvider(); + + return $baseCommandProvider->addPrimaryKeySql($this->getConnection()); + } + + public function addunique(): array + { + $baseCommandProvider = new BaseCommandProvider(); + + return $baseCommandProvider->addUnique(); + } + + public function adduniqueSql(): array + { + $baseCommandProvider = new BaseCommandProvider(); + + return $baseCommandProvider->addUniqueSql($this->getConnection()); + } + + public function batchInsert(): array + { + $baseCommandProvider = new BaseCommandProvider(); + + return $baseCommandProvider->batchInsert($this->getConnection()); + } + public function createIndex(): array { $baseCommandProvider = new BaseCommandProvider(); - return $baseCommandProvider->createIndex($this->getConnection()); + return $baseCommandProvider->createIndex(); + } + + public function createIndexSql(): array + { + $baseCommandProvider = new BaseCommandProvider(); + + return $baseCommandProvider->createIndexSql($this->getConnection()); + } + + public function invalidSelectColumns(): array + { + $baseCommandProvider = new BaseCommandProvider(); + + return $baseCommandProvider->invalidSelectColumns(); } public function rawSql(): array { $commandProvider = new BaseCommandProvider(); - return $commandProvider->rawSql(); + return $commandProvider->rawSql($this->getConnection()); } public function update(): array diff --git a/tests/Provider/QuoterProvider.php b/tests/Provider/QuoterProvider.php index f7d999539..a7f49453d 100644 --- a/tests/Provider/QuoterProvider.php +++ b/tests/Provider/QuoterProvider.php @@ -18,6 +18,40 @@ public function columnName(): array ]; } + /** + * @return string[][] + */ + public function ensureColumnName(): array + { + return [ + ['*', '*'], + ['`*`', '`*`'], + ['[[*]]', '[[*]]'], + ['{{*}}', '{{*}}'], + ['table.column', 'column'], + ['`table`.`column`', '`column`'], + ['[[table]].[[column]]', 'column'], + ['{{table}}.{{column}}', '{{column}}'], + ]; + } + + /** + * @return string[][] + */ + public function ensureNameQuoted(): array + { + return [ + ['name', '{{name}}'], + ['`name`', '{{name}}'], + ['[[name]]', '{{name}}'], + ['{{name}}', '{{name}}'], + ['table.name', '{{table.name}}'], + ['`table`.`name`', '{{table.name}}'], + ['[[table]].[[name]]', '{{table.name}}'], + ['{{table}}.{{name}}', '{{table}}.{{name}}'], + ]; + } + /** * @return string[][] */ diff --git a/tests/Schema/QuoterTest.php b/tests/Schema/QuoterTest.php index 1eef9fe79..5257361c0 100644 --- a/tests/Schema/QuoterTest.php +++ b/tests/Schema/QuoterTest.php @@ -4,87 +4,15 @@ namespace Yiisoft\Db\Tests\Schema; -use PHPUnit\Framework\TestCase; +use Yiisoft\Db\Tests\AbstractQuoterTest; use Yiisoft\Db\Tests\Support\TestTrait; -use function array_reverse; - /** * @group db * * @psalm-suppress PropertyNotSetInConstructor */ -final class QuoterTest extends TestCase +final class QuoterTest extends AbstractQuoterTest { use TestTrait; - - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::tableNameParts() - */ - public function testGetTableNameParts(string $tableName, string ...$expected): void - { - $db = $this->getConnection(); - - $this->assertSame($expected, array_reverse($db->getQuoter()->getTableNameParts($tableName))); - } - - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::columnName() - */ - public function testQuoteColumnName(string $columnName, string $expected): void - { - $db = $this->getConnection(); - - $this->assertSame($expected, $db->getQuoter()->quoteColumnName($columnName)); - } - - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::columnName() - */ - public function testQuoteSimpleColumnName(string $columnName, string $expected): void - { - $db = $this->getConnection(); - - $this->assertSame($expected, $db->getQuoter()->quoteSimpleColumnName($columnName)); - } - - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::simpleTableName() - */ - public function testQuoteSimpleTableName(string $columnName, string $expected): void - { - $db = $this->getConnection(); - - $this->assertSame($expected, $db->getQuoter()->quoteSimpleTableName($columnName)); - } - - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::tableName() - */ - public function testQuoteTableName(string $tableName, string $expected): void - { - $db = $this->getConnection(); - - $this->assertSame($expected, $db->getQuoter()->quoteTableName($tableName)); - } - - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::unquoteSimpleColumnName() - */ - public function testUnquoteSimpleColumnName(string $columnName, string $expected): void - { - $db = $this->getConnection(); - - $this->assertSame($expected, $db->getQuoter()->unquoteSimpleColumnName($columnName)); - } - - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::unquoteSimpleTableName() - */ - public function testUnquoteSimpleTableName(string $tableName, string $expected): void - { - $db = $this->getConnection(); - - $this->assertSame($expected, $db->getQuoter()->unquoteSimpleTableName($tableName)); - } } diff --git a/tests/Support/DbHelper.php b/tests/Support/DbHelper.php index b71ce3a69..1879319ee 100644 --- a/tests/Support/DbHelper.php +++ b/tests/Support/DbHelper.php @@ -26,6 +26,74 @@ public static function getCache(): CacheInterface return new Cache(new ArrayCache()); } + public static function getCommmentsFromColumn( + string $table, + string $column, + ConnectionPDOInterface $db + ): array|null { + return match ($db->getName()) { + 'sqlsrv' => $db->createCommand( + << $table, 'column' => $column] + )->queryOne(), + }; + } + + public static function getCommmentsFromTable( + string $table, + ConnectionPDOInterface $db, + string $schema = 'yiitest' + ): array|null { + return match ($db->getName()) { + 'mysql' => $db->createCommand( + << $schema, 'table' => $table] + )->queryOne(), + 'pgsql' => $db->createCommand( + << $table] + )->queryOne(), + 'sqlsrv' => $db->createCommand( + << $table] + )->queryOne(), + 'oci' => $db->createCommand( + << $table] + )->queryOne(), + }; + } + public static function getQueryCache(): QueryCache { return new QueryCache(self::getCache()); @@ -45,7 +113,19 @@ public static function getSchemaCache(): SchemaCache public static function loadFixture(ConnectionPDOInterface $db, string $fixture): void { $db->open(); - $lines = explode(';', file_get_contents($fixture)); + + if ($db->getName() === 'oci') { + [$drops, $creates] = explode('/* STATEMENTS */', file_get_contents($fixture), 2); + [$statements, $triggers, $data] = explode('/* TRIGGERS */', $creates, 3); + $lines = array_merge( + explode('--', $drops), + explode(';', $statements), + explode('/', $triggers), + explode(';', $data) + ); + } else { + $lines = explode(';', file_get_contents($fixture)); + } foreach ($lines as $line) { if (trim($line) !== '') { diff --git a/tests/Support/Fixture/customer.sql b/tests/Support/Fixture/db.sql similarity index 99% rename from tests/Support/Fixture/customer.sql rename to tests/Support/Fixture/db.sql index 354f08f29..f8d935121 100644 --- a/tests/Support/Fixture/customer.sql +++ b/tests/Support/Fixture/db.sql @@ -1,4 +1,5 @@ DROP TABLE IF EXISTS "customer"; + CREATE TABLE "customer" ( id INTEGER NOT NULL, email varchar(128) NOT NULL, @@ -8,6 +9,7 @@ CREATE TABLE "customer" ( profile_id INTEGER, PRIMARY KEY (id) ); + INSERT INTO "customer" (email, name, address, status, profile_id) VALUES ('user1@example.com', 'user1', 'address1', 1, 1); INSERT INTO "customer" (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1); INSERT INTO "customer" (email, name, address, status, profile_id) VALUES ('user3@example.com', 'user3', 'address3', 2, 2); diff --git a/tests/Support/Stub/Command.php b/tests/Support/Stub/Command.php index 20d8d6381..cffcecdc8 100644 --- a/tests/Support/Stub/Command.php +++ b/tests/Support/Stub/Command.php @@ -13,7 +13,7 @@ final class Command extends CommandPDO implements CommandPDOInterface { public function insertEx(string $table, array $columns): bool|array { - throw new NotSupportedException(__METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } public function queryBuilder(): QueryBuilderInterface @@ -23,6 +23,6 @@ public function queryBuilder(): QueryBuilderInterface protected function internalExecute(string|null $rawSql): void { - throw new NotSupportedException(__METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } } diff --git a/tests/Support/Stub/Schema.php b/tests/Support/Stub/Schema.php index 011105f3d..97b02bce4 100644 --- a/tests/Support/Stub/Schema.php +++ b/tests/Support/Stub/Schema.php @@ -24,12 +24,12 @@ public function createColumnSchemaBuilder(string $type, array|int|string $length public function findUniqueIndexes(TableSchemaInterface $table): array { - return $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } public function getLastInsertID(string $sequenceName = null): string { - $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } protected function getCacheKey(string $name): array @@ -44,41 +44,36 @@ protected function getCacheTag(): string protected function loadTableChecks(string $tableName): array { - $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } protected function loadTableDefaultValues(string $tableName): array { - $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } protected function loadTableForeignKeys(string $tableName): array { - $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } protected function loadTableIndexes(string $tableName): array { - $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } protected function loadTablePrimaryKey(string $tableName): Constraint|null { - $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } protected function loadTableUniques(string $tableName): array { - $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } protected function loadTableSchema(string $name): TableSchemaInterface|null { - $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); - } - - private function getException(string $message): void - { - throw new NotSupportedException($message); + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); } } diff --git a/tests/Support/TestTrait.php b/tests/Support/TestTrait.php index 64a3e246f..72e14be16 100644 --- a/tests/Support/TestTrait.php +++ b/tests/Support/TestTrait.php @@ -9,16 +9,16 @@ trait TestTrait { - private CacheInterface|null $cache = null; - private QueryCache|null $queryCache = null; - private SchemaCache|null $schemaCache = null; - - protected function getConnection(string $fixture = '', string $dsn = 'sqlite::memory:'): ConnectionPDOInterface + protected function getConnection(bool $fixture = false): ConnectionPDOInterface { - $db = new Stub\Connection(new PDODriver($dsn), DbHelper::getQueryCache(), DbHelper::getSchemaCache()); + $db = new Stub\Connection( + new PDODriver('sqlite::memory:'), + DbHelper::getQueryCache(), + DbHelper::getSchemaCache(), + ); - if ($fixture !== '') { - DbHelper::loadFixture($db, __DIR__ . "/Fixture/$fixture.sql"); + if ($fixture) { + DbHelper::loadFixture($db, __DIR__ . '/Fixture/db.sql'); } return $db;