Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c846471
Add ability to pass `FROM` clause to `UPDATE` command
rustamwin Aug 18, 2025
30e1183
Fix parameters order, add test cases
rustamwin Aug 20, 2025
b024bf3
Fix tests
rustamwin Aug 20, 2025
a4cf434
Fix CS
rustamwin Aug 20, 2025
2e088fa
Fix psalm
rustamwin Aug 20, 2025
08a5f3a
Improve test cases
rustamwin Aug 21, 2025
c495e75
Merge branch 'master' into fix-94
rustamwin Aug 21, 2025
4de75f5
Add changelog
rustamwin Aug 22, 2025
e85af52
Merge branch 'master' into fix-94
vjik Aug 23, 2025
28f2461
Minor fix
rustamwin Aug 24, 2025
9b91bbf
Merge remote-tracking branch 'origin/fix-94' into fix-94
rustamwin Aug 24, 2025
b639ce3
Minor fix
rustamwin Aug 24, 2025
1f6ca70
Merge branch 'master' into fix-94
vjik Aug 24, 2025
c75a2fe
Merge remote-tracking branch 'origin/master' into fix-94
rustamwin Aug 27, 2025
632e5aa
Fix test providers
rustamwin Aug 27, 2025
e2999ce
Fix tests
rustamwin Aug 28, 2025
8452445
Merge branch 'master' into fix-94
vjik Aug 31, 2025
673003a
Merge remote-tracking branch 'origin/master' into fix-94
rustamwin Sep 3, 2025
0147a8f
Merge branch 'master' into fix-94
vjik Sep 4, 2025
5b32a20
Merge branch 'master' into fix-94
vjik Sep 4, 2025
155b005
Merge branch 'master' into fix-94
vjik Sep 5, 2025
10c490c
Fix changelog
rustamwin Sep 6, 2025
06a87ae
Bring back $params argument
rustamwin Sep 8, 2025
0064c31
Apply Rector changes (CI)
rustamwin Sep 8, 2025
64b6e53
Apply suggestions from code review
rustamwin Sep 9, 2025
e1a0c9e
Merge branch 'master' into fix-94
Tigrov Sep 22, 2025
c94a597
Close connections in tests
Tigrov Sep 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@
- Chg #1037: Change result type of `QueryBuilderInterface::getExpressionBuilder()` and
`DQLQueryBuilderInterface::getExpressionBuilder()` methods to `ExpressionBuilderInterface` (@vjik)
- New #1029, #1048: Add functions as expressions (@Tigrov)
- Enh #1038: Add ability to pass `FROM` clause to `CommandInterface::update()` and `DMLQueryBuilderInterface::update()` methods (@rustamwin)
- Enh #1038: Allow passing `ExpressionInterface` as condition in `CommandInterface::update()` and `DMLQueryBuilderInterface::update()` methods (@rustamwin)
- Enh #1042: Refactor `AbstractDMLQueryBuilder` class to `upsert()` method (@Tigrov)
- New #1040, #1043: Add `DateTimeValue` class (@vjik, @Tigrov)
- Enh #1045: Support multi-operand functions in `CommandInterface::upsert()` and `DMLQueryBuilderInterface::upsert()`
Expand Down
12 changes: 9 additions & 3 deletions src/Command/AbstractCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Throwable;
use Yiisoft\Db\Connection\ConnectionInterface;
use Yiisoft\Db\Exception\Exception;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Expression\Value\Param;
use Yiisoft\Db\Query\DataReaderInterface;
use Yiisoft\Db\Query\QueryInterface;
Expand Down Expand Up @@ -510,9 +511,14 @@ public function truncateTable(string $table): static
return $this->setSql($sql);
}

