Skip to content

Commit 3f8798c

Browse files
committed
feat(db): introduce a ignoreConflictsOnInsert method on the QueryBuilder
Since the DB adapters insertIgnoreConflict methods are using the conn directly to alter the produced SQL, bypassing the ShardedQueryBuilder logic, hinting for sharded keys didn't work. We need to expose to the QueryBuilder the information that we want to ignore conflicts, and how to handle it. Signed-off-by: Thomas Citharel <tcit@tcit.fr>
1 parent 77bd093 commit 3f8798c

9 files changed

Lines changed: 66 additions & 25 deletions

File tree

lib/private/DB/Adapter.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,8 @@ public function insertIgnoreConflict(string $table, array $values, array $hintSh
129129
throw $e;
130130
}
131131
}
132+
133+
public function getInsertIgnoreSqlTransformer(): ?callable {
134+
return null;
135+
}
132136
}

lib/private/DB/AdapterMySQL.php

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,22 @@ public function insertIgnoreConflict(string $table, array $values, array $hintSh
4747
$builder->hintShardKey($hintShardKey['column'], $hintShardKey['value'], $hintShardKey['overwrite'] ?? false);
4848
}
4949

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

67-
return $res;
54+
/**
55+
* We can't use ON DUPLICATE KEY UPDATE here because Nextcloud use the CLIENT_FOUND_ROWS flag
56+
* With this flag the MySQL returns the number of selected rows
57+
* instead of the number of affected/modified rows
58+
* It's impossible to change this behaviour at runtime or for a single query
59+
* Then, the result is 1 if a row is inserted and also 1 if a row is updated with same or different values
60+
*
61+
* With INSERT IGNORE, the result is 1 when a row is inserted, 0 otherwise
62+
*
63+
* Risk: it can also ignore other errors like type mismatch or truncated data…
64+
*/
65+
public function getInsertIgnoreSqlTransformer(): callable {
66+
return fn (string $sql) => preg_replace('/^INSERT/i', 'INSERT IGNORE', $sql);
6867
}
6968
}

lib/private/DB/AdapterPgSql.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ public function insertIgnoreConflict(string $table, array $values, array $hintSh
3434
if (isset($hintShardKey['column'], $hintShardKey['value'])) {
3535
$builder->hintShardKey($hintShardKey['column'], $hintShardKey['value'], $hintShardKey['overwrite'] ?? false);
3636
}
37-
$queryString = $builder->getSQL() . ' ON CONFLICT DO NOTHING';
38-
return $this->conn->executeUpdate($queryString, $builder->getParameters(), $builder->getParameterTypes());
37+
$builder->ignoreConflictsOnInsert();
38+
return $builder->executeStatement();
39+
}
40+
41+
public function getInsertIgnoreSqlTransformer(): callable {
42+
return fn (string $sql) => $sql . ' ON CONFLICT DO NOTHING';
3943
}
4044
}

lib/private/DB/AdapterSqlite.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,11 @@ public function insertIgnoreConflict(string $table, array $values, array $hintSh
8888
$builder->hintShardKey($hintShardKey['column'], $hintShardKey['value'], $hintShardKey['overwrite'] ?? false);
8989
}
9090

91-
return $this->conn->executeStatement(
92-
$builder->getSQL() . ' ON CONFLICT DO NOTHING',
93-
$builder->getParameters(),
94-
$builder->getParameterTypes()
95-
);
91+
$builder->ignoreConflictsOnInsert();
92+
return $builder->executeStatement();
93+
}
94+
95+
public function getInsertIgnoreSqlTransformer(): callable {
96+
return fn (string $sql) => $sql . ' ON CONFLICT DO NOTHING';
9697
}
9798
}

lib/private/DB/Connection.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,4 +961,8 @@ public function getShardDefinition(string $name): ?ShardDefinition {
961961
public function getCrossShardMoveHelper(): CrossShardMoveHelper {
962962
return new CrossShardMoveHelper($this->shardConnectionManager);
963963
}
964+
965+
public function getInsertIgnoreSqlTransformer(): ?callable {
966+
return $this->adapter->getInsertIgnoreSqlTransformer();
967+
}
964968
}

lib/private/DB/ConnectionAdapter.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,4 +265,8 @@ public function getShardDefinition(string $name): ?ShardDefinition {
265265
public function getCrossShardMoveHelper(): CrossShardMoveHelper {
266266
return $this->inner->getCrossShardMoveHelper();
267267
}
268+
269+
public function getInsertIgnoreSqlTransformer(): ?callable {
270+
return $this->inner->getInsertIgnoreSqlTransformer();
271+
}
268272
}

lib/private/DB/QueryBuilder/ExtendedQueryBuilder.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public function getState() {
4747
return $this->builder->getState();
4848
}
4949

50+
public function ignoreConflictsOnInsert(): self {
51+
$this->builder->ignoreConflictsOnInsert();
52+
return $this;
53+
}
54+
5055
public function getSQL() {
5156
return $this->builder->getSQL();
5257
}

lib/private/DB/QueryBuilder/QueryBuilder.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class QueryBuilder extends TypedQueryBuilder {
3939
private bool $nonEmptyWhere = false;
4040
protected ?string $lastInsertedTable = null;
4141
private array $selectedColumns = [];
42+
private bool $insertIgnoreConflicts = false;
4243

4344
/**
4445
* Initializes a new QueryBuilder.
@@ -275,6 +276,13 @@ public function executeStatement(?IDBConnection $connection = null): int {
275276
);
276277
}
277278

279+
public function ignoreConflictsOnInsert(): self {
280+
if ($this->getType() !== \Doctrine\DBAL\Query\QueryBuilder::INSERT) {
281+
throw new \LogicException('ignoreConflictsOnInsert() can only be used on INSERT queries');
282+
}
283+
$this->insertIgnoreConflicts = true;
284+
return $this;
285+
}
278286

279287
/**
280288
* Gets the complete SQL string formed by the current specifications of this QueryBuilder.
@@ -289,7 +297,11 @@ public function executeStatement(?IDBConnection $connection = null): int {
289297
* @return string The SQL query string.
290298
*/
291299
public function getSQL() {
292-
return $this->queryBuilder->getSQL();
300+
$sql = $this->queryBuilder->getSQL();
301+
if ($this->insertIgnoreConflicts && $this->connection->getInsertIgnoreSqlTransformer() !== null) {
302+
return ($this->connection->getInsertIgnoreSqlTransformer())($sql);
303+
}
304+
return $sql;
293305
}
294306

295307
/**

lib/public/DB/QueryBuilder/IQueryBuilder.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ public function executeQuery(?IDBConnection $connection = null): IResult;
216216
*/
217217
public function executeStatement(?IDBConnection $connection = null): int;
218218

219+
/**
220+
* Set to ignore conflicts on insert
221+
*
222+
* @since 34.0.0
223+
* @return self
224+
*/
225+
public function ignoreConflictsOnInsert(): self;
226+
219227
/**
220228
* Gets the complete SQL string formed by the current specifications of this QueryBuilder.
221229
*

0 commit comments

Comments
 (0)