Completed
Push — master ( 18d0f3...d0e25a )
by
unknown
31:22
created
lib/public/DB/QueryBuilder/IQueryBuilder.php 1 patch
Indentation   +1068 added lines, -1068 removed lines patch added patch discarded remove patch
@@ -22,1072 +22,1072 @@
 block discarded – undo
22 22
  * @psalm-taint-specialize
23 23
  */
24 24
 interface IQueryBuilder {
25
-	/**
26
-	 * @since 9.0.0
27
-	 */
28
-	public const PARAM_NULL = ParameterType::NULL;
29
-	/**
30
-	 * @since 9.0.0
31
-	 */
32
-	public const PARAM_BOOL = Types::BOOLEAN;
33
-	/**
34
-	 * @since 9.0.0
35
-	 */
36
-	public const PARAM_INT = ParameterType::INTEGER;
37
-	/**
38
-	 * @since 9.0.0
39
-	 */
40
-	public const PARAM_STR = ParameterType::STRING;
41
-	/**
42
-	 * @since 9.0.0
43
-	 */
44
-	public const PARAM_LOB = ParameterType::LARGE_OBJECT;
45
-
46
-	/**
47
-	 * @since 9.0.0
48
-	 * @deprecated 31.0.0 - use PARAM_DATETIME_MUTABLE instead
49
-	 */
50
-	public const PARAM_DATE = Types::DATETIME_MUTABLE;
51
-
52
-	/**
53
-	 * For passing a \DateTime instance when only interested in the time part (without timezone support)
54
-	 * @since 31.0.0
55
-	 */
56
-	public const PARAM_TIME_MUTABLE = Types::TIME_MUTABLE;
57
-
58
-	/**
59
-	 * For passing a \DateTime instance when only interested in the date part (without timezone support)
60
-	 * @since 31.0.0
61
-	 */
62
-	public const PARAM_DATE_MUTABLE = Types::DATE_MUTABLE;
63
-
64
-	/**
65
-	 * For passing a \DateTime instance (without timezone support)
66
-	 * @since 31.0.0
67
-	 */
68
-	public const PARAM_DATETIME_MUTABLE = Types::DATETIME_MUTABLE;
69
-
70
-	/**
71
-	 * For passing a \DateTime instance with timezone support
72
-	 * @since 31.0.0
73
-	 */
74
-	public const PARAM_DATETIME_TZ_MUTABLE = Types::DATETIMETZ_MUTABLE;
75
-
76
-	/**
77
-	 * For passing a \DateTimeImmutable instance when only interested in the time part (without timezone support)
78
-	 * @since 31.0.0
79
-	 */
80
-	public const PARAM_TIME_IMMUTABLE = Types::TIME_MUTABLE;
81
-
82
-	/**
83
-	 * For passing a \DateTime instance when only interested in the date part (without timezone support)
84
-	 * @since 9.0.0
85
-	 */
86
-	public const PARAM_DATE_IMMUTABLE = Types::DATE_IMMUTABLE;
87
-
88
-	/**
89
-	 * For passing a \DateTime instance (without timezone support)
90
-	 * @since 31.0.0
91
-	 */
92
-	public const PARAM_DATETIME_IMMUTABLE = Types::DATETIME_IMMUTABLE;
93
-
94
-	/**
95
-	 * For passing a \DateTime instance with timezone support
96
-	 * @since 31.0.0
97
-	 */
98
-	public const PARAM_DATETIME_TZ_IMMUTABLE = Types::DATETIMETZ_IMMUTABLE;
99
-
100
-	/**
101
-	 * @since 24.0.0
102
-	 */
103
-	public const PARAM_JSON = 'json';
104
-
105
-	/**
106
-	 * @since 9.0.0
107
-	 */
108
-	public const PARAM_INT_ARRAY = ArrayParameterType::INTEGER;
109
-	/**
110
-	 * @since 9.0.0
111
-	 */
112
-	public const PARAM_STR_ARRAY = ArrayParameterType::STRING;
113
-
114
-	/**
115
-	 * @since 24.0.0 Indicates how many rows can be deleted at once with MySQL
116
-	 * database server.
117
-	 */
118
-	public const MAX_ROW_DELETION = 100000;
119
-
120
-	/**
121
-	 * Enable/disable automatic prefixing of table names with the oc_ prefix
122
-	 *
123
-	 * @param bool $enabled If set to true table names will be prefixed with the
124
-	 *                      owncloud database prefix automatically.
125
-	 * @since 8.2.0
126
-	 */
127
-	public function automaticTablePrefix($enabled);
128
-
129
-	/**
130
-	 * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
131
-	 * This producer method is intended for convenient inline usage. Example:
132
-	 *
133
-	 * <code>
134
-	 *     $qb = $conn->getQueryBuilder()
135
-	 *         ->select('u')
136
-	 *         ->from('users', 'u')
137
-	 *         ->where($qb->expr()->eq('u.id', 1));
138
-	 * </code>
139
-	 *
140
-	 * For more complex expression construction, consider storing the expression
141
-	 * builder object in a local variable.
142
-	 *
143
-	 * @return \OCP\DB\QueryBuilder\IExpressionBuilder
144
-	 * @since 8.2.0
145
-	 */
146
-	public function expr();
147
-
148
-	/**
149
-	 * Gets an FunctionBuilder used for object-oriented construction of query functions.
150
-	 * This producer method is intended for convenient inline usage. Example:
151
-	 *
152
-	 * <code>
153
-	 *     $qb = $conn->getQueryBuilder()
154
-	 *         ->select('u')
155
-	 *         ->from('users', 'u')
156
-	 *         ->where($qb->fun()->md5('u.id'));
157
-	 * </code>
158
-	 *
159
-	 * For more complex function construction, consider storing the function
160
-	 * builder object in a local variable.
161
-	 *
162
-	 * @return \OCP\DB\QueryBuilder\IFunctionBuilder
163
-	 * @since 12.0.0
164
-	 */
165
-	public function func();
166
-
167
-	/**
168
-	 * Gets the type of the currently built query.
169
-	 *
170
-	 * @return integer
171
-	 * @since 8.2.0
172
-	 */
173
-	public function getType();
174
-
175
-	/**
176
-	 * Gets the associated DBAL Connection for this query builder.
177
-	 *
178
-	 * @return \OCP\IDBConnection
179
-	 * @since 8.2.0
180
-	 */
181
-	public function getConnection();
182
-
183
-	/**
184
-	 * Gets the state of this query builder instance.
185
-	 *
186
-	 * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
187
-	 * @since 8.2.0
188
-	 * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update
189
-	 *    and we can not fix this in our wrapper.
190
-	 */
191
-	public function getState();
192
-
193
-	/**
194
-	 * Execute for select statements
195
-	 *
196
-	 * @param ?IDBConnection $connection (optional) the connection to run the query against. since 30.0
197
-	 * @return IResult
198
-	 * @since 22.0.0
199
-	 *
200
-	 * @throws Exception
201
-	 * @throws \RuntimeException in case of usage with non select query
202
-	 */
203
-	public function executeQuery(?IDBConnection $connection = null): IResult;
204
-
205
-	/**
206
-	 * Execute insert, update and delete statements
207
-	 *
208
-	 * @param ?IDBConnection $connection (optional) the connection to run the query against. since 30.0
209
-	 * @return int the number of affected rows
210
-	 * @since 22.0.0
211
-	 *
212
-	 * @throws Exception
213
-	 * @throws \RuntimeException in case of usage with select query
214
-	 */
215
-	public function executeStatement(?IDBConnection $connection = null): int;
216
-
217
-	/**
218
-	 * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
219
-	 *
220
-	 * <code>
221
-	 *     $qb = $conn->getQueryBuilder()
222
-	 *         ->select('u')
223
-	 *         ->from('User', 'u')
224
-	 *     echo $qb->getSQL(); // SELECT u FROM User u
225
-	 * </code>
226
-	 *
227
-	 * @return string The SQL query string.
228
-	 * @since 8.2.0
229
-	 */
230
-	public function getSQL();
231
-
232
-	/**
233
-	 * Sets a query parameter for the query being constructed.
234
-	 *
235
-	 * <code>
236
-	 *     $qb = $conn->getQueryBuilder()
237
-	 *         ->select('u')
238
-	 *         ->from('users', 'u')
239
-	 *         ->where('u.id = :user_id')
240
-	 *         ->setParameter(':user_id', 1);
241
-	 * </code>
242
-	 *
243
-	 * @param string|integer $key The parameter position or name.
244
-	 * @param mixed $value The parameter value.
245
-	 * @param string|null|int $type One of the IQueryBuilder::PARAM_* constants.
246
-	 *
247
-	 * @return $this This QueryBuilder instance.
248
-	 * @since 8.2.0
249
-	 */
250
-	public function setParameter($key, $value, $type = null);
251
-
252
-	/**
253
-	 * Sets a collection of query parameters for the query being constructed.
254
-	 *
255
-	 * <code>
256
-	 *     $qb = $conn->getQueryBuilder()
257
-	 *         ->select('u')
258
-	 *         ->from('users', 'u')
259
-	 *         ->where('u.id = :user_id1 OR u.id = :user_id2')
260
-	 *         ->setParameters(array(
261
-	 *             ':user_id1' => 1,
262
-	 *             ':user_id2' => 2
263
-	 *         ));
264
-	 * </code>
265
-	 *
266
-	 * @param array $params The query parameters to set.
267
-	 * @param array $types The query parameters types to set.
268
-	 *
269
-	 * @return $this This QueryBuilder instance.
270
-	 * @since 8.2.0
271
-	 */
272
-	public function setParameters(array $params, array $types = []);
273
-
274
-	/**
275
-	 * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
276
-	 *
277
-	 * @return array The currently defined query parameters indexed by parameter index or name.
278
-	 * @since 8.2.0
279
-	 */
280
-	public function getParameters();
281
-
282
-	/**
283
-	 * Gets a (previously set) query parameter of the query being constructed.
284
-	 *
285
-	 * @param mixed $key The key (index or name) of the bound parameter.
286
-	 *
287
-	 * @return mixed The value of the bound parameter.
288
-	 * @since 8.2.0
289
-	 */
290
-	public function getParameter($key);
291
-
292
-	/**
293
-	 * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
294
-	 *
295
-	 * @return array The currently defined query parameter types indexed by parameter index or name.
296
-	 * @since 8.2.0
297
-	 */
298
-	public function getParameterTypes();
299
-
300
-	/**
301
-	 * Gets a (previously set) query parameter type of the query being constructed.
302
-	 *
303
-	 * @param mixed $key The key (index or name) of the bound parameter type.
304
-	 *
305
-	 * @return mixed The value of the bound parameter type.
306
-	 * @since 8.2.0
307
-	 */
308
-	public function getParameterType($key);
309
-
310
-	/**
311
-	 * Sets the position of the first result to retrieve (the "offset").
312
-	 *
313
-	 * @param int $firstResult The first result to return.
314
-	 *
315
-	 * @return $this This QueryBuilder instance.
316
-	 * @since 8.2.0
317
-	 */
318
-	public function setFirstResult($firstResult);
319
-
320
-	/**
321
-	 * Gets the position of the first result the query object was set to retrieve (the "offset").
322
-	 * Returns 0 if {@link setFirstResult} was not applied to this QueryBuilder.
323
-	 *
324
-	 * @return int The position of the first result.
325
-	 * @since 8.2.0
326
-	 */
327
-	public function getFirstResult();
328
-
329
-	/**
330
-	 * Sets the maximum number of results to retrieve (the "limit").
331
-	 *
332
-	 * @param int|null $maxResults The maximum number of results to retrieve.
333
-	 *
334
-	 * @return $this This QueryBuilder instance.
335
-	 * @since 8.2.0
336
-	 */
337
-	public function setMaxResults($maxResults);
338
-
339
-	/**
340
-	 * Gets the maximum number of results the query object was set to retrieve (the "limit").
341
-	 * Returns NULL if {@link setMaxResults} was not applied to this query builder.
342
-	 *
343
-	 * @return int|null The maximum number of results.
344
-	 * @since 8.2.0
345
-	 */
346
-	public function getMaxResults();
347
-
348
-	/**
349
-	 * Specifies an item that is to be returned in the query result.
350
-	 * Replaces any previously specified selections, if any.
351
-	 *
352
-	 * <code>
353
-	 *     $qb = $conn->getQueryBuilder()
354
-	 *         ->select('u.id', 'p.id')
355
-	 *         ->from('users', 'u')
356
-	 *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
357
-	 * </code>
358
-	 *
359
-	 * @param mixed ...$selects The selection expressions.
360
-	 *
361
-	 * @return $this This QueryBuilder instance.
362
-	 * @since 8.2.0
363
-	 *
364
-	 * @psalm-taint-sink sql $selects
365
-	 */
366
-	public function select(...$selects);
367
-
368
-	/**
369
-	 * Specifies an item that is to be returned with a different name in the query result.
370
-	 *
371
-	 * <code>
372
-	 *     $qb = $conn->getQueryBuilder()
373
-	 *         ->selectAlias('u.id', 'user_id')
374
-	 *         ->from('users', 'u')
375
-	 *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
376
-	 * </code>
377
-	 *
378
-	 * @param mixed $select The selection expressions.
379
-	 * @param string $alias The column alias used in the constructed query.
380
-	 *
381
-	 * @return $this This QueryBuilder instance.
382
-	 * @since 8.2.1
383
-	 *
384
-	 * @psalm-taint-sink sql $select
385
-	 * @psalm-taint-sink sql $alias
386
-	 */
387
-	public function selectAlias($select, $alias);
388
-
389
-	/**
390
-	 * Specifies an item that is to be returned uniquely in the query result.
391
-	 *
392
-	 * <code>
393
-	 *     $qb = $conn->getQueryBuilder()
394
-	 *         ->selectDistinct('type')
395
-	 *         ->from('users');
396
-	 * </code>
397
-	 *
398
-	 * @param mixed $select The selection expressions.
399
-	 *
400
-	 * @return $this This QueryBuilder instance.
401
-	 * @since 9.0.0
402
-	 *
403
-	 * @psalm-taint-sink sql $select
404
-	 */
405
-	public function selectDistinct($select);
406
-
407
-	/**
408
-	 * Adds an item that is to be returned in the query result.
409
-	 *
410
-	 * <code>
411
-	 *     $qb = $conn->getQueryBuilder()
412
-	 *         ->select('u.id')
413
-	 *         ->addSelect('p.id')
414
-	 *         ->from('users', 'u')
415
-	 *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
416
-	 * </code>
417
-	 *
418
-	 * @param mixed ...$select The selection expression.
419
-	 *
420
-	 * @return $this This QueryBuilder instance.
421
-	 * @since 8.2.0
422
-	 *
423
-	 * @psalm-taint-sink sql $select
424
-	 */
425
-	public function addSelect(...$select);
426
-
427
-	/**
428
-	 * Turns the query being built into a bulk delete query that ranges over
429
-	 * a certain table.
430
-	 *
431
-	 * <code>
432
-	 *     $qb = $conn->getQueryBuilder()
433
-	 *         ->delete('users')
434
-	 *         ->where('id = :user_id');
435
-	 *         ->setParameter(':user_id', 1);
436
-	 * </code>
437
-	 *
438
-	 * @param string $delete The table whose rows are subject to the deletion.
439
-	 * @param string $alias The table alias used in the constructed query.
440
-	 *
441
-	 * @return $this This QueryBuilder instance.
442
-	 * @since 8.2.0
443
-	 * @since 30.0.0 Alias is deprecated and will no longer be used with the next Doctrine/DBAL update
444
-	 *
445
-	 * @psalm-taint-sink sql $delete
446
-	 */
447
-	public function delete($delete = null, $alias = null);
448
-
449
-	/**
450
-	 * Turns the query being built into a bulk update query that ranges over
451
-	 * a certain table
452
-	 *
453
-	 * <code>
454
-	 *     $qb = $conn->getQueryBuilder()
455
-	 *         ->update('users')
456
-	 *         ->set('email', ':email')
457
-	 *         ->where('id = :user_id');
458
-	 *         ->setParameter(':user_id', 1);
459
-	 * </code>
460
-	 *
461
-	 * @param string $update The table whose rows are subject to the update.
462
-	 * @param string $alias The table alias used in the constructed query.
463
-	 *
464
-	 * @return $this This QueryBuilder instance.
465
-	 * @since 8.2.0
466
-	 * @since 30.0.0 Alias is deprecated and will no longer be used with the next Doctrine/DBAL update
467
-	 *
468
-	 * @psalm-taint-sink sql $update
469
-	 */
470
-	public function update($update = null, $alias = null);
471
-
472
-	/**
473
-	 * Turns the query being built into an insert query that inserts into
474
-	 * a certain table
475
-	 *
476
-	 * <code>
477
-	 *     $qb = $conn->getQueryBuilder()
478
-	 *         ->insert('users')
479
-	 *         ->values(
480
-	 *             array(
481
-	 *                 'name' => '?',
482
-	 *                 'password' => '?'
483
-	 *             )
484
-	 *         );
485
-	 * </code>
486
-	 *
487
-	 * @param string $insert The table into which the rows should be inserted.
488
-	 *
489
-	 * @return $this This QueryBuilder instance.
490
-	 * @since 8.2.0
491
-	 *
492
-	 * @psalm-taint-sink sql $insert
493
-	 */
494
-	public function insert($insert = null);
495
-
496
-	/**
497
-	 * Creates and adds a query root corresponding to the table identified by the
498
-	 * given alias, forming a cartesian product with any existing query roots.
499
-	 *
500
-	 * <code>
501
-	 *     $qb = $conn->getQueryBuilder()
502
-	 *         ->select('u.id')
503
-	 *         ->from('users', 'u')
504
-	 * </code>
505
-	 *
506
-	 * @param string|IQueryFunction $from The table.
507
-	 * @param string|null $alias The alias of the table.
508
-	 *
509
-	 * @return $this This QueryBuilder instance.
510
-	 * @since 8.2.0
511
-	 *
512
-	 * @psalm-taint-sink sql $from
513
-	 */
514
-	public function from($from, $alias = null);
515
-
516
-	/**
517
-	 * Creates and adds a join to the query.
518
-	 *
519
-	 * <code>
520
-	 *     $qb = $conn->getQueryBuilder()
521
-	 *         ->select('u.name')
522
-	 *         ->from('users', 'u')
523
-	 *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
524
-	 * </code>
525
-	 *
526
-	 * @param string $fromAlias The alias that points to a from clause.
527
-	 * @param string|IQueryFunction $join The table name to join.
528
-	 * @param string $alias The alias of the join table.
529
-	 * @param string|ICompositeExpression|null $condition The condition for the join.
530
-	 *
531
-	 * @return $this This QueryBuilder instance.
532
-	 * @since 8.2.0
533
-	 *
534
-	 * @psalm-taint-sink sql $fromAlias
535
-	 * @psalm-taint-sink sql $join
536
-	 * @psalm-taint-sink sql $alias
537
-	 * @psalm-taint-sink sql $condition
538
-	 */
539
-	public function join($fromAlias, $join, $alias, $condition = null);
540
-
541
-	/**
542
-	 * Creates and adds a join to the query.
543
-	 *
544
-	 * <code>
545
-	 *     $qb = $conn->getQueryBuilder()
546
-	 *         ->select('u.name')
547
-	 *         ->from('users', 'u')
548
-	 *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
549
-	 * </code>
550
-	 *
551
-	 * @param string $fromAlias The alias that points to a from clause.
552
-	 * @param string|IQueryFunction $join The table name to join.
553
-	 * @param string $alias The alias of the join table.
554
-	 * @param string|ICompositeExpression|null $condition The condition for the join.
555
-	 *
556
-	 * @return $this This QueryBuilder instance.
557
-	 * @since 8.2.0
558
-	 *
559
-	 * @psalm-taint-sink sql $fromAlias
560
-	 * @psalm-taint-sink sql $join
561
-	 * @psalm-taint-sink sql $alias
562
-	 * @psalm-taint-sink sql $condition
563
-	 */
564
-	public function innerJoin($fromAlias, $join, $alias, $condition = null);
565
-
566
-	/**
567
-	 * Creates and adds a left join to the query.
568
-	 *
569
-	 * <code>
570
-	 *     $qb = $conn->getQueryBuilder()
571
-	 *         ->select('u.name')
572
-	 *         ->from('users', 'u')
573
-	 *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
574
-	 * </code>
575
-	 *
576
-	 * @param string $fromAlias The alias that points to a from clause.
577
-	 * @param string|IQueryFunction $join The table name to join.
578
-	 * @param string $alias The alias of the join table.
579
-	 * @param string|ICompositeExpression|null $condition The condition for the join.
580
-	 *
581
-	 * @return $this This QueryBuilder instance.
582
-	 * @since 8.2.0
583
-	 * @since 30.0.0 Allow passing IQueryFunction as parameter for `$join` to allow join with a sub-query.
584
-	 *
585
-	 * @psalm-taint-sink sql $fromAlias
586
-	 * @psalm-taint-sink sql $join
587
-	 * @psalm-taint-sink sql $alias
588
-	 * @psalm-taint-sink sql $condition
589
-	 */
590
-	public function leftJoin($fromAlias, $join, $alias, $condition = null);
591
-
592
-	/**
593
-	 * Creates and adds a right join to the query.
594
-	 *
595
-	 * <code>
596
-	 *     $qb = $conn->getQueryBuilder()
597
-	 *         ->select('u.name')
598
-	 *         ->from('users', 'u')
599
-	 *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
600
-	 * </code>
601
-	 *
602
-	 * @param string $fromAlias The alias that points to a from clause.
603
-	 * @param string|IQueryFunction $join The table name to join.
604
-	 * @param string $alias The alias of the join table.
605
-	 * @param string|ICompositeExpression|null $condition The condition for the join.
606
-	 *
607
-	 * @return $this This QueryBuilder instance.
608
-	 * @since 8.2.0
609
-	 *
610
-	 * @psalm-taint-sink sql $fromAlias
611
-	 * @psalm-taint-sink sql $join
612
-	 * @psalm-taint-sink sql $alias
613
-	 * @psalm-taint-sink sql $condition
614
-	 */
615
-	public function rightJoin($fromAlias, $join, $alias, $condition = null);
616
-
617
-	/**
618
-	 * Sets a new value for a column in a bulk update query.
619
-	 *
620
-	 * <code>
621
-	 *     $qb = $conn->getQueryBuilder()
622
-	 *         ->update('users', 'u')
623
-	 *         ->set('u.password', md5('password'))
624
-	 *         ->where('u.id = ?');
625
-	 * </code>
626
-	 *
627
-	 * @param string $key The column to set.
628
-	 * @param ILiteral|IParameter|IQueryFunction|string $value The value, expression, placeholder, etc.
629
-	 *
630
-	 * @return $this This QueryBuilder instance.
631
-	 * @since 8.2.0
632
-	 *
633
-	 * @psalm-taint-sink sql $key
634
-	 * @psalm-taint-sink sql $value
635
-	 */
636
-	public function set($key, $value);
637
-
638
-	/**
639
-	 * Specifies one or more restrictions to the query result.
640
-	 * Replaces any previously specified restrictions, if any.
641
-	 *
642
-	 * <code>
643
-	 *     $qb = $conn->getQueryBuilder()
644
-	 *         ->select('u.name')
645
-	 *         ->from('users', 'u')
646
-	 *         ->where('u.id = ?');
647
-	 *
648
-	 *     // You can optionally programmatically build and/or expressions
649
-	 *     $qb = $conn->getQueryBuilder();
650
-	 *
651
-	 *     $or = $qb->expr()->orx(
652
-	 *         $qb->expr()->eq('u.id', 1),
653
-	 *         $qb->expr()->eq('u.id', 2),
654
-	 *     );
655
-	 *
656
-	 *     $qb->update('users', 'u')
657
-	 *         ->set('u.password', md5('password'))
658
-	 *         ->where($or);
659
-	 * </code>
660
-	 *
661
-	 * @param mixed $predicates The restriction predicates.
662
-	 *
663
-	 * @return $this This QueryBuilder instance.
664
-	 * @since 8.2.0
665
-	 *
666
-	 * @psalm-taint-sink sql $predicates
667
-	 */
668
-	public function where(...$predicates);
669
-
670
-	/**
671
-	 * Adds one or more restrictions to the query results, forming a logical
672
-	 * conjunction with any previously specified restrictions.
673
-	 *
674
-	 * <code>
675
-	 *     $qb = $conn->getQueryBuilder()
676
-	 *         ->select('u')
677
-	 *         ->from('users', 'u')
678
-	 *         ->where('u.username LIKE ?')
679
-	 *         ->andWhere('u.is_active = 1');
680
-	 * </code>
681
-	 *
682
-	 * @param mixed ...$where The query restrictions.
683
-	 *
684
-	 * @return $this This QueryBuilder instance.
685
-	 *
686
-	 * @see where()
687
-	 * @since 8.2.0
688
-	 *
689
-	 * @psalm-taint-sink sql $where
690
-	 */
691
-	public function andWhere(...$where);
692
-
693
-	/**
694
-	 * Adds one or more restrictions to the query results, forming a logical
695
-	 * disjunction with any previously specified restrictions.
696
-	 *
697
-	 * <code>
698
-	 *     $qb = $conn->getQueryBuilder()
699
-	 *         ->select('u.name')
700
-	 *         ->from('users', 'u')
701
-	 *         ->where('u.id = 1')
702
-	 *         ->orWhere('u.id = 2');
703
-	 * </code>
704
-	 *
705
-	 * @param mixed ...$where The WHERE statement.
706
-	 *
707
-	 * @return $this This QueryBuilder instance.
708
-	 *
709
-	 * @see where()
710
-	 * @since 8.2.0
711
-	 *
712
-	 * @psalm-taint-sink sql $where
713
-	 */
714
-	public function orWhere(...$where);
715
-
716
-	/**
717
-	 * Specifies a grouping over the results of the query.
718
-	 * Replaces any previously specified groupings, if any.
719
-	 *
720
-	 * <code>
721
-	 *     $qb = $conn->getQueryBuilder()
722
-	 *         ->select('u.name')
723
-	 *         ->from('users', 'u')
724
-	 *         ->groupBy('u.id');
725
-	 * </code>
726
-	 *
727
-	 * @param mixed ...$groupBys The grouping expression.
728
-	 *
729
-	 * @return $this This QueryBuilder instance.
730
-	 * @since 8.2.0
731
-	 *
732
-	 * @psalm-taint-sink sql $groupBys
733
-	 */
734
-	public function groupBy(...$groupBys);
735
-
736
-	/**
737
-	 * Adds a grouping expression to the query.
738
-	 *
739
-	 * <code>
740
-	 *     $qb = $conn->getQueryBuilder()
741
-	 *         ->select('u.name')
742
-	 *         ->from('users', 'u')
743
-	 *         ->groupBy('u.lastLogin');
744
-	 *         ->addGroupBy('u.createdAt')
745
-	 * </code>
746
-	 *
747
-	 * @param mixed ...$groupBy The grouping expression.
748
-	 *
749
-	 * @return $this This QueryBuilder instance.
750
-	 * @since 8.2.0
751
-	 *
752
-	 * @psalm-taint-sink sql $groupby
753
-	 */
754
-	public function addGroupBy(...$groupBy);
755
-
756
-	/**
757
-	 * Sets a value for a column in an insert query.
758
-	 *
759
-	 * <code>
760
-	 *     $qb = $conn->getQueryBuilder()
761
-	 *         ->insert('users')
762
-	 *         ->values(
763
-	 *             array(
764
-	 *                 'name' => '?'
765
-	 *             )
766
-	 *         )
767
-	 *         ->setValue('password', '?');
768
-	 * </code>
769
-	 *
770
-	 * @param string $column The column into which the value should be inserted.
771
-	 * @param IParameter|string $value The value that should be inserted into the column.
772
-	 *
773
-	 * @return $this This QueryBuilder instance.
774
-	 * @since 8.2.0
775
-	 *
776
-	 * @psalm-taint-sink sql $column
777
-	 * @psalm-taint-sink sql $value
778
-	 */
779
-	public function setValue($column, $value);
780
-
781
-	/**
782
-	 * Specifies values for an insert query indexed by column names.
783
-	 * Replaces any previous values, if any.
784
-	 *
785
-	 * <code>
786
-	 *     $qb = $conn->getQueryBuilder()
787
-	 *         ->insert('users')
788
-	 *         ->values(
789
-	 *             array(
790
-	 *                 'name' => '?',
791
-	 *                 'password' => '?'
792
-	 *             )
793
-	 *         );
794
-	 * </code>
795
-	 *
796
-	 * @param array $values The values to specify for the insert query indexed by column names.
797
-	 *
798
-	 * @return $this This QueryBuilder instance.
799
-	 * @since 8.2.0
800
-	 *
801
-	 * @psalm-taint-sink sql $values
802
-	 */
803
-	public function values(array $values);
804
-
805
-	/**
806
-	 * Specifies a restriction over the groups of the query.
807
-	 * Replaces any previous having restrictions, if any.
808
-	 *
809
-	 * @param mixed ...$having The restriction over the groups.
810
-	 *
811
-	 * @return $this This QueryBuilder instance.
812
-	 * @since 8.2.0
813
-	 *
814
-	 * @psalm-taint-sink sql $having
815
-	 */
816
-	public function having(...$having);
817
-
818
-	/**
819
-	 * Adds a restriction over the groups of the query, forming a logical
820
-	 * conjunction with any existing having restrictions.
821
-	 *
822
-	 * @param mixed ...$having The restriction to append.
823
-	 *
824
-	 * @return $this This QueryBuilder instance.
825
-	 * @since 8.2.0
826
-	 *
827
-	 * @psalm-taint-sink sql $andHaving
828
-	 */
829
-	public function andHaving(...$having);
830
-
831
-	/**
832
-	 * Adds a restriction over the groups of the query, forming a logical
833
-	 * disjunction with any existing having restrictions.
834
-	 *
835
-	 * @param mixed ...$having The restriction to add.
836
-	 *
837
-	 * @return $this This QueryBuilder instance.
838
-	 * @since 8.2.0
839
-	 *
840
-	 * @psalm-taint-sink sql $having
841
-	 */
842
-	public function orHaving(...$having);
843
-
844
-	/**
845
-	 * Specifies an ordering for the query results.
846
-	 * Replaces any previously specified orderings, if any.
847
-	 *
848
-	 * @param string|IQueryFunction|ILiteral|IParameter $sort The ordering expression.
849
-	 * @param string $order The ordering direction.
850
-	 *
851
-	 * @return $this This QueryBuilder instance.
852
-	 * @since 8.2.0
853
-	 *
854
-	 * @psalm-taint-sink sql $sort
855
-	 * @psalm-taint-sink sql $order
856
-	 */
857
-	public function orderBy($sort, $order = null);
858
-
859
-	/**
860
-	 * Adds an ordering to the query results.
861
-	 *
862
-	 * @param string|ILiteral|IParameter|IQueryFunction $sort The ordering expression.
863
-	 * @param string $order The ordering direction.
864
-	 *
865
-	 * @return $this This QueryBuilder instance.
866
-	 * @since 8.2.0
867
-	 *
868
-	 * @psalm-taint-sink sql $sort
869
-	 * @psalm-taint-sink sql $order
870
-	 */
871
-	public function addOrderBy($sort, $order = null);
872
-
873
-	/**
874
-	 * Gets a query part by its name.
875
-	 *
876
-	 * @param string $queryPartName
877
-	 *
878
-	 * @return mixed
879
-	 * @since 8.2.0
880
-	 * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update
881
-	 *  and we can not fix this in our wrapper. Please track the details you need, outside the object.
882
-	 */
883
-	public function getQueryPart($queryPartName);
884
-
885
-	/**
886
-	 * Gets all query parts.
887
-	 *
888
-	 * @return array
889
-	 * @since 8.2.0
890
-	 * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update
891
-	 *  and we can not fix this in our wrapper. Please track the details you need, outside the object.
892
-	 */
893
-	public function getQueryParts();
894
-
895
-	/**
896
-	 * Resets SQL parts.
897
-	 *
898
-	 * @param array|null $queryPartNames
899
-	 *
900
-	 * @return $this This QueryBuilder instance.
901
-	 * @since 8.2.0
902
-	 * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update
903
-	 * and we can not fix this in our wrapper. Please create a new IQueryBuilder instead.
904
-	 */
905
-	public function resetQueryParts($queryPartNames = null);
906
-
907
-	/**
908
-	 * Resets a single SQL part.
909
-	 *
910
-	 * @param string $queryPartName
911
-	 *
912
-	 * @return $this This QueryBuilder instance.
913
-	 * @since 8.2.0
914
-	 * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update
915
-	 *  and we can not fix this in our wrapper. Please create a new IQueryBuilder instead.
916
-	 */
917
-	public function resetQueryPart($queryPartName);
918
-
919
-	/**
920
-	 * Creates a new named parameter and bind the value $value to it.
921
-	 *
922
-	 * This method provides a shortcut for PDOStatement::bindValue
923
-	 * when using prepared statements.
924
-	 *
925
-	 * The parameter $value specifies the value that you want to bind. If
926
-	 * $placeholder is not provided bindValue() will automatically create a
927
-	 * placeholder for you. An automatic placeholder will be of the name
928
-	 * ':dcValue1', ':dcValue2' etc.
929
-	 *
930
-	 * For more information see {@link https://www.php.net/pdostatement-bindparam}
931
-	 *
932
-	 * Example:
933
-	 * <code>
934
-	 * $value = 2;
935
-	 * $q->eq( 'id', $q->bindValue( $value ) );
936
-	 * $stmt = $q->executeQuery(); // executed with 'id = 2'
937
-	 * </code>
938
-	 *
939
-	 * @license New BSD License
940
-	 * @link http://www.zetacomponents.org
941
-	 *
942
-	 * @param mixed $value
943
-	 * @param self::PARAM_* $type
944
-	 * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
945
-	 *
946
-	 * @return IParameter
947
-	 * @since 8.2.0
948
-	 *
949
-	 * @psalm-taint-escape sql
950
-	 */
951
-	public function createNamedParameter($value, $type = self::PARAM_STR, $placeHolder = null);
952
-
953
-	/**
954
-	 * Creates a new positional parameter and bind the given value to it.
955
-	 *
956
-	 * Attention: If you are using positional parameters with the query builder you have
957
-	 * to be very careful to bind all parameters in the order they appear in the SQL
958
-	 * statement , otherwise they get bound in the wrong order which can lead to serious
959
-	 * bugs in your code.
960
-	 *
961
-	 * Example:
962
-	 * <code>
963
-	 *  $qb = $conn->getQueryBuilder();
964
-	 *  $qb->select('u.*')
965
-	 *     ->from('users', 'u')
966
-	 *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', IQueryBuilder::PARAM_STR))
967
-	 *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', IQueryBuilder::PARAM_STR))
968
-	 * </code>
969
-	 *
970
-	 * @param mixed $value
971
-	 * @param self::PARAM_* $type
972
-	 *
973
-	 * @return IParameter
974
-	 * @since 8.2.0
975
-	 *
976
-	 * @psalm-taint-escape sql
977
-	 */
978
-	public function createPositionalParameter($value, $type = self::PARAM_STR);
979
-
980
-	/**
981
-	 * Creates a new parameter
982
-	 *
983
-	 * Example:
984
-	 * <code>
985
-	 *  $qb = $conn->getQueryBuilder();
986
-	 *  $qb->select('u.*')
987
-	 *     ->from('users', 'u')
988
-	 *     ->where('u.username = ' . $qb->createParameter('name'))
989
-	 *     ->setParameter('name', 'Bar', IQueryBuilder::PARAM_STR))
990
-	 * </code>
991
-	 *
992
-	 * @param string $name
993
-	 *
994
-	 * @return IParameter
995
-	 * @since 8.2.0
996
-	 *
997
-	 * @psalm-taint-escape sql
998
-	 */
999
-	public function createParameter($name);
1000
-
1001
-	/**
1002
-	 * Creates a new function
1003
-	 *
1004
-	 * Attention: Column names inside the call have to be quoted before hand
1005
-	 *
1006
-	 * Example:
1007
-	 * <code>
1008
-	 *  $qb = $conn->getQueryBuilder();
1009
-	 *  $qb->select($qb->createFunction('COUNT(*)'))
1010
-	 *     ->from('users', 'u')
1011
-	 *  echo $qb->getSQL(); // SELECT COUNT(*) FROM `users` u
1012
-	 * </code>
1013
-	 * <code>
1014
-	 *  $qb = $conn->getQueryBuilder();
1015
-	 *  $qb->select($qb->createFunction('COUNT(`column`)'))
1016
-	 *     ->from('users', 'u')
1017
-	 *  echo $qb->getSQL(); // SELECT COUNT(`column`) FROM `users` u
1018
-	 * </code>
1019
-	 *
1020
-	 * @param string $call
1021
-	 *
1022
-	 * @return IQueryFunction
1023
-	 * @since 8.2.0
1024
-	 *
1025
-	 * @psalm-taint-sink sql $call
1026
-	 */
1027
-	public function createFunction($call);
1028
-
1029
-	/**
1030
-	 * Used to get the id of the last inserted element
1031
-	 * @return int
1032
-	 * @throws \BadMethodCallException When being called before an insert query has been run.
1033
-	 * @since 9.0.0
1034
-	 */
1035
-	public function getLastInsertId(): int;
1036
-
1037
-	/**
1038
-	 * Returns the table name quoted and with database prefix as needed by the implementation.
1039
-	 * If a query function is passed the function is casted to string,
1040
-	 * this allows passing functions as sub-queries for join expression.
1041
-	 *
1042
-	 * @param string|IQueryFunction $table
1043
-	 * @return string
1044
-	 * @since 9.0.0
1045
-	 * @since 24.0.0 accepts IQueryFunction as parameter
1046
-	 */
1047
-	public function getTableName($table);
1048
-
1049
-	/**
1050
-	 * Returns the table name with database prefix as needed by the implementation
1051
-	 *
1052
-	 * @param string $table
1053
-	 * @return string
1054
-	 * @since 30.0.0
1055
-	 */
1056
-	public function prefixTableName(string $table): string;
1057
-
1058
-	/**
1059
-	 * Returns the column name quoted and with table alias prefix as needed by the implementation
1060
-	 *
1061
-	 * @param string $column
1062
-	 * @param string $tableAlias
1063
-	 * @return string
1064
-	 * @since 9.0.0
1065
-	 */
1066
-	public function getColumnName($column, $tableAlias = '');
1067
-
1068
-	/**
1069
-	 * Provide a hint for the shard key for queries where this can't be detected otherwise
1070
-	 *
1071
-	 * @param string $column
1072
-	 * @param mixed $value
1073
-	 * @return $this
1074
-	 * @since 30.0.0
1075
-	 */
1076
-	public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self;
1077
-
1078
-	/**
1079
-	 * Set the query to run across all shards if sharding is enabled.
1080
-	 *
1081
-	 * @return $this
1082
-	 * @since 30.0.0
1083
-	 */
1084
-	public function runAcrossAllShards(): self;
1085
-
1086
-	/**
1087
-	 * Get a list of column names that are expected in the query output
1088
-	 *
1089
-	 * @return array
1090
-	 * @since 30.0.0
1091
-	 */
1092
-	public function getOutputColumns(): array;
25
+    /**
26
+     * @since 9.0.0
27
+     */
28
+    public const PARAM_NULL = ParameterType::NULL;
29
+    /**
30
+     * @since 9.0.0
31
+     */
32
+    public const PARAM_BOOL = Types::BOOLEAN;
33
+    /**
34
+     * @since 9.0.0
35
+     */
36
+    public const PARAM_INT = ParameterType::INTEGER;
37
+    /**
38
+     * @since 9.0.0
39
+     */
40
+    public const PARAM_STR = ParameterType::STRING;
41
+    /**
42
+     * @since 9.0.0
43
+     */
44
+    public const PARAM_LOB = ParameterType::LARGE_OBJECT;
45
+
46
+    /**
47
+     * @since 9.0.0
48
+     * @deprecated 31.0.0 - use PARAM_DATETIME_MUTABLE instead
49
+     */
50
+    public const PARAM_DATE = Types::DATETIME_MUTABLE;
51
+
52
+    /**
53
+     * For passing a \DateTime instance when only interested in the time part (without timezone support)
54
+     * @since 31.0.0
55
+     */
56
+    public const PARAM_TIME_MUTABLE = Types::TIME_MUTABLE;
57
+
58
+    /**
59
+     * For passing a \DateTime instance when only interested in the date part (without timezone support)
60
+     * @since 31.0.0
61
+     */
62
+    public const PARAM_DATE_MUTABLE = Types::DATE_MUTABLE;
63
+
64
+    /**
65
+     * For passing a \DateTime instance (without timezone support)
66
+     * @since 31.0.0
67
+     */
68
+    public const PARAM_DATETIME_MUTABLE = Types::DATETIME_MUTABLE;
69
+
70
+    /**
71
+     * For passing a \DateTime instance with timezone support
72
+     * @since 31.0.0
73
+     */
74
+    public const PARAM_DATETIME_TZ_MUTABLE = Types::DATETIMETZ_MUTABLE;
75
+
76
+    /**
77
+     * For passing a \DateTimeImmutable instance when only interested in the time part (without timezone support)
78
+     * @since 31.0.0
79
+     */
80
+    public const PARAM_TIME_IMMUTABLE = Types::TIME_MUTABLE;
81
+
82
+    /**
83
+     * For passing a \DateTime instance when only interested in the date part (without timezone support)
84
+     * @since 9.0.0
85
+     */
86
+    public const PARAM_DATE_IMMUTABLE = Types::DATE_IMMUTABLE;
87
+
88
+    /**
89
+     * For passing a \DateTime instance (without timezone support)
90
+     * @since 31.0.0
91
+     */
92
+    public const PARAM_DATETIME_IMMUTABLE = Types::DATETIME_IMMUTABLE;
93
+
94
+    /**
95
+     * For passing a \DateTime instance with timezone support
96
+     * @since 31.0.0
97
+     */
98
+    public const PARAM_DATETIME_TZ_IMMUTABLE = Types::DATETIMETZ_IMMUTABLE;
99
+
100
+    /**
101
+     * @since 24.0.0
102
+     */
103
+    public const PARAM_JSON = 'json';
104
+
105
+    /**
106
+     * @since 9.0.0
107
+     */
108
+    public const PARAM_INT_ARRAY = ArrayParameterType::INTEGER;
109
+    /**
110
+     * @since 9.0.0
111
+     */
112
+    public const PARAM_STR_ARRAY = ArrayParameterType::STRING;
113
+
114
+    /**
115
+     * @since 24.0.0 Indicates how many rows can be deleted at once with MySQL
116
+     * database server.
117
+     */
118
+    public const MAX_ROW_DELETION = 100000;
119
+
120
+    /**
121
+     * Enable/disable automatic prefixing of table names with the oc_ prefix
122
+     *
123
+     * @param bool $enabled If set to true table names will be prefixed with the
124
+     *                      owncloud database prefix automatically.
125
+     * @since 8.2.0
126
+     */
127
+    public function automaticTablePrefix($enabled);
128
+
129
+    /**
130
+     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
131
+     * This producer method is intended for convenient inline usage. Example:
132
+     *
133
+     * <code>
134
+     *     $qb = $conn->getQueryBuilder()
135
+     *         ->select('u')
136
+     *         ->from('users', 'u')
137
+     *         ->where($qb->expr()->eq('u.id', 1));
138
+     * </code>
139
+     *
140
+     * For more complex expression construction, consider storing the expression
141
+     * builder object in a local variable.
142
+     *
143
+     * @return \OCP\DB\QueryBuilder\IExpressionBuilder
144
+     * @since 8.2.0
145
+     */
146
+    public function expr();
147
+
148
+    /**
149
+     * Gets an FunctionBuilder used for object-oriented construction of query functions.
150
+     * This producer method is intended for convenient inline usage. Example:
151
+     *
152
+     * <code>
153
+     *     $qb = $conn->getQueryBuilder()
154
+     *         ->select('u')
155
+     *         ->from('users', 'u')
156
+     *         ->where($qb->fun()->md5('u.id'));
157
+     * </code>
158
+     *
159
+     * For more complex function construction, consider storing the function
160
+     * builder object in a local variable.
161
+     *
162
+     * @return \OCP\DB\QueryBuilder\IFunctionBuilder
163
+     * @since 12.0.0
164
+     */
165
+    public function func();
166
+
167
+    /**
168
+     * Gets the type of the currently built query.
169
+     *
170
+     * @return integer
171
+     * @since 8.2.0
172
+     */
173
+    public function getType();
174
+
175
+    /**
176
+     * Gets the associated DBAL Connection for this query builder.
177
+     *
178
+     * @return \OCP\IDBConnection
179
+     * @since 8.2.0
180
+     */
181
+    public function getConnection();
182
+
183
+    /**
184
+     * Gets the state of this query builder instance.
185
+     *
186
+     * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
187
+     * @since 8.2.0
188
+     * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update
189
+     *    and we can not fix this in our wrapper.
190
+     */
191
+    public function getState();
192
+
193
+    /**
194
+     * Execute for select statements
195
+     *
196
+     * @param ?IDBConnection $connection (optional) the connection to run the query against. since 30.0
197
+     * @return IResult
198
+     * @since 22.0.0
199
+     *
200
+     * @throws Exception
201
+     * @throws \RuntimeException in case of usage with non select query
202
+     */
203
+    public function executeQuery(?IDBConnection $connection = null): IResult;
204
+
205
+    /**
206
+     * Execute insert, update and delete statements
207
+     *
208
+     * @param ?IDBConnection $connection (optional) the connection to run the query against. since 30.0
209
+     * @return int the number of affected rows
210
+     * @since 22.0.0
211
+     *
212
+     * @throws Exception
213
+     * @throws \RuntimeException in case of usage with select query
214
+     */
215
+    public function executeStatement(?IDBConnection $connection = null): int;
216
+
217
+    /**
218
+     * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
219
+     *
220
+     * <code>
221
+     *     $qb = $conn->getQueryBuilder()
222
+     *         ->select('u')
223
+     *         ->from('User', 'u')
224
+     *     echo $qb->getSQL(); // SELECT u FROM User u
225
+     * </code>
226
+     *
227
+     * @return string The SQL query string.
228
+     * @since 8.2.0
229
+     */
230
+    public function getSQL();
231
+
232
+    /**
233
+     * Sets a query parameter for the query being constructed.
234
+     *
235
+     * <code>
236
+     *     $qb = $conn->getQueryBuilder()
237
+     *         ->select('u')
238
+     *         ->from('users', 'u')
239
+     *         ->where('u.id = :user_id')
240
+     *         ->setParameter(':user_id', 1);
241
+     * </code>
242
+     *
243
+     * @param string|integer $key The parameter position or name.
244
+     * @param mixed $value The parameter value.
245
+     * @param string|null|int $type One of the IQueryBuilder::PARAM_* constants.
246
+     *
247
+     * @return $this This QueryBuilder instance.
248
+     * @since 8.2.0
249
+     */
250
+    public function setParameter($key, $value, $type = null);
251
+
252
+    /**
253
+     * Sets a collection of query parameters for the query being constructed.
254
+     *
255
+     * <code>
256
+     *     $qb = $conn->getQueryBuilder()
257
+     *         ->select('u')
258
+     *         ->from('users', 'u')
259
+     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
260
+     *         ->setParameters(array(
261
+     *             ':user_id1' => 1,
262
+     *             ':user_id2' => 2
263
+     *         ));
264
+     * </code>
265
+     *
266
+     * @param array $params The query parameters to set.
267
+     * @param array $types The query parameters types to set.
268
+     *
269
+     * @return $this This QueryBuilder instance.
270
+     * @since 8.2.0
271
+     */
272
+    public function setParameters(array $params, array $types = []);
273
+
274
+    /**
275
+     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
276
+     *
277
+     * @return array The currently defined query parameters indexed by parameter index or name.
278
+     * @since 8.2.0
279
+     */
280
+    public function getParameters();
281
+
282
+    /**
283
+     * Gets a (previously set) query parameter of the query being constructed.
284
+     *
285
+     * @param mixed $key The key (index or name) of the bound parameter.
286
+     *
287
+     * @return mixed The value of the bound parameter.
288
+     * @since 8.2.0
289
+     */
290
+    public function getParameter($key);
291
+
292
+    /**
293
+     * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
294
+     *
295
+     * @return array The currently defined query parameter types indexed by parameter index or name.
296
+     * @since 8.2.0
297
+     */
298
+    public function getParameterTypes();
299
+
300
+    /**
301
+     * Gets a (previously set) query parameter type of the query being constructed.
302
+     *
303
+     * @param mixed $key The key (index or name) of the bound parameter type.
304
+     *
305
+     * @return mixed The value of the bound parameter type.
306
+     * @since 8.2.0
307
+     */
308
+    public function getParameterType($key);
309
+
310
+    /**
311
+     * Sets the position of the first result to retrieve (the "offset").
312
+     *
313
+     * @param int $firstResult The first result to return.
314
+     *
315
+     * @return $this This QueryBuilder instance.
316
+     * @since 8.2.0
317
+     */
318
+    public function setFirstResult($firstResult);
319
+
320
+    /**
321
+     * Gets the position of the first result the query object was set to retrieve (the "offset").
322
+     * Returns 0 if {@link setFirstResult} was not applied to this QueryBuilder.
323
+     *
324
+     * @return int The position of the first result.
325
+     * @since 8.2.0
326
+     */
327
+    public function getFirstResult();
328
+
329
+    /**
330
+     * Sets the maximum number of results to retrieve (the "limit").
331
+     *
332
+     * @param int|null $maxResults The maximum number of results to retrieve.
333
+     *
334
+     * @return $this This QueryBuilder instance.
335
+     * @since 8.2.0
336
+     */
337
+    public function setMaxResults($maxResults);
338
+
339
+    /**
340
+     * Gets the maximum number of results the query object was set to retrieve (the "limit").
341
+     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
342
+     *
343
+     * @return int|null The maximum number of results.
344
+     * @since 8.2.0
345
+     */
346
+    public function getMaxResults();
347
+
348
+    /**
349
+     * Specifies an item that is to be returned in the query result.
350
+     * Replaces any previously specified selections, if any.
351
+     *
352
+     * <code>
353
+     *     $qb = $conn->getQueryBuilder()
354
+     *         ->select('u.id', 'p.id')
355
+     *         ->from('users', 'u')
356
+     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
357
+     * </code>
358
+     *
359
+     * @param mixed ...$selects The selection expressions.
360
+     *
361
+     * @return $this This QueryBuilder instance.
362
+     * @since 8.2.0
363
+     *
364
+     * @psalm-taint-sink sql $selects
365
+     */
366
+    public function select(...$selects);
367
+
368
+    /**
369
+     * Specifies an item that is to be returned with a different name in the query result.
370
+     *
371
+     * <code>
372
+     *     $qb = $conn->getQueryBuilder()
373
+     *         ->selectAlias('u.id', 'user_id')
374
+     *         ->from('users', 'u')
375
+     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
376
+     * </code>
377
+     *
378
+     * @param mixed $select The selection expressions.
379
+     * @param string $alias The column alias used in the constructed query.
380
+     *
381
+     * @return $this This QueryBuilder instance.
382
+     * @since 8.2.1
383
+     *
384
+     * @psalm-taint-sink sql $select
385
+     * @psalm-taint-sink sql $alias
386
+     */
387
+    public function selectAlias($select, $alias);
388
+
389
+    /**
390
+     * Specifies an item that is to be returned uniquely in the query result.
391
+     *
392
+     * <code>
393
+     *     $qb = $conn->getQueryBuilder()
394
+     *         ->selectDistinct('type')
395
+     *         ->from('users');
396
+     * </code>
397
+     *
398
+     * @param mixed $select The selection expressions.
399
+     *
400
+     * @return $this This QueryBuilder instance.
401
+     * @since 9.0.0
402
+     *
403
+     * @psalm-taint-sink sql $select
404
+     */
405
+    public function selectDistinct($select);
406
+
407
+    /**
408
+     * Adds an item that is to be returned in the query result.
409
+     *
410
+     * <code>
411
+     *     $qb = $conn->getQueryBuilder()
412
+     *         ->select('u.id')
413
+     *         ->addSelect('p.id')
414
+     *         ->from('users', 'u')
415
+     *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
416
+     * </code>
417
+     *
418
+     * @param mixed ...$select The selection expression.
419
+     *
420
+     * @return $this This QueryBuilder instance.
421
+     * @since 8.2.0
422
+     *
423
+     * @psalm-taint-sink sql $select
424
+     */
425
+    public function addSelect(...$select);
426
+
427
+    /**
428
+     * Turns the query being built into a bulk delete query that ranges over
429
+     * a certain table.
430
+     *
431
+     * <code>
432
+     *     $qb = $conn->getQueryBuilder()
433
+     *         ->delete('users')
434
+     *         ->where('id = :user_id');
435
+     *         ->setParameter(':user_id', 1);
436
+     * </code>
437
+     *
438
+     * @param string $delete The table whose rows are subject to the deletion.
439
+     * @param string $alias The table alias used in the constructed query.
440
+     *
441
+     * @return $this This QueryBuilder instance.
442
+     * @since 8.2.0
443
+     * @since 30.0.0 Alias is deprecated and will no longer be used with the next Doctrine/DBAL update
444
+     *
445
+     * @psalm-taint-sink sql $delete
446
+     */
447
+    public function delete($delete = null, $alias = null);
448
+
449
+    /**
450
+     * Turns the query being built into a bulk update query that ranges over
451
+     * a certain table
452
+     *
453
+     * <code>
454
+     *     $qb = $conn->getQueryBuilder()
455
+     *         ->update('users')
456
+     *         ->set('email', ':email')
457
+     *         ->where('id = :user_id');
458
+     *         ->setParameter(':user_id', 1);
459
+     * </code>
460
+     *
461
+     * @param string $update The table whose rows are subject to the update.
462
+     * @param string $alias The table alias used in the constructed query.
463
+     *
464
+     * @return $this This QueryBuilder instance.
465
+     * @since 8.2.0
466
+     * @since 30.0.0 Alias is deprecated and will no longer be used with the next Doctrine/DBAL update
467
+     *
468
+     * @psalm-taint-sink sql $update
469
+     */
470
+    public function update($update = null, $alias = null);
471
+
472
+    /**
473
+     * Turns the query being built into an insert query that inserts into
474
+     * a certain table
475
+     *
476
+     * <code>
477
+     *     $qb = $conn->getQueryBuilder()
478
+     *         ->insert('users')
479
+     *         ->values(
480
+     *             array(
481
+     *                 'name' => '?',
482
+     *                 'password' => '?'
483
+     *             )
484
+     *         );
485
+     * </code>
486
+     *
487
+     * @param string $insert The table into which the rows should be inserted.
488
+     *
489
+     * @return $this This QueryBuilder instance.
490
+     * @since 8.2.0
491
+     *
492
+     * @psalm-taint-sink sql $insert
493
+     */
494
+    public function insert($insert = null);
495
+
496
+    /**
497
+     * Creates and adds a query root corresponding to the table identified by the
498
+     * given alias, forming a cartesian product with any existing query roots.
499
+     *
500
+     * <code>
501
+     *     $qb = $conn->getQueryBuilder()
502
+     *         ->select('u.id')
503
+     *         ->from('users', 'u')
504
+     * </code>
505
+     *
506
+     * @param string|IQueryFunction $from The table.
507
+     * @param string|null $alias The alias of the table.
508
+     *
509
+     * @return $this This QueryBuilder instance.
510
+     * @since 8.2.0
511
+     *
512
+     * @psalm-taint-sink sql $from
513
+     */
514
+    public function from($from, $alias = null);
515
+
516
+    /**
517
+     * Creates and adds a join to the query.
518
+     *
519
+     * <code>
520
+     *     $qb = $conn->getQueryBuilder()
521
+     *         ->select('u.name')
522
+     *         ->from('users', 'u')
523
+     *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
524
+     * </code>
525
+     *
526
+     * @param string $fromAlias The alias that points to a from clause.
527
+     * @param string|IQueryFunction $join The table name to join.
528
+     * @param string $alias The alias of the join table.
529
+     * @param string|ICompositeExpression|null $condition The condition for the join.
530
+     *
531
+     * @return $this This QueryBuilder instance.
532
+     * @since 8.2.0
533
+     *
534
+     * @psalm-taint-sink sql $fromAlias
535
+     * @psalm-taint-sink sql $join
536
+     * @psalm-taint-sink sql $alias
537
+     * @psalm-taint-sink sql $condition
538
+     */
539
+    public function join($fromAlias, $join, $alias, $condition = null);
540
+
541
+    /**
542
+     * Creates and adds a join to the query.
543
+     *
544
+     * <code>
545
+     *     $qb = $conn->getQueryBuilder()
546
+     *         ->select('u.name')
547
+     *         ->from('users', 'u')
548
+     *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
549
+     * </code>
550
+     *
551
+     * @param string $fromAlias The alias that points to a from clause.
552
+     * @param string|IQueryFunction $join The table name to join.
553
+     * @param string $alias The alias of the join table.
554
+     * @param string|ICompositeExpression|null $condition The condition for the join.
555
+     *
556
+     * @return $this This QueryBuilder instance.
557
+     * @since 8.2.0
558
+     *
559
+     * @psalm-taint-sink sql $fromAlias
560
+     * @psalm-taint-sink sql $join
561
+     * @psalm-taint-sink sql $alias
562
+     * @psalm-taint-sink sql $condition
563
+     */
564
+    public function innerJoin($fromAlias, $join, $alias, $condition = null);
565
+
566
+    /**
567
+     * Creates and adds a left join to the query.
568
+     *
569
+     * <code>
570
+     *     $qb = $conn->getQueryBuilder()
571
+     *         ->select('u.name')
572
+     *         ->from('users', 'u')
573
+     *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
574
+     * </code>
575
+     *
576
+     * @param string $fromAlias The alias that points to a from clause.
577
+     * @param string|IQueryFunction $join The table name to join.
578
+     * @param string $alias The alias of the join table.
579
+     * @param string|ICompositeExpression|null $condition The condition for the join.
580
+     *
581
+     * @return $this This QueryBuilder instance.
582
+     * @since 8.2.0
583
+     * @since 30.0.0 Allow passing IQueryFunction as parameter for `$join` to allow join with a sub-query.
584
+     *
585
+     * @psalm-taint-sink sql $fromAlias
586
+     * @psalm-taint-sink sql $join
587
+     * @psalm-taint-sink sql $alias
588
+     * @psalm-taint-sink sql $condition
589
+     */
590
+    public function leftJoin($fromAlias, $join, $alias, $condition = null);
591
+
592
+    /**
593
+     * Creates and adds a right join to the query.
594
+     *
595
+     * <code>
596
+     *     $qb = $conn->getQueryBuilder()
597
+     *         ->select('u.name')
598
+     *         ->from('users', 'u')
599
+     *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
600
+     * </code>
601
+     *
602
+     * @param string $fromAlias The alias that points to a from clause.
603
+     * @param string|IQueryFunction $join The table name to join.
604
+     * @param string $alias The alias of the join table.
605
+     * @param string|ICompositeExpression|null $condition The condition for the join.
606
+     *
607
+     * @return $this This QueryBuilder instance.
608
+     * @since 8.2.0
609
+     *
610
+     * @psalm-taint-sink sql $fromAlias
611
+     * @psalm-taint-sink sql $join
612
+     * @psalm-taint-sink sql $alias
613
+     * @psalm-taint-sink sql $condition
614
+     */
615
+    public function rightJoin($fromAlias, $join, $alias, $condition = null);
616
+
617
+    /**
618
+     * Sets a new value for a column in a bulk update query.
619
+     *
620
+     * <code>
621
+     *     $qb = $conn->getQueryBuilder()
622
+     *         ->update('users', 'u')
623
+     *         ->set('u.password', md5('password'))
624
+     *         ->where('u.id = ?');
625
+     * </code>
626
+     *
627
+     * @param string $key The column to set.
628
+     * @param ILiteral|IParameter|IQueryFunction|string $value The value, expression, placeholder, etc.
629
+     *
630
+     * @return $this This QueryBuilder instance.
631
+     * @since 8.2.0
632
+     *
633
+     * @psalm-taint-sink sql $key
634
+     * @psalm-taint-sink sql $value
635
+     */
636
+    public function set($key, $value);
637
+
638
+    /**
639
+     * Specifies one or more restrictions to the query result.
640
+     * Replaces any previously specified restrictions, if any.
641
+     *
642
+     * <code>
643
+     *     $qb = $conn->getQueryBuilder()
644
+     *         ->select('u.name')
645
+     *         ->from('users', 'u')
646
+     *         ->where('u.id = ?');
647
+     *
648
+     *     // You can optionally programmatically build and/or expressions
649
+     *     $qb = $conn->getQueryBuilder();
650
+     *
651
+     *     $or = $qb->expr()->orx(
652
+     *         $qb->expr()->eq('u.id', 1),
653
+     *         $qb->expr()->eq('u.id', 2),
654
+     *     );
655
+     *
656
+     *     $qb->update('users', 'u')
657
+     *         ->set('u.password', md5('password'))
658
+     *         ->where($or);
659
+     * </code>
660
+     *
661
+     * @param mixed $predicates The restriction predicates.
662
+     *
663
+     * @return $this This QueryBuilder instance.
664
+     * @since 8.2.0
665
+     *
666
+     * @psalm-taint-sink sql $predicates
667
+     */
668
+    public function where(...$predicates);
669
+
670
+    /**
671
+     * Adds one or more restrictions to the query results, forming a logical
672
+     * conjunction with any previously specified restrictions.
673
+     *
674
+     * <code>
675
+     *     $qb = $conn->getQueryBuilder()
676
+     *         ->select('u')
677
+     *         ->from('users', 'u')
678
+     *         ->where('u.username LIKE ?')
679
+     *         ->andWhere('u.is_active = 1');
680
+     * </code>
681
+     *
682
+     * @param mixed ...$where The query restrictions.
683
+     *
684
+     * @return $this This QueryBuilder instance.
685
+     *
686
+     * @see where()
687
+     * @since 8.2.0
688
+     *
689
+     * @psalm-taint-sink sql $where
690
+     */
691
+    public function andWhere(...$where);
692
+
693
+    /**
694
+     * Adds one or more restrictions to the query results, forming a logical
695
+     * disjunction with any previously specified restrictions.
696
+     *
697
+     * <code>
698
+     *     $qb = $conn->getQueryBuilder()
699
+     *         ->select('u.name')
700
+     *         ->from('users', 'u')
701
+     *         ->where('u.id = 1')
702
+     *         ->orWhere('u.id = 2');
703
+     * </code>
704
+     *
705
+     * @param mixed ...$where The WHERE statement.
706
+     *
707
+     * @return $this This QueryBuilder instance.
708
+     *
709
+     * @see where()
710
+     * @since 8.2.0
711
+     *
712
+     * @psalm-taint-sink sql $where
713
+     */
714
+    public function orWhere(...$where);
715
+
716
+    /**
717
+     * Specifies a grouping over the results of the query.
718
+     * Replaces any previously specified groupings, if any.
719
+     *
720
+     * <code>
721
+     *     $qb = $conn->getQueryBuilder()
722
+     *         ->select('u.name')
723
+     *         ->from('users', 'u')
724
+     *         ->groupBy('u.id');
725
+     * </code>
726
+     *
727
+     * @param mixed ...$groupBys The grouping expression.
728
+     *
729
+     * @return $this This QueryBuilder instance.
730
+     * @since 8.2.0
731
+     *
732
+     * @psalm-taint-sink sql $groupBys
733
+     */
734
+    public function groupBy(...$groupBys);
735
+
736
+    /**
737
+     * Adds a grouping expression to the query.
738
+     *
739
+     * <code>
740
+     *     $qb = $conn->getQueryBuilder()
741
+     *         ->select('u.name')
742
+     *         ->from('users', 'u')
743
+     *         ->groupBy('u.lastLogin');
744
+     *         ->addGroupBy('u.createdAt')
745
+     * </code>
746
+     *
747
+     * @param mixed ...$groupBy The grouping expression.
748
+     *
749
+     * @return $this This QueryBuilder instance.
750
+     * @since 8.2.0
751
+     *
752
+     * @psalm-taint-sink sql $groupby
753
+     */
754
+    public function addGroupBy(...$groupBy);
755
+
756
+    /**
757
+     * Sets a value for a column in an insert query.
758
+     *
759
+     * <code>
760
+     *     $qb = $conn->getQueryBuilder()
761
+     *         ->insert('users')
762
+     *         ->values(
763
+     *             array(
764
+     *                 'name' => '?'
765
+     *             )
766
+     *         )
767
+     *         ->setValue('password', '?');
768
+     * </code>
769
+     *
770
+     * @param string $column The column into which the value should be inserted.
771
+     * @param IParameter|string $value The value that should be inserted into the column.
772
+     *
773
+     * @return $this This QueryBuilder instance.
774
+     * @since 8.2.0
775
+     *
776
+     * @psalm-taint-sink sql $column
777
+     * @psalm-taint-sink sql $value
778
+     */
779
+    public function setValue($column, $value);
780
+
781
+    /**
782
+     * Specifies values for an insert query indexed by column names.
783
+     * Replaces any previous values, if any.
784
+     *
785
+     * <code>
786
+     *     $qb = $conn->getQueryBuilder()
787
+     *         ->insert('users')
788
+     *         ->values(
789
+     *             array(
790
+     *                 'name' => '?',
791
+     *                 'password' => '?'
792
+     *             )
793
+     *         );
794
+     * </code>
795
+     *
796
+     * @param array $values The values to specify for the insert query indexed by column names.
797
+     *
798
+     * @return $this This QueryBuilder instance.
799
+     * @since 8.2.0
800
+     *
801
+     * @psalm-taint-sink sql $values
802
+     */
803
+    public function values(array $values);
804
+
805
+    /**
806
+     * Specifies a restriction over the groups of the query.
807
+     * Replaces any previous having restrictions, if any.
808
+     *
809
+     * @param mixed ...$having The restriction over the groups.
810
+     *
811
+     * @return $this This QueryBuilder instance.
812
+     * @since 8.2.0
813
+     *
814
+     * @psalm-taint-sink sql $having
815
+     */
816
+    public function having(...$having);
817
+
818
+    /**
819
+     * Adds a restriction over the groups of the query, forming a logical
820
+     * conjunction with any existing having restrictions.
821
+     *
822
+     * @param mixed ...$having The restriction to append.
823
+     *
824
+     * @return $this This QueryBuilder instance.
825
+     * @since 8.2.0
826
+     *
827
+     * @psalm-taint-sink sql $andHaving
828
+     */
829
+    public function andHaving(...$having);
830
+
831
+    /**
832
+     * Adds a restriction over the groups of the query, forming a logical
833
+     * disjunction with any existing having restrictions.
834
+     *
835
+     * @param mixed ...$having The restriction to add.
836
+     *
837
+     * @return $this This QueryBuilder instance.
838
+     * @since 8.2.0
839
+     *
840
+     * @psalm-taint-sink sql $having
841
+     */
842
+    public function orHaving(...$having);
843
+
844
+    /**
845
+     * Specifies an ordering for the query results.
846
+     * Replaces any previously specified orderings, if any.
847
+     *
848
+     * @param string|IQueryFunction|ILiteral|IParameter $sort The ordering expression.
849
+     * @param string $order The ordering direction.
850
+     *
851
+     * @return $this This QueryBuilder instance.
852
+     * @since 8.2.0
853
+     *
854
+     * @psalm-taint-sink sql $sort
855
+     * @psalm-taint-sink sql $order
856
+     */
857
+    public function orderBy($sort, $order = null);
858
+
859
+    /**
860
+     * Adds an ordering to the query results.
861
+     *
862
+     * @param string|ILiteral|IParameter|IQueryFunction $sort The ordering expression.
863
+     * @param string $order The ordering direction.
864
+     *
865
+     * @return $this This QueryBuilder instance.
866
+     * @since 8.2.0
867
+     *
868
+     * @psalm-taint-sink sql $sort
869
+     * @psalm-taint-sink sql $order
870
+     */
871
+    public function addOrderBy($sort, $order = null);
872
+
873
+    /**
874
+     * Gets a query part by its name.
875
+     *
876
+     * @param string $queryPartName
877
+     *
878
+     * @return mixed
879
+     * @since 8.2.0
880
+     * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update
881
+     *  and we can not fix this in our wrapper. Please track the details you need, outside the object.
882
+     */
883
+    public function getQueryPart($queryPartName);
884
+
885
+    /**
886
+     * Gets all query parts.
887
+     *
888
+     * @return array
889
+     * @since 8.2.0
890
+     * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update
891
+     *  and we can not fix this in our wrapper. Please track the details you need, outside the object.
892
+     */
893
+    public function getQueryParts();
894
+
895
+    /**
896
+     * Resets SQL parts.
897
+     *
898
+     * @param array|null $queryPartNames
899
+     *
900
+     * @return $this This QueryBuilder instance.
901
+     * @since 8.2.0
902
+     * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update
903
+     * and we can not fix this in our wrapper. Please create a new IQueryBuilder instead.
904
+     */
905
+    public function resetQueryParts($queryPartNames = null);
906
+
907
+    /**
908
+     * Resets a single SQL part.
909
+     *
910
+     * @param string $queryPartName
911
+     *
912
+     * @return $this This QueryBuilder instance.
913
+     * @since 8.2.0
914
+     * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update
915
+     *  and we can not fix this in our wrapper. Please create a new IQueryBuilder instead.
916
+     */
917
+    public function resetQueryPart($queryPartName);
918
+
919
+    /**
920
+     * Creates a new named parameter and bind the value $value to it.
921
+     *
922
+     * This method provides a shortcut for PDOStatement::bindValue
923
+     * when using prepared statements.
924
+     *
925
+     * The parameter $value specifies the value that you want to bind. If
926
+     * $placeholder is not provided bindValue() will automatically create a
927
+     * placeholder for you. An automatic placeholder will be of the name
928
+     * ':dcValue1', ':dcValue2' etc.
929
+     *
930
+     * For more information see {@link https://www.php.net/pdostatement-bindparam}
931
+     *
932
+     * Example:
933
+     * <code>
934
+     * $value = 2;
935
+     * $q->eq( 'id', $q->bindValue( $value ) );
936
+     * $stmt = $q->executeQuery(); // executed with 'id = 2'
937
+     * </code>
938
+     *
939
+     * @license New BSD License
940
+     * @link http://www.zetacomponents.org
941
+     *
942
+     * @param mixed $value
943
+     * @param self::PARAM_* $type
944
+     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
945
+     *
946
+     * @return IParameter
947
+     * @since 8.2.0
948
+     *
949
+     * @psalm-taint-escape sql
950
+     */
951
+    public function createNamedParameter($value, $type = self::PARAM_STR, $placeHolder = null);
952
+
953
+    /**
954
+     * Creates a new positional parameter and bind the given value to it.
955
+     *
956
+     * Attention: If you are using positional parameters with the query builder you have
957
+     * to be very careful to bind all parameters in the order they appear in the SQL
958
+     * statement , otherwise they get bound in the wrong order which can lead to serious
959
+     * bugs in your code.
960
+     *
961
+     * Example:
962
+     * <code>
963
+     *  $qb = $conn->getQueryBuilder();
964
+     *  $qb->select('u.*')
965
+     *     ->from('users', 'u')
966
+     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', IQueryBuilder::PARAM_STR))
967
+     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', IQueryBuilder::PARAM_STR))
968
+     * </code>
969
+     *
970
+     * @param mixed $value
971
+     * @param self::PARAM_* $type
972
+     *
973
+     * @return IParameter
974
+     * @since 8.2.0
975
+     *
976
+     * @psalm-taint-escape sql
977
+     */
978
+    public function createPositionalParameter($value, $type = self::PARAM_STR);
979
+
980
+    /**
981
+     * Creates a new parameter
982
+     *
983
+     * Example:
984
+     * <code>
985
+     *  $qb = $conn->getQueryBuilder();
986
+     *  $qb->select('u.*')
987
+     *     ->from('users', 'u')
988
+     *     ->where('u.username = ' . $qb->createParameter('name'))
989
+     *     ->setParameter('name', 'Bar', IQueryBuilder::PARAM_STR))
990
+     * </code>
991
+     *
992
+     * @param string $name
993
+     *
994
+     * @return IParameter
995
+     * @since 8.2.0
996
+     *
997
+     * @psalm-taint-escape sql
998
+     */
999
+    public function createParameter($name);
1000
+
1001
+    /**
1002
+     * Creates a new function
1003
+     *
1004
+     * Attention: Column names inside the call have to be quoted before hand
1005
+     *
1006
+     * Example:
1007
+     * <code>
1008
+     *  $qb = $conn->getQueryBuilder();
1009
+     *  $qb->select($qb->createFunction('COUNT(*)'))
1010
+     *     ->from('users', 'u')
1011
+     *  echo $qb->getSQL(); // SELECT COUNT(*) FROM `users` u
1012
+     * </code>
1013
+     * <code>
1014
+     *  $qb = $conn->getQueryBuilder();
1015
+     *  $qb->select($qb->createFunction('COUNT(`column`)'))
1016
+     *     ->from('users', 'u')
1017
+     *  echo $qb->getSQL(); // SELECT COUNT(`column`) FROM `users` u
1018
+     * </code>
1019
+     *
1020
+     * @param string $call
1021
+     *
1022
+     * @return IQueryFunction
1023
+     * @since 8.2.0
1024
+     *
1025
+     * @psalm-taint-sink sql $call
1026
+     */
1027
+    public function createFunction($call);
1028
+
1029
+    /**
1030
+     * Used to get the id of the last inserted element
1031
+     * @return int
1032
+     * @throws \BadMethodCallException When being called before an insert query has been run.
1033
+     * @since 9.0.0
1034
+     */
1035
+    public function getLastInsertId(): int;
1036
+
1037
+    /**
1038
+     * Returns the table name quoted and with database prefix as needed by the implementation.
1039
+     * If a query function is passed the function is casted to string,
1040
+     * this allows passing functions as sub-queries for join expression.
1041
+     *
1042
+     * @param string|IQueryFunction $table
1043
+     * @return string
1044
+     * @since 9.0.0
1045
+     * @since 24.0.0 accepts IQueryFunction as parameter
1046
+     */
1047
+    public function getTableName($table);
1048
+
1049
+    /**
1050
+     * Returns the table name with database prefix as needed by the implementation
1051
+     *
1052
+     * @param string $table
1053
+     * @return string
1054
+     * @since 30.0.0
1055
+     */
1056
+    public function prefixTableName(string $table): string;
1057
+
1058
+    /**
1059
+     * Returns the column name quoted and with table alias prefix as needed by the implementation
1060
+     *
1061
+     * @param string $column
1062
+     * @param string $tableAlias
1063
+     * @return string
1064
+     * @since 9.0.0
1065
+     */
1066
+    public function getColumnName($column, $tableAlias = '');
1067
+
1068
+    /**
1069
+     * Provide a hint for the shard key for queries where this can't be detected otherwise
1070
+     *
1071
+     * @param string $column
1072
+     * @param mixed $value
1073
+     * @return $this
1074
+     * @since 30.0.0
1075
+     */
1076
+    public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self;
1077
+
1078
+    /**
1079
+     * Set the query to run across all shards if sharding is enabled.
1080
+     *
1081
+     * @return $this
1082
+     * @since 30.0.0
1083
+     */
1084
+    public function runAcrossAllShards(): self;
1085
+
1086
+    /**
1087
+     * Get a list of column names that are expected in the query output
1088
+     *
1089
+     * @return array
1090
+     * @since 30.0.0
1091
+     */
1092
+    public function getOutputColumns(): array;
1093 1093
 }
