From 27a7bfe57f1f736d136e7b52b65ab4d8498b7d68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Oct 2023 15:23:31 +0300 Subject: [PATCH 01/25] Remove `yiisoft/cache` and update `yiisoft/cache-file` to ^3.1 (#279) * Update yiisoft/cache-file requirement from ^2.0 to ^3.1 Updates the requirements on [yiisoft/cache-file](https://github.com/yiisoft/cache-file) to permit the latest version. - [Release notes](https://github.com/yiisoft/cache-file/releases) - [Changelog](https://github.com/yiisoft/cache-file/blob/master/CHANGELOG.md) - [Commits](https://github.com/yiisoft/cache-file/compare/2.0.0...3.1.0) --- updated-dependencies: - dependency-name: yiisoft/cache-file dependency-type: direct:development ... Signed-off-by: dependabot[bot] * cache also * Remove yiisoft/cache --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sergei Predvoditelev --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e72b8f0d..7c1347f0 100644 --- a/composer.json +++ b/composer.json @@ -33,8 +33,7 @@ "spatie/phpunit-watcher": "^1.23", "vimeo/psalm": "^4.3|^5.6", "yiisoft/aliases": "^2.0", - "yiisoft/cache": "^2.0", - "yiisoft/cache-file": "^2.0", + "yiisoft/cache-file": "^3.1", "yiisoft/var-dumper": "^1.5" }, "autoload": { From bd3e2906c7cfce44e010d931a6ee75490b5c69b5 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Sat, 14 Oct 2023 14:43:13 +0700 Subject: [PATCH 02/25] Fix CTE query expressions (#278) * Fix CTE * Fix psalm issues * Add line to CHANGELOG.md --- CHANGELOG.md | 2 +- src/DQLQueryBuilder.php | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 283f3a3c..12140e25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 1.0.2 under development -- no changes in this release. +- Bug #278: Remove `RECURSIVE` expression from CTE queries (@Tigrov) ## 1.0.1 July 24, 2023 diff --git a/src/DQLQueryBuilder.php b/src/DQLQueryBuilder.php index e9b37507..cd91f722 100644 --- a/src/DQLQueryBuilder.php +++ b/src/DQLQueryBuilder.php @@ -9,6 +9,7 @@ use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Mssql\Builder\InConditionBuilder; use Yiisoft\Db\Mssql\Builder\LikeConditionBuilder; +use Yiisoft\Db\Query\Query; use Yiisoft\Db\QueryBuilder\AbstractDQLQueryBuilder; use Yiisoft\Db\QueryBuilder\Condition\InCondition; use Yiisoft\Db\QueryBuilder\Condition\LikeCondition; @@ -109,4 +110,14 @@ protected function extractAlias(string $table): array|bool return parent::extractAlias($table); } + + public function buildWithQueries(array $withs, array &$params): string + { + /** @psalm-var array{query:string|Query, alias:ExpressionInterface|string, recursive:bool}[] $withs */ + foreach ($withs as &$with) { + $with['recursive'] = false; + } + + return parent::buildWithQueries($withs, $params); + } } From d46f41cfaaf552702ebd02900f37edbd1a74ba23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Oct 2023 10:30:57 +0300 Subject: [PATCH 03/25] Update rector/rector requirement from ^0.17 to ^0.18 (#274) Co-authored-by: Sergei Predvoditelev --- composer.json | 2 +- rector.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 7c1347f0..ab7f709f 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "require-dev": { "maglnet/composer-require-checker": "^4.2", "phpunit/phpunit": "^9.6|^10.0", - "rector/rector": "^0.17", + "rector/rector": "^0.18", "roave/infection-static-analysis-plugin": "^1.16", "spatie/phpunit-watcher": "^1.23", "vimeo/psalm": "^4.3|^5.6", diff --git a/rector.php b/rector.php index 20acccd4..c80d86e9 100644 --- a/rector.php +++ b/rector.php @@ -4,7 +4,7 @@ use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; use Rector\Config\RectorConfig; -use Rector\Php56\Rector\FunctionLike\AddDefaultValueForUndefinedVariableRector; +use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector; use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector; use Rector\Set\ValueObject\LevelSetList; @@ -24,6 +24,6 @@ $rectorConfig->skip([ ClosureToArrowFunctionRector::class, - AddDefaultValueForUndefinedVariableRector::class, + JsonThrowOnErrorRector::class, ]); }; From 1be5ddb57888d37e446b192e02cdf08840a3d66a Mon Sep 17 00:00:00 2001 From: Wilmer Arambula <42547589+terabytesoftw@users.noreply.github.com> Date: Wed, 25 Oct 2023 15:11:39 -0300 Subject: [PATCH 04/25] Fix boolean type. (#280) --- .scrutinizer.yml | 3 +-- CHANGELOG.md | 1 + src/Schema.php | 14 ++++---------- tests/ColumnSchemaTest.php | 2 +- tests/CommandTest.php | 16 ---------------- tests/Provider/CommandProvider.php | 16 ---------------- tests/Provider/QueryBuilderProvider.php | 2 +- tests/Provider/SchemaProvider.php | 14 +++++++------- tests/Provider/Type/BitProvider.php | 5 ++--- tests/Support/Fixture/Type/bit.sql | 1 - tests/Support/Fixture/mssql.sql | 4 ++-- tests/Type/BitTest.php | 10 ++++------ 12 files changed, 23 insertions(+), 65 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index b57b3b6a..ab7dc881 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -56,8 +56,7 @@ build: - /opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE yiitest' - pecl channel-update pecl.php.net - pecl install pdo_sqlsrv - - composer config preferred-install.yiisoft/db source - - composer update --no-interaction --no-progress --optimize-autoloader --ansi + - composer require yiisoft/db:dev-master --ansi tests: override: diff --git a/CHANGELOG.md b/CHANGELOG.md index 12140e25..25dcaf9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.0.2 under development - Bug #278: Remove `RECURSIVE` expression from CTE queries (@Tigrov) +- Bug #280: Fix type boolean (@terabytesoftw) ## 1.0.1 July 24, 2023 diff --git a/src/Schema.php b/src/Schema.php index f1fb7caa..1d9bfcde 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -444,6 +444,10 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface $column->type($this->typeMap[$type]); } + if ($type === 'bit') { + $column->type(self::TYPE_BOOLEAN); + } + if (!empty($matches[2])) { $values = explode(',', $matches[2]); $column->precision((int) $values[0]); @@ -452,16 +456,6 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface if (isset($values[1])) { $column->scale((int) $values[1]); } - - if ($column->getSize() === 1 && ($type === 'tinyint' || $type === 'bit')) { - $column->type(self::TYPE_BOOLEAN); - } elseif ($type === 'bit') { - if ($column->getSize() > 32) { - $column->type(self::TYPE_BIGINT); - } elseif ($column->getSize() === 32) { - $column->type(self::TYPE_INTEGER); - } - } } } diff --git a/tests/ColumnSchemaTest.php b/tests/ColumnSchemaTest.php index f7ef59a6..9642c05a 100644 --- a/tests/ColumnSchemaTest.php +++ b/tests/ColumnSchemaTest.php @@ -56,7 +56,7 @@ public function testPhpTypeCast(): void $this->assertSame(1.234, $floatColPhpType); $this->assertSame("\x10\x11\x12", $blobColPhpType); $this->assertSame('2023-07-11 14:50:00.123', $datetimeColPhpType); - $this->assertEquals(false, $boolColPhpType); + $this->assertFalse($boolColPhpType); $db->close(); } diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 79768656..2c1896c4 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -51,22 +51,6 @@ public function testAlterColumn(): void $this->assertSame('ntext', $columns['email']->getDbType()); } - /** - * @dataProvider \Yiisoft\Db\Mssql\Tests\Provider\CommandProvider::batchInsert - * - * @throws Throwable - */ - public function testBatchInsert( - string $table, - array $columns, - array $values, - string $expected, - array $expectedParams = [], - int $insertedRow = 1 - ): void { - parent::testBatchInsert($table, $columns, $values, $expected, $expectedParams, $insertedRow); - } - /** * @throws Exception * @throws InvalidConfigException diff --git a/tests/Provider/CommandProvider.php b/tests/Provider/CommandProvider.php index 70326b2b..5f429c40 100644 --- a/tests/Provider/CommandProvider.php +++ b/tests/Provider/CommandProvider.php @@ -18,22 +18,6 @@ final class CommandProvider extends \Yiisoft\Db\Tests\Provider\CommandProvider protected static string $driverName = 'sqlsrv'; - public static function batchInsert(): array - { - $batchInsert = parent::batchInsert(); - - $batchInsert['multirow']['expectedParams'][':qp3'] = 1; - $batchInsert['multirow']['expectedParams'][':qp7'] = 0; - - $batchInsert['issue11242']['expectedParams'][':qp3'] = 1; - - $batchInsert['wrongBehavior']['expectedParams'][':qp3'] = 0; - - $batchInsert['batchInsert binds params from expression']['expectedParams'][':qp3'] = 0; - - return $batchInsert; - } - /** * @throws JsonException */ diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index aea94267..21c71be3 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -128,7 +128,7 @@ public static function insertWithReturningPks(): array ['{{%type}}.[[related_id]]' => null, '[[time]]' => new Expression('now()')], [], << null], ], diff --git a/tests/Provider/SchemaProvider.php b/tests/Provider/SchemaProvider.php index 039aa5c1..fd8b8ac6 100644 --- a/tests/Provider/SchemaProvider.php +++ b/tests/Provider/SchemaProvider.php @@ -171,9 +171,9 @@ public static function columns(): array 'defaultValue' => '2002-01-01 00:00:00', ], 'bool_col' => [ - 'type' => 'tinyint', - 'dbType' => 'tinyint', - 'phpType' => 'integer', + 'type' => 'boolean', + 'dbType' => 'bit', + 'phpType' => 'boolean', 'primaryKey' => false, 'allowNull' => false, 'autoIncrement' => false, @@ -184,9 +184,9 @@ public static function columns(): array 'defaultValue' => null, ], 'bool_col2' => [ - 'type' => 'tinyint', - 'dbType' => 'tinyint', - 'phpType' => 'integer', + 'type' => 'boolean', + 'dbType' => 'bit', + 'phpType' => 'boolean', 'primaryKey' => false, 'allowNull' => true, 'autoIncrement' => false, @@ -194,7 +194,7 @@ public static function columns(): array 'size' => null, 'precision' => null, 'scale' => null, - 'defaultValue' => 1, + 'defaultValue' => true, ], ], 'tableName' => 'type', diff --git a/tests/Provider/Type/BitProvider.php b/tests/Provider/Type/BitProvider.php index 4b28ae78..7872310b 100644 --- a/tests/Provider/Type/BitProvider.php +++ b/tests/Provider/Type/BitProvider.php @@ -9,9 +9,8 @@ final class BitProvider public static function columns(): array { return [ - ['Mybit1', 'bit', 'integer', 0], - ['Mybit2', 'bit', 'integer', 1], - ['Mybit3', 'bit', 'integer', 2], + ['Mybit1', 'bit', 'boolean', false], + ['Mybit2', 'bit', 'boolean', true], ]; } } diff --git a/tests/Support/Fixture/Type/bit.sql b/tests/Support/Fixture/Type/bit.sql index ab003f9b..2701be7e 100644 --- a/tests/Support/Fixture/Type/bit.sql +++ b/tests/Support/Fixture/Type/bit.sql @@ -15,7 +15,6 @@ CREATE TABLE [dbo].[bit_default] ( [id] [int] IDENTITY NOT NULL, [Mybit1] [bit] NOT NULL DEFAULT 0, [Mybit2] [bit] NOT NULL DEFAULT 1, - [Mybit3] [bit] NOT NULL DEFAULT 2, CONSTRAINT [PK_bit_default_pk] PRIMARY KEY CLUSTERED ( [id] ASC ) ON [PRIMARY] diff --git a/tests/Support/Fixture/mssql.sql b/tests/Support/Fixture/mssql.sql index b5028353..3fdc5fd5 100644 --- a/tests/Support/Fixture/mssql.sql +++ b/tests/Support/Fixture/mssql.sql @@ -156,8 +156,8 @@ CREATE TABLE [dbo].[type] ( [blob_col] [varbinary](MAX), [numeric_col] [decimal](5,2) DEFAULT '33.22', [datetime_col] [datetime] NOT NULL DEFAULT '2002-01-01 00:00:00', - [bool_col] [tinyint] NOT NULL, - [bool_col2] [tinyint] DEFAULT '1' + [bool_col] [bit] NOT NULL, + [bool_col2] [bit] DEFAULT '1' ); CREATE TABLE [dbo].[animal] ( diff --git a/tests/Type/BitTest.php b/tests/Type/BitTest.php index 2b5141ef..afde50ce 100644 --- a/tests/Type/BitTest.php +++ b/tests/Type/BitTest.php @@ -37,7 +37,7 @@ public function testCreateTableWithDefaultValue( string $column, string $dbType, string $phpType, - int $defaultValue + bool $defaultValue ): void { $db = $this->buildTable(); @@ -89,7 +89,7 @@ public function testDefaultValue( string $column, string $dbType, string $phpType, - int $defaultValue + bool $defaultValue ): void { $this->setFixture('Type/bit.sql'); @@ -198,7 +198,7 @@ public function testMaxValue(): void $this->assertSame( [ 'id' => '2', - 'Mybit1' => '0', + 'Mybit1' => '1', 'Mybit2' => '1', 'Mybit3' => '1', ], @@ -251,7 +251,7 @@ public function testMinValue(): void [ 'id' => '2', 'Mybit1' => null, - 'Mybit2' => '0', + 'Mybit2' => '1', 'Mybit3' => '1', ], $command->setSql( @@ -280,7 +280,6 @@ private function buildTable(): ConnectionInterface 'id' => 'INT IDENTITY NOT NULL', 'Mybit1' => 'BIT DEFAULT 0', // Min value 'Mybit2' => 'BIT DEFAULT 1', // Max value - 'Mybit3' => 'BIT DEFAULT 2', // Max value ], )->execute(); @@ -293,7 +292,6 @@ private function getColumns(): array 'id' => '1', 'Mybit1' => '0', 'Mybit2' => '1', - 'Mybit3' => '1', ]; } } From 997ae8bbd73eebc3b5a157c44b8923b36306c93b Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Wed, 1 Nov 2023 10:37:16 +0700 Subject: [PATCH 05/25] Refactor DMLQueryBuilder (#275) * Refactor DMLQueryBuilder * Improve test * Add line to CHANGELOG.md --- CHANGELOG.md | 1 + src/DMLQueryBuilder.php | 84 +++++++------------------ tests/Provider/CommandProvider.php | 17 +++++ tests/Provider/QueryBuilderProvider.php | 8 +++ 4 files changed, 49 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25dcaf9e..20d915ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 1.0.2 under development +- Bug #275: Refactor `DMLQueryBuilder`, related with yiisoft/db#746 (@Tigrov) - Bug #278: Remove `RECURSIVE` expression from CTE queries (@Tigrov) - Bug #280: Fix type boolean (@terabytesoftw) diff --git a/src/DMLQueryBuilder.php b/src/DMLQueryBuilder.php index 76af105e..0c2d2eef 100644 --- a/src/DMLQueryBuilder.php +++ b/src/DMLQueryBuilder.php @@ -11,16 +11,11 @@ use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Expression\Expression; -use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\AbstractDMLQueryBuilder; -use Yiisoft\Db\Schema\SchemaInterface; use function implode; use function in_array; -use function ltrim; -use function strrpos; -use function is_array; /** * Implements a DML (Data Manipulation Language) SQL statements for MSSQL Server. @@ -35,16 +30,12 @@ final class DMLQueryBuilder extends AbstractDMLQueryBuilder */ public function insertWithReturningPks(string $table, QueryInterface|array $columns, array &$params = []): string { - /** - * @psalm-var string[] $names - * @psalm-var string[] $placeholders - */ [$names, $placeholders, $values, $params] = $this->prepareInsertValues($table, $columns, $params); + $createdCols = []; + $insertedCols = []; + $returnColumns = $this->schema->getTableSchema($table)?->getColumns() ?? []; - $createdCols = $insertedCols = []; - $tableSchema = $this->schema->getTableSchema($table); - $returnColumns = $tableSchema?->getColumns() ?? []; foreach ($returnColumns as $returnColumn) { if ($returnColumn->isComputed()) { continue; @@ -54,9 +45,7 @@ public function insertWithReturningPks(string $table, QueryInterface|array $colu if (in_array($dbType, ['char', 'varchar', 'nchar', 'nvarchar', 'binary', 'varbinary'], true)) { $dbType .= '(MAX)'; - } - - if ($returnColumn->getDbType() === SchemaInterface::TYPE_TIMESTAMP) { + } elseif ($dbType === 'timestamp') { $dbType = $returnColumn->isAllowNull() ? 'varbinary(8)' : 'binary(8)'; } @@ -65,12 +54,10 @@ public function insertWithReturningPks(string $table, QueryInterface|array $colu $insertedCols[] = 'INSERTED.' . $quotedName; } - $sql = 'INSERT INTO ' - . $this->quoter->quoteTableName($table) + $sql = 'INSERT INTO ' . $this->quoter->quoteTableName($table) . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '') . ' OUTPUT ' . implode(',', $insertedCols) . ' INTO @temporary_inserted' - . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : (string) $values); - + . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' ' . $values); return 'SET NOCOUNT ON;DECLARE @temporary_inserted TABLE (' . implode(', ', $createdCols) . ');' . $sql . ';SELECT * FROM @temporary_inserted;'; @@ -118,7 +105,6 @@ public function upsert( /** @psalm-var Constraint[] $constraints */ $constraints = []; - /** @psalm-var string[] $insertNames */ [$uniqueNames, $insertNames, $updateNames] = $this->prepareUpsertColumns( $table, $insertColumns, @@ -135,15 +121,12 @@ public function upsert( foreach ($constraints as $constraint) { $constraintCondition = ['and']; + $columnNames = (array) $constraint->getColumnNames(); - $columnNames = $constraint->getColumnNames() ?? []; - - if (is_array($columnNames)) { - /** @psalm-var string[] $columnNames */ - foreach ($columnNames as $name) { - $quotedName = $this->quoter->quoteColumnName($name); - $constraintCondition[] = "$quotedTableName.$quotedName=[EXCLUDED].$quotedName"; - } + /** @psalm-var string[] $columnNames */ + foreach ($columnNames as $name) { + $quotedName = $this->quoter->quoteColumnName($name); + $constraintCondition[] = "$quotedTableName.$quotedName=[EXCLUDED].$quotedName"; } $onCondition[] = $constraintCondition; @@ -151,32 +134,22 @@ public function upsert( $on = $this->queryBuilder->buildCondition($onCondition, $params); - /** @psalm-var string[] $placeholders */ [, $placeholders, $values, $params] = $this->prepareInsertValues($table, $insertColumns, $params); - $mergeSql = 'MERGE ' . $this->quoter->quoteTableName($table) . ' WITH (HOLDLOCK) ' - . 'USING (' . (!empty($placeholders) - ? 'VALUES (' . implode(', ', $placeholders) . ')' - : ltrim((string) $values, ' ')) . ') AS [EXCLUDED] (' . implode(', ', $insertNames) . ') ' . "ON ($on)"; - $insertValues = []; - foreach ($insertNames as $name) { - $quotedName = $this->quoter->quoteColumnName($name); + $mergeSql = 'MERGE ' . $quotedTableName . ' WITH (HOLDLOCK) USING (' + . (!empty($placeholders) ? 'VALUES (' . implode(', ', $placeholders) . ')' : $values) + . ') AS [EXCLUDED] (' . implode(', ', $insertNames) . ') ' . "ON ($on)"; - if (strrpos($quotedName, '.') === false) { - $quotedName = '[EXCLUDED].' . $quotedName; - } + $insertValues = []; - $insertValues[] = $quotedName; + foreach ($insertNames as $quotedName) { + $insertValues[] = '[EXCLUDED].' . $quotedName; } - $insertSql = 'INSERT (' . implode(', ', $insertNames) . ')' . ' VALUES (' . implode(', ', $insertValues) . ')'; + $insertSql = 'INSERT (' . implode(', ', $insertNames) . ') VALUES (' . implode(', ', $insertValues) . ')'; - if ($updateNames === []) { + if ($updateColumns === false || $updateNames === []) { /** there are no columns to update */ - $updateColumns = false; - } - - if ($updateColumns === false) { return "$mergeSql WHEN NOT MATCHED THEN $insertSql;"; } @@ -184,25 +157,14 @@ public function upsert( $updateColumns = []; /** @psalm-var string[] $updateNames */ - foreach ($updateNames as $name) { - $quotedName = $this->quoter->quoteColumnName($name); - if (strrpos($quotedName, '.') === false) { - $quotedName = '[EXCLUDED].' . $quotedName; - } - - $updateColumns[$name] = new Expression($quotedName); + foreach ($updateNames as $quotedName) { + $updateColumns[$quotedName] = new Expression('[EXCLUDED].' . $quotedName); } } - /** - * @var array $params - * - * @psalm-var string[] $updates - * @psalm-var array $updateColumns - */ [$updates, $params] = $this->prepareUpdateSets($table, $updateColumns, $params); - $updateSql = 'UPDATE SET ' . implode(', ', $updates); - return "$mergeSql WHEN MATCHED THEN $updateSql WHEN NOT MATCHED THEN $insertSql;"; + return "$mergeSql WHEN MATCHED THEN UPDATE SET " . implode(', ', $updates) + . " WHEN NOT MATCHED THEN $insertSql;"; } } diff --git a/tests/Provider/CommandProvider.php b/tests/Provider/CommandProvider.php index 5f429c40..e6a2e658 100644 --- a/tests/Provider/CommandProvider.php +++ b/tests/Provider/CommandProvider.php @@ -18,6 +18,23 @@ final class CommandProvider extends \Yiisoft\Db\Tests\Provider\CommandProvider protected static string $driverName = 'sqlsrv'; + public static function batchInsert(): array + { + $batchInsert = parent::batchInsert(); + + $batchInsert['multirow']['expectedParams'][':qp3'] = 1; + $batchInsert['multirow']['expectedParams'][':qp7'] = 0; + + $batchInsert['issue11242']['expectedParams'][':qp3'] = 1; + + $batchInsert['table name with column name with brackets']['expectedParams'][':qp3'] = 0; + + $batchInsert['batchInsert binds params from expression']['expectedParams'][':qp3'] = 0; + $batchInsert['with associative values']['expectedParams'][':qp3'] = 1; + + return $batchInsert; + } + /** * @throws JsonException */ diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index 21c71be3..30c16df5 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -218,6 +218,14 @@ public static function upsert(): array '[EXCLUDED].[address], [EXCLUDED].[status], [EXCLUDED].[profile_id]);', ], + 'regular values with unique at not the first position' => [ + 3 => 'MERGE [T_upsert] WITH (HOLDLOCK) USING (VALUES (:qp0, :qp1, :qp2, :qp3)) AS [EXCLUDED] ' . + '([address], [email], [status], [profile_id]) ON ([T_upsert].[email]=[EXCLUDED].[email]) ' . + 'WHEN MATCHED THEN UPDATE SET [address]=[EXCLUDED].[address], [status]=[EXCLUDED].[status], [profile_id]=[EXCLUDED].[profile_id] ' . + 'WHEN NOT MATCHED THEN INSERT ([address], [email], [status], [profile_id]) VALUES (' . + '[EXCLUDED].[address], [EXCLUDED].[email], [EXCLUDED].[status], [EXCLUDED].[profile_id]);', + ], + 'regular values with update part' => [ 3 => 'MERGE [T_upsert] WITH (HOLDLOCK) USING (VALUES (:qp0, :qp1, :qp2, :qp3)) AS [EXCLUDED] ' . '([email], [address], [status], [profile_id]) ON ([T_upsert].[email]=[EXCLUDED].[email]) ' . From 9f3d91c137a1a75358f056cc10cfc29138007c1c Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Tue, 7 Nov 2023 17:47:59 +0700 Subject: [PATCH 06/25] Move methods from `Command` to `AbstractPDOCommand` (#283) * Move methods from `Command` to `AbstractPdoCommand` class --------- Co-authored-by: StyleCI Bot --- CHANGELOG.md | 1 + src/Command.php | 46 ---------------------------------------------- 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20d915ea..f3675b7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Bug #275: Refactor `DMLQueryBuilder`, related with yiisoft/db#746 (@Tigrov) - Bug #278: Remove `RECURSIVE` expression from CTE queries (@Tigrov) - Bug #280: Fix type boolean (@terabytesoftw) +- Enh #283: Move methods from `Command` to `AbstractPdoCommand` class (@Tigrov) ## 1.0.1 July 24, 2023 diff --git a/src/Command.php b/src/Command.php index c1928b6f..3862c8ad 100644 --- a/src/Command.php +++ b/src/Command.php @@ -4,12 +4,7 @@ namespace Yiisoft\Db\Mssql; -use PDOException; -use Throwable; use Yiisoft\Db\Driver\Pdo\AbstractPdoCommand; -use Yiisoft\Db\Exception\ConvertException; -use Yiisoft\Db\Exception\Exception; -use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; /** * Implements a database command that can be executed against a PDO (PHP Data Object) database connection for MSSQL @@ -25,45 +20,4 @@ public function showDatabases(): array return $this->setSql($sql)->queryColumn(); } - - /** - * @psalm-suppress UnusedClosureParam - * - * @throws Exception - * @throws Throwable - */ - protected function internalExecute(string|null $rawSql): void - { - $attempt = 0; - - while (true) { - try { - if ( - ++$attempt === 1 - && $this->isolationLevel !== null - && $this->db->getTransaction() === null - ) { - $this->db->transaction( - fn () => $this->internalExecute($rawSql), - $this->isolationLevel - ); - } else { - $this->pdoStatement?->execute(); - } - break; - } catch (PDOException $e) { - $rawSql = $rawSql ?: $this->getRawSql(); - $e = (new ConvertException($e, $rawSql))->run(); - - if ($this->retryHandler === null || !($this->retryHandler)($e, $attempt)) { - throw $e; - } - } - } - } - - protected function getQueryBuilder(): QueryBuilderInterface - { - return $this->db->getQueryBuilder(); - } } From ff5d43cd4fe77415bcc032645c19f7b3853064c6 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Fri, 10 Nov 2023 11:28:29 +0700 Subject: [PATCH 07/25] Fix alter column with default null (#282) * Fix alter column with default null * Add line to CHANGELOG.md --- CHANGELOG.md | 1 + src/DDLQueryBuilder.php | 13 +- tests/CommandTest.php | 24 ++ tests/QueryBuilderTest.php | 476 +++++++++++++++++++------------------ 4 files changed, 279 insertions(+), 235 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3675b7e..2443f51b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Bug #275: Refactor `DMLQueryBuilder`, related with yiisoft/db#746 (@Tigrov) - Bug #278: Remove `RECURSIVE` expression from CTE queries (@Tigrov) - Bug #280: Fix type boolean (@terabytesoftw) +- Bug #282: Fix `DDLQueryBuilder::alterColumn()` for columns with default null (@Tigrov) - Enh #283: Move methods from `Command` to `AbstractPdoCommand` class (@Tigrov) ## 1.0.1 July 24, 2023 diff --git a/src/DDLQueryBuilder.php b/src/DDLQueryBuilder.php index 7430d31e..ad610ec0 100644 --- a/src/DDLQueryBuilder.php +++ b/src/DDLQueryBuilder.php @@ -53,8 +53,7 @@ public function addDefaultValue(string $table, string $name, string $column, mix */ public function alterColumn(string $table, string $column, ColumnInterface|string $type): string { - $sqlAfter = [$this->dropConstraintsForColumn($table, $column, 'D')]; - + $sqlAfter = []; $columnName = $this->quoter->quoteColumnName($column); $tableName = $this->quoter->quoteTableName($table); $constraintBase = preg_replace('/[^a-z0-9_]/i', '', $table . '_' . $column); @@ -85,11 +84,11 @@ public function alterColumn(string $table, string $column, ColumnInterface|strin } } - return 'ALTER TABLE ' . $tableName - . ' ALTER COLUMN ' - . $columnName . ' ' - . $this->queryBuilder->getColumnType($type) . "\n" - . implode("\n", $sqlAfter); + return implode("\n", [ + $this->dropConstraintsForColumn($table, $column, 'D'), + "ALTER TABLE $tableName ALTER COLUMN $columnName {$this->queryBuilder->getColumnType($type)}", + ...$sqlAfter, + ]); } /** diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 2c1896c4..5ce86c4c 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -12,6 +12,7 @@ use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Expression\Expression; +use Yiisoft\Db\Mssql\Column; use Yiisoft\Db\Mssql\Connection; use Yiisoft\Db\Mssql\Dsn; use Yiisoft\Db\Mssql\Driver; @@ -353,4 +354,27 @@ public function testShowDatabases(): void $this->assertSame('sqlsrv:Server=localhost,1433;', $db->getDriver()->getDsn()); $this->assertSame(['yiitest'], $command->showDatabases()); } + + /** @link https://github.com/yiisoft/db-migration/issues/11 */ + public function testAlterColumnWithDefaultNull() + { + $db = $this->getConnection(); + $command = $db->createCommand(); + + if ($db->getTableSchema('column_with_constraint', true) !== null) { + $command->dropTable('column_with_constraint')->execute(); + } + + $command->createTable('column_with_constraint', ['id' => 'pk'])->execute(); + $command->addColumn('column_with_constraint', 'field', (new Column('integer'))->null()->asString())->execute(); + $command->alterColumn('column_with_constraint', 'field', (new Column('string', 40))->notNull()->asString())->execute(); + + $fieldCol = $db->getTableSchema('column_with_constraint', true)->getColumn('field'); + + $this->assertFalse($fieldCol->isAllowNull()); + $this->assertNull($fieldCol->getDefaultValue()); + $this->assertSame('nvarchar(40)', $fieldCol->getDbType()); + + $command->dropTable('column_with_constraint'); + } } diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 1591f9c4..cf1a397b 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -602,53 +602,57 @@ public function testAlterColumn(): void $qb = $db->getQueryBuilder(); - $expected = "ALTER TABLE [foo1] ALTER COLUMN [bar] varchar(255) -DECLARE @tableName VARCHAR(MAX) = '[foo1]' -DECLARE @columnName VARCHAR(MAX) = 'bar' -WHILE 1=1 BEGIN - DECLARE @constraintName NVARCHAR(128) - SET @constraintName = (SELECT TOP 1 OBJECT_NAME(cons.[object_id]) - FROM ( - SELECT sc.[constid] object_id - FROM [sys].[sysconstraints] sc - JOIN [sys].[columns] c ON c.[object_id]=sc.[id] AND c.[column_id]=sc.[colid] AND c.[name]=@columnName - WHERE sc.[id] = OBJECT_ID(@tableName) - UNION - SELECT object_id(i.[name]) FROM [sys].[indexes] i - JOIN [sys].[columns] c ON c.[object_id]=i.[object_id] AND c.[name]=@columnName - JOIN [sys].[index_columns] ic ON ic.[object_id]=i.[object_id] AND i.[index_id]=ic.[index_id] AND c.[column_id]=ic.[column_id] - WHERE i.[is_unique_constraint]=1 and i.[object_id]=OBJECT_ID(@tableName) - ) cons - JOIN [sys].[objects] so ON so.[object_id]=cons.[object_id] - WHERE so.[type]='D') - IF @constraintName IS NULL BREAK - EXEC (N'ALTER TABLE ' + @tableName + ' DROP CONSTRAINT [' + @constraintName + ']') -END"; + $expected = <<alterColumn('foo1', 'bar', 'varchar(255)'); $this->assertEquals($expected, $sql); - $expected = "ALTER TABLE [foo1] ALTER COLUMN [bar] nvarchar(255) NOT NULL -DECLARE @tableName VARCHAR(MAX) = '[foo1]' -DECLARE @columnName VARCHAR(MAX) = 'bar' -WHILE 1=1 BEGIN - DECLARE @constraintName NVARCHAR(128) - SET @constraintName = (SELECT TOP 1 OBJECT_NAME(cons.[object_id]) - FROM ( - SELECT sc.[constid] object_id - FROM [sys].[sysconstraints] sc - JOIN [sys].[columns] c ON c.[object_id]=sc.[id] AND c.[column_id]=sc.[colid] AND c.[name]=@columnName - WHERE sc.[id] = OBJECT_ID(@tableName) - UNION - SELECT object_id(i.[name]) FROM [sys].[indexes] i - JOIN [sys].[columns] c ON c.[object_id]=i.[object_id] AND c.[name]=@columnName - JOIN [sys].[index_columns] ic ON ic.[object_id]=i.[object_id] AND i.[index_id]=ic.[index_id] AND c.[column_id]=ic.[column_id] - WHERE i.[is_unique_constraint]=1 and i.[object_id]=OBJECT_ID(@tableName) - ) cons - JOIN [sys].[objects] so ON so.[object_id]=cons.[object_id] - WHERE so.[type]='D') - IF @constraintName IS NULL BREAK - EXEC (N'ALTER TABLE ' + @tableName + ' DROP CONSTRAINT [' + @constraintName + ']') -END"; + $expected = <<alterColumn( 'foo1', 'bar', @@ -656,29 +660,31 @@ public function testAlterColumn(): void ); $this->assertEquals($expected, $sql); - $expected = "ALTER TABLE [foo1] ALTER COLUMN [bar] nvarchar(255) -DECLARE @tableName VARCHAR(MAX) = '[foo1]' -DECLARE @columnName VARCHAR(MAX) = 'bar' -WHILE 1=1 BEGIN - DECLARE @constraintName NVARCHAR(128) - SET @constraintName = (SELECT TOP 1 OBJECT_NAME(cons.[object_id]) - FROM ( - SELECT sc.[constid] object_id - FROM [sys].[sysconstraints] sc - JOIN [sys].[columns] c ON c.[object_id]=sc.[id] AND c.[column_id]=sc.[colid] AND c.[name]=@columnName - WHERE sc.[id] = OBJECT_ID(@tableName) - UNION - SELECT object_id(i.[name]) FROM [sys].[indexes] i - JOIN [sys].[columns] c ON c.[object_id]=i.[object_id] AND c.[name]=@columnName - JOIN [sys].[index_columns] ic ON ic.[object_id]=i.[object_id] AND i.[index_id]=ic.[index_id] AND c.[column_id]=ic.[column_id] - WHERE i.[is_unique_constraint]=1 and i.[object_id]=OBJECT_ID(@tableName) - ) cons - JOIN [sys].[objects] so ON so.[object_id]=cons.[object_id] - WHERE so.[type]='D') - IF @constraintName IS NULL BREAK - EXEC (N'ALTER TABLE ' + @tableName + ' DROP CONSTRAINT [' + @constraintName + ']') -END -ALTER TABLE [foo1] ADD CONSTRAINT [CK_foo1_bar] CHECK (LEN(bar) > 5)"; + $expected = << 5) + SQL; $sql = $qb->alterColumn( 'foo1', 'bar', @@ -686,29 +692,31 @@ public function testAlterColumn(): void ); $this->assertEquals($expected, $sql); - $expected = "ALTER TABLE [foo1] ALTER COLUMN [bar] nvarchar(255) -DECLARE @tableName VARCHAR(MAX) = '[foo1]' -DECLARE @columnName VARCHAR(MAX) = 'bar' -WHILE 1=1 BEGIN - DECLARE @constraintName NVARCHAR(128) - SET @constraintName = (SELECT TOP 1 OBJECT_NAME(cons.[object_id]) - FROM ( - SELECT sc.[constid] object_id - FROM [sys].[sysconstraints] sc - JOIN [sys].[columns] c ON c.[object_id]=sc.[id] AND c.[column_id]=sc.[colid] AND c.[name]=@columnName - WHERE sc.[id] = OBJECT_ID(@tableName) - UNION - SELECT object_id(i.[name]) FROM [sys].[indexes] i - JOIN [sys].[columns] c ON c.[object_id]=i.[object_id] AND c.[name]=@columnName - JOIN [sys].[index_columns] ic ON ic.[object_id]=i.[object_id] AND i.[index_id]=ic.[index_id] AND c.[column_id]=ic.[column_id] - WHERE i.[is_unique_constraint]=1 and i.[object_id]=OBJECT_ID(@tableName) - ) cons - JOIN [sys].[objects] so ON so.[object_id]=cons.[object_id] - WHERE so.[type]='D') - IF @constraintName IS NULL BREAK - EXEC (N'ALTER TABLE ' + @tableName + ' DROP CONSTRAINT [' + @constraintName + ']') -END -ALTER TABLE [foo1] ADD CONSTRAINT [DF_foo1_bar] DEFAULT '' FOR [bar]"; + $expected = <<alterColumn( 'foo1', 'bar', @@ -716,29 +724,31 @@ public function testAlterColumn(): void ); $this->assertEquals($expected, $sql); - $expected = "ALTER TABLE [foo1] ALTER COLUMN [bar] nvarchar(255) -DECLARE @tableName VARCHAR(MAX) = '[foo1]' -DECLARE @columnName VARCHAR(MAX) = 'bar' -WHILE 1=1 BEGIN - DECLARE @constraintName NVARCHAR(128) - SET @constraintName = (SELECT TOP 1 OBJECT_NAME(cons.[object_id]) - FROM ( - SELECT sc.[constid] object_id - FROM [sys].[sysconstraints] sc - JOIN [sys].[columns] c ON c.[object_id]=sc.[id] AND c.[column_id]=sc.[colid] AND c.[name]=@columnName - WHERE sc.[id] = OBJECT_ID(@tableName) - UNION - SELECT object_id(i.[name]) FROM [sys].[indexes] i - JOIN [sys].[columns] c ON c.[object_id]=i.[object_id] AND c.[name]=@columnName - JOIN [sys].[index_columns] ic ON ic.[object_id]=i.[object_id] AND i.[index_id]=ic.[index_id] AND c.[column_id]=ic.[column_id] - WHERE i.[is_unique_constraint]=1 and i.[object_id]=OBJECT_ID(@tableName) - ) cons - JOIN [sys].[objects] so ON so.[object_id]=cons.[object_id] - WHERE so.[type]='D') - IF @constraintName IS NULL BREAK - EXEC (N'ALTER TABLE ' + @tableName + ' DROP CONSTRAINT [' + @constraintName + ']') -END -ALTER TABLE [foo1] ADD CONSTRAINT [DF_foo1_bar] DEFAULT 'AbCdE' FOR [bar]"; + $expected = <<alterColumn( 'foo1', 'bar', @@ -746,29 +756,31 @@ public function testAlterColumn(): void ); $this->assertEquals($expected, $sql); - $expected = "ALTER TABLE [foo1] ALTER COLUMN [bar] datetime -DECLARE @tableName VARCHAR(MAX) = '[foo1]' -DECLARE @columnName VARCHAR(MAX) = 'bar' -WHILE 1=1 BEGIN - DECLARE @constraintName NVARCHAR(128) - SET @constraintName = (SELECT TOP 1 OBJECT_NAME(cons.[object_id]) - FROM ( - SELECT sc.[constid] object_id - FROM [sys].[sysconstraints] sc - JOIN [sys].[columns] c ON c.[object_id]=sc.[id] AND c.[column_id]=sc.[colid] AND c.[name]=@columnName - WHERE sc.[id] = OBJECT_ID(@tableName) - UNION - SELECT object_id(i.[name]) FROM [sys].[indexes] i - JOIN [sys].[columns] c ON c.[object_id]=i.[object_id] AND c.[name]=@columnName - JOIN [sys].[index_columns] ic ON ic.[object_id]=i.[object_id] AND i.[index_id]=ic.[index_id] AND c.[column_id]=ic.[column_id] - WHERE i.[is_unique_constraint]=1 and i.[object_id]=OBJECT_ID(@tableName) - ) cons - JOIN [sys].[objects] so ON so.[object_id]=cons.[object_id] - WHERE so.[type]='D') - IF @constraintName IS NULL BREAK - EXEC (N'ALTER TABLE ' + @tableName + ' DROP CONSTRAINT [' + @constraintName + ']') -END -ALTER TABLE [foo1] ADD CONSTRAINT [DF_foo1_bar] DEFAULT CURRENT_TIMESTAMP FOR [bar]"; + $expected = <<alterColumn( 'foo1', 'bar', @@ -776,29 +788,31 @@ public function testAlterColumn(): void ); $this->assertEquals($expected, $sql); - $expected = "ALTER TABLE [foo1] ALTER COLUMN [bar] nvarchar(30) -DECLARE @tableName VARCHAR(MAX) = '[foo1]' -DECLARE @columnName VARCHAR(MAX) = 'bar' -WHILE 1=1 BEGIN - DECLARE @constraintName NVARCHAR(128) - SET @constraintName = (SELECT TOP 1 OBJECT_NAME(cons.[object_id]) - FROM ( - SELECT sc.[constid] object_id - FROM [sys].[sysconstraints] sc - JOIN [sys].[columns] c ON c.[object_id]=sc.[id] AND c.[column_id]=sc.[colid] AND c.[name]=@columnName - WHERE sc.[id] = OBJECT_ID(@tableName) - UNION - SELECT object_id(i.[name]) FROM [sys].[indexes] i - JOIN [sys].[columns] c ON c.[object_id]=i.[object_id] AND c.[name]=@columnName - JOIN [sys].[index_columns] ic ON ic.[object_id]=i.[object_id] AND i.[index_id]=ic.[index_id] AND c.[column_id]=ic.[column_id] - WHERE i.[is_unique_constraint]=1 and i.[object_id]=OBJECT_ID(@tableName) - ) cons - JOIN [sys].[objects] so ON so.[object_id]=cons.[object_id] - WHERE so.[type]='D') - IF @constraintName IS NULL BREAK - EXEC (N'ALTER TABLE ' + @tableName + ' DROP CONSTRAINT [' + @constraintName + ']') -END -ALTER TABLE [foo1] ADD CONSTRAINT [UQ_foo1_bar] UNIQUE ([bar])"; + $expected = <<alterColumn( 'foo1', 'bar', @@ -1007,29 +1021,31 @@ public function testAlterColumnWithNull(): void { $qb = $this->getConnection()->getQueryBuilder(); - $expected = "ALTER TABLE [foo1] ALTER COLUMN [bar] int NULL -DECLARE @tableName VARCHAR(MAX) = '[foo1]' -DECLARE @columnName VARCHAR(MAX) = 'bar' -WHILE 1=1 BEGIN - DECLARE @constraintName NVARCHAR(128) - SET @constraintName = (SELECT TOP 1 OBJECT_NAME(cons.[object_id]) - FROM ( - SELECT sc.[constid] object_id - FROM [sys].[sysconstraints] sc - JOIN [sys].[columns] c ON c.[object_id]=sc.[id] AND c.[column_id]=sc.[colid] AND c.[name]=@columnName - WHERE sc.[id] = OBJECT_ID(@tableName) - UNION - SELECT object_id(i.[name]) FROM [sys].[indexes] i - JOIN [sys].[columns] c ON c.[object_id]=i.[object_id] AND c.[name]=@columnName - JOIN [sys].[index_columns] ic ON ic.[object_id]=i.[object_id] AND i.[index_id]=ic.[index_id] AND c.[column_id]=ic.[column_id] - WHERE i.[is_unique_constraint]=1 and i.[object_id]=OBJECT_ID(@tableName) - ) cons - JOIN [sys].[objects] so ON so.[object_id]=cons.[object_id] - WHERE so.[type]='D') - IF @constraintName IS NULL BREAK - EXEC (N'ALTER TABLE ' + @tableName + ' DROP CONSTRAINT [' + @constraintName + ']') -END -ALTER TABLE [foo1] ADD CONSTRAINT [DF_foo1_bar] DEFAULT NULL FOR [bar]"; + $expected = <<alterColumn( 'foo1', 'bar', @@ -1037,29 +1053,31 @@ public function testAlterColumnWithNull(): void ); $this->assertEquals($expected, $sql); - $expected = "ALTER TABLE [foo1] ALTER COLUMN [bar] int NULL -DECLARE @tableName VARCHAR(MAX) = '[foo1]' -DECLARE @columnName VARCHAR(MAX) = 'bar' -WHILE 1=1 BEGIN - DECLARE @constraintName NVARCHAR(128) - SET @constraintName = (SELECT TOP 1 OBJECT_NAME(cons.[object_id]) - FROM ( - SELECT sc.[constid] object_id - FROM [sys].[sysconstraints] sc - JOIN [sys].[columns] c ON c.[object_id]=sc.[id] AND c.[column_id]=sc.[colid] AND c.[name]=@columnName - WHERE sc.[id] = OBJECT_ID(@tableName) - UNION - SELECT object_id(i.[name]) FROM [sys].[indexes] i - JOIN [sys].[columns] c ON c.[object_id]=i.[object_id] AND c.[name]=@columnName - JOIN [sys].[index_columns] ic ON ic.[object_id]=i.[object_id] AND i.[index_id]=ic.[index_id] AND c.[column_id]=ic.[column_id] - WHERE i.[is_unique_constraint]=1 and i.[object_id]=OBJECT_ID(@tableName) - ) cons - JOIN [sys].[objects] so ON so.[object_id]=cons.[object_id] - WHERE so.[type]='D') - IF @constraintName IS NULL BREAK - EXEC (N'ALTER TABLE ' + @tableName + ' DROP CONSTRAINT [' + @constraintName + ']') -END -ALTER TABLE [foo1] ADD CONSTRAINT [DF_foo1_bar] DEFAULT NULL FOR [bar]"; + $expected = <<alterColumn( 'foo1', 'bar', @@ -1076,29 +1094,31 @@ public function testAlterColumnWithExpression(): void { $qb = $this->getConnection()->getQueryBuilder(); - $expected = "ALTER TABLE [foo1] ALTER COLUMN [bar] int NULL -DECLARE @tableName VARCHAR(MAX) = '[foo1]' -DECLARE @columnName VARCHAR(MAX) = 'bar' -WHILE 1=1 BEGIN - DECLARE @constraintName NVARCHAR(128) - SET @constraintName = (SELECT TOP 1 OBJECT_NAME(cons.[object_id]) - FROM ( - SELECT sc.[constid] object_id - FROM [sys].[sysconstraints] sc - JOIN [sys].[columns] c ON c.[object_id]=sc.[id] AND c.[column_id]=sc.[colid] AND c.[name]=@columnName - WHERE sc.[id] = OBJECT_ID(@tableName) - UNION - SELECT object_id(i.[name]) FROM [sys].[indexes] i - JOIN [sys].[columns] c ON c.[object_id]=i.[object_id] AND c.[name]=@columnName - JOIN [sys].[index_columns] ic ON ic.[object_id]=i.[object_id] AND i.[index_id]=ic.[index_id] AND c.[column_id]=ic.[column_id] - WHERE i.[is_unique_constraint]=1 and i.[object_id]=OBJECT_ID(@tableName) - ) cons - JOIN [sys].[objects] so ON so.[object_id]=cons.[object_id] - WHERE so.[type]='D') - IF @constraintName IS NULL BREAK - EXEC (N'ALTER TABLE ' + @tableName + ' DROP CONSTRAINT [' + @constraintName + ']') -END -ALTER TABLE [foo1] ADD CONSTRAINT [DF_foo1_bar] DEFAULT CAST(GETDATE() AS INT) FOR [bar]"; + $expected = <<alterColumn( 'foo1', 'bar', From 61bb7282ca3014e3da6e3a0fa1e4db0b8ba18ef8 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Sun, 12 Nov 2023 14:47:23 +0700 Subject: [PATCH 08/25] Enable BC test (#284) --- .github/workflows/bc.yml | 23 +++++++++++++++++++++++ .github/workflows/bc.yml_ | 15 --------------- 2 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/bc.yml delete mode 100644 .github/workflows/bc.yml_ diff --git a/.github/workflows/bc.yml b/.github/workflows/bc.yml new file mode 100644 index 00000000..5970206c --- /dev/null +++ b/.github/workflows/bc.yml @@ -0,0 +1,23 @@ +on: + pull_request: + paths: + - 'src/**' + - '.github/workflows/bc.yml' + - 'composer.json' + push: + branches: ['master'] + paths: + - 'src/**' + - '.github/workflows/bc.yml' + - 'composer.json' + +name: backwards compatibility + +jobs: + roave_bc_check: + uses: yiisoft/actions/.github/workflows/bc.yml@master + with: + os: >- + ['ubuntu-latest'] + php: >- + ['8.1'] diff --git a/.github/workflows/bc.yml_ b/.github/workflows/bc.yml_ deleted file mode 100644 index 35b3a862..00000000 --- a/.github/workflows/bc.yml_ +++ /dev/null @@ -1,15 +0,0 @@ -on: - - pull_request - - push - -name: backwards compatibility -jobs: - roave_bc_check: - name: Roave BC Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: fetch tags - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - name: Roave BC Check - uses: docker://nyholm/roave-bc-check-ga From efa5d2674373b4cf36be42540d43f1551e6773c8 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 12 Nov 2023 19:13:25 +0300 Subject: [PATCH 09/25] Raise `yiisoft/db` to `^1.2` (#285) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ab7f709f..c2181157 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "require": { "php": "^8.0", "ext-pdo": "*", - "yiisoft/db": "^1.0" + "yiisoft/db": "^1.2" }, "require-dev": { "maglnet/composer-require-checker": "^4.2", From 907f8e5593be3381dfc12783277d0c6f22f3a72f Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 12 Nov 2023 16:14:10 +0000 Subject: [PATCH 10/25] Release version 1.1.0 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2443f51b..babf8d54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,12 @@ # MSSQL Server driver for Yii Database Change Log -## 1.0.2 under development +## 1.1.0 November 12, 2023 +- Enh #283: Move methods from `Command` to `AbstractPdoCommand` class (@Tigrov) - Bug #275: Refactor `DMLQueryBuilder`, related with yiisoft/db#746 (@Tigrov) - Bug #278: Remove `RECURSIVE` expression from CTE queries (@Tigrov) - Bug #280: Fix type boolean (@terabytesoftw) - Bug #282: Fix `DDLQueryBuilder::alterColumn()` for columns with default null (@Tigrov) -- Enh #283: Move methods from `Command` to `AbstractPdoCommand` class (@Tigrov) ## 1.0.1 July 24, 2023 From cb2d47a7105d5e76311ea7da7e62ecdb66a8b132 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 12 Nov 2023 16:14:11 +0000 Subject: [PATCH 11/25] Prepare for next release --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index babf8d54..6d780854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # MSSQL Server driver for Yii Database Change Log +## 1.1.1 under development + +- no changes in this release. + ## 1.1.0 November 12, 2023 - Enh #283: Move methods from `Command` to `AbstractPdoCommand` class (@Tigrov) From 52767023a0a2717887e50c5c8c6b33f69958c78d Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Thu, 16 Nov 2023 13:10:12 +0700 Subject: [PATCH 12/25] Change property `Schema::$typeMap` to constant `Schema::TYPE_MAP` (#286) --- CHANGELOG.md | 2 +- src/Schema.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d780854..d3b68110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 1.1.1 under development -- no changes in this release. +- Enh #286: Change property `Schema::$typeMap` to constant `Schema::TYPE_MAP` (@Tigrov) ## 1.1.0 November 12, 2023 diff --git a/src/Schema.php b/src/Schema.php index 1d9bfcde..0da2199b 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -63,11 +63,11 @@ final class Schema extends AbstractPdoSchema protected string|null $defaultSchema = 'dbo'; /** - * @var array Mapping from physical column types (keys) to abstract column types (values). + * Mapping from physical column types (keys) to abstract column types (values). * - * @psalm-var string[] + * @var string[] */ - private array $typeMap = [ + private const TYPE_MAP = [ /** Exact numbers */ 'bigint' => self::TYPE_BIGINT, 'numeric' => self::TYPE_DECIMAL, @@ -440,8 +440,8 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $dbType, $matches)) { $type = $matches[1]; - if (isset($this->typeMap[$type])) { - $column->type($this->typeMap[$type]); + if (isset(self::TYPE_MAP[$type])) { + $column->type(self::TYPE_MAP[$type]); } if ($type === 'bit') { From 666a8e818024997e730b3eadd99c7bd2dc762886 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Thu, 21 Dec 2023 14:43:45 +0700 Subject: [PATCH 13/25] Fix `DMLQueryBuilder::insertWithReturningPks()` (#287) --- CHANGELOG.md | 1 + src/Command.php | 13 +++++++++ src/DMLQueryBuilder.php | 17 ++++++++---- src/Schema.php | 2 +- tests/CommandTest.php | 37 ++----------------------- tests/Provider/QueryBuilderProvider.php | 10 +++---- tests/SchemaTest.php | 17 ++++++++++++ tests/Support/Fixture/mssql.sql | 11 ++++---- 8 files changed, 56 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3b68110..2e15aba8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.1.1 under development - Enh #286: Change property `Schema::$typeMap` to constant `Schema::TYPE_MAP` (@Tigrov) +- Bug #287: Fix `DMLQueryBuilder::insertWithReturningPks()` and `Command::insertWithReturningPks()` methods (@Tigrov) ## 1.1.0 November 12, 2023 diff --git a/src/Command.php b/src/Command.php index 3862c8ad..69e30f76 100644 --- a/src/Command.php +++ b/src/Command.php @@ -12,6 +12,19 @@ */ final class Command extends AbstractPdoCommand { + public function insertWithReturningPks(string $table, array $columns): bool|array + { + if (empty($this->db->getSchema()->getTableSchema($table)?->getPrimaryKey())) { + if (parent::insert($table, $columns)->execute() === 0) { + return false; + } + + return []; + } + + return parent::insertWithReturningPks($table, $columns); + } + public function showDatabases(): array { $sql = <<prepareInsertValues($table, $columns, $params); + $tableSchema = $this->schema->getTableSchema($table); + $primaryKeys = $tableSchema?->getPrimaryKey(); + + if (empty($primaryKeys)) { + return $this->insert($table, $columns, $params); + } $createdCols = []; $insertedCols = []; - $returnColumns = $this->schema->getTableSchema($table)?->getColumns() ?? []; + $returnColumns = array_intersect_key($tableSchema?->getColumns() ?? [], array_flip($primaryKeys)); foreach ($returnColumns as $returnColumn) { - if ($returnColumn->isComputed()) { - continue; - } - $dbType = $returnColumn->getDbType(); if (in_array($dbType, ['char', 'varchar', 'nchar', 'nvarchar', 'binary', 'varbinary'], true)) { @@ -54,6 +57,8 @@ public function insertWithReturningPks(string $table, QueryInterface|array $colu $insertedCols[] = 'INSERTED.' . $quotedName; } + [$names, $placeholders, $values, $params] = $this->prepareInsertValues($table, $columns, $params); + $sql = 'INSERT INTO ' . $this->quoter->quoteTableName($table) . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '') . ' OUTPUT ' . implode(',', $insertedCols) . ' INTO @temporary_inserted' diff --git a/src/Schema.php b/src/Schema.php index 0da2199b..ca7a32c5 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -305,7 +305,7 @@ protected function loadTableForeignKeys(string $tableName): array * @throws InvalidConfigException * @throws Throwable * - * @return array Indexes for the given table. + * @return IndexConstraint[] Indexes for the given table. */ protected function loadTableIndexes(string $tableName): array { diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 5ce86c4c..387e4617 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -127,31 +127,6 @@ public function testInsertVarbinary(mixed $expectedData, mixed $testData): void $this->assertSame($expectedData, $resultData['blob_col']); } - /** - * @throws Exception - * @throws InvalidCallException - * @throws InvalidConfigException - * @throws Throwable - */ - public function testInsertWithReturningPks(): void - { - $db = $this->getConnection(true); - - $command = $db->createCommand(); - - $this->assertSame( - [ - 'id' => '4', - 'email' => 'test_1@example.com', - 'name' => 'test_1', - 'address' => null, - 'status' => '0', - 'profile_id' => null, - ], - $command->insertWithReturningPks('{{customer}}', ['name' => 'test_1', 'email' => 'test_1@example.com']), - ); - } - /** * @throws Exception * @throws InvalidCallException @@ -189,9 +164,7 @@ public function testInsertWithReturningPksWithComputedColumn(): void $result = $command->insertWithReturningPks('{{test_trigger}}', ['stringcol' => $insertedString]); $transaction->commit(); - $this->assertIsArray($result); - $this->assertSame($insertedString, $result['stringcol']); - $this->assertSame('1', $result['id']); + $this->assertSame(['id' => '1'], $result); } /** @@ -213,9 +186,7 @@ public function testInsertWithReturningPksWithRowVersionColumn(): void $insertedString = 'test'; $result = $command->insertWithReturningPks('{{test_trigger}}', ['stringcol' => $insertedString]); - $this->assertIsArray($result); - $this->assertSame($insertedString, $result['stringcol']); - $this->assertSame('1', $result['id']); + $this->assertSame(['id' => '1'], $result); } /** @@ -240,9 +211,7 @@ public function testInsertWithReturningPksWithRowVersionNullColumn(): void ['stringcol' => $insertedString, 'RV' => new Expression('DEFAULT')], ); - $this->assertIsArray($result); - $this->assertSame($insertedString, $result['stringcol']); - $this->assertSame('1', $result['id']); + $this->assertSame(['id' => '1'], $result); } /** diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index 30c16df5..b0712e50 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -113,7 +113,7 @@ public static function insertWithReturningPks(): array ], [], << 'test@example.com', @@ -128,7 +128,7 @@ public static function insertWithReturningPks(): array ['{{%type}}.[[related_id]]' => null, '[[time]]' => new Expression('now()')], [], << null], ], @@ -144,7 +144,7 @@ public static function insertWithReturningPks(): array ], [':phBar' => 'bar'], << 'bar', @@ -173,7 +173,7 @@ public static function insertWithReturningPks(): array ), [':phBar' => 'bar'], << 'bar', @@ -189,7 +189,7 @@ public static function insertWithReturningPks(): array ['order_id' => 1, 'item_id' => 1, 'quantity' => 1, 'subtotal' => 1.0], [], << 1, ':qp1' => 1, ':qp2' => 1, ':qp3' => 1.0,], ], diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index 7af67978..ab15dd7c 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -179,4 +179,21 @@ public function testNotConnectionPDO(): void $schema->refreshTableSchema('customer'); } + + public function testNegativeDefaultValues(): void + { + $db = $this->getConnection(true); + + $schema = $db->getSchema(); + $table = $schema->getTableSchema('negative_default_values'); + + $this->assertNotNull($table); + $this->assertSame(-123, $table->getColumn('smallint_col')?->getDefaultValue()); + $this->assertSame(-123, $table->getColumn('int_col')?->getDefaultValue()); + $this->assertSame(-123, $table->getColumn('bigint_col')?->getDefaultValue()); + $this->assertSame(-12345.6789, $table->getColumn('float_col')?->getDefaultValue()); + $this->assertEquals(-33.22, $table->getColumn('numeric_col')?->getDefaultValue()); + + $db->close(); + } } diff --git a/tests/Support/Fixture/mssql.sql b/tests/Support/Fixture/mssql.sql index 3fdc5fd5..5871068c 100644 --- a/tests/Support/Fixture/mssql.sql +++ b/tests/Support/Fixture/mssql.sql @@ -135,12 +135,11 @@ CREATE TABLE [dbo].[null_values] ( ); CREATE TABLE [dbo].[negative_default_values] ( - [tinyint_col] [tinyint] DEFAULT '-123', - [smallint_col] [tinyint] DEFAULT '-123', - [int_col] [smallint] DEFAULT '-123', - [bigint_col] [int] DEFAULT '-123', - [float_col] [float] DEFAULT '-12345.6789', - [numeric_col] [decimal](5,2) DEFAULT '-33.22' + [smallint_col] [smallint] DEFAULT -123, + [int_col] [int] DEFAULT -123, + [bigint_col] [bigint] DEFAULT -123, + [float_col] [float] DEFAULT -12345.6789, + [numeric_col] [decimal](5,2) DEFAULT -33.22 ); CREATE TABLE [dbo].[type] ( From 2964f967935b6cf047502569a9fc77147958c1e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 08:51:28 +0300 Subject: [PATCH 14/25] Update rector/rector requirement from ^0.18 to ^0.19 (#290) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/0.18.0...0.19.0) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c2181157..29561e3e 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "require-dev": { "maglnet/composer-require-checker": "^4.2", "phpunit/phpunit": "^9.6|^10.0", - "rector/rector": "^0.18", + "rector/rector": "^0.19", "roave/infection-static-analysis-plugin": "^1.16", "spatie/phpunit-watcher": "^1.23", "vimeo/psalm": "^4.3|^5.6", From ff1068608d10b14acb06f4b868c74de73067b674 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Sat, 3 Feb 2024 20:27:11 +0700 Subject: [PATCH 15/25] Minor refactoring of `Command` and `Quoter` + Fix psalm issues (#292) Co-authored-by: Sergei Predvoditelev --- .github/workflows/static.yml | 9 ++++++--- CHANGELOG.md | 1 + composer.json | 2 +- psalm.xml | 3 +++ psalm4.xml | 17 +++++++++++++++++ src/Command.php | 2 +- src/Quoter.php | 2 +- 7 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 psalm4.xml diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 0b170765..bba89642 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -39,6 +39,7 @@ jobs: - '8.0' - '8.1' - '8.2' + - '8.3' steps: - name: Checkout. @@ -66,8 +67,10 @@ jobs: FULL_BRANCH_NAME: ${{ env.FULL_BRANCH_NAME }} WORK_PACKAGE_URL: ${{ env.WORK_PACKAGE_URL }} - - name: Install dependencies with composer. - run: composer update --no-interaction --no-progress --optimize-autoloader --ansi - - name: Static analysis. + if: ${{ matrix.php != '8.0' }} run: vendor/bin/psalm --config=${{ inputs.psalm-config }} --shepherd --stats --output-format=github --php-version=${{ matrix.php }} + + - name: Static analysis. + if: ${{ matrix.php == '8.0' }} + run: vendor/bin/psalm --config=psalm4.xml --shepherd --stats --output-format=github --php-version=${{ matrix.php }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e15aba8..b2b24f21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Enh #286: Change property `Schema::$typeMap` to constant `Schema::TYPE_MAP` (@Tigrov) - Bug #287: Fix `DMLQueryBuilder::insertWithReturningPks()` and `Command::insertWithReturningPks()` methods (@Tigrov) +- Enh #292: Minor refactoring of `Command` and `Quoter` (@Tigrov) ## 1.1.0 November 12, 2023 diff --git a/composer.json b/composer.json index 29561e3e..90826a54 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "rector/rector": "^0.19", "roave/infection-static-analysis-plugin": "^1.16", "spatie/phpunit-watcher": "^1.23", - "vimeo/psalm": "^4.3|^5.6", + "vimeo/psalm": "^4.30|^5.20", "yiisoft/aliases": "^2.0", "yiisoft/cache-file": "^3.1", "yiisoft/var-dumper": "^1.5" diff --git a/psalm.xml b/psalm.xml index 23bfcce1..5001d0a7 100644 --- a/psalm.xml +++ b/psalm.xml @@ -14,4 +14,7 @@ + + + diff --git a/psalm4.xml b/psalm4.xml new file mode 100644 index 00000000..23bfcce1 --- /dev/null +++ b/psalm4.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/src/Command.php b/src/Command.php index 69e30f76..c7c665b7 100644 --- a/src/Command.php +++ b/src/Command.php @@ -15,7 +15,7 @@ final class Command extends AbstractPdoCommand public function insertWithReturningPks(string $table, array $columns): bool|array { if (empty($this->db->getSchema()->getTableSchema($table)?->getPrimaryKey())) { - if (parent::insert($table, $columns)->execute() === 0) { + if ($this->insert($table, $columns)->execute() === 0) { return false; } diff --git a/src/Quoter.php b/src/Quoter.php index 2a7fd735..5ab8d06c 100644 --- a/src/Quoter.php +++ b/src/Quoter.php @@ -25,7 +25,7 @@ public function quoteColumnName(string $name): string public function getTableNameParts(string $name, bool $withColumn = false): array { - if (preg_match_all('/([^.\[\]]+)|\[([^\[\]]+)]/', $name, $matches)) { + if (preg_match_all('/([^.\[\]]+)|\[([^\[\]]+)]/', $name, $matches) > 0) { $parts = array_slice($matches[0], -4, 4); } else { $parts = [$name]; From f19738b0a546be37565b531d91fce73b1183d791 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Sat, 10 Feb 2024 17:55:59 +0700 Subject: [PATCH 16/25] Resolve deprecated methods (#291) * Resolve deprecated methods * Add line to CHANGELOG.md [skip ci] * Fix test * Fix psalm issues --------- Co-authored-by: Sergei Predvoditelev --- CHANGELOG.md | 1 + src/Schema.php | 7 +++++-- tests/SchemaTest.php | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2b24f21..99a15a2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Enh #286: Change property `Schema::$typeMap` to constant `Schema::TYPE_MAP` (@Tigrov) - Bug #287: Fix `DMLQueryBuilder::insertWithReturningPks()` and `Command::insertWithReturningPks()` methods (@Tigrov) - Enh #292: Minor refactoring of `Command` and `Quoter` (@Tigrov) +- Enh #291: Resolve deprecated methods (@Tigrov) ## 1.1.0 November 12, 2023 diff --git a/src/Schema.php b/src/Schema.php index ca7a32c5..109fb07a 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -18,6 +18,7 @@ use Yiisoft\Db\Schema\ColumnSchemaInterface; use Yiisoft\Db\Schema\TableSchemaInterface; +use function array_map; use function explode; use function is_array; use function md5; @@ -328,7 +329,7 @@ protected function loadTableIndexes(string $tableName): array $indexes = $this->db->createCommand($sql, [':fullName' => $resolvedName->getFullName()])->queryAll(); /** @psalm-var array[] $indexes */ - $indexes = $this->normalizeRowKeyCase($indexes, true); + $indexes = array_map('array_change_key_case', $indexes); $indexes = DbArrayHelper::index($indexes, null, ['name']); $result = []; @@ -832,7 +833,7 @@ private function loadTableConstraints(string $tableName, string $returnType): mi $constraints = $this->db->createCommand($sql, [':fullName' => $resolvedName->getFullName()])->queryAll(); /** @psalm-var array[] $constraints */ - $constraints = $this->normalizeRowKeyCase($constraints, true); + $constraints = array_map('array_change_key_case', $constraints); $constraints = DbArrayHelper::index($constraints, null, ['type', 'name']); $result = [ @@ -901,6 +902,8 @@ private function loadTableConstraints(string $tableName, string $returnType): mi * @param string $name The table name. * * @return array The cache key. + * + * @psalm-suppress DeprecatedMethod */ protected function getCacheKey(string $name): array { diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index ab15dd7c..58d2a9ee 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -177,7 +177,7 @@ public function testNotConnectionPDO(): void $this->expectException(NotSupportedException::class); $this->expectExceptionMessage('Only PDO connections are supported.'); - $schema->refreshTableSchema('customer'); + $schema->refresh(); } public function testNegativeDefaultValues(): void From 276af87bde21f2e8546de08350ebc92aa91a44c0 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula <42547589+terabytesoftw@users.noreply.github.com> Date: Sun, 11 Feb 2024 17:16:34 -0300 Subject: [PATCH 17/25] Fix workflow mutation. (#295) --- .github/workflows/mutation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 669a9c63..bb184b91 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -84,6 +84,6 @@ jobs: - name: Run infection. run: | - vendor/bin/roave-infection-static-analysis-plugin -j2 --ignore-msi-with-no-mutations --only-covered + vendor/bin/roave-infection-static-analysis-plugin --threads=2 --ignore-msi-with-no-mutations --only-covered env: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} From 71ab38bc3b2498edc85d4210ad0a4a73f795649d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 17:33:22 +0300 Subject: [PATCH 18/25] Update rector/rector requirement from ^0.19 to ^1.0 (#294) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/0.19.0...1.0.0) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sergei Predvoditelev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 90826a54..f44917f2 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "require-dev": { "maglnet/composer-require-checker": "^4.2", "phpunit/phpunit": "^9.6|^10.0", - "rector/rector": "^0.19", + "rector/rector": "^1.0", "roave/infection-static-analysis-plugin": "^1.16", "spatie/phpunit-watcher": "^1.23", "vimeo/psalm": "^4.30|^5.20", From fb77c8369ee22799d88a8fa147f2e4ee068e5057 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 21 Mar 2024 07:21:06 +0000 Subject: [PATCH 19/25] Release version 1.2.0 --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a15a2f..98442992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # MSSQL Server driver for Yii Database Change Log -## 1.1.1 under development +## 1.2.0 March 21, 2024 - Enh #286: Change property `Schema::$typeMap` to constant `Schema::TYPE_MAP` (@Tigrov) -- Bug #287: Fix `DMLQueryBuilder::insertWithReturningPks()` and `Command::insertWithReturningPks()` methods (@Tigrov) -- Enh #292: Minor refactoring of `Command` and `Quoter` (@Tigrov) - Enh #291: Resolve deprecated methods (@Tigrov) +- Enh #292: Minor refactoring of `Command` and `Quoter` (@Tigrov) +- Bug #287: Fix `DMLQueryBuilder::insertWithReturningPks()` and `Command::insertWithReturningPks()` methods (@Tigrov) ## 1.1.0 November 12, 2023 From a0ab9e82cbbce9906fee4a520de153a6c8d3f161 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 21 Mar 2024 07:21:07 +0000 Subject: [PATCH 20/25] Prepare for next release --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98442992..4b8514d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # MSSQL Server driver for Yii Database Change Log +## 1.2.1 under development + +- no changes in this release. + ## 1.2.0 March 21, 2024 - Enh #286: Change property `Schema::$typeMap` to constant `Schema::TYPE_MAP` (@Tigrov) From ca6e276102ef079daac1ceae276463d6884346b6 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Fri, 12 Apr 2024 18:45:18 +0700 Subject: [PATCH 21/25] Update test according to main PR (#293) Co-authored-by: Sergei Predvoditelev --- CHANGELOG.md | 4 +-- rector.php | 5 +++- src/Builder/ExpressionBuilder.php | 16 ++++++++++ src/DQLQueryBuilder.php | 3 ++ src/SqlParser.php | 45 ++++++++++++++++++++++++++++ tests/CommandTest.php | 5 ++-- tests/Provider/SqlParserProvider.php | 25 ++++++++++++++++ tests/QueryBuilderTest.php | 12 ++++++++ tests/SqlParserTest.php | 25 ++++++++++++++++ 9 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 src/Builder/ExpressionBuilder.php create mode 100644 src/SqlParser.php create mode 100644 tests/Provider/SqlParserProvider.php create mode 100644 tests/SqlParserTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b8514d9..3ffbea6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # MSSQL Server driver for Yii Database Change Log -## 1.2.1 under development +## 2.0.0 under development -- no changes in this release. +- Enh #293: Implement `SqlParser` and `ExpressionBuilder` driver classes (@Tigrov) ## 1.2.0 March 21, 2024 diff --git a/rector.php b/rector.php index c80d86e9..7e1860ee 100644 --- a/rector.php +++ b/rector.php @@ -11,7 +11,10 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__ . '/src', - __DIR__ . '/tests', + /** + * Disabled ./tests directory due to different branches with main package when testing + */ + // __DIR__ . '/tests', ]); // register a single rule diff --git a/src/Builder/ExpressionBuilder.php b/src/Builder/ExpressionBuilder.php new file mode 100644 index 00000000..b9dac17a --- /dev/null +++ b/src/Builder/ExpressionBuilder.php @@ -0,0 +1,16 @@ + InConditionBuilder::class, LikeCondition::class => LikeConditionBuilder::class, + Expression::class => ExpressionBuilder::class, ]); } diff --git a/src/SqlParser.php b/src/SqlParser.php new file mode 100644 index 00000000..95bda82d --- /dev/null +++ b/src/SqlParser.php @@ -0,0 +1,45 @@ +length - 1; + + while ($this->position < $length) { + $pos = $this->position++; + + match ($this->sql[$pos]) { + ':' => ($word = $this->parseWord()) === '' + ? $this->skipChars(':') + : $result = ':' . $word, + '"', "'" => $this->skipQuotedWithoutEscape($this->sql[$pos]), + '[' => $this->sql[$this->position] === '[' + ? $this->skipToAfterString(']]') + : $this->skipQuotedWithoutEscape(']'), + '-' => $this->sql[$this->position] === '-' + ? ++$this->position && $this->skipToAfterChar("\n") + : null, + '/' => $this->sql[$this->position] === '*' + ? ++$this->position && $this->skipToAfterString('*/') + : null, + default => null, + }; + + if ($result !== null) { + $position = $pos; + + return $result; + } + } + + return null; + } +} diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 387e4617..13a2d037 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -241,9 +241,10 @@ public function testUpdate( array $columns, array|string $conditions, array $params, - string $expected + array $expectedValues, + int $expectedCount, ): void { - parent::testUpdate($table, $columns, $conditions, $params, $expected); + parent::testUpdate($table, $columns, $conditions, $params, $expectedValues, $expectedCount); } /** diff --git a/tests/Provider/SqlParserProvider.php b/tests/Provider/SqlParserProvider.php new file mode 100644 index 00000000..2accafbd --- /dev/null +++ b/tests/Provider/SqlParserProvider.php @@ -0,0 +1,25 @@ + Date: Sat, 13 Apr 2024 15:03:03 +0700 Subject: [PATCH 22/25] Allow scalar values for `Query::select()` (#296) * Update tests according main PR * Apply Rector changes (CI) * Revert "Apply Rector changes (CI)" This reverts commit 866463384f5fb34458e7b68fc09c8c2f6f303c4c. * Disable Rector for tests * Add type string to test --------- Co-authored-by: Tigrov Co-authored-by: Sergei Predvoditelev --- tests/QueryBuilderTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 43e9eb29..626f59b8 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -1141,4 +1141,10 @@ public function testAlterColumnWithExpression(): void $this->assertEquals($expected, $sql); } + + /** @dataProvider \Yiisoft\Db\Mssql\Tests\Provider\QueryBuilderProvider::selectScalar */ + public function testSelectScalar(array|bool|float|int|string $columns, string $expected): void + { + parent::testSelectScalar($columns, $expected); + } } From 55e713967234f41b9e0a46c3e1403dd045c8b41f Mon Sep 17 00:00:00 2001 From: Luiz Marin <67489841+luizcmarin@users.noreply.github.com> Date: Tue, 23 Apr 2024 06:48:10 -0300 Subject: [PATCH 23/25] Docs folder standardization and other fixes (#299) --- LICENSE.md | 8 ++++---- README.md | 23 +++++++++++++++++------ composer.json | 2 +- docs/{en/testing.md => internals.md} | 10 +++++----- phpunit.xml.dist | 2 +- src/DQLQueryBuilder.php | 2 +- src/Dsn.php | 2 +- 7 files changed, 30 insertions(+), 19 deletions(-) rename docs/{en/testing.md => internals.md} (91%) diff --git a/LICENSE.md b/LICENSE.md index bc5674fe..6a920d60 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,17 +1,17 @@ -Copyright © 2008 by Yii Software (https://www.yiiframework.com/) +Copyright © 2008 by Yii Software () All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Yii Software nor the names of its +* Neither the name of Yii Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/README.md b/README.md index 393c5588..ecb517ff 100644 --- a/README.md +++ b/README.md @@ -37,19 +37,30 @@ have access to a SQL Server database and the necessary credentials to connect to The package could be installed via composer: -```php +```shell composer require yiisoft/db-mssql ``` -## Usage +## Documentation -To configure connection to MSSQL database check [Connecting MSSQL](https://github.com/yiisoft/db/blob/master/docs/en/connection/mssql.md). +English: -[Check the documentation docs](https://github.com/yiisoft/db/blob/master/docs/en/README.md) to learn about usage. +- To configure connection to MSSQL database check [Connecting MSSQL](https://github.com/yiisoft/db/blob/master/docs/en/connection/mssql.md). +- [Check the documentation docs](https://github.com/yiisoft/db/blob/master/docs/en/README.md) to learn about usage. -## Testing +Português - Brasil: -[Check the documentation](/docs/en/testing.md) to learn about testing. +- Para configurar a conexão com MSSQL leia [Connecting MSSQL](https://github.com/yiisoft/db/blob/master/docs/pt-BR/connection/mssql.md). +- [Confira a documentação](https://github.com/yiisoft/db/blob/master/docs/pt-BR/README.md) para aprender como usar. + +Testing: + +- [Internals](docs/internals.md) + +## Support + +If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for that. +You may also check out other [Yii Community Resources](https://www.yiiframework.com/community). ## Support the project diff --git a/composer.json b/composer.json index f44917f2..46517c86 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "issues": "https://github.com/yiisoft/db-mssql/issues", "forum": "https://www.yiiframework.com/forum/", "wiki": "https://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", + "irc": "ircs://irc.libera.chat:6697/yii", "chat": "https://t.me/yii3en", "source": "https://github.com/yiisoft/db-mssql" }, diff --git a/docs/en/testing.md b/docs/internals.md similarity index 91% rename from docs/en/testing.md rename to docs/internals.md index 2ca487fc..fb1e89fb 100644 --- a/docs/en/testing.md +++ b/docs/internals.md @@ -1,4 +1,4 @@ -# Testing +# Internals ## Github actions @@ -10,7 +10,7 @@ All our packages have github actions by default, so you can test your [contribut For greater ease it is recommended to use docker containers, for this you can use the [docker-compose.yml](https://docs.docker.com/compose/compose-file/) file that is in the docs folder. -1. [MSSQL 2022](/docker-compose.yml) +1. [MSSQL 2022](../../../docker-compose.yml) For running the docker containers you can use the following command: @@ -32,7 +32,7 @@ The following steps are required to run the tests: vendor/bin/phpunit ``` -### Mutation testing +## Mutation testing The package tests are checked with [Infection](https://infection.github.io/) mutation framework with [Infection Static Analysis Plugin](https://github.com/Roave/infection-static-analysis-plugin). To run it: @@ -51,8 +51,8 @@ The code is statically analyzed with [Psalm](https://psalm.dev/). To run static ## Rector -Use [Rector](https://github.com/rectorphp/rector) to make codebase follow some specific rules or -use either newest or any specific version of PHP: +Use [Rector](https://github.com/rectorphp/rector) to make codebase follow some specific rules or +use either newest or any specific version of PHP: ```shell ./vendor/bin/rector diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 76dd054b..4504c2cd 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,5 @@ - + diff --git a/src/DQLQueryBuilder.php b/src/DQLQueryBuilder.php index dfabd174..2dbeaa92 100644 --- a/src/DQLQueryBuilder.php +++ b/src/DQLQueryBuilder.php @@ -86,7 +86,7 @@ protected function newBuildOrderByAndLimit( $sql .= $this->separator . $orderByString; /** - * @link http://technet.microsoft.com/en-us/library/gg699618.aspx + * @link https://technet.microsoft.com/en-us/library/gg699618.aspx */ $offsetString = $this->hasOffset($offset) ? ($offset instanceof ExpressionInterface ? $this->buildExpression($offset) : (string)$offset) : '0'; diff --git a/src/Dsn.php b/src/Dsn.php index 24137bb0..6bcb7419 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -24,7 +24,7 @@ public function __construct( /** * @return string the Data Source Name, or DSN, has the information required to connect to the database. - * Please refer to the [PHP manual](http://php.net/manual/en/pdo.construct.php) on the format of the DSN string. + * Please refer to the [PHP manual](https://php.net/manual/en/pdo.construct.php) on the format of the DSN string. * * The `driver` array key is used as the driver prefix of the DSN, all further key-value pairs are rendered as * `key=value` and concatenated by `;`. For example: From 94340d98e077e5ade1dcd02dacc3da32648a3cf6 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Sat, 27 Apr 2024 19:27:02 +0700 Subject: [PATCH 24/25] Remove Scrutinizer [skip ci] (#301) --- .gitattributes | 1 - .scrutinizer.yml | 67 ------------------------------------------------ 2 files changed, 68 deletions(-) delete mode 100644 .scrutinizer.yml diff --git a/.gitattributes b/.gitattributes index 0e7b2047..e2b21a8d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -26,7 +26,6 @@ /.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.scrutinizer.yml export-ignore /phpunit.xml.dist export-ignore /docs export-ignore diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index ab7dc881..00000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,67 +0,0 @@ -checks: - php: true - -filter: - paths: - - src/ - -build: - image: default-bionic - - environment: - php: - version: 8.1.18 - ini: - xdebug.mode: coverage - - nodes: - analysis: - tests: - override: - - php-scrutinizer-run - - phpunit: - services: - db-mssql: - image: mcr.microsoft.com/mssql/server:2017-latest - - # Define any additional environment variables that are needed by the service. - env: - SA_PASSWORD: YourStrong!Passw0rd - ACCEPT_EULA: Y - MSSQL_PID: Developer - - # We automatically forward these ports from your localhost to the service's port. - # Alternatively, you can also access the service on the "$SERVICE_SOME_NAME_IP" - # environment variable. - ports: - # Forward 127.0.0.1:12345 -> SERVICE_IP:12345 - - 1433 - - # If your service writes data to disk like most databases do, you can significantly - # speed up tests by mounting a ramdisk at those paths. - ramdisks: - - /var/lib/data - - dependencies: - override: - - curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - - - curl https://packages.microsoft.com/config/ubuntu/18.04/prod.list | sudo tee /etc/apt/sources.list.d/msprod.list - - sudo apt-get install apt-transport-https ca-certificates -y - - sudo apt-get install make -y - - sudo apt-get update -y - - sudo apt-get install unixodbc-dev=2.3.7 unixodbc=2.3.7 odbcinst1debian2=2.3.7 odbcinst=2.3.7 -y - - sudo ACCEPT_EULA=Y apt-get install mssql-tools -y - - sudo ls /opt/mssql-tools/bin/sqlcmd* - - /opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE yiitest' - - pecl channel-update pecl.php.net - - pecl install pdo_sqlsrv - - composer require yiisoft/db:dev-master --ansi - - tests: - override: - - command: ./vendor/bin/phpunit --coverage-clover ./coverage.xml - on_node: 1 - coverage: - file: coverage.xml - format: php-clover From 89bddac62662481e917021505330c187b9f7b497 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Mon, 29 Apr 2024 10:01:17 +0700 Subject: [PATCH 25/25] Change db dependency to `dev-master` [skip ci] (#303) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 46517c86..06e43b48 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "require": { "php": "^8.0", "ext-pdo": "*", - "yiisoft/db": "^1.2" + "yiisoft/db": "dev-master" }, "require-dev": { "maglnet/composer-require-checker": "^4.2",