public function update(string $table, array $columns, array|string $condition = '', array $params = []): static
{
$sql = $this->getQueryBuilder()->update($table, $columns, $condition, $params);
public function update(
string $table,
array $columns,
array|ExpressionInterface|string $condition = '',
array|ExpressionInterface|string|null $from = null,
array $params = []
): static {
$sql = $this->getQueryBuilder()->update($table, $columns, $condition, $from, $params);
return $this->setSql($sql)->bindValues($params);
}

Expand Down
18 changes: 13 additions & 5 deletions src/Command/CommandInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -829,18 +829,26 @@ public function truncateTable(string $table): static;
*
* @param string $table The name of the table to update.
* @param array $columns The column data (name => value) to update.
* @param array|string $condition The condition to put in the WHERE part. Please refer to
* {@see QueryInterface::where()} on how to specify condition.
* @param array|ExpressionInterface|string $condition The condition to put in the WHERE part. Please refer to
* {@see QueryPartsInterface::where()} on how to specify condition.
* @param array|ExpressionInterface|string|null $from The FROM part. Please refer to {@see QueryPartsInterface::from()}
* on how to specify FROM part.
* @param array $params The parameters to bind to the command.
*
* @psalm-param ParamsType $params
*
* @throws Exception
* @throws InvalidArgumentException
*
* @psalm-param ParamsType $params
*
* Note: The method will quote the `table` and `columns` parameter before using it in the generated SQL.
*/
public function update(string $table, array $columns, array|string $condition = '', array $params = []): static;
public function update(
string $table,
array $columns,
array|ExpressionInterface|string $condition = '',
array|ExpressionInterface|string|null $from = null,
array $params = []
): static;

/**
* Creates a command to insert rows into a database table if they don't already exist (matching unique constraints)
Expand Down
10 changes: 8 additions & 2 deletions src/Debug/CommandInterfaceProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Closure;
use Throwable;
use Yiisoft\Db\Command\CommandInterface;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Query\DataReaderInterface;
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\Schema\Column\ColumnInterface;
Expand Down Expand Up @@ -481,8 +482,13 @@ public function truncateTable(string $table): static
/**
* @psalm-suppress MixedArgument
*/
public function update(string $table, array $columns, array|string $condition = '', array $params = []): static
{
public function update(
string $table,
array $columns,
array|ExpressionInterface|string $condition = '',
array|ExpressionInterface|string|null $from = null,
array $params = []
): static {
return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector);
}

Expand Down
27 changes: 25 additions & 2 deletions src/QueryBuilder/AbstractDMLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use IteratorAggregate;
use Traversable;
use Yiisoft\Db\Connection\ConnectionInterface;
use Yiisoft\Db\Constant\GettypeResult;
use Yiisoft\Db\Constraint\Index;
use Yiisoft\Db\Exception\Exception;
use InvalidArgumentException;
Expand Down Expand Up @@ -136,12 +137,21 @@ public function resetSequence(string $table, int|string|null $value = null): str
throw new NotSupportedException(__METHOD__ . '() is not supported by this DBMS.');
}

