Skip to content

Commit

Permalink
Merge pull request #45655 from nextcloud/feat/mysql_ignore_conflics
Browse files Browse the repository at this point in the history
  • Loading branch information
Altahrim authored Jun 27, 2024
2 parents ec39228 + b724368 commit 2482688
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 0 deletions.
28 changes: 28 additions & 0 deletions lib/private/DB/AdapterMySQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,32 @@ protected function getCollation(): string {

return $this->collation;
}

public function insertIgnoreConflict(string $table, array $values): int {
$builder = $this->conn->getQueryBuilder();
$builder->insert($table);
$updates = [];
foreach ($values as $key => $value) {
$builder->setValue($key, $builder->createNamedParameter($value));
}

/*
* We can't use ON DUPLICATE KEY UPDATE here because Nextcloud use the CLIENT_FOUND_ROWS flag
* With this flag the MySQL returns the number of selected rows
* instead of the number of affected/modified rows
* It's impossible to change this behaviour at runtime or for a single query
* Then, the result is 1 if a row is inserted and also 1 if a row is updated with same or different values
*
* With INSERT IGNORE, the result is 1 when a row is inserted, 0 otherwise
*
* Risk: it can also ignore other errors like type mismatch or truncated data…
*/
$res = $this->conn->executeStatement(
preg_replace('/^INSERT/i', 'INSERT IGNORE', $builder->getSQL()),
$builder->getParameters(),
$builder->getParameterTypes()
);

return $res;
}
}
15 changes: 15 additions & 0 deletions lib/private/DB/AdapterSqlite.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,19 @@ public function insertIfNotExist($table, $input, ?array $compare = null) {
return 0;
}
}

public function insertIgnoreConflict(string $table, array $values): int {
$builder = $this->conn->getQueryBuilder();
$builder->insert($table);
$updates = [];
foreach ($values as $key => $value) {
$builder->setValue($key, $builder->createNamedParameter($value));
}

return $this->conn->executeStatement(
$builder->getSQL() . ' ON CONFLICT DO NOTHING',
$builder->getParameters(),
$builder->getParameterTypes()
);
}
}
66 changes: 66 additions & 0 deletions tests/lib/DB/AdapterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace Test\DB;

use Test\TestCase;

class AdapterTest extends TestCase {
private string $appId;
private $connection;

public function setUp(): void {
$this->connection = \OC::$server->getDatabaseConnection();
$this->appId = uniqid('test_db_adapter', true);
}

public function tearDown(): void {
$qb = $this->connection->getQueryBuilder();

$qb->delete('appconfig')
->from('appconfig')
->where($qb->expr()->eq('appid', $qb->createNamedParameter($this->appId)))
->execute();
}

public function testInsertIgnoreOnConflictDuplicate(): void {
$configKey = uniqid('key', true);
$expected = [
[
'configkey' => $configKey,
'configvalue' => '1',
]
];
$result = $this->connection->insertIgnoreConflict('appconfig', [
'appid' => $this->appId,
'configkey' => $configKey,
'configvalue' => '1',
]);
$this->assertEquals(1, $result);
$rows = $this->getRows($configKey);
$this->assertSame($expected, $rows);


$result = $this->connection->insertIgnoreConflict('appconfig', [
'appid' => $this->appId,
'configkey' => $configKey,
'configvalue' => '2',
]);
$this->assertEquals(0, $result);
$rows = $this->getRows($configKey);
$this->assertSame($expected, $rows);
}

private function getRows(string $configKey): array {
$qb = $this->connection->getQueryBuilder();
return $qb->select(['configkey', 'configvalue'])
->from('appconfig')
->where($qb->expr()->eq('appid', $qb->createNamedParameter($this->appId)))
->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($configKey)))
->execute()
->fetchAll();
}
}

0 comments on commit 2482688

Please sign in to comment.