Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix BaseBuilder setAlias() and RawSql use with key value pairs #6741

Merged
merged 13 commits into from
Oct 26, 2022
22 changes: 19 additions & 3 deletions system/Database/BaseBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -1969,6 +1969,7 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
public function setAlias(string $alias): BaseBuilder
{
if ($alias !== '') {
$this->db->addTableAlias($alias);
$this->QBOptions['alias'] = $this->db->protectIdentifiers($alias);
}

Expand Down Expand Up @@ -2044,6 +2045,10 @@ public function onConstraint($set)
$value = $this->db->protectIdentifiers($value);
}

if (is_string($key)) {
$key = $this->db->protectIdentifiers($key);
}

$this->QBOptions['constraints'][$key] = $value;
}
}
Expand Down Expand Up @@ -2462,9 +2467,20 @@ protected function _updateBatch(string $table, array $keys, array $values): stri
$sql .= 'WHERE ' . implode(
' AND ',
array_map(
static fn ($key) => ($key instanceof RawSql ?
$key :
$table . '.' . $key . ' = ' . $alias . '.' . $key),
static fn ($key, $value) => (
($value instanceof RawSql && is_string($key))
?
$table . '.' . $key . ' = ' . $value
:
(
$value instanceof RawSql
?
$value
:
$table . '.' . $value . ' = ' . $alias . '.' . $value
)
),
array_keys($constraints),
$constraints
)
);
Expand Down
17 changes: 14 additions & 3 deletions system/Database/MySQLi/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,20 @@ protected function _updateBatch(string $table, array $keys, array $values): stri
$sql .= 'ON ' . implode(
' AND ',
array_map(
static fn ($key) => ($key instanceof RawSql ?
$key :
$table . '.' . $key . ' = ' . $alias . '.' . $key),
static fn ($key, $value) => (
($value instanceof RawSql && is_string($key))
?
$table . '.' . $key . ' = ' . $value
:
(
$value instanceof RawSql
?
$value
:
$table . '.' . $value . ' = ' . $alias . '.' . $value
)
),
array_keys($constraints),
$constraints
)
) . "\n";
Expand Down
44 changes: 34 additions & 10 deletions system/Database/OCI8/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,20 @@ protected function _updateBatch(string $table, array $keys, array $values): stri
$sql .= 'ON (' . implode(
' AND ',
array_map(
static fn ($key) => ($key instanceof RawSql ?
$key :
$table . '.' . $key . ' = ' . $alias . '.' . $key),
static fn ($key, $value) => (
($value instanceof RawSql && is_string($key))
?
$table . '.' . $key . ' = ' . $value
:
(
$value instanceof RawSql
?
$value
:
$table . '.' . $value . ' = ' . $alias . '.' . $value
)
),
array_keys($constraints),
$constraints
)
) . ")\n";
Expand Down Expand Up @@ -354,18 +365,31 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
return ''; // @codeCoverageIgnore
}

$alias = $this->QBOptions['alias'] ?? '"_upsert"';

$updateFields = $this->QBOptions['updateFields'] ?? $this->updateFields($keys, false, $constraints)->QBOptions['updateFields'] ?? [];

$sql = 'MERGE INTO ' . $table . "\nUSING (\n{:_table_:}";

$sql .= ') "_upsert"' . "\nON (";
$sql .= ") {$alias}\nON (";

$sql .= implode(
' AND ',
array_map(
static fn ($key) => ($key instanceof RawSql ?
$key :
$table . '.' . $key . ' = "_upsert".' . $key),
static fn ($key, $value) => (
($value instanceof RawSql && is_string($key))
?
$table . '.' . $key . ' = ' . $value
:
(
$value instanceof RawSql
?
$value
:
$table . '.' . $value . ' = ' . $alias . '.' . $value
)
),
array_keys($constraints),
$constraints
)
) . ")\n";
Expand All @@ -376,8 +400,8 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
",\n",
array_map(
static fn ($key, $value) => $key . ($value instanceof RawSql ?
' = ' . $value :
' = "_upsert".' . $value),
" = {$value}" :
" = {$alias}.{$value}"),
array_keys($updateFields),
$updateFields
)
Expand All @@ -386,7 +410,7 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
$sql .= "\nWHEN NOT MATCHED THEN INSERT (" . implode(', ', $keys) . ")\nVALUES ";

$sql .= (' ('
. implode(', ', array_map(static fn ($columnName) => '"_upsert".' . $columnName, $keys))
. implode(', ', array_map(static fn ($columnName) => "{$alias}.{$columnName}", $keys))
. ')');

$this->QBOptions['sql'] = $sql;
Expand Down
11 changes: 9 additions & 2 deletions system/Database/Postgre/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\RawSql;
use InvalidArgumentException;

