From caebdf3b4fad353638cb1ca5f8a28fd2a7443fbd Mon Sep 17 00:00:00 2001 From: Rustam Date: Wed, 20 Aug 2025 17:27:51 +0500 Subject: [PATCH 1/9] Adapt to db changes --- tests/CommandTest.php | 7 ++++--- tests/QueryBuilderTest.php | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 58063842..1e29d157 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\Attributes\DataProviderExternal; use Yiisoft\Db\Exception\NotSupportedException; +use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Mysql\Tests\Provider\CommandProvider; use Yiisoft\Db\Mysql\Tests\Support\TestTrait; use Yiisoft\Db\Query\Query; @@ -118,12 +119,12 @@ public function testInsertReturningPksWithSubqueryAndNoAutoincrement(): void public function testUpdate( string $table, array $columns, - array|string $conditions, - array $params, + array|ExpressionInterface|string $conditions, + array|ExpressionInterface|string|null $from, array $expectedValues, int $expectedCount, ): void { - parent::testUpdate($table, $columns, $conditions, $params, $expectedValues, $expectedCount); + parent::testUpdate($table, $columns, $conditions, $from, $expectedValues, $expectedCount); } #[DataProviderExternal(CommandProvider::class, 'upsert')] diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 69498d4b..839ffe2b 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -564,12 +564,13 @@ public function testResetSequence(): void public function testUpdate( string $table, array $columns, - array|string $condition, + array|ExpressionInterface|string $condition, + array|ExpressionInterface|string|null $from, array $params, string $expectedSql, array $expectedParams, ): void { - parent::testUpdate($table, $columns, $condition, $params, $expectedSql, $expectedParams); + parent::testUpdate($table, $columns, $condition, $from, $params, $expectedSql, $expectedParams); } #[DataProviderExternal(QueryBuilderProvider::class, 'upsert')] From c2ec54138731efd858767feee16c91409347c454 Mon Sep 17 00:00:00 2001 From: Rustam Date: Thu, 21 Aug 2025 15:35:02 +0500 Subject: [PATCH 2/9] Fix unsupported clause & add tests --- src/DMLQueryBuilder.php | 13 + tests/Provider/QueryBuilderProvider.php | 362 +++++++++++++++++++++++- 2 files changed, 372 insertions(+), 3 deletions(-) diff --git a/src/DMLQueryBuilder.php b/src/DMLQueryBuilder.php index 25629ce3..9e66c9df 100644 --- a/src/DMLQueryBuilder.php +++ b/src/DMLQueryBuilder.php @@ -62,6 +62,19 @@ public function resetSequence(string $table, int|string|null $value = null): str EXECUTE autoincrement_stmt"; } + public function update( + string $table, + array $columns, + array|string|ExpressionInterface $condition, + array|string|ExpressionInterface|null $from = null, + array &$params = [] + ): string { + /** + * MySQL does not support UPDATE ... FROM ... + */ + return parent::update($table, $columns, $condition, null, $params); + } + public function upsert( string $table, array|QueryInterface $insertColumns, diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index ac753a76..485c6f9e 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -5,8 +5,10 @@ namespace Yiisoft\Db\Mysql\Tests\Provider; use Yiisoft\Db\Constant\ColumnType; +use Yiisoft\Db\Constant\DataType; use Yiisoft\Db\Constant\PseudoType; use Yiisoft\Db\Expression\Expression; +use Yiisoft\Db\Expression\Param; use Yiisoft\Db\Mysql\Column\ColumnBuilder; use Yiisoft\Db\Mysql\Tests\Support\TestTrait; @@ -117,6 +119,343 @@ public static function upsert(): array return $upsert; } + public static function update(): array + { + return [ + [ + '{{table}}', + ['name' => '{{test}}'], + [], + null, + [], + static::replaceQuotes( + << '{{test}}', + ], + ], + [ + '{{table}}', + ['name' => '{{test}}'], + ['id' => 1], + null, + [], + static::replaceQuotes( + << '{{test}}', + ], + ], + [ + '{{table}}', + ['name' => '{{tmp}}.{{name}}'], + [], + 'tmp', + [], + static::replaceQuotes( + << '{{tmp}}.{{name}}', + ], + ], + [ + '{{table}}', + ['name' => '{{tmp}}.{{name}}'], + [], + ['tmp'], + [], + static::replaceQuotes( + << '{{tmp}}.{{name}}', + ], + ], + [ + '{{table}}', + ['name' => '{{tmp}}.{{name}}'], + ['id' => 1], + 'tmp', + [], + static::replaceQuotes( + << '{{tmp}}.{{name}}', + ], + ], + [ + '{{table}}', + ['name' => '{{tmp}}.{{name}}'], + [], + new Expression('SELECT [[name]] FROM [[tmp]] WHERE [[id]] = 1'), + [], + static::replaceQuotes( + << '{{tmp}}.{{name}}', + ], + ], + [ + '{{table}}', + ['name' => '{{tmp}}.{{name}}'], + [], + [static::getDb()->select('name')->from('tmp')->where(['id' => 1])], + [], + static::replaceQuotes( + << '{{tmp}}.{{name}}', + ], + ], + [ + '{{table}}', + ['name' => '{{tmp}}'], + [], + ['tmp' => static::getDb()->select('name')->from('tmp')->where(['id' => 1])], + [], + static::replaceQuotes( + << '{{tmp}}', + ], + ], + [ + '{{table}}', + ['{{table}}.name' => '{{test}}'], + ['id' => 1], + null, + ['id' => 'boolean'], + static::replaceQuotes( + << 'boolean', + ':qp1' => '{{test}}', + ], + ], + [ + 'customer', + ['status' => 1, 'updated_at' => new Expression('now()')], + ['id' => 100], + null, + [], + static::replaceQuotes( + << 1], + ], + 'Expressions without params' => [ + '{{product}}', + ['name' => new Expression('UPPER([[name]])')], + '[[name]] = :name', + null, + ['name' => new Expression('LOWER([[name]])')], + static::replaceQuotes( + << [ + '{{product}}', + ['price' => new Expression('[[price]] + :val', [':val' => 1])], + '[[start_at]] < :date', + null, + ['date' => new Expression('NOW()')], + static::replaceQuotes( + << 1], + ], + 'Expression without params and with params' => [ + '{{product}}', + ['name' => new Expression('UPPER([[name]])')], + '[[name]] = :name', + null, + ['name' => new Expression('LOWER(:val)', [':val' => 'Apple'])], + static::replaceQuotes( + << 'Apple'], + ], + 'Expressions with the same params' => [ + '{{product}}', + ['name' => new Expression('LOWER(:val)', ['val' => 'Apple'])], + '[[name]] != :name', + null, + ['name' => new Expression('UPPER(:val)', ['val' => 'Banana'])], + static::replaceQuotes( + << 'Apple', + 'val_0' => 'Banana', + ], + ], + 'Expressions with the same params starting with and without colon' => [ + '{{product}}', + ['name' => new Expression('LOWER(:val)', [':val' => 'Apple'])], + '[[name]] != :name', + null, + ['name' => new Expression('UPPER(:val)', ['val' => 'Banana'])], + static::replaceQuotes( + << 'Apple', + 'val_0' => 'Banana', + ], + ], + 'Expressions with the same and different params' => [ + '{{product}}', + ['price' => new Expression('[[price]] * :val + :val1', ['val' => 1.2, 'val1' => 2])], + '[[name]] IN :values', + null, + ['values' => new Expression('(:val, :val2)', ['val' => 'Banana', 'val2' => 'Cherry'])], + static::replaceQuotes( + << 1.2, + 'val1' => 2, + 'val_0' => 'Banana', + 'val2' => 'Cherry', + ], + ], + 'Expressions with the different params' => [ + '{{product}}', + ['name' => new Expression('LOWER(:val)', ['val' => 'Apple'])], + '[[name]] != :name', + null, + ['name' => new Expression('UPPER(:val1)', ['val1' => 'Banana'])], + static::replaceQuotes( + << 'Apple', + 'val1' => 'Banana', + ], + ], + 'Expressions with nested Expressions' => [ + '{{table}}', + [ + 'name' => new Expression( + ':val || :val_0', + [ + 'val' => new Expression('LOWER(:val || :val_0)', ['val' => 'A', 'val_0' => 'B']), + 'val_0' => new Param('C', DataType::STRING), + ], + ), + ], + '[[name]] != :val || :val_0', + null, + [ + 'val_0' => new Param('F', DataType::STRING), + 'val' => new Expression('UPPER(:val || :val_0)', ['val' => 'D', 'val_0' => 'E']), + ], + static::replaceQuotes( + << 'A', + 'val_0_1' => 'B', + 'val_0_0' => new Param('C', DataType::STRING), + 'val_1' => 'D', + 'val_0_2' => 'E', + 'val_0' => new Param('F', DataType::STRING), + ], + ], + 'Expressions with indexed params' => [ + '{{product}}', + ['name' => new Expression('LOWER(?)', ['Apple'])], + '[[name]] != ?', + null, + ['Banana'], + static::replaceQuotes( + << [ + '{{product}}', + ['price' => 10], + ':val', + null, + [':val' => new Expression("label=':val' AND name=:val", [':val' => 'Apple'])], + static::replaceQuotes( + << 10, + ':val_0' => 'Apple', + ], + ], + 'Expressions without placeholders in SQL statement' => [ + '{{product}}', + ['price' => 10], + ':val', + null, + [':val' => new Expression("label=':val'", [':val' => 'Apple'])], + static::replaceQuotes( + << 10, + ':val_0' => 'Apple', + ], + ], + ]; + } + public static function upsertReturning(): array { $upsert = self::upsert(); @@ -195,7 +534,16 @@ public static function upsertReturning(): array ['int_col', 'char_col', 'char_col2', 'char_col3'], 'INSERT INTO `type` (`int_col`, `char_col`, `float_col`, `bool_col`) VALUES (:qp0, :qp1, :qp2, :qp3);' . 'SELECT :qp4 AS `int_col`, :qp5 AS `char_col`, :qp6 AS `char_col2`, :qp7 AS `char_col3`', - [':qp0' => 3, ':qp1' => 'a', ':qp2' => 1.2, ':qp3' => true, ':qp4' => 3, ':qp5' => 'a', ':qp6' => 'something', ':qp7' => null], + [ + ':qp0' => 3, + ':qp1' => 'a', + ':qp2' => 1.2, + ':qp3' => true, + ':qp4' => 3, + ':qp5' => 'a', + ':qp6' => 'something', + ':qp7' => null, + ], ], 'no primary key but unique' => [ 'without_pk', @@ -282,8 +630,16 @@ public static function buildColumnDefinition(): array $values[PseudoType::UUID_PK_SEQ][0] = 'binary(16) PRIMARY KEY'; $values['uuidPrimaryKey()'][0] = 'binary(16) PRIMARY KEY'; $values['defaultValue($expression)'] = ['int DEFAULT 3', ColumnBuilder::integer()->defaultValue(3)]; - $values['timestamp(6)'] = ['timestamp(6) DEFAULT CURRENT_TIMESTAMP(6)', ColumnBuilder::timestamp(6)->defaultValue(new Expression('CURRENT_TIMESTAMP(6)'))]; - $values['timestamp(null)'] = ['timestamp DEFAULT CURRENT_TIMESTAMP', ColumnBuilder::timestamp(null)->defaultValue(new Expression('CURRENT_TIMESTAMP'))]; + $values['timestamp(6)'] = [ + 'timestamp(6) DEFAULT CURRENT_TIMESTAMP(6)', + ColumnBuilder::timestamp(6)->defaultValue(new Expression('CURRENT_TIMESTAMP(6)')), + ]; + $values['timestamp(null)'] = [ + 'timestamp DEFAULT CURRENT_TIMESTAMP', + ColumnBuilder::timestamp(null)->defaultValue( + new Expression('CURRENT_TIMESTAMP') + ), + ]; } return $values; From facd1e194df168d8f189e2d58d245dde2f9fb6c7 Mon Sep 17 00:00:00 2001 From: Rustam Date: Thu, 21 Aug 2025 15:53:04 +0500 Subject: [PATCH 3/9] Fix tests --- tests/Provider/QueryBuilderProvider.php | 46 ++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index c819c0a5..ba529703 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -132,7 +132,7 @@ public static function update(): array [], null, [], - static::replaceQuotes( + self::replaceQuotes( << 1], null, [], - static::replaceQuotes( + self::replaceQuotes( << 1], 'tmp', [], - static::replaceQuotes( + self::replaceQuotes( << '{{tmp}}.{{name}}'], [], - [static::getDb()->select('name')->from('tmp')->where(['id' => 1])], + [self::getDb()->select('name')->from('tmp')->where(['id' => 1])], [], - static::replaceQuotes( + self::replaceQuotes( << '{{tmp}}'], [], - ['tmp' => static::getDb()->select('name')->from('tmp')->where(['id' => 1])], + ['tmp' => self::getDb()->select('name')->from('tmp')->where(['id' => 1])], [], - static::replaceQuotes( + self::replaceQuotes( << 1], null, ['id' => 'boolean'], - static::replaceQuotes( + self::replaceQuotes( << 100], null, [], - static::replaceQuotes( + self::replaceQuotes( << new Expression('LOWER([[name]])')], - static::replaceQuotes( + self::replaceQuotes( << new Expression('NOW()')], - static::replaceQuotes( + self::replaceQuotes( << new Expression('LOWER(:val)', [':val' => 'Apple'])], - static::replaceQuotes( + self::replaceQuotes( << new Expression('UPPER(:val)', ['val' => 'Banana'])], - static::replaceQuotes( + self::replaceQuotes( << new Expression('UPPER(:val)', ['val' => 'Banana'])], - static::replaceQuotes( + self::replaceQuotes( << new Expression('(:val, :val2)', ['val' => 'Banana', 'val2' => 'Cherry'])], - static::replaceQuotes( + self::replaceQuotes( << new Expression('UPPER(:val1)', ['val1' => 'Banana'])], - static::replaceQuotes( + self::replaceQuotes( << new Param('F', DataType::STRING), 'val' => new Expression('UPPER(:val || :val_0)', ['val' => 'D', 'val_0' => 'E']), ], - static::replaceQuotes( + self::replaceQuotes( << new Expression("label=':val' AND name=:val", [':val' => 'Apple'])], - static::replaceQuotes( + self::replaceQuotes( << new Expression("label=':val'", [':val' => 'Apple'])], - static::replaceQuotes( + self::replaceQuotes( << Date: Wed, 27 Aug 2025 16:11:38 +0500 Subject: [PATCH 4/9] Fix test providers --- tests/Provider/QueryBuilderProvider.php | 340 +----------------------- 1 file changed, 7 insertions(+), 333 deletions(-) diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index 6a73abd8..6a9b3296 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -125,339 +125,13 @@ public static function upsert(): array public static function update(): array { - return [ - [ - '{{table}}', - ['name' => '{{test}}'], - [], - null, - [], - self::replaceQuotes( - << '{{test}}', - ], - ], - [ - '{{table}}', - ['name' => '{{test}}'], - ['id' => 1], - null, - [], - self::replaceQuotes( - << '{{test}}', - ], - ], - [ - '{{table}}', - ['name' => '{{tmp}}.{{name}}'], - [], - 'tmp', - [], - self::replaceQuotes( - << '{{tmp}}.{{name}}', - ], - ], - [ - '{{table}}', - ['name' => '{{tmp}}.{{name}}'], - [], - ['tmp'], - [], - self::replaceQuotes( - << '{{tmp}}.{{name}}', - ], - ], - [ - '{{table}}', - ['name' => '{{tmp}}.{{name}}'], - ['id' => 1], - 'tmp', - [], - self::replaceQuotes( - << '{{tmp}}.{{name}}', - ], - ], - [ - '{{table}}', - ['name' => '{{tmp}}.{{name}}'], - [], - new Expression('SELECT [[name]] FROM [[tmp]] WHERE [[id]] = 1'), - [], - self::replaceQuotes( - << '{{tmp}}.{{name}}', - ], - ], - [ - '{{table}}', - ['name' => '{{tmp}}.{{name}}'], - [], - [self::getDb()->select('name')->from('tmp')->where(['id' => 1])], - [], - self::replaceQuotes( - << '{{tmp}}.{{name}}', - ], - ], - [ - '{{table}}', - ['name' => '{{tmp}}'], - [], - ['tmp' => self::getDb()->select('name')->from('tmp')->where(['id' => 1])], - [], - self::replaceQuotes( - << '{{tmp}}', - ], - ], - [ - '{{table}}', - ['{{table}}.name' => '{{test}}'], - ['id' => 1], - null, - ['id' => 'boolean'], - self::replaceQuotes( - << 'boolean', - ':qp1' => '{{test}}', - ], - ], - [ - 'customer', - ['status' => 1, 'updated_at' => new Expression('now()')], - ['id' => 100], - null, - [], - self::replaceQuotes( - << 1], - ], - 'Expressions without params' => [ - '{{product}}', - ['name' => new Expression('UPPER([[name]])')], - '[[name]] = :name', - null, - ['name' => new Expression('LOWER([[name]])')], - self::replaceQuotes( - << [ - '{{product}}', - ['price' => new Expression('[[price]] + :val', [':val' => 1])], - '[[start_at]] < :date', - null, - ['date' => new Expression('NOW()')], - self::replaceQuotes( - << 1], - ], - 'Expression without params and with params' => [ - '{{product}}', - ['name' => new Expression('UPPER([[name]])')], - '[[name]] = :name', - null, - ['name' => new Expression('LOWER(:val)', [':val' => 'Apple'])], - self::replaceQuotes( - << 'Apple'], - ], - 'Expressions with the same params' => [ - '{{product}}', - ['name' => new Expression('LOWER(:val)', ['val' => 'Apple'])], - '[[name]] != :name', - null, - ['name' => new Expression('UPPER(:val)', ['val' => 'Banana'])], - self::replaceQuotes( - << 'Apple', - 'val_0' => 'Banana', - ], - ], - 'Expressions with the same params starting with and without colon' => [ - '{{product}}', - ['name' => new Expression('LOWER(:val)', [':val' => 'Apple'])], - '[[name]] != :name', - null, - ['name' => new Expression('UPPER(:val)', ['val' => 'Banana'])], - self::replaceQuotes( - << 'Apple', - 'val_0' => 'Banana', - ], - ], - 'Expressions with the same and different params' => [ - '{{product}}', - ['price' => new Expression('[[price]] * :val + :val1', ['val' => 1.2, 'val1' => 2])], - '[[name]] IN :values', - null, - ['values' => new Expression('(:val, :val2)', ['val' => 'Banana', 'val2' => 'Cherry'])], - self::replaceQuotes( - << 1.2, - 'val1' => 2, - 'val_0' => 'Banana', - 'val2' => 'Cherry', - ], - ], - 'Expressions with the different params' => [ - '{{product}}', - ['name' => new Expression('LOWER(:val)', ['val' => 'Apple'])], - '[[name]] != :name', - null, - ['name' => new Expression('UPPER(:val1)', ['val1' => 'Banana'])], - self::replaceQuotes( - << 'Apple', - 'val1' => 'Banana', - ], - ], - 'Expressions with nested Expressions' => [ - '{{table}}', - [ - 'name' => new Expression( - ':val || :val_0', - [ - 'val' => new Expression('LOWER(:val || :val_0)', ['val' => 'A', 'val_0' => 'B']), - 'val_0' => new Param('C', DataType::STRING), - ], - ), - ], - '[[name]] != :val || :val_0', - null, - [ - 'val_0' => new Param('F', DataType::STRING), - 'val' => new Expression('UPPER(:val || :val_0)', ['val' => 'D', 'val_0' => 'E']), - ], - self::replaceQuotes( - << 'A', - 'val_0_1' => 'B', - 'val_0_0' => new Param('C', DataType::STRING), - 'val_1' => 'D', - 'val_0_2' => 'E', - 'val_0' => new Param('F', DataType::STRING), - ], - ], - 'Expressions with indexed params' => [ - '{{product}}', - ['name' => new Expression('LOWER(?)', ['Apple'])], - '[[name]] != ?', - null, - ['Banana'], - self::replaceQuotes( - << [ - '{{product}}', - ['price' => 10], - ':val', - null, - [':val' => new Expression("label=':val' AND name=:val", [':val' => 'Apple'])], - self::replaceQuotes( - << 10, - ':val_0' => 'Apple', - ], - ], - 'Expressions without placeholders in SQL statement' => [ - '{{product}}', - ['price' => 10], - ':val', - null, - [':val' => new Expression("label=':val'", [':val' => 'Apple'])], - self::replaceQuotes( - << 10, - ':val_0' => 'Apple', - ], - ], - ]; + $data = parent::update(); + foreach ($data as $key => $item) { + if ($item[3] !== null) { + unset($data[$key]); + } + } + return $data; } public static function upsertReturning(): array From 02f0b60df1b10893048a47e4aed4b25d27ea676a Mon Sep 17 00:00:00 2001 From: Rustam Date: Wed, 27 Aug 2025 17:57:39 +0500 Subject: [PATCH 5/9] Fix tests --- src/DMLQueryBuilder.php | 1 + tests/Provider/QueryBuilderProvider.php | 36 ++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/DMLQueryBuilder.php b/src/DMLQueryBuilder.php index 5168510f..44a037b0 100644 --- a/src/DMLQueryBuilder.php +++ b/src/DMLQueryBuilder.php @@ -6,6 +6,7 @@ use InvalidArgumentException; use Yiisoft\Db\Exception\NotSupportedException; +use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\AbstractDMLQueryBuilder; use Yiisoft\Db\Schema\TableSchema; diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index 6a9b3296..99046f12 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -131,7 +131,41 @@ public static function update(): array unset($data[$key]); } } - return $data; + return array_merge( + $data, + [ + [ + '{{table}}', + ['name' => '{{tmp}}.{{name}}'], + [], + 'tmp', + [], + self::replaceQuotes( + << new Param('{{tmp}}.{{name}}', DataType::STRING), + ], + ], + [ + '{{table}}', + ['name' => '{{tmp}}.{{name}}'], + [], + ['tmp' => self::getDb()->select()->from('{{tmp}}')], + [], + self::replaceQuotes( + << new Param('{{tmp}}.{{name}}', DataType::STRING), + ], + ], + ] + ); } public static function upsertReturning(): array From 92949423ada26207c91e5c29415bca71f10caec2 Mon Sep 17 00:00:00 2001 From: Rustam Date: Sun, 7 Sep 2025 11:43:17 +0500 Subject: [PATCH 6/9] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd49bd12..67d8f0bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ - New #420, #427: Implement `ArrayMergeBuilder`, `LongestBuilder` and `ShortestBuilder` classes (@Tigrov) - Enh #423: Refactor `DMLQueryBuilder::upsert()` method (@Tigrov) - Chg #428: Update expression namespaces according to changes in `yiisoft/db` package (@Tigrov) +- Enh #442: Update `DMLQueryBuilder::update()` method to adapt changes in `yiisoft/db` (@rustamwin) ## 1.2.0 March 21, 2024 From e0f6044e6caa3c2f10689389b044c7a0073b153a Mon Sep 17 00:00:00 2001 From: Rustam Date: Mon, 8 Sep 2025 19:13:06 +0500 Subject: [PATCH 7/9] Bring back $params argument --- tests/CommandTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 1e29d157..64c33920 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -121,10 +121,11 @@ public function testUpdate( array $columns, array|ExpressionInterface|string $conditions, array|ExpressionInterface|string|null $from, + array $params, array $expectedValues, int $expectedCount, ): void { - parent::testUpdate($table, $columns, $conditions, $from, $expectedValues, $expectedCount); + parent::testUpdate($table, $columns, $conditions, $from, $params, $expectedValues, $expectedCount); } #[DataProviderExternal(CommandProvider::class, 'upsert')] From 632337ff54959eabbe35a962b0b69ad2c58734fb Mon Sep 17 00:00:00 2001 From: Rustam Date: Tue, 9 Sep 2025 21:20:51 +0500 Subject: [PATCH 8/9] Prevent usage of unsupported UPDATE with FROM clause. Added exceptions in Oracle, MySQL, and SQL Server query builders to explicitly throw errors if the FROM clause is used in an UPDATE statement. This ensures clarity and avoids unexpected behavior in unsupported scenarios. --- src/DMLQueryBuilder.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DMLQueryBuilder.php b/src/DMLQueryBuilder.php index 44a037b0..0119a7e6 100644 --- a/src/DMLQueryBuilder.php +++ b/src/DMLQueryBuilder.php @@ -69,9 +69,9 @@ public function update( array|string|ExpressionInterface|null $from = null, array &$params = [] ): string { - /** - * MySQL does not support UPDATE ... FROM ... - */ + if ($from !== null) { + throw new NotSupportedException('MySQL does not support UPDATE with FROM clause.'); + } return parent::update($table, $columns, $condition, null, $params); } From dd20ff1698c051eb4093c5a6ae76d9e6cb5548f7 Mon Sep 17 00:00:00 2001 From: Rustam Date: Wed, 10 Sep 2025 14:08:54 +0500 Subject: [PATCH 9/9] Fix tests --- src/DMLQueryBuilder.php | 2 +- tests/CommandTest.php | 4 +++ tests/Provider/QueryBuilderProvider.php | 45 ------------------------- tests/QueryBuilderTest.php | 4 +++ 4 files changed, 9 insertions(+), 46 deletions(-) diff --git a/src/DMLQueryBuilder.php b/src/DMLQueryBuilder.php index 0119a7e6..5c529b2a 100644 --- a/src/DMLQueryBuilder.php +++ b/src/DMLQueryBuilder.php @@ -70,7 +70,7 @@ public function update( array &$params = [] ): string { if ($from !== null) { - throw new NotSupportedException('MySQL does not support UPDATE with FROM clause.'); + throw new NotSupportedException('MySQL does not support FROM clause in UPDATE statement.'); } return parent::update($table, $columns, $condition, null, $params); } diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 64c33920..f058582c 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -125,6 +125,10 @@ public function testUpdate( array $expectedValues, int $expectedCount, ): void { + if ($from !== null) { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage('MySQL does not support FROM clause in UPDATE statement.'); + } parent::testUpdate($table, $columns, $conditions, $from, $params, $expectedValues, $expectedCount); } diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index 17af2ea9..6cdbba0a 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -123,51 +123,6 @@ public static function upsert(): array return $upsert; } - public static function update(): array - { - $data = parent::update(); - foreach ($data as $key => $item) { - if ($item[3] !== null) { - unset($data[$key]); - } - } - return array_merge( - $data, - [ - [ - '{{table}}', - ['name' => '{{tmp}}.{{name}}'], - [], - 'tmp', - [], - self::replaceQuotes( - << new Param('{{tmp}}.{{name}}', DataType::STRING), - ], - ], - [ - '{{table}}', - ['name' => '{{tmp}}.{{name}}'], - [], - ['tmp' => self::getDb()->select()->from('{{tmp}}')], - [], - self::replaceQuotes( - << new Param('{{tmp}}.{{name}}', DataType::STRING), - ], - ], - ] - ); - } - public static function upsertReturning(): array { $upsert = self::upsert(); diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 596a32dc..f844b970 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -574,6 +574,10 @@ public function testUpdate( string $expectedSql, array $expectedParams = [], ): void { + if ($from !== null) { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage('MySQL does not support FROM clause in UPDATE statement.'); + } parent::testUpdate($table, $columns, $condition, $from, $params, $expectedSql, $expectedParams); }