Please login to merge, or discard this patch.
lib/private/Preview/PreviewService.php 2 patches
Indentation   +107 added lines, -107 removed lines patch added patch discarded remove patch
@@ -16,111 +16,111 @@
 block discarded – undo
16 16
 use OCP\IDBConnection;
17 17
 
18 18
 class PreviewService {
19
-	public function __construct(
20
-		private readonly StorageFactory $storageFactory,
21
-		private readonly PreviewMapper $previewMapper,
22
-		private readonly IDBConnection $connection,
23
-	) {
24
-	}
25
-
26
-	public function deletePreview(Preview $preview): void {
27
-		$this->storageFactory->deletePreview($preview);
28
-		$this->previewMapper->delete($preview);
29
-	}
30
-
31
-	/**
32
-	 * Get storageId and fileIds for which we have at least one preview.
33
-	 *
34
-	 * @return \Generator<array{storageId: int, fileIds: int[]}>
35
-	 */
36
-	public function getAvailableFileIds(): \Generator {
37
-		$lastId = null;
38
-		while (true) {
39
-			$maxQb = $this->connection->getQueryBuilder();
40
-			$maxQb->selectAlias($maxQb->func()->max('id'), 'max_id')
41
-				->from($this->previewMapper->getTableName())
42
-				->groupBy('file_id')
43
-				->orderBy('max_id', 'ASC');
44
-
45
-			$qb = $this->connection->getQueryBuilder();
46
-
47
-			if ($lastId !== null) {
48
-				$maxQb->andWhere($maxQb->expr()->gt('id', $qb->createNamedParameter($lastId)));
49
-			}
50
-
51
-			$qb->select('id', 'file_id', 'storage_id')
52
-				->from($this->previewMapper->getTableName(), 'p1')
53
-				->innerJoin('p1', $qb->createFunction('(' . $maxQb->getSQL() . ')'), 'p2', $qb->expr()->eq('p1.id', 'p2.max_id'))
54
-				->setMaxResults(1000);
55
-
56
-			$result = $qb->executeQuery();
57
-
58
-			$lastStorageId = -1;
59
-			/** @var int[] $fileIds */
60
-			$fileIds = [];
61
-
62
-			$found = false;
63
-			// Previews next to each others in the database are likely in the same storage, so group them
64
-			while ($row = $result->fetch()) {
65
-				$found = true;
66
-				if ($lastStorageId !== (int)$row['storage_id']) {
67
-					if ($lastStorageId !== -1) {
68
-						yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
69
-						$fileIds = [];
70
-					}
71
-					$lastStorageId = (int)$row['storage_id'];
72
-				}
73
-				$fileIds[] = (int)$row['file_id'];
74
-				$lastId = $row['id'];
75
-			}
76
-
77
-			if (count($fileIds) > 0) {
78
-				yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
79
-			}
80
-
81
-			if (!$found) {
82
-				break;
83
-			}
84
-		}
85
-	}
86
-
87
-	/**
88
-	 * @return \Generator<Preview>
89
-	 */
90
-	public function getAvailablePreviewsForFile(int $fileId): \Generator {
91
-		return $this->previewMapper->getAvailablePreviewsForFile($fileId);
92
-	}
93
-
94
-	/**
95
-	 * @param string[] $mimeTypes
96
-	 * @return \Generator<Preview>
97
-	 */
98
-	public function getPreviewsForMimeTypes(array $mimeTypes): \Generator {
99
-		return $this->previewMapper->getPreviewsForMimeTypes($mimeTypes);
100
-	}
101
-
102
-	public function deleteAll(): void {
103
-		$lastId = 0;
104
-		while (true) {
105
-			$previews = $this->previewMapper->getPreviews($lastId, 1000);
106
-			$i = 0;
107
-			foreach ($previews as $preview) {
108
-				$this->deletePreview($preview);
109
-				$i++;
110
-				$lastId = $preview->getId();
111
-			}
112
-
113
-			if ($i !== 1000) {
114
-				break;
115
-			}
116
-		}
117
-	}
118
-
119
-	/**
120
-	 * @param int[] $fileIds
121
-	 * @return array<int, Preview[]>
122
-	 */
123
-	public function getAvailablePreviews(array $fileIds): array {
124
-		return $this->previewMapper->getAvailablePreviews($fileIds);
125
-	}
19
+    public function __construct(
20
+        private readonly StorageFactory $storageFactory,
21
+        private readonly PreviewMapper $previewMapper,
22
+        private readonly IDBConnection $connection,
23
+    ) {
24
+    }
25
+
26
+    public function deletePreview(Preview $preview): void {
27
+        $this->storageFactory->deletePreview($preview);
28
+        $this->previewMapper->delete($preview);
29
+    }
30
+
31
+    /**
32
+     * Get storageId and fileIds for which we have at least one preview.
33
+     *
34
+     * @return \Generator<array{storageId: int, fileIds: int[]}>
35
+     */
36
+    public function getAvailableFileIds(): \Generator {
37
+        $lastId = null;
38
+        while (true) {
39
+            $maxQb = $this->connection->getQueryBuilder();
40
+            $maxQb->selectAlias($maxQb->func()->max('id'), 'max_id')
41
+                ->from($this->previewMapper->getTableName())
42
+                ->groupBy('file_id')
43
+                ->orderBy('max_id', 'ASC');
44
+
45
+            $qb = $this->connection->getQueryBuilder();
46
+
47
+            if ($lastId !== null) {
48
+                $maxQb->andWhere($maxQb->expr()->gt('id', $qb->createNamedParameter($lastId)));
49
+            }
50
+
51
+            $qb->select('id', 'file_id', 'storage_id')
52
+                ->from($this->previewMapper->getTableName(), 'p1')
53
+                ->innerJoin('p1', $qb->createFunction('(' . $maxQb->getSQL() . ')'), 'p2', $qb->expr()->eq('p1.id', 'p2.max_id'))
54
+                ->setMaxResults(1000);
55
+
56
+            $result = $qb->executeQuery();
57
+
58
+            $lastStorageId = -1;
59
+            /** @var int[] $fileIds */
60
+            $fileIds = [];
61
+
62
+            $found = false;
63
+            // Previews next to each others in the database are likely in the same storage, so group them
64
+            while ($row = $result->fetch()) {
65
+                $found = true;
66
+                if ($lastStorageId !== (int)$row['storage_id']) {
67
+                    if ($lastStorageId !== -1) {
68
+                        yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
69
+                        $fileIds = [];
70
+                    }
71
+                    $lastStorageId = (int)$row['storage_id'];
72
+                }
73
+                $fileIds[] = (int)$row['file_id'];
74
+                $lastId = $row['id'];
75
+            }
76
+
77
+            if (count($fileIds) > 0) {
78
+                yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
79
+            }
80
+
81
+            if (!$found) {
82
+                break;
83
+            }
84
+        }
85
+    }
86
+
87
+    /**
88
+     * @return \Generator<Preview>
89
+     */
90
+    public function getAvailablePreviewsForFile(int $fileId): \Generator {
91
+        return $this->previewMapper->getAvailablePreviewsForFile($fileId);
92
+    }
93
+
94
+    /**
95
+     * @param string[] $mimeTypes
96
+     * @return \Generator<Preview>
97
+     */
98
+    public function getPreviewsForMimeTypes(array $mimeTypes): \Generator {
99
+        return $this->previewMapper->getPreviewsForMimeTypes($mimeTypes);
100
+    }
101
+
102
+    public function deleteAll(): void {
103
+        $lastId = 0;
104
+        while (true) {
105
+            $previews = $this->previewMapper->getPreviews($lastId, 1000);
106
+            $i = 0;
107
+            foreach ($previews as $preview) {
108
+                $this->deletePreview($preview);
109
+                $i++;
110
+                $lastId = $preview->getId();
111
+            }
112
+
113
+            if ($i !== 1000) {
114
+                break;
115
+            }
116
+        }
117
+    }
118
+
119
+    /**
120
+     * @param int[] $fileIds
121
+     * @return array<int, Preview[]>
122
+     */
123
+    public function getAvailablePreviews(array $fileIds): array {
124
+        return $this->previewMapper->getAvailablePreviews($fileIds);
125
+    }
126 126
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -50,7 +50,7 @@  discard block
 block discarded – undo
50 50
 
51 51
 			$qb->select('id', 'file_id', 'storage_id')
52 52
 				->from($this->previewMapper->getTableName(), 'p1')
53
-				->innerJoin('p1', $qb->createFunction('(' . $maxQb->getSQL() . ')'), 'p2', $qb->expr()->eq('p1.id', 'p2.max_id'))
53
+				->innerJoin('p1', $qb->createFunction('('.$maxQb->getSQL().')'), 'p2', $qb->expr()->eq('p1.id', 'p2.max_id'))
54 54
 				->setMaxResults(1000);
55 55
 
56 56
 			$result = $qb->executeQuery();
@@ -63,14 +63,14 @@  discard block
 block discarded – undo
63 63
 			// Previews next to each others in the database are likely in the same storage, so group them
64 64
 			while ($row = $result->fetch()) {
65 65
 				$found = true;
66
-				if ($lastStorageId !== (int)$row['storage_id']) {
66
+				if ($lastStorageId !== (int) $row['storage_id']) {
67 67
 					if ($lastStorageId !== -1) {
68 68
 						yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
69 69
 						$fileIds = [];
70 70
 					}
71
-					$lastStorageId = (int)$row['storage_id'];
71
+					$lastStorageId = (int) $row['storage_id'];
72 72
 				}
73
-				$fileIds[] = (int)$row['file_id'];
73
+				$fileIds[] = (int) $row['file_id'];
74 74
 				$lastId = $row['id'];
75 75
 			}
76 76
 
Please login to merge, or discard this patch.
lib/private/DB/QueryBuilder/Sharded/ShardedQueryBuilder.php 1 patch
Indentation   +381 added lines, -381 removed lines patch added patch discarded remove patch
@@ -25,387 +25,387 @@
 block discarded – undo
25 25
  * from the query. The logic for actually running the query across the shards is mostly delegated to `ShardQueryRunner`.
26 26
  */
27 27
 class ShardedQueryBuilder extends ExtendedQueryBuilder {
28
-	private array $shardKeys = [];
29
-	private array $primaryKeys = [];
30
-	private ?ShardDefinition $shardDefinition = null;
31
-	/** @var bool Run the query across all shards */
32
-	private bool $allShards = false;
33
-	private ?string $insertTable = null;
34
-	private mixed $lastInsertId = null;
35
-	private ?IDBConnection $lastInsertConnection = null;
36
-	private ?int $updateShardKey = null;
37
-	private ?int $limit = null;
38
-	private ?int $offset = null;
39
-	/** @var array{column: string, order: string}[] */
40
-	private array $sortList = [];
41
-	private string $mainTable = '';
42
-
43
-	public function __construct(
44
-		IQueryBuilder $builder,
45
-		protected array $shardDefinitions,
46
-		protected ShardConnectionManager $shardConnectionManager,
47
-		protected AutoIncrementHandler $autoIncrementHandler,
48
-	) {
49
-		parent::__construct($builder);
50
-	}
51
-
52
-	public function getShardKeys(): array {
53
-		return $this->getKeyValues($this->shardKeys);
54
-	}
55
-
56
-	public function getPrimaryKeys(): array {
57
-		return $this->getKeyValues($this->primaryKeys);
58
-	}
59
-
60
-	private function getKeyValues(array $keys): array {
61
-		$values = [];
62
-		foreach ($keys as $key) {
63
-			$values = array_merge($values, $this->getKeyValue($key));
64
-		}
65
-		return array_values(array_unique($values));
66
-	}
67
-
68
-	private function getKeyValue($value): array {
69
-		if ($value instanceof Parameter) {
70
-			$value = (string)$value;
71
-		}
72
-		if (is_string($value) && str_starts_with($value, ':')) {
73
-			$param = $this->getParameter(substr($value, 1));
74
-			if (is_array($param)) {
75
-				return $param;
76
-			} else {
77
-				return [$param];
78
-			}
79
-		} elseif ($value !== null) {
80
-			return [$value];
81
-		} else {
82
-			return [];
83
-		}
84
-	}
85
-
86
-	public function where(...$predicates) {
87
-		return $this->andWhere(...$predicates);
88
-	}
89
-
90
-	public function andWhere(...$where) {
91
-		if ($where) {
92
-			foreach ($where as $predicate) {
93
-				$this->tryLoadShardKey($predicate);
94
-			}
95
-			parent::andWhere(...$where);
96
-		}
97
-		return $this;
98
-	}
99
-
100
-	private function tryLoadShardKey($predicate): void {
101
-		if (!$this->shardDefinition) {
102
-			return;
103
-		}
104
-		if ($keys = $this->tryExtractShardKeys($predicate, $this->shardDefinition->shardKey)) {
105
-			$this->shardKeys += $keys;
106
-		}
107
-		if ($keys = $this->tryExtractShardKeys($predicate, $this->shardDefinition->primaryKey)) {
108
-			$this->primaryKeys += $keys;
109
-		}
110
-		foreach ($this->shardDefinition->companionKeys as $companionKey) {
111
-			if ($keys = $this->tryExtractShardKeys($predicate, $companionKey)) {
112
-				$this->primaryKeys += $keys;
113
-			}
114
-		}
115
-	}
116
-
117
-	/**
118
-	 * @param $predicate
119
-	 * @param string $column
120
-	 * @return string[]
121
-	 */
122
-	private function tryExtractShardKeys($predicate, string $column): array {
123
-		if ($predicate instanceof CompositeExpression) {
124
-			$values = [];
125
-			foreach ($predicate->getParts() as $part) {
126
-				$partValues = $this->tryExtractShardKeys($part, $column);
127
-				// for OR expressions, we can only rely on the predicate if all parts contain the comparison
128
-				if ($predicate->getType() === CompositeExpression::TYPE_OR && !$partValues) {
129
-					return [];
130
-				}
131
-				$values = array_merge($values, $partValues);
132
-			}
133
-			return $values;
134
-		}
135
-		$predicate = (string)$predicate;
136
-		// expect a condition in the form of 'alias1.column1 = placeholder' or 'alias1.column1 in placeholder'
137
-		if (substr_count($predicate, ' ') > 2) {
138
-			return [];
139
-		}
140
-		if (str_contains($predicate, ' = ')) {
141
-			$parts = explode(' = ', $predicate);
142
-			if ($parts[0] === "`{$column}`" || str_ends_with($parts[0], "`.`{$column}`")) {
143
-				return [$parts[1]];
144
-			} else {
145
-				return [];
146
-			}
147
-		}
148
-
149
-		if (str_contains($predicate, ' IN ')) {
150
-			$parts = explode(' IN ', $predicate);
151
-			if ($parts[0] === "`{$column}`" || str_ends_with($parts[0], "`.`{$column}`")) {
152
-				return [trim(trim($parts[1], '('), ')')];
153
-			} else {
154
-				return [];
155
-			}
156
-		}
157
-
158
-		return [];
159
-	}
160
-
161
-	public function set($key, $value) {
162
-		if ($this->shardDefinition && $key === $this->shardDefinition->shardKey) {
163
-			$updateShardKey = $value;
164
-		}
165
-		return parent::set($key, $value);
166
-	}
167
-
168
-	public function setValue($column, $value) {
169
-		if ($this->shardDefinition) {
170
-			if ($this->shardDefinition->isKey($column)) {
171
-				$this->primaryKeys[] = $value;
172
-			}
173
-			if ($column === $this->shardDefinition->shardKey) {
174
-				$this->shardKeys[] = $value;
175
-			}
176
-		}
177
-		return parent::setValue($column, $value);
178
-	}
179
-
180
-	public function values(array $values) {
181
-		foreach ($values as $column => $value) {
182
-			$this->setValue($column, $value);
183
-		}
184
-		return $this;
185
-	}
186
-
187
-	private function actOnTable(string $table): void {
188
-		$this->mainTable = $table;
189
-		foreach ($this->shardDefinitions as $shardDefinition) {
190
-			if ($shardDefinition->hasTable($table)) {
191
-				$this->shardDefinition = $shardDefinition;
192
-			}
193
-		}
194
-	}
195
-
196
-	public function from($from, $alias = null) {
197
-		if (is_string($from) && $from) {
198
-			$this->actOnTable($from);
199
-		}
200
-		return parent::from($from, $alias);
201
-	}
202
-
203
-	public function update($update = null, $alias = null) {
204
-		if (is_string($update) && $update) {
205
-			$this->actOnTable($update);
206
-		}
207
-		return parent::update($update, $alias);
208
-	}
209
-
210
-	public function insert($insert = null) {
211
-		if (is_string($insert) && $insert) {
212
-			$this->insertTable = $insert;
213
-			$this->actOnTable($insert);
214
-		}
215
-		return parent::insert($insert);
216
-	}
217
-
218
-	public function delete($delete = null, $alias = null) {
219
-		if (is_string($delete) && $delete) {
220
-			$this->actOnTable($delete);
221
-		}
222
-		return parent::delete($delete, $alias);
223
-	}
224
-
225
-	private function checkJoin(string $table): void {
226
-		if ($this->shardDefinition) {
227
-			if ($table === $this->mainTable) {
228
-				throw new InvalidShardedQueryException("Sharded query on {$this->mainTable} isn't allowed to join on itself");
229
-			}
230
-			if (!$this->shardDefinition->hasTable($table)) {
231
-				// this generally shouldn't happen as the partitioning logic should prevent this
232
-				// but the check is here just in case
233
-				throw new InvalidShardedQueryException("Sharded query on {$this->shardDefinition->table} isn't allowed to join on $table");
234
-			}
235
-		}
236
-	}
237
-
238
-	public function innerJoin($fromAlias, $join, $alias, $condition = null) {
239
-		if (is_string($join)) {
240
-			$this->checkJoin($join);
241
-		}
242
-		return parent::innerJoin($fromAlias, $join, $alias, $condition);
243
-	}
244
-
245
-	public function leftJoin($fromAlias, $join, $alias, $condition = null) {
246
-		if (is_string($join)) {
247
-			$this->checkJoin($join);
248
-		}
249
-		return parent::leftJoin($fromAlias, $join, $alias, $condition);
250
-	}
251
-
252
-	public function rightJoin($fromAlias, $join, $alias, $condition = null) {
253
-		if ($this->shardDefinition) {
254
-			throw new InvalidShardedQueryException("Sharded query on {$this->shardDefinition->table} isn't allowed to right join");
255
-		}
256
-		return parent::rightJoin($fromAlias, $join, $alias, $condition);
257
-	}
258
-
259
-	public function join($fromAlias, $join, $alias, $condition = null) {
260
-		return $this->innerJoin($fromAlias, $join, $alias, $condition);
261
-	}
262
-
263
-	public function setMaxResults($maxResults) {
264
-		if ($maxResults > 0) {
265
-			$this->limit = (int)$maxResults;
266
-		}
267
-		return parent::setMaxResults($maxResults);
268
-	}
269
-
270
-	public function setFirstResult($firstResult) {
271
-		if ($firstResult > 0) {
272
-			$this->offset = (int)$firstResult;
273
-		}
274
-		if ($this->shardDefinition && count($this->shardDefinition->shards) > 1) {
275
-			// we have to emulate offset
276
-			return $this;
277
-		} else {
278
-			return parent::setFirstResult($firstResult);
279
-		}
280
-	}
281
-
282
-	public function addOrderBy($sort, $order = null) {
283
-		$this->registerOrder((string)$sort, (string)$order ?? 'ASC');
284
-		return parent::addOrderBy($sort, $order);
285
-	}
286
-
287
-	public function orderBy($sort, $order = null) {
288
-		$this->sortList = [];
289
-		$this->registerOrder((string)$sort, (string)$order ?? 'ASC');
290
-		return parent::orderBy($sort, $order);
291
-	}
292
-
293
-	private function registerOrder(string $column, string $order): void {
294
-		// handle `mime + 0` and similar by just sorting on the first part of the expression
295
-		[$column] = explode(' ', $column);
296
-		$column = trim($column, '`');
297
-		$this->sortList[] = [
298
-			'column' => $column,
299
-			'order' => strtoupper($order),
300
-		];
301
-	}
302
-
303
-	public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self {
304
-		if ($overwrite) {
305
-			$this->primaryKeys = [];
306
-			$this->shardKeys = [];
307
-		}
308
-		if ($this->shardDefinition?->isKey($column)) {
309
-			$this->primaryKeys[] = $value;
310
-		}
311
-		if ($column === $this->shardDefinition?->shardKey) {
312
-			$this->shardKeys[] = $value;
313
-		}
314
-		return $this;
315
-	}
316
-
317
-	public function runAcrossAllShards(): self {
318
-		$this->allShards = true;
319
-		return $this;
320
-	}
321
-
322
-	/**
323
-	 * @throws InvalidShardedQueryException
324
-	 */
325
-	public function validate(): void {
326
-		if ($this->shardDefinition && $this->insertTable) {
327
-			if ($this->allShards) {
328
-				throw new InvalidShardedQueryException("Can't insert across all shards");
329
-			}
330
-			if (empty($this->getShardKeys())) {
331
-				throw new InvalidShardedQueryException("Can't insert without shard key");
332
-			}
333
-		}
334
-		if ($this->shardDefinition && !$this->allShards) {
335
-			if (empty($this->getShardKeys()) && empty($this->getPrimaryKeys())) {
336
-				throw new InvalidShardedQueryException('No shard key or primary key set for query');
337
-			}
338
-		}
339
-		if ($this->shardDefinition && $this->updateShardKey) {
340
-			$newShardKey = $this->getKeyValue($this->updateShardKey);
341
-			$oldShardKeys = $this->getShardKeys();
342
-			if (count($newShardKey) !== 1) {
343
-				throw new InvalidShardedQueryException("Can't set shard key to an array");
344
-			}
345
-			$newShardKey = current($newShardKey);
346
-			if (empty($oldShardKeys)) {
347
-				throw new InvalidShardedQueryException("Can't update without shard key");
348
-			}
349
-			$oldShards = array_values(array_unique(array_map(function ($shardKey) {
350
-				return $this->shardDefinition->getShardForKey((int)$shardKey);
351
-			}, $oldShardKeys)));
352
-			$newShard = $this->shardDefinition->getShardForKey((int)$newShardKey);
353
-			if ($oldShards === [$newShard]) {
354
-				throw new InvalidShardedQueryException('Update statement would move rows to a different shard');
355
-			}
356
-		}
357
-	}
358
-
359
-	public function executeQuery(?IDBConnection $connection = null): IResult {
360
-		$this->validate();
361
-		if ($this->shardDefinition) {
362
-			$runner = new ShardQueryRunner($this->shardConnectionManager, $this->shardDefinition);
363
-			return $runner->executeQuery($this->builder, $this->allShards, $this->getShardKeys(), $this->getPrimaryKeys(), $this->sortList, $this->limit, $this->offset);
364
-		}
365
-		return parent::executeQuery($connection);
366
-	}
367
-
368
-	public function executeStatement(?IDBConnection $connection = null): int {
369
-		$this->validate();
370
-		if ($this->shardDefinition) {
371
-			$runner = new ShardQueryRunner($this->shardConnectionManager, $this->shardDefinition);
372
-			if ($this->insertTable) {
373
-				$shards = $runner->getShards($this->allShards, $this->getShardKeys());
374
-				if (!$shards) {
375
-					throw new InvalidShardedQueryException("Can't insert without shard key");
376
-				}
377
-				$count = 0;
378
-				foreach ($shards as $shard) {
379
-					$shardConnection = $this->shardConnectionManager->getConnection($this->shardDefinition, $shard);
380
-					if (!$this->primaryKeys && $this->shardDefinition->table === $this->insertTable) {
381
-						$id = $this->autoIncrementHandler->getNextPrimaryKey($this->shardDefinition, $shard);
382
-						parent::setValue($this->shardDefinition->primaryKey, $this->createParameter('__generated_primary_key'));
383
-						$this->setParameter('__generated_primary_key', $id, self::PARAM_INT);
384
-						$this->lastInsertId = $id;
385
-					}
386
-					$count += parent::executeStatement($shardConnection);
387
-
388
-					$this->lastInsertConnection = $shardConnection;
389
-				}
390
-				return $count;
391
-			} else {
392
-				return $runner->executeStatement($this->builder, $this->allShards, $this->getShardKeys(), $this->getPrimaryKeys());
393
-			}
394
-		}
395
-		return parent::executeStatement($connection);
396
-	}
397
-
398
-	public function getLastInsertId(): int {
399
-		if ($this->lastInsertId) {
400
-			return $this->lastInsertId;
401
-		}
402
-		if ($this->lastInsertConnection) {
403
-			$table = $this->builder->prefixTableName($this->insertTable);
404
-			return $this->lastInsertConnection->lastInsertId($table);
405
-		} else {
406
-			return parent::getLastInsertId();
407
-		}
408
-	}
28
+    private array $shardKeys = [];
29
+    private array $primaryKeys = [];
30
+    private ?ShardDefinition $shardDefinition = null;
31
+    /** @var bool Run the query across all shards */
32
+    private bool $allShards = false;
33
+    private ?string $insertTable = null;
34
+    private mixed $lastInsertId = null;
35
+    private ?IDBConnection $lastInsertConnection = null;
36
+    private ?int $updateShardKey = null;
37
+    private ?int $limit = null;
38
+    private ?int $offset = null;
39
+    /** @var array{column: string, order: string}[] */
40
+    private array $sortList = [];
41
+    private string $mainTable = '';
42
+
43
+    public function __construct(
44
+        IQueryBuilder $builder,
45
+        protected array $shardDefinitions,
46
+        protected ShardConnectionManager $shardConnectionManager,
47
+        protected AutoIncrementHandler $autoIncrementHandler,
48
+    ) {
49
+        parent::__construct($builder);
50
+    }
51
+
52
+    public function getShardKeys(): array {
53
+        return $this->getKeyValues($this->shardKeys);
54
+    }
55
+
56
+    public function getPrimaryKeys(): array {
57
+        return $this->getKeyValues($this->primaryKeys);
58
+    }
59
+
60
+    private function getKeyValues(array $keys): array {
61
+        $values = [];
62
+        foreach ($keys as $key) {
63
+            $values = array_merge($values, $this->getKeyValue($key));
64
+        }
65
+        return array_values(array_unique($values));
66
+    }
67
+
68
+    private function getKeyValue($value): array {
69
+        if ($value instanceof Parameter) {
70
+            $value = (string)$value;
71
+        }
72
+        if (is_string($value) && str_starts_with($value, ':')) {
73
+            $param = $this->getParameter(substr($value, 1));
74
+            if (is_array($param)) {
75
+                return $param;
76
+            } else {
77
+                return [$param];
78
+            }
79
+        } elseif ($value !== null) {
80
+            return [$value];
81
+        } else {
82
+            return [];
83
+        }
84
+    }
85
+
86
+    public function where(...$predicates) {
87
+        return $this->andWhere(...$predicates);
88
+    }
89
+
90
+    public function andWhere(...$where) {
91
+        if ($where) {
92
+            foreach ($where as $predicate) {
93
+                $this->tryLoadShardKey($predicate);
94
+            }
95
+            parent::andWhere(...$where);
96
+        }
97
+        return $this;
98
+    }
99
+
100
+    private function tryLoadShardKey($predicate): void {
101
+        if (!$this->shardDefinition) {
102
+            return;
103
+        }
104
+        if ($keys = $this->tryExtractShardKeys($predicate, $this->shardDefinition->shardKey)) {
105
+            $this->shardKeys += $keys;
106
+        }
107
+        if ($keys = $this->tryExtractShardKeys($predicate, $this->shardDefinition->primaryKey)) {
108
+            $this->primaryKeys += $keys;
109
+        }
110
+        foreach ($this->shardDefinition->companionKeys as $companionKey) {
111
+            if ($keys = $this->tryExtractShardKeys($predicate, $companionKey)) {
112
+                $this->primaryKeys += $keys;
113
+            }
114
+        }
115
+    }
116
+
117
+    /**
118
+     * @param $predicate
119
+     * @param string $column
120
+     * @return string[]
121
+     */
122
+    private function tryExtractShardKeys($predicate, string $column): array {
123
+        if ($predicate instanceof CompositeExpression) {
124
+            $values = [];
125
+            foreach ($predicate->getParts() as $part) {
126
+                $partValues = $this->tryExtractShardKeys($part, $column);
127
+                // for OR expressions, we can only rely on the predicate if all parts contain the comparison
128
+                if ($predicate->getType() === CompositeExpression::TYPE_OR && !$partValues) {
129
+                    return [];
130
+                }
131
+                $values = array_merge($values, $partValues);
132
+            }
133
+            return $values;
134
+        }
135
+        $predicate = (string)$predicate;
136
+        // expect a condition in the form of 'alias1.column1 = placeholder' or 'alias1.column1 in placeholder'
137
+        if (substr_count($predicate, ' ') > 2) {
138
+            return [];
139
+        }
140
+        if (str_contains($predicate, ' = ')) {
141
+            $parts = explode(' = ', $predicate);
142
+            if ($parts[0] === "`{$column}`" || str_ends_with($parts[0], "`.`{$column}`")) {
143
+                return [$parts[1]];
144
+            } else {
145
+                return [];
146
+            }
147
+        }
148
+
149
+        if (str_contains($predicate, ' IN ')) {
150
+            $parts = explode(' IN ', $predicate);
151
+            if ($parts[0] === "`{$column}`" || str_ends_with($parts[0], "`.`{$column}`")) {
152
+                return [trim(trim($parts[1], '('), ')')];
153
+            } else {
154
+                return [];
155
+            }
156
+        }
157
+
158
+        return [];
159
+    }
160
+
161
+    public function set($key, $value) {
162
+        if ($this->shardDefinition && $key === $this->shardDefinition->shardKey) {
163
+            $updateShardKey = $value;
164
+        }
165
+        return parent::set($key, $value);
166
+    }
167
+
168
+    public function setValue($column, $value) {
169
+        if ($this->shardDefinition) {
170
+            if ($this->shardDefinition->isKey($column)) {
171
+                $this->primaryKeys[] = $value;
172
+            }
173
+            if ($column === $this->shardDefinition->shardKey) {
174
+                $this->shardKeys[] = $value;
175
+            }
176
+        }
177
+        return parent::setValue($column, $value);
178
+    }
179
+
180
+    public function values(array $values) {
181
+        foreach ($values as $column => $value) {
182
+            $this->setValue($column, $value);
183
+        }
184
+        return $this;
185
+    }
186
+
187
+    private function actOnTable(string $table): void {
188
+        $this->mainTable = $table;
189
+        foreach ($this->shardDefinitions as $shardDefinition) {
190
+            if ($shardDefinition->hasTable($table)) {
191
+                $this->shardDefinition = $shardDefinition;
192
+            }
193
+        }
194
+    }
195
+
196
+    public function from($from, $alias = null) {
197
+        if (is_string($from) && $from) {
198
+            $this->actOnTable($from);
199
+        }
200
+        return parent::from($from, $alias);
201
+    }
202
+
203
+    public function update($update = null, $alias = null) {
204
+        if (is_string($update) && $update) {
205
+            $this->actOnTable($update);
206
+        }
207
+        return parent::update($update, $alias);
208
+    }
209
+
210
+    public function insert($insert = null) {
211
+        if (is_string($insert) && $insert) {
212
+            $this->insertTable = $insert;
213
+            $this->actOnTable($insert);
214
+        }
215
+        return parent::insert($insert);
216
+    }
217
+
218
+    public function delete($delete = null, $alias = null) {
219
+        if (is_string($delete) && $delete) {
220
+            $this->actOnTable($delete);
221
+        }
222
+        return parent::delete($delete, $alias);
223
+    }
224
+
225
+    private function checkJoin(string $table): void {
226
+        if ($this->shardDefinition) {
227
+            if ($table === $this->mainTable) {
228
+                throw new InvalidShardedQueryException("Sharded query on {$this->mainTable} isn't allowed to join on itself");
229
+            }
230
+            if (!$this->shardDefinition->hasTable($table)) {
231
+                // this generally shouldn't happen as the partitioning logic should prevent this
232
+                // but the check is here just in case
233
+                throw new InvalidShardedQueryException("Sharded query on {$this->shardDefinition->table} isn't allowed to join on $table");
234
+            }
235
+        }
236
+    }
237
+
238
+    public function innerJoin($fromAlias, $join, $alias, $condition = null) {
239
+        if (is_string($join)) {
240
+            $this->checkJoin($join);
241
+        }
242
+        return parent::innerJoin($fromAlias, $join, $alias, $condition);
243
+    }
244
+
245
+    public function leftJoin($fromAlias, $join, $alias, $condition = null) {
246
+        if (is_string($join)) {
247
+            $this->checkJoin($join);
248
+        }
249
+        return parent::leftJoin($fromAlias, $join, $alias, $condition);
250
+    }
251
+
252
+    public function rightJoin($fromAlias, $join, $alias, $condition = null) {
253
+        if ($this->shardDefinition) {
254
+            throw new InvalidShardedQueryException("Sharded query on {$this->shardDefinition->table} isn't allowed to right join");
255
+        }
256
+        return parent::rightJoin($fromAlias, $join, $alias, $condition);
257
+    }
258
+
259
+    public function join($fromAlias, $join, $alias, $condition = null) {
260
+        return $this->innerJoin($fromAlias, $join, $alias, $condition);
261
+    }
262
+
263
+    public function setMaxResults($maxResults) {
264
+        if ($maxResults > 0) {
265
+            $this->limit = (int)$maxResults;
266
+        }
267
+        return parent::setMaxResults($maxResults);
268
+    }
269
+
270
+    public function setFirstResult($firstResult) {
271
+        if ($firstResult > 0) {
272
+            $this->offset = (int)$firstResult;
273
+        }
274
+        if ($this->shardDefinition && count($this->shardDefinition->shards) > 1) {
275
+            // we have to emulate offset
276
+            return $this;
277
+        } else {
278
+            return parent::setFirstResult($firstResult);
279
+        }
280
+    }
281
+
282
+    public function addOrderBy($sort, $order = null) {
283
+        $this->registerOrder((string)$sort, (string)$order ?? 'ASC');
284
+        return parent::addOrderBy($sort, $order);
285
+    }
286
+
287
+    public function orderBy($sort, $order = null) {
288
+        $this->sortList = [];
289
+        $this->registerOrder((string)$sort, (string)$order ?? 'ASC');
290
+        return parent::orderBy($sort, $order);
291
+    }
292
+
293
+    private function registerOrder(string $column, string $order): void {
294
+        // handle `mime + 0` and similar by just sorting on the first part of the expression
295
+        [$column] = explode(' ', $column);
296
+        $column = trim($column, '`');
297
+        $this->sortList[] = [
298
+            'column' => $column,
299
+            'order' => strtoupper($order),
300
+        ];
301
+    }
302
+
303
+    public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self {
304
+        if ($overwrite) {
305
+            $this->primaryKeys = [];
306
+            $this->shardKeys = [];
307
+        }
308
+        if ($this->shardDefinition?->isKey($column)) {
309
+            $this->primaryKeys[] = $value;
310
+        }
311
+        if ($column === $this->shardDefinition?->shardKey) {
312
+            $this->shardKeys[] = $value;
313
+        }
314
+        return $this;
315
+    }
316
+
317
+    public function runAcrossAllShards(): self {
318
+        $this->allShards = true;
319
+        return $this;
320
+    }
321
+
322
+    /**
323
+     * @throws InvalidShardedQueryException
324
+     */
325
+    public function validate(): void {
326
+        if ($this->shardDefinition && $this->insertTable) {
327
+            if ($this->allShards) {
328
+                throw new InvalidShardedQueryException("Can't insert across all shards");
329
+            }
330
+            if (empty($this->getShardKeys())) {
331
+                throw new InvalidShardedQueryException("Can't insert without shard key");
332
+            }
333
+        }
334
+        if ($this->shardDefinition && !$this->allShards) {
335
+            if (empty($this->getShardKeys()) && empty($this->getPrimaryKeys())) {
336
+                throw new InvalidShardedQueryException('No shard key or primary key set for query');
337
+            }
338
+        }
339
+        if ($this->shardDefinition && $this->updateShardKey) {
340
+            $newShardKey = $this->getKeyValue($this->updateShardKey);
341
+            $oldShardKeys = $this->getShardKeys();
342
+            if (count($newShardKey) !== 1) {
343
+                throw new InvalidShardedQueryException("Can't set shard key to an array");
344
+            }
345
+            $newShardKey = current($newShardKey);
346
+            if (empty($oldShardKeys)) {
347
+                throw new InvalidShardedQueryException("Can't update without shard key");
348
+            }
349
+            $oldShards = array_values(array_unique(array_map(function ($shardKey) {
350
+                return $this->shardDefinition->getShardForKey((int)$shardKey);
351
+            }, $oldShardKeys)));
352
+            $newShard = $this->shardDefinition->getShardForKey((int)$newShardKey);
353
+            if ($oldShards === [$newShard]) {
354
+                throw new InvalidShardedQueryException('Update statement would move rows to a different shard');
355
+            }
356
+        }
357
+    }
358
+
359
+    public function executeQuery(?IDBConnection $connection = null): IResult {
360
+        $this->validate();
361
+        if ($this->shardDefinition) {
362
+            $runner = new ShardQueryRunner($this->shardConnectionManager, $this->shardDefinition);
363
+            return $runner->executeQuery($this->builder, $this->allShards, $this->getShardKeys(), $this->getPrimaryKeys(), $this->sortList, $this->limit, $this->offset);
364
+        }
365
+        return parent::executeQuery($connection);
366
+    }
367
+
368
+    public function executeStatement(?IDBConnection $connection = null): int {
369
+        $this->validate();
370
+        if ($this->shardDefinition) {
371
+            $runner = new ShardQueryRunner($this->shardConnectionManager, $this->shardDefinition);
372
+            if ($this->insertTable) {
373
+                $shards = $runner->getShards($this->allShards, $this->getShardKeys());
374
+                if (!$shards) {
375
+                    throw new InvalidShardedQueryException("Can't insert without shard key");
376
+                }
377
+                $count = 0;
378
+                foreach ($shards as $shard) {
379
+                    $shardConnection = $this->shardConnectionManager->getConnection($this->shardDefinition, $shard);
380
+                    if (!$this->primaryKeys && $this->shardDefinition->table === $this->insertTable) {
381
+                        $id = $this->autoIncrementHandler->getNextPrimaryKey($this->shardDefinition, $shard);
382
+                        parent::setValue($this->shardDefinition->primaryKey, $this->createParameter('__generated_primary_key'));
383
+                        $this->setParameter('__generated_primary_key', $id, self::PARAM_INT);
384
+                        $this->lastInsertId = $id;
385
+                    }
386
+                    $count += parent::executeStatement($shardConnection);
387
+
388
+                    $this->lastInsertConnection = $shardConnection;
389
+                }
390
+                return $count;
391
+            } else {
392
+                return $runner->executeStatement($this->builder, $this->allShards, $this->getShardKeys(), $this->getPrimaryKeys());
393
+            }
394
+        }
395
+        return parent::executeStatement($connection);
396
+    }
397
+
398
+    public function getLastInsertId(): int {
399
+        if ($this->lastInsertId) {
400
+            return $this->lastInsertId;
401
+        }
402
+        if ($this->lastInsertConnection) {
403
+            $table = $this->builder->prefixTableName($this->insertTable);
404
+            return $this->lastInsertConnection->lastInsertId($table);
405
+        } else {
406
+            return parent::getLastInsertId();
407
+        }
408
+    }
409 409
 
410 410
 
411 411
 }
Please login to merge, or discard this patch.
lib/private/DB/QueryBuilder/Partitioned/PartitionedQueryBuilder.php 2 patches
Indentation   +431 added lines, -431 removed lines patch added patch discarded remove patch
@@ -36,435 +36,435 @@
 block discarded – undo
36 36
  * [1]: A set of tables which can't be queried together with the rest of the tables, such as when sharding is used.
37 37
  */
38 38
 class PartitionedQueryBuilder extends ShardedQueryBuilder {
39
-	/** @var array<string, PartitionQuery> $splitQueries */
40
-	private array $splitQueries = [];
41
-	/** @var list<PartitionSplit> */
42
-	private array $partitions = [];
43
-
44
-	/** @var array{'select': string|array, 'alias': ?string}[] */
45
-	private array $selects = [];
46
-	private ?PartitionSplit $mainPartition = null;
47
-	private bool $hasPositionalParameter = false;
48
-	private QuoteHelper $quoteHelper;
49
-	private ?int $limit = null;
50
-	private ?int $offset = null;
51
-
52
-	public function __construct(
53
-		IQueryBuilder $builder,
54
-		array $shardDefinitions,
55
-		ShardConnectionManager $shardConnectionManager,
56
-		AutoIncrementHandler $autoIncrementHandler,
57
-	) {
58
-		parent::__construct($builder, $shardDefinitions, $shardConnectionManager, $autoIncrementHandler);
59
-		$this->quoteHelper = new QuoteHelper();
60
-	}
61
-
62
-	private function newQuery(): IQueryBuilder {
63
-		// get a fresh, non-partitioning query builder
64
-		$builder = $this->builder->getConnection()->getQueryBuilder();
65
-		if ($builder instanceof PartitionedQueryBuilder) {
66
-			$builder = $builder->builder;
67
-		}
68
-
69
-		return new ShardedQueryBuilder(
70
-			$builder,
71
-			$this->shardDefinitions,
72
-			$this->shardConnectionManager,
73
-			$this->autoIncrementHandler,
74
-		);
75
-	}
76
-
77
-	// we need to save selects until we know all the table aliases
78
-	public function select(...$selects) {
79
-		if (count($selects) === 1 && is_array($selects[0])) {
80
-			$selects = $selects[0];
81
-		}
82
-		$this->selects = [];
83
-		$this->addSelect(...$selects);
84
-		return $this;
85
-	}
86
-
87
-	public function addSelect(...$select) {
88
-		$select = array_map(function ($select) {
89
-			return ['select' => $select, 'alias' => null];
90
-		}, $select);
91
-		$this->selects = array_merge($this->selects, $select);
92
-		return $this;
93
-	}
94
-
95
-	public function selectAlias($select, $alias) {
96
-		$this->selects[] = ['select' => $select, 'alias' => $alias];
97
-		return $this;
98
-	}
99
-
100
-	/**
101
-	 * Ensure that a column is being selected by the query
102
-	 *
103
-	 * This is mainly used to ensure that the returned rows from both sides of a partition contains the columns of the join predicate
104
-	 *
105
-	 * @param string|IQueryFunction $column
106
-	 * @return void
107
-	 */
108
-	private function ensureSelect(string|IQueryFunction $column, ?string $alias = null): void {
109
-		$checkColumn = $alias ?: $column;
110
-		if (str_contains($checkColumn, '.')) {
111
-			[$table, $checkColumn] = explode('.', $checkColumn);
112
-			$partition = $this->getPartition($table);
113
-		} else {
114
-			$partition = null;
115
-		}
116
-		foreach ($this->selects as $select) {
117
-			$select = $select['select'];
118
-			if (!is_string($select)) {
119
-				continue;
120
-			}
121
-
122
-			if (str_contains($select, '.')) {
123
-				[$table, $select] = explode('.', $select);
124
-				$selectPartition = $this->getPartition($table);
125
-			} else {
126
-				$selectPartition = null;
127
-			}
128
-			if (
129
-				($select === $checkColumn || $select === '*')
130
-				&& $selectPartition === $partition
131
-			) {
132
-				return;
133
-			}
134
-		}
135
-		if ($alias) {
136
-			$this->selectAlias($column, $alias);
137
-		} else {
138
-			$this->addSelect($column);
139
-		}
140
-	}
141
-
142
-	/**
143
-	 * Distribute the select statements to the correct partition
144
-	 *
145
-	 * This is done at the end instead of when the `select` call is made, because the `select` calls are generally done
146
-	 * before we know what tables are involved in the query
147
-	 *
148
-	 * @return void
149
-	 */
150
-	private function applySelects(): void {
151
-		foreach ($this->selects as $select) {
152
-			foreach ($this->partitions as $partition) {
153
-				if (is_string($select['select']) && (
154
-					$select['select'] === '*'
155
-					|| $partition->isColumnInPartition($select['select']))
156
-				) {
157
-					if (isset($this->splitQueries[$partition->name])) {
158
-						if ($select['alias']) {
159
-							$this->splitQueries[$partition->name]->query->selectAlias($select['select'], $select['alias']);
160
-						} else {
161
-							$this->splitQueries[$partition->name]->query->addSelect($select['select']);
162
-						}
163
-						if ($select['select'] !== '*') {
164
-							continue 2;
165
-						}
166
-					}
167
-				}
168
-			}
169
-
170
-			if ($select['alias']) {
171
-				parent::selectAlias($select['select'], $select['alias']);
172
-			} else {
173
-				parent::addSelect($select['select']);
174
-			}
175
-		}
176
-		$this->selects = [];
177
-	}
178
-
179
-
180
-	public function addPartition(PartitionSplit $partition): void {
181
-		$this->partitions[] = $partition;
182
-	}
183
-
184
-	private function getPartition(string $table): ?PartitionSplit {
185
-		foreach ($this->partitions as $partition) {
186
-			if ($partition->containsTable($table) || $partition->containsAlias($table)) {
187
-				return $partition;
188
-			}
189
-		}
190
-		return null;
191
-	}
192
-
193
-	public function from($from, $alias = null) {
194
-		if (is_string($from) && $partition = $this->getPartition($from)) {
195
-			$this->mainPartition = $partition;
196
-			if ($alias) {
197
-				$this->mainPartition->addAlias($from, $alias);
198
-			}
199
-		}
200
-		return parent::from($from, $alias);
201
-	}
202
-
203
-	public function innerJoin($fromAlias, $join, $alias, $condition = null): self {
204
-		return $this->join($fromAlias, $join, $alias, $condition);
205
-	}
206
-
207
-	public function leftJoin($fromAlias, $join, $alias, $condition = null): self {
208
-		return $this->join($fromAlias, $join, $alias, $condition, PartitionQuery::JOIN_MODE_LEFT);
209
-	}
210
-
211
-	public function join($fromAlias, $join, $alias, $condition = null, $joinMode = PartitionQuery::JOIN_MODE_INNER): self {
212
-		if ($join instanceof IQueryFunction) {
213
-			$partition = null;
214
-			$fromPartition = null;
215
-		} else {
216
-			$partition = $this->getPartition($join);
217
-			$fromPartition = $this->getPartition($fromAlias);
218
-		}
219
-
220
-		if ($partition && $partition !== $this->mainPartition) {
221
-			/** @var string $join */
222
-			// join from the main db to a partition
223
-
224
-			$joinCondition = JoinCondition::parse($condition, $join, $alias, $fromAlias);
225
-			$partition->addAlias($join, $alias);
226
-
227
-			if (!isset($this->splitQueries[$partition->name])) {
228
-				$this->splitQueries[$partition->name] = new PartitionQuery(
229
-					$this->newQuery(),
230
-					$joinCondition->fromAlias ?? $joinCondition->fromColumn, $joinCondition->toAlias ?? $joinCondition->toColumn,
231
-					$joinMode
232
-				);
233
-				$this->splitQueries[$partition->name]->query->from($join, $alias);
234
-				$this->ensureSelect($joinCondition->fromColumn, $joinCondition->fromAlias);
235
-				$this->ensureSelect($joinCondition->toColumn, $joinCondition->toAlias);
236
-			} else {
237
-				$query = $this->splitQueries[$partition->name]->query;
238
-				if ($partition->containsAlias($fromAlias)) {
239
-					$query->innerJoin($fromAlias, $join, $alias, $condition);
240
-				} else {
241
-					throw new InvalidPartitionedQueryException("Can't join across partition boundaries more than once");
242
-				}
243
-			}
244
-			$this->splitQueries[$partition->name]->query->andWhere(...$joinCondition->toConditions);
245
-			parent::andWhere(...$joinCondition->fromConditions);
246
-			return $this;
247
-		} elseif ($fromPartition && $fromPartition !== $partition) {
248
-			/** @var string $join */
249
-			// join from partition, to the main db
250
-
251
-			$joinCondition = JoinCondition::parse($condition, $join, $alias, $fromAlias);
252
-			if (str_starts_with($fromPartition->name, 'from_')) {
253
-				$partitionName = $fromPartition->name;
254
-			} else {
255
-				$partitionName = 'from_' . $fromPartition->name;
256
-			}
257
-
258
-			if (!isset($this->splitQueries[$partitionName])) {
259
-				$newPartition = new PartitionSplit($partitionName, [$join]);
260
-				$newPartition->addAlias($join, $alias);
261
-				$this->partitions[] = $newPartition;
262
-
263
-				$this->splitQueries[$partitionName] = new PartitionQuery(
264
-					$this->newQuery(),
265
-					$joinCondition->fromAlias ?? $joinCondition->fromColumn, $joinCondition->toAlias ?? $joinCondition->toColumn,
266
-					$joinMode
267
-				);
268
-				$this->ensureSelect($joinCondition->fromColumn, $joinCondition->fromAlias);
269
-				$this->ensureSelect($joinCondition->toColumn, $joinCondition->toAlias);
270
-				$this->splitQueries[$partitionName]->query->from($join, $alias);
271
-				$this->splitQueries[$partitionName]->query->andWhere(...$joinCondition->toConditions);
272
-				parent::andWhere(...$joinCondition->fromConditions);
273
-			} else {
274
-				$fromPartition->addTable($join);
275
-				$fromPartition->addAlias($join, $alias);
276
-
277
-				$query = $this->splitQueries[$partitionName]->query;
278
-				$query->innerJoin($fromAlias, $join, $alias, $condition);
279
-			}
280
-			return $this;
281
-		} else {
282
-			// join within the main db or a partition
283
-			if ($joinMode === PartitionQuery::JOIN_MODE_INNER) {
284
-				return parent::innerJoin($fromAlias, $join, $alias, $condition);
285
-			} elseif ($joinMode === PartitionQuery::JOIN_MODE_LEFT) {
286
-				return parent::leftJoin($fromAlias, $join, $alias, $condition);
287
-			} elseif ($joinMode === PartitionQuery::JOIN_MODE_RIGHT) {
288
-				return parent::rightJoin($fromAlias, $join, $alias, $condition);
289
-			} else {
290
-				throw new \InvalidArgumentException("Invalid join mode: $joinMode");
291
-			}
292
-		}
293
-	}
294
-
295
-	/**
296
-	 * Flatten a list of predicates by merging the parts of any "AND" expression into the list of predicates
297
-	 *
298
-	 * @param array $predicates
299
-	 * @return array
300
-	 */
301
-	private function flattenPredicates(array $predicates): array {
302
-		$result = [];
303
-		foreach ($predicates as $predicate) {
304
-			if ($predicate instanceof CompositeExpression && $predicate->getType() === CompositeExpression::TYPE_AND) {
305
-				$result = array_merge($result, $this->flattenPredicates($predicate->getParts()));
306
-			} else {
307
-				$result[] = $predicate;
308
-			}
309
-		}
310
-		return $result;
311
-	}
312
-
313
-	/**
314
-	 * Split an array of predicates (WHERE query parts) by the partition they reference
315
-	 *
316
-	 * @param array $predicates
317
-	 * @return array<string, array>
318
-	 */
319
-	private function splitPredicatesByParts(array $predicates): array {
320
-		$predicates = $this->flattenPredicates($predicates);
321
-
322
-		$partitionPredicates = [];
323
-		foreach ($predicates as $predicate) {
324
-			$partition = $this->getPartitionForPredicate((string)$predicate);
325
-			if ($this->mainPartition === $partition) {
326
-				$partitionPredicates[''][] = $predicate;
327
-			} elseif ($partition) {
328
-				$partitionPredicates[$partition->name][] = $predicate;
329
-			} else {
330
-				$partitionPredicates[''][] = $predicate;
331
-			}
332
-		}
333
-		return $partitionPredicates;
334
-	}
335
-
336
-	public function where(...$predicates) {
337
-		return $this->andWhere(...$predicates);
338
-	}
339
-
340
-	public function andWhere(...$where) {
341
-		if ($where) {
342
-			foreach ($this->splitPredicatesByParts($where) as $alias => $predicates) {
343
-				if (isset($this->splitQueries[$alias])) {
344
-					// when there is a condition on a table being left-joined it starts to behave as if it's an inner join
345
-					// since any joined column that doesn't have the left part will not match the condition
346
-					// when there the condition is `$joinToColumn IS NULL` we instead mark the query as excluding the left half
347
-					if ($this->splitQueries[$alias]->joinMode === PartitionQuery::JOIN_MODE_LEFT) {
348
-						$this->splitQueries[$alias]->joinMode = PartitionQuery::JOIN_MODE_INNER;
349
-
350
-						$column = $this->quoteHelper->quoteColumnName($this->splitQueries[$alias]->joinToColumn);
351
-						foreach ($predicates as $predicate) {
352
-							if ((string)$predicate === "$column IS NULL") {
353
-								$this->splitQueries[$alias]->joinMode = PartitionQuery::JOIN_MODE_LEFT_NULL;
354
-							} else {
355
-								$this->splitQueries[$alias]->query->andWhere($predicate);
356
-							}
357
-						}
358
-					} else {
359
-						$this->splitQueries[$alias]->query->andWhere(...$predicates);
360
-					}
361
-				} else {
362
-					parent::andWhere(...$predicates);
363
-				}
364
-			}
365
-		}
366
-		return $this;
367
-	}
368
-
369
-
370
-	private function getPartitionForPredicate(string $predicate): ?PartitionSplit {
371
-		foreach ($this->partitions as $partition) {
372
-
373
-			if (str_contains($predicate, '?')) {
374
-				$this->hasPositionalParameter = true;
375
-			}
376
-			if ($partition->checkPredicateForTable($predicate)) {
377
-				return $partition;
378
-			}
379
-		}
380
-		return null;
381
-	}
382
-
383
-	public function update($update = null, $alias = null) {
384
-		return parent::update($update, $alias);
385
-	}
386
-
387
-	public function insert($insert = null) {
388
-		return parent::insert($insert);
389
-	}
390
-
391
-	public function delete($delete = null, $alias = null) {
392
-		return parent::delete($delete, $alias);
393
-	}
394
-
395
-	public function setMaxResults($maxResults) {
396
-		if ($maxResults > 0) {
397
-			$this->limit = (int)$maxResults;
398
-		}
399
-		return parent::setMaxResults($maxResults);
400
-	}
401
-
402
-	public function setFirstResult($firstResult) {
403
-		if ($firstResult > 0) {
404
-			$this->offset = (int)$firstResult;
405
-		}
406
-		return parent::setFirstResult($firstResult);
407
-	}
408
-
409
-	public function executeQuery(?IDBConnection $connection = null): IResult {
410
-		$this->applySelects();
411
-		if ($this->splitQueries && $this->hasPositionalParameter) {
412
-			throw new InvalidPartitionedQueryException("Partitioned queries aren't allowed to to positional arguments");
413
-		}
414
-		foreach ($this->splitQueries as $split) {
415
-			$split->query->setParameters($this->getParameters(), $this->getParameterTypes());
416
-		}
417
-		if (count($this->splitQueries) > 0) {
418
-			$hasNonLeftJoins = array_reduce($this->splitQueries, function (bool $hasNonLeftJoins, PartitionQuery $query) {
419
-				return $hasNonLeftJoins || $query->joinMode !== PartitionQuery::JOIN_MODE_LEFT;
420
-			}, false);
421
-			if ($hasNonLeftJoins) {
422
-				if (is_int($this->limit)) {
423
-					throw new InvalidPartitionedQueryException('Limit is not allowed in partitioned queries');
424
-				}
425
-				if (is_int($this->offset)) {
426
-					throw new InvalidPartitionedQueryException('Offset is not allowed in partitioned queries');
427
-				}
428
-			}
429
-		}
430
-
431
-		$s = $this->getSQL();
432
-		$result = parent::executeQuery($connection);
433
-		if (count($this->splitQueries) > 0) {
434
-			return new PartitionedResult($this->splitQueries, $result);
435
-		} else {
436
-			return $result;
437
-		}
438
-	}
439
-
440
-	public function executeStatement(?IDBConnection $connection = null): int {
441
-		if (count($this->splitQueries)) {
442
-			throw new InvalidPartitionedQueryException("Partitioning write queries isn't supported");
443
-		}
444
-		return parent::executeStatement($connection);
445
-	}
446
-
447
-	public function getSQL() {
448
-		$this->applySelects();
449
-		return parent::getSQL();
450
-	}
451
-
452
-	public function getPartitionCount(): int {
453
-		return count($this->splitQueries) + 1;
454
-	}
455
-
456
-	public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self {
457
-		if (str_contains($column, '.')) {
458
-			[$alias, $column] = explode('.', $column);
459
-			$partition = $this->getPartition($alias);
460
-			if ($partition) {
461
-				$this->splitQueries[$partition->name]->query->hintShardKey($column, $value, $overwrite);
462
-			} else {
463
-				parent::hintShardKey($column, $value, $overwrite);
464
-			}
465
-		} else {
466
-			parent::hintShardKey($column, $value, $overwrite);
467
-		}
468
-		return $this;
469
-	}
39
+    /** @var array<string, PartitionQuery> $splitQueries */
40
+    private array $splitQueries = [];
41
+    /** @var list<PartitionSplit> */
42
+    private array $partitions = [];
43
+
44
+    /** @var array{'select': string|array, 'alias': ?string}[] */
45
+    private array $selects = [];
46
+    private ?PartitionSplit $mainPartition = null;
47
+    private bool $hasPositionalParameter = false;
48
+    private QuoteHelper $quoteHelper;
49
+    private ?int $limit = null;
50
+    private ?int $offset = null;
51
+
52
+    public function __construct(
53
+        IQueryBuilder $builder,
54
+        array $shardDefinitions,
55
+        ShardConnectionManager $shardConnectionManager,
56
+        AutoIncrementHandler $autoIncrementHandler,
57
+    ) {
58
+        parent::__construct($builder, $shardDefinitions, $shardConnectionManager, $autoIncrementHandler);
59
+        $this->quoteHelper = new QuoteHelper();
60
+    }
61
+
62
+    private function newQuery(): IQueryBuilder {
63
+        // get a fresh, non-partitioning query builder
64
+        $builder = $this->builder->getConnection()->getQueryBuilder();
65
+        if ($builder instanceof PartitionedQueryBuilder) {
66
+            $builder = $builder->builder;
67
+        }
68
+
69
+        return new ShardedQueryBuilder(
70
+            $builder,
71
+            $this->shardDefinitions,
72
+            $this->shardConnectionManager,
73
+            $this->autoIncrementHandler,
74
+        );
75
+    }
76
+
77
+    // we need to save selects until we know all the table aliases
78
+    public function select(...$selects) {
79
+        if (count($selects) === 1 && is_array($selects[0])) {
80
+            $selects = $selects[0];
81
+        }
82
+        $this->selects = [];
83
+        $this->addSelect(...$selects);
84
+        return $this;
85
+    }
86
+
87
+    public function addSelect(...$select) {
88
+        $select = array_map(function ($select) {
89
+            return ['select' => $select, 'alias' => null];
90
+        }, $select);
91
+        $this->selects = array_merge($this->selects, $select);
92
+        return $this;
93
+    }
94
+
95
+    public function selectAlias($select, $alias) {
96
+        $this->selects[] = ['select' => $select, 'alias' => $alias];
97
+        return $this;
98
+    }
99
+
100
+    /**
101
+     * Ensure that a column is being selected by the query
102
+     *
103
+     * This is mainly used to ensure that the returned rows from both sides of a partition contains the columns of the join predicate
104
+     *
105
+     * @param string|IQueryFunction $column
106
+     * @return void
107
+     */
108
+    private function ensureSelect(string|IQueryFunction $column, ?string $alias = null): void {
109
+        $checkColumn = $alias ?: $column;
110
+        if (str_contains($checkColumn, '.')) {
111
+            [$table, $checkColumn] = explode('.', $checkColumn);
112
+            $partition = $this->getPartition($table);
113
+        } else {
114
+            $partition = null;
115
+        }
116
+        foreach ($this->selects as $select) {
117
+            $select = $select['select'];
118
+            if (!is_string($select)) {
119
+                continue;
120
+            }
121
+
122
+            if (str_contains($select, '.')) {
123
+                [$table, $select] = explode('.', $select);
124
+                $selectPartition = $this->getPartition($table);
125
+            } else {
126
+                $selectPartition = null;
127
+            }
128
+            if (
129
+                ($select === $checkColumn || $select === '*')
130
+                && $selectPartition === $partition
131
+            ) {
132
+                return;
133
+            }
134
+        }
135
+        if ($alias) {
136
+            $this->selectAlias($column, $alias);
137
+        } else {
138
+            $this->addSelect($column);
139
+        }
140
+    }
141
+
142
+    /**
143
+     * Distribute the select statements to the correct partition
144
+     *
145
+     * This is done at the end instead of when the `select` call is made, because the `select` calls are generally done
146
+     * before we know what tables are involved in the query
147
+     *
148
+     * @return void
149
+     */
150
+    private function applySelects(): void {
151
+        foreach ($this->selects as $select) {
152
+            foreach ($this->partitions as $partition) {
153
+                if (is_string($select['select']) && (
154
+                    $select['select'] === '*'
155
+                    || $partition->isColumnInPartition($select['select']))
156
+                ) {
157
+                    if (isset($this->splitQueries[$partition->name])) {
158
+                        if ($select['alias']) {
159
+                            $this->splitQueries[$partition->name]->query->selectAlias($select['select'], $select['alias']);
160
+                        } else {
161
+                            $this->splitQueries[$partition->name]->query->addSelect($select['select']);
162
+                        }
163
+                        if ($select['select'] !== '*') {
164
+                            continue 2;
165
+                        }
166
+                    }
167
+                }
168
+            }
169
+
170
+            if ($select['alias']) {
171
+                parent::selectAlias($select['select'], $select['alias']);
172
+            } else {
173
+                parent::addSelect($select['select']);
174
+            }
175
+        }
176
+        $this->selects = [];
177
+    }
178
+
179
+
180
+    public function addPartition(PartitionSplit $partition): void {
181
+        $this->partitions[] = $partition;
182
+    }
183
+
184
+    private function getPartition(string $table): ?PartitionSplit {
185
+        foreach ($this->partitions as $partition) {
186
+            if ($partition->containsTable($table) || $partition->containsAlias($table)) {
187
+                return $partition;
188
+            }
189
+        }
190
+        return null;
191
+    }
192
+
193
+    public function from($from, $alias = null) {
194
+        if (is_string($from) && $partition = $this->getPartition($from)) {
195
+            $this->mainPartition = $partition;
196
+            if ($alias) {
197
+                $this->mainPartition->addAlias($from, $alias);
198
+            }
199
+        }
200
+        return parent::from($from, $alias);
201
+    }
202
+
203
+    public function innerJoin($fromAlias, $join, $alias, $condition = null): self {
204
+        return $this->join($fromAlias, $join, $alias, $condition);
205
+    }
206
+
207
+    public function leftJoin($fromAlias, $join, $alias, $condition = null): self {
208
+        return $this->join($fromAlias, $join, $alias, $condition, PartitionQuery::JOIN_MODE_LEFT);
209
+    }
210
+
211
+    public function join($fromAlias, $join, $alias, $condition = null, $joinMode = PartitionQuery::JOIN_MODE_INNER): self {
212
+        if ($join instanceof IQueryFunction) {
213
+            $partition = null;
214
+            $fromPartition = null;
215
+        } else {
216
+            $partition = $this->getPartition($join);
217
+            $fromPartition = $this->getPartition($fromAlias);
218
+        }
219
+
220
+        if ($partition && $partition !== $this->mainPartition) {
221
+            /** @var string $join */
222
+            // join from the main db to a partition
223
+
224
+            $joinCondition = JoinCondition::parse($condition, $join, $alias, $fromAlias);
225
+            $partition->addAlias($join, $alias);
226
+
227
+            if (!isset($this->splitQueries[$partition->name])) {
228
+                $this->splitQueries[$partition->name] = new PartitionQuery(
229
+                    $this->newQuery(),
230
+                    $joinCondition->fromAlias ?? $joinCondition->fromColumn, $joinCondition->toAlias ?? $joinCondition->toColumn,
231
+                    $joinMode
232
+                );
233
+                $this->splitQueries[$partition->name]->query->from($join, $alias);
234
+                $this->ensureSelect($joinCondition->fromColumn, $joinCondition->fromAlias);
235
+                $this->ensureSelect($joinCondition->toColumn, $joinCondition->toAlias);
236
+            } else {
237
+                $query = $this->splitQueries[$partition->name]->query;
238
+                if ($partition->containsAlias($fromAlias)) {
239
+                    $query->innerJoin($fromAlias, $join, $alias, $condition);
240
+                } else {
241
+                    throw new InvalidPartitionedQueryException("Can't join across partition boundaries more than once");
242
+                }
243
+            }
244
+            $this->splitQueries[$partition->name]->query->andWhere(...$joinCondition->toConditions);
245
+            parent::andWhere(...$joinCondition->fromConditions);
246
+            return $this;
247
+        } elseif ($fromPartition && $fromPartition !== $partition) {
248
+            /** @var string $join */
249
+            // join from partition, to the main db
250
+
251
+            $joinCondition = JoinCondition::parse($condition, $join, $alias, $fromAlias);
252
+            if (str_starts_with($fromPartition->name, 'from_')) {
253
+                $partitionName = $fromPartition->name;
254
+            } else {
255
+                $partitionName = 'from_' . $fromPartition->name;
256
+            }
257
+
258
+            if (!isset($this->splitQueries[$partitionName])) {
259
+                $newPartition = new PartitionSplit($partitionName, [$join]);
260
+                $newPartition->addAlias($join, $alias);
261
+                $this->partitions[] = $newPartition;
262
+
263
+                $this->splitQueries[$partitionName] = new PartitionQuery(
264
+                    $this->newQuery(),
265
+                    $joinCondition->fromAlias ?? $joinCondition->fromColumn, $joinCondition->toAlias ?? $joinCondition->toColumn,
266
+                    $joinMode
267
+                );
268
+                $this->ensureSelect($joinCondition->fromColumn, $joinCondition->fromAlias);
269
+                $this->ensureSelect($joinCondition->toColumn, $joinCondition->toAlias);
270
+                $this->splitQueries[$partitionName]->query->from($join, $alias);
271
+                $this->splitQueries[$partitionName]->query->andWhere(...$joinCondition->toConditions);
272
+                parent::andWhere(...$joinCondition->fromConditions);
273
+            } else {
274
+                $fromPartition->addTable($join);
275
+                $fromPartition->addAlias($join, $alias);
276
+
277
+                $query = $this->splitQueries[$partitionName]->query;
278
+                $query->innerJoin($fromAlias, $join, $alias, $condition);
279
+            }
280
+            return $this;
281
+        } else {
282
+            // join within the main db or a partition
283
+            if ($joinMode === PartitionQuery::JOIN_MODE_INNER) {
284
+                return parent::innerJoin($fromAlias, $join, $alias, $condition);
285
+            } elseif ($joinMode === PartitionQuery::JOIN_MODE_LEFT) {
286
+                return parent::leftJoin($fromAlias, $join, $alias, $condition);
287
+            } elseif ($joinMode === PartitionQuery::JOIN_MODE_RIGHT) {
288
+                return parent::rightJoin($fromAlias, $join, $alias, $condition);
289
+            } else {
290
+                throw new \InvalidArgumentException("Invalid join mode: $joinMode");
291
+            }
292
+        }
293
+    }
294
+
295
+    /**
296
+     * Flatten a list of predicates by merging the parts of any "AND" expression into the list of predicates
297
+     *
298
+     * @param array $predicates
299
+     * @return array
300
+     */
301
+    private function flattenPredicates(array $predicates): array {
302
+        $result = [];
303
+        foreach ($predicates as $predicate) {
304
+            if ($predicate instanceof CompositeExpression && $predicate->getType() === CompositeExpression::TYPE_AND) {
305
+                $result = array_merge($result, $this->flattenPredicates($predicate->getParts()));
306
+            } else {
307
+                $result[] = $predicate;
308
+            }
309
+        }
310
+        return $result;
311
+    }
312
+
313
+    /**
314
+     * Split an array of predicates (WHERE query parts) by the partition they reference
315
+     *
316
+     * @param array $predicates
317
+     * @return array<string, array>
318
+     */
319
+    private function splitPredicatesByParts(array $predicates): array {
320
+        $predicates = $this->flattenPredicates($predicates);
321
+
322
+        $partitionPredicates = [];
323
+        foreach ($predicates as $predicate) {
324
+            $partition = $this->getPartitionForPredicate((string)$predicate);
325
+            if ($this->mainPartition === $partition) {
326
+                $partitionPredicates[''][] = $predicate;
327
+            } elseif ($partition) {
328
+                $partitionPredicates[$partition->name][] = $predicate;
329
+            } else {
330
+                $partitionPredicates[''][] = $predicate;
331
+            }
332
+        }
333
+        return $partitionPredicates;
334
+    }
335
+
336
+    public function where(...$predicates) {
337
+        return $this->andWhere(...$predicates);
338
+    }
339
+
340
+    public function andWhere(...$where) {
341
+        if ($where) {
342
+            foreach ($this->splitPredicatesByParts($where) as $alias => $predicates) {
343
+                if (isset($this->splitQueries[$alias])) {
344
+                    // when there is a condition on a table being left-joined it starts to behave as if it's an inner join
345
+                    // since any joined column that doesn't have the left part will not match the condition
346
+                    // when there the condition is `$joinToColumn IS NULL` we instead mark the query as excluding the left half
347
+                    if ($this->splitQueries[$alias]->joinMode === PartitionQuery::JOIN_MODE_LEFT) {
348
+                        $this->splitQueries[$alias]->joinMode = PartitionQuery::JOIN_MODE_INNER;
349
+
350
+                        $column = $this->quoteHelper->quoteColumnName($this->splitQueries[$alias]->joinToColumn);
351
+                        foreach ($predicates as $predicate) {
352
+                            if ((string)$predicate === "$column IS NULL") {
353
+                                $this->splitQueries[$alias]->joinMode = PartitionQuery::JOIN_MODE_LEFT_NULL;
354
+                            } else {
355
+                                $this->splitQueries[$alias]->query->andWhere($predicate);
356
+                            }
357
+                        }
358
+                    } else {
359
+                        $this->splitQueries[$alias]->query->andWhere(...$predicates);
360
+                    }
361
+                } else {
362
+                    parent::andWhere(...$predicates);
363
+                }
364
+            }
365
+        }
366
+        return $this;
367
+    }
368
+
369
+
370
+    private function getPartitionForPredicate(string $predicate): ?PartitionSplit {
371
+        foreach ($this->partitions as $partition) {
372
+
373
+            if (str_contains($predicate, '?')) {
374
+                $this->hasPositionalParameter = true;
375
+            }
376
+            if ($partition->checkPredicateForTable($predicate)) {
377
+                return $partition;
378
+            }
379
+        }
380
+        return null;
381
+    }
382
+
383
+    public function update($update = null, $alias = null) {
384
+        return parent::update($update, $alias);
385
+    }
386
+
387
+    public function insert($insert = null) {
388
+        return parent::insert($insert);
389
+    }
390
+
391
+    public function delete($delete = null, $alias = null) {
392
+        return parent::delete($delete, $alias);
393
+    }
394
+
395
+    public function setMaxResults($maxResults) {
396
+        if ($maxResults > 0) {
397
+            $this->limit = (int)$maxResults;
398
+        }
399
+        return parent::setMaxResults($maxResults);
400
+    }
401
+
402
+    public function setFirstResult($firstResult) {
403
+        if ($firstResult > 0) {
404
+            $this->offset = (int)$firstResult;
405
+        }
406
+        return parent::setFirstResult($firstResult);
407
+    }
408
+
409
+    public function executeQuery(?IDBConnection $connection = null): IResult {
410
+        $this->applySelects();
411
+        if ($this->splitQueries && $this->hasPositionalParameter) {
412
+            throw new InvalidPartitionedQueryException("Partitioned queries aren't allowed to to positional arguments");
413
+        }
414
+        foreach ($this->splitQueries as $split) {
415
+            $split->query->setParameters($this->getParameters(), $this->getParameterTypes());
416
+        }
417
+        if (count($this->splitQueries) > 0) {
418
+            $hasNonLeftJoins = array_reduce($this->splitQueries, function (bool $hasNonLeftJoins, PartitionQuery $query) {
419
+                return $hasNonLeftJoins || $query->joinMode !== PartitionQuery::JOIN_MODE_LEFT;
420
+            }, false);
421
+            if ($hasNonLeftJoins) {
422
+                if (is_int($this->limit)) {
423
+                    throw new InvalidPartitionedQueryException('Limit is not allowed in partitioned queries');
424
+                }
425
+                if (is_int($this->offset)) {
426
+                    throw new InvalidPartitionedQueryException('Offset is not allowed in partitioned queries');
427
+                }
428
+            }
429
+        }
430
+
431
+        $s = $this->getSQL();
432
+        $result = parent::executeQuery($connection);
433
+        if (count($this->splitQueries) > 0) {
434
+            return new PartitionedResult($this->splitQueries, $result);
435
+        } else {
436
+            return $result;
437
+        }
438
+    }
439
+
440
+    public function executeStatement(?IDBConnection $connection = null): int {
441
+        if (count($this->splitQueries)) {
442
+            throw new InvalidPartitionedQueryException("Partitioning write queries isn't supported");
443
+        }
444
+        return parent::executeStatement($connection);
445
+    }
446
+
447
+    public function getSQL() {
448
+        $this->applySelects();
449
+        return parent::getSQL();
450
+    }
451
+
452
+    public function getPartitionCount(): int {
453
+        return count($this->splitQueries) + 1;
454
+    }
455
+
456
+    public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self {
457
+        if (str_contains($column, '.')) {
458
+            [$alias, $column] = explode('.', $column);
459
+            $partition = $this->getPartition($alias);
460
+            if ($partition) {
461
+                $this->splitQueries[$partition->name]->query->hintShardKey($column, $value, $overwrite);
462
+            } else {
463
+                parent::hintShardKey($column, $value, $overwrite);
464
+            }
465
+        } else {
466
+            parent::hintShardKey($column, $value, $overwrite);
467
+        }
468
+        return $this;
469
+    }
470 470
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -85,7 +85,7 @@  discard block
 block discarded – undo
85 85
 	}
86 86
 
87 87
 	public function addSelect(...$select) {
88
-		$select = array_map(function ($select) {
88
+		$select = array_map(function($select) {
89 89
 			return ['select' => $select, 'alias' => null];
90 90
 		}, $select);
91 91
 		$this->selects = array_merge($this->selects, $select);
@@ -105,7 +105,7 @@  discard block
 block discarded – undo
105 105
 	 * @param string|IQueryFunction $column
106 106
 	 * @return void
107 107
 	 */
108
-	private function ensureSelect(string|IQueryFunction $column, ?string $alias = null): void {
108
+	private function ensureSelect(string | IQueryFunction $column, ?string $alias = null): void {
109 109
 		$checkColumn = $alias ?: $column;
110 110
 		if (str_contains($checkColumn, '.')) {
111 111
 			[$table, $checkColumn] = explode('.', $checkColumn);
@@ -252,7 +252,7 @@  discard block
 block discarded – undo
252 252
 			if (str_starts_with($fromPartition->name, 'from_')) {
253 253
 				$partitionName = $fromPartition->name;
254 254
 			} else {
255
-				$partitionName = 'from_' . $fromPartition->name;
255
+				$partitionName = 'from_'.$fromPartition->name;
256 256
 			}
257 257
 
258 258
 			if (!isset($this->splitQueries[$partitionName])) {
@@ -321,7 +321,7 @@  discard block
 block discarded – undo
321 321
 
322 322
 		$partitionPredicates = [];
323 323
 		foreach ($predicates as $predicate) {
324
-			$partition = $this->getPartitionForPredicate((string)$predicate);
324
+			$partition = $this->getPartitionForPredicate((string) $predicate);
325 325
 			if ($this->mainPartition === $partition) {
326 326
 				$partitionPredicates[''][] = $predicate;
327 327
 			} elseif ($partition) {
@@ -349,7 +349,7 @@  discard block
 block discarded – undo
349 349
 
350 350
 						$column = $this->quoteHelper->quoteColumnName($this->splitQueries[$alias]->joinToColumn);
351 351
 						foreach ($predicates as $predicate) {
352
-							if ((string)$predicate === "$column IS NULL") {
352
+							if ((string) $predicate === "$column IS NULL") {
353 353
 								$this->splitQueries[$alias]->joinMode = PartitionQuery::JOIN_MODE_LEFT_NULL;
354 354
 							} else {
355 355
 								$this->splitQueries[$alias]->query->andWhere($predicate);
@@ -394,14 +394,14 @@  discard block
 block discarded – undo
394 394
 
395 395
 	public function setMaxResults($maxResults) {
396 396
 		if ($maxResults > 0) {
397
-			$this->limit = (int)$maxResults;
397
+			$this->limit = (int) $maxResults;
398 398
 		}
399 399
 		return parent::setMaxResults($maxResults);
400 400
 	}
401 401
 
402 402
 	public function setFirstResult($firstResult) {
403 403
 		if ($firstResult > 0) {
404
-			$this->offset = (int)$firstResult;
404
+			$this->offset = (int) $firstResult;
405 405
 		}
406 406
 		return parent::setFirstResult($firstResult);
407 407
 	}
@@ -415,7 +415,7 @@  discard block
 block discarded – undo
415 415
 			$split->query->setParameters($this->getParameters(), $this->getParameterTypes());
416 416
 		}
417 417
 		if (count($this->splitQueries) > 0) {
418
-			$hasNonLeftJoins = array_reduce($this->splitQueries, function (bool $hasNonLeftJoins, PartitionQuery $query) {
418
+			$hasNonLeftJoins = array_reduce($this->splitQueries, function(bool $hasNonLeftJoins, PartitionQuery $query) {
419 419
 				return $hasNonLeftJoins || $query->joinMode !== PartitionQuery::JOIN_MODE_LEFT;
420 420
 			}, false);
421 421
 			if ($hasNonLeftJoins) {
Please login to merge, or discard this patch.