/**
* Builder for Postgre
Expand Down Expand Up @@ -364,6 +365,12 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
}
}

$alias = $this->QBOptions['alias'] ?? '"excluded"';

if (strtolower($alias) !== '"excluded"') {
throw new InvalidArgumentException('Postgres alias is always named "excluded". A custom alias cannot be used.');
}
sclubricants marked this conversation as resolved.
Show resolved Hide resolved

$updateFields = $this->QBOptions['updateFields'] ?? $this->updateFields($keys, false, $constraints)->QBOptions['updateFields'] ?? [];

$sql = 'INSERT INTO ' . $table . ' (';
Expand All @@ -382,8 +389,8 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
",\n",
array_map(
static fn ($key, $value) => $key . ($value instanceof RawSql ?
' = ' . $value :
' = "excluded".' . $value),
" = {$value}" :
" = {$alias}.{$value}"),
array_keys($updateFields),
$updateFields
)
Expand Down
29 changes: 21 additions & 8 deletions system/Database/SQLSRV/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -685,13 +685,15 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
return ''; // @codeCoverageIgnore
}

$alias = $this->QBOptions['alias'] ?? '"_upsert"';

$updateFields = $this->QBOptions['updateFields'] ?? $this->updateFields($keys, false, $constraints)->QBOptions['updateFields'] ?? [];

$sql = 'MERGE INTO ' . $fullTableName . "\nUSING (\n";

$sql .= '{:_table_:}';

$sql .= ') "_upsert" (';
$sql .= ") {$alias} (";

$sql .= implode(', ', $keys);

Expand All @@ -702,9 +704,20 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
$sql .= implode(
' AND ',
array_map(
static fn ($key) => ($key instanceof RawSql ?
$key :
$fullTableName . '.' . $key . ' = "_upsert".' . $key),
static fn ($key, $value) => (
($value instanceof RawSql && is_string($key))
?
$fullTableName . '.' . $key . ' = ' . $value
:
(
$value instanceof RawSql
?
$value
:
$fullTableName . '.' . $value . ' = ' . $alias . '.' . $value
)
),
array_keys($constraints),
$constraints
)
) . ")\n";
Expand All @@ -716,7 +729,7 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
array_map(
static fn ($key, $value) => $key . ($value instanceof RawSql ?
' = ' . $value :
' = "_upsert".' . $value),
" = {$alias}.{$value}"),
array_keys($updateFields),
$updateFields
)
Expand All @@ -729,10 +742,10 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
', ',
array_map(
static fn ($columnName) => $columnName === $tableIdentity
? 'CASE WHEN "_upsert".' . $columnName . ' IS NULL THEN (SELECT '
? "CASE WHEN {$alias}.{$columnName} IS NULL THEN (SELECT "
. 'isnull(IDENT_CURRENT(\'' . $fullTableName . '\')+IDENT_INCR(\''
. $fullTableName . '\'),1)) ELSE "_upsert".' . $columnName . ' END'
: '"_upsert".' . $columnName,
. $fullTableName . "'),1)) ELSE {$alias}.{$columnName} END"
: "{$alias}.{$columnName}",
$keys
)
) . ');'
Expand Down
11 changes: 9 additions & 2 deletions system/Database/SQLite3/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\RawSql;
use InvalidArgumentException;

/**
* Builder for SQLite3
Expand Down Expand Up @@ -164,6 +165,12 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
return ''; // @codeCoverageIgnore
}

$alias = $this->QBOptions['alias'] ?? '`excluded`';

if (strtolower($alias) !== '`excluded`') {
throw new InvalidArgumentException('SQLite alias is always named "excluded". A custom alias cannot be used.');
}

$updateFields = $this->QBOptions['updateFields'] ??
$this->updateFields($keys, false, $constraints)->QBOptions['updateFields'] ??
[];
Expand All @@ -184,8 +191,8 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
",\n",
array_map(
static fn ($key, $value) => $key . ($value instanceof RawSql ?
' = ' . $value :
' = `excluded`.' . $value),
" = {$value}" :
" = {$alias}.{$value}"),
array_keys($updateFields),
$updateFields
)
Expand Down
26 changes: 25 additions & 1 deletion tests/system/Database/Live/UpdateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -475,14 +475,38 @@ public function testRawSqlConstraint()

$builder = $this->db->table('user');

$builder->setData($data, true, 'db_myalias')
$builder->setData($data, true, 'myalias')
sclubricants marked this conversation as resolved.
Show resolved Hide resolved
->updateFields('name, country')
->onConstraint(new RawSql($this->db->protectIdentifiers('user.email') . ' = ' . $this->db->protectIdentifiers('myalias.email')))
->updateBatch();

$this->seeInDatabase('user', ['email' => '[email protected]', 'country' => 'Germany']);
}

public function testRawSqlConstraintWithKey()
{
if ($this->db->DBDriver === 'SQLite3' && ! (version_compare($this->db->getVersion(), '3.33.0') >= 0)) {
$this->markTestSkipped('Only SQLite 3.33 and newer can complete this test.');
}

$data = [
[
'name' => 'Derek Jones',
'email' => '[email protected]',
'country' => 'Germany',
],
];

$builder = $this->db->table('user');

$builder->setData($data, true, 'myalias')
->updateFields('name, country')
->onConstraint(['email' => new RawSql($this->db->protectIdentifiers('myalias.email'))])
->updateBatch();

$this->seeInDatabase('user', ['email' => '[email protected]', 'country' => 'Germany']);
}

public function testNoConstraintFound()
{
$jobData = [
Expand Down