Skip to content

Commit

Permalink
persistence upsert cross-database
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubkulhan committed May 29, 2024
1 parent 7b0e74b commit f40ed1f
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 15 deletions.
60 changes: 45 additions & 15 deletions data-access-kit/src/Persistence.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

use DataAccessKit\Attribute\Column;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\MariaDBPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use LogicException;
use function array_map;
use function array_merge;
use function count;
use function implode;
Expand Down Expand Up @@ -84,10 +86,11 @@ private function insertUpsertAll(array $objects, ?array $upsertColumns = null):
$columnNames = [];
$rows = [];
$values = [];
$update = [];
$updateColumnNames = [];
$generatedColumnNames = [];
/** @var Column[] $generatedColumns */
$generatedColumns = [];
$primaryKeyColumnNames = [];
/** @var Column|null $primaryKeyColumn */
$primaryKeyColumn = null;
$supportsReturning = match (true) {
Expand All @@ -96,34 +99,49 @@ private function insertUpsertAll(array $objects, ?array $upsertColumns = null):
$platform instanceof SQLitePlatform => true,
default => false,
};
$supportsDefault = match (true) {
$platform instanceof AbstractMySQLPlatform => true,
$platform instanceof PostgreSQLPlatform => true,
default => false,
};

foreach ($table->columns as $column) {
if ($column->generated) {
if ($column->primary) {
if ($primaryKeyColumn !== null) {
throw new LogicException("Multiple generated primary columns.");
}
$primaryKeyColumn = $column;
if ($column->primary) {
if ($supportsDefault || $upsertColumns !== []) {
$columnNames[] = $platform->quoteSingleIdentifier($column->name);
}
$primaryKeyColumnNames[] = $platform->quoteSingleIdentifier($column->name);

if ($primaryKeyColumn !== null) {
throw new LogicException("Multiple generated primary columns.");
}
$primaryKeyColumn = $column;

if ($column->generated && $supportsReturning) {
$generatedColumnNames[] = $platform->quoteSingleIdentifier($column->name);
$generatedColumns[] = $column;
}

} else if ($column->generated) {
if ($supportsReturning) {
$generatedColumnNames[] = $platform->quoteSingleIdentifier($column->name);
$generatedColumns[] = $column;
}

} else {
$columnNames[] = $platform->quoteSingleIdentifier($column->name);

if ($upsertColumns === null || in_array($column->name, $upsertColumns, true)) {
$update[] = $platform->quoteSingleIdentifier($column->name) . " = VALUES(" . $platform->quoteSingleIdentifier($column->name) . ")";
$updateColumnNames[] = $platform->quoteSingleIdentifier($column->name);
}
}
}

foreach ($objects as $object) {
foreach ($objects as $index => $object) {
$row = [];

foreach ($table->columns as $column) {
if ($column->generated) {
if ($column->generated && !($column->primary && ($supportsDefault || $upsertColumns !== []))) {
continue;
}

Expand All @@ -133,8 +151,15 @@ private function insertUpsertAll(array $objects, ?array $upsertColumns = null):
$row[] = "?";
$values[] = $value;

} else {
} else if ($supportsDefault) {
$row[] = "DEFAULT";

} else {
throw new LogicException(sprintf(
"Property [%s] of object at index [%d] not initialized and the database doesn't support default values, cannot insert.",
$column->reflection->getName(),
$index,
));
}
}

Expand All @@ -146,17 +171,22 @@ private function insertUpsertAll(array $objects, ?array $upsertColumns = null):
$platform->quoteSingleIdentifier($table->name),
implode(", ", $columnNames),
implode(", ", $rows),
match (count($update)) {
0 => "",
default => sprintf(" ON DUPLICATE KEY UPDATE %s", implode(", ", $update)),
match (true) {
count($updateColumnNames) > 0 && $platform instanceof AbstractMySQLPlatform => sprintf(" ON DUPLICATE KEY UPDATE %s", implode(", ", array_map(fn(string $it) => $it . " = VALUES(" . $it . ")", $updateColumnNames))),
count($updateColumnNames) > 0 && ($platform instanceof PostgreSQLPlatform || $platform instanceof SQLitePlatform) => sprintf(" ON CONFLICT (%s) DO UPDATE SET %s", implode(", ", $primaryKeyColumnNames), implode(", ", array_map(fn(string $it) => $it . " = EXCLUDED." . $it, $updateColumnNames))),
count($updateColumnNames) > 0 => throw new LogicException(sprintf(
"Upsert not supported on platform [%s].",
get_class($platform),
)),
default => "",
},
match ($supportsReturning) {
true => sprintf(" RETURNING %s", implode(", ", $generatedColumnNames)),
default => "",
},
);

$result = $this->connection->executeQuery($sql, $values);

if ($supportsReturning && count($generatedColumns) > 0) {
foreach ($result->iterateAssociative() as $index => $row) {
$object = $objects[$index];
Expand Down
15 changes: 15 additions & 0 deletions data-access-kit/test/PersistenceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,19 @@ public function testInsertAll(): void
}
}

public function testUpsert(): void
{
$this->setUpUsersTable();
$user = new User();
$user->id = 1;
$user->firstName = "Charlie";
$this->persistence->upsert($user);
$this->assertEquals(1, $user->id);

$users = iterator_to_array($this->persistence->select(User::class, "SELECT user_id, first_name FROM users WHERE user_id = ?", [$user->id]));
$this->assertCount(1, $users);
$this->assertEquals($user->id, $users[0]->id);
$this->assertEquals($user->firstName, $users[0]->firstName);
}

}

0 comments on commit f40ed1f

Please sign in to comment.