public function update(string $table, array $columns, array|string $condition, array &$params = []): string
{
public function update(
string $table,
array $columns,
array|ExpressionInterface|string $condition,
array|ExpressionInterface|string|null $from = null,
array &$params = []
): string {
$updates = $this->prepareUpdateSets($table, $columns, $params);

$sql = 'UPDATE ' . $this->quoter->quoteTableName($table) . ' SET ' . implode(', ', $updates);
$where = $this->queryBuilder->buildWhere($condition, $params);
if ($from !== null) {
$fromClause = $this->queryBuilder->buildFrom($this->prepareFromTables($from), $params);
$sql .= $fromClause === '' ? '' : ' ' . $fromClause;
}

return $where === '' ? $sql : $sql . ' ' . $where;
}
Expand Down Expand Up @@ -493,6 +503,19 @@ protected function prepareUpsertColumns(
return [$uniqueNames, $insertNames, null];
}

protected function prepareFromTables(array|ExpressionInterface|string $from): array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method identical to Query::from(). Can we remove duplicates?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can but we need to refactor a bit.

{
/**
* @var array
* @psalm-suppress PossiblyInvalidArgument
*/
return match (gettype($from)) {
GettypeResult::ARRAY => $from,
GettypeResult::STRING => preg_split('/\s*,\s*/', trim($from), -1, PREG_SPLIT_NO_EMPTY),
default => [$from],
};
}

/**
* Returns all column names belonging to constraints enforcing uniqueness (`PRIMARY KEY`, `UNIQUE INDEX`, etc.)
* for the named table removing constraints which didn't cover the specified column list.
Expand Down
11 changes: 8 additions & 3 deletions src/QueryBuilder/AbstractQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -575,9 +575,14 @@ public function truncateTable(string $table): string
return $this->ddlBuilder->truncateTable($table);
}

public function update(string $table, array $columns, array|string $condition, array &$params = []): string
{
return $this->dmlBuilder->update($table, $columns, $condition, $params);
public function update(
string $table,
array $columns,
array|ExpressionInterface|string $condition,
array|ExpressionInterface|string|null $from = null,
array &$params = []
): string {
return $this->dmlBuilder->update($table, $columns, $condition, $from, $params);
}

public function upsert(
Expand Down
15 changes: 12 additions & 3 deletions src/QueryBuilder/DMLQueryBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use InvalidArgumentException;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Query\QueryInterface;

/**
Expand Down Expand Up @@ -177,8 +178,10 @@ public function resetSequence(string $table, int|string|null $value = null): str
*
* @param string $table The table to update.
* @param array $columns The column data (name => value) to update the table.
* @param array|string $condition The condition to put in the `WHERE` part. Please refer to
* {@see Query::where()} On how to specify condition.
* @param array|ExpressionInterface|string $condition The condition to put in the `WHERE` part. Please refer to
* {@see QueryPartsInterface::where()} On how to specify condition.
* @param array|ExpressionInterface|string|null $from The FROM part. Please refer to
* {@see QueryPartsInterface::from()} On how to specify FROM part.
* @param array $params The binding parameters that will be modified by this method so that they can be bound to
* DB command later.
*
Expand All @@ -191,7 +194,13 @@ public function resetSequence(string $table, int|string|null $value = null): str
*
* Note: The method will escape the table and column names.
*/
public function update(string $table, array $columns, array|string $condition, array &$params = []): string;
public function update(
string $table,
array $columns,
array|ExpressionInterface|string $condition,
array|ExpressionInterface|string|null $from = null,
array &$params = []
): string;

/**
* Creates an SQL statement to insert rows into a database table if they don't already exist (matching unique
Expand Down
19 changes: 17 additions & 2 deletions tests/AbstractQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2223,6 +2223,8 @@ public function testSetSeparator(): void
$sql,
);
$this->assertEmpty($params);

$db->close();
}

public function testTruncateTable(): void
Expand Down Expand Up @@ -2251,25 +2253,30 @@ public function testTruncateTable(): void
),
$sql,
);

$db->close();
}

#[DataProviderExternal(QueryBuilderProvider::class, 'update')]
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 {
$db = $this->getConnection();
$qb = $db->getQueryBuilder();

$sql = $qb->update($table, $columns, $condition, $params);
$sql = $qb->update($table, $columns, $condition, $from, $params);
$sql = $db->getQuoter()->quoteSql($sql);

$this->assertSame($expectedSql, $sql);
$this->assertEquals($expectedParams, $params);

$db->close();
}

#[DataProviderExternal(QueryBuilderProvider::class, 'upsert')]
Expand Down Expand Up @@ -2299,6 +2306,8 @@ public function testUpsert(
$this->assertSame(1, $countAfter - $countBefore);

$db->createCommand($sql, $params)->execute();

$db->close();
}

#[DataProviderExternal(QueryBuilderProvider::class, 'upsertReturning')]
Expand Down Expand Up @@ -2329,6 +2338,8 @@ public function testUpsertReturning(
$this->assertSame(1, $countAfter - $countBefore);

$db->createCommand($sql, $params)->execute();

$db->close();
}

public function testOverrideParameters1(): void
Expand All @@ -2352,6 +2363,8 @@ public function testOverrideParameters1(): void
static::replaceQuotes('SELECT * FROM [[animal]] WHERE (id = 1 AND type = \'test\') AND ([[type]] = \'test1\')'),
$command->getRawSql()
);

$db->close();
}

public function testOverrideParameters2(): void
Expand All @@ -2375,6 +2388,8 @@ public function testOverrideParameters2(): void
static::replaceQuotes('SELECT * FROM [[animal]] WHERE (id = 1) AND ([[type]] = \'test2\')'),
$command->getRawSql()
);

$db->close();
}

#[DataProviderExternal(QueryBuilderProvider::class, 'buildColumnDefinition')]
Expand Down
5 changes: 3 additions & 2 deletions tests/Common/CommonCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1689,15 +1689,16 @@ public function testTruncateTable(): void
public function testUpdate(
string $table,
array $columns,
array|string $conditions,
array|ExpressionInterface|string $conditions,
array|ExpressionInterface|string|null $from,
array $params,
array $expectedValues,
int $expectedCount,
): void {
$db = $this->getConnection(true);

$command = $db->createCommand();
$count = $command->update($table, $columns, $conditions, $params)->execute();
$count = $command->update($table, $columns, $conditions, $from, $params)->execute();

$this->assertSame($expectedCount, $count);

Expand Down
6 changes: 4 additions & 2 deletions tests/Common/CommonQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ public function testUpdateWithoutTypecasting(): void
];

$params = [];
$qb->update('{{type}}', $values, [], $params);
$qb->update('{{type}}', $values, [], null, $params);

$expectedParams = [':qp0' => new Param('test', DataType::STRING)];

Expand All @@ -185,14 +185,16 @@ public function testUpdateWithoutTypecasting(): void
Assert::arraysEquals($expectedParams, $params);

$params = [];
$qb->withTypecasting(false)->update('{{type}}', $values, [], $params);
$qb->withTypecasting(false)->update('{{type}}', $values, [], null, $params);

Assert::arraysEquals([
':qp0' => new Param('1', DataType::STRING),
':qp1' => new Param('test', DataType::STRING),
':qp2' => new Param('3.14', DataType::STRING),
':qp3' => new Param('1', DataType::STRING),
], $params);

$db->close();
}

#[DataProviderExternal(QueryBuilderProvider::class, 'caseXBuilder')]
Expand Down
12 changes: 12 additions & 0 deletions tests/Common/CommonQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public function testAllEmpty(): void
$query = (new Query($db))->from('customer')->where(['id' => 0]);

$this->assertSame([], $query->all());

$db->close();
}

public function testAllWithIndexBy(): void
Expand Down Expand Up @@ -154,6 +156,8 @@ public function testLikeDefaultCaseSensitive(): void


$this->assertSame('user1', $result);

$db->close();
}

public static function dataLikeCaseSensitive(): iterable
Expand All @@ -174,6 +178,8 @@ public function testLikeCaseSensitive(mixed $expected, string $value): void
->scalar();

$this->assertSame($expected, $result);

$db->close();
}

public static function dataLikeCaseInsensitive(): iterable
Expand All @@ -194,6 +200,8 @@ public function testLikeCaseInsensitive(mixed $expected, string $value): void
->scalar();

$this->assertSame($expected, $result);

$db->close();
}

public function testBatchWithResultCallback(): void
Expand Down Expand Up @@ -224,6 +232,8 @@ public function testBatchWithResultCallback(): void
],
$results,
);

$db->close();
}

public function testBatchWithIndexBy(): void
Expand All @@ -249,5 +259,7 @@ public function testBatchWithIndexBy(): void
],
$results,
);

$db->close();
}
}
7 changes: 5 additions & 2 deletions tests/Db/QueryBuilder/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -243,19 +243,22 @@ 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 {
$db = $this->getConnection();
$qb = $db->getQueryBuilder();

$sql = $qb->update($table, $columns, $condition, $params);
$sql = $qb->update($table, $columns, $condition, $from, $params);
$sql = $db->getQuoter()->quoteSql($sql);

$this->assertSame($expectedSql, $sql);
$this->assertEquals($expectedParams, $params);

$db->close();
}

#[DataProviderExternal(QueryBuilderProvider::class, 'upsert')]
Expand Down
Loading
Loading