Passed
Push — master ( a8d37b...dabdd0 )
by Wilmer
10:00
created

Query::setJoin()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Query;
6
7
use Closure;
8
use Iterator;
9
use Throwable;
10
use Yiisoft\Db\Command\Command;
11
use Yiisoft\Db\Connection\ConnectionInterface;
12
use Yiisoft\Db\Exception\Exception;
13
use Yiisoft\Db\Exception\InvalidArgumentException;
14
use Yiisoft\Db\Exception\InvalidConfigException;
15
use Yiisoft\Db\Exception\NotSupportedException;
16
use Yiisoft\Db\Expression\Expression;
17
use Yiisoft\Db\Expression\ExpressionInterface;
18
use Yiisoft\Arrays\ArrayHelper;
19
use Yiisoft\Cache\Dependency\Dependency;
20
21
use function array_merge;
22
use function count;
23
use function gettype;
24
use function is_array;
25
use function is_int;
26
use function is_string;
27
use function key;
28
use function preg_match;
29
use function preg_split;
30
use function reset;
31
use function str_replace;
32
use function strcasecmp;
33
use function strlen;
34
use function strpos;
35
use function substr;
36
use function trim;
37
38
/**
39
 * Query represents a SELECT SQL statement in a way that is independent of DBMS.
40
 *
41
 * Query provides a set of methods to facilitate the specification of different clauses in a SELECT statement. These
42
 * methods can be chained together.
43
 *
44
 * By calling {@see createCommand()}, we can get a {@see Command} instance which can be further used to perform/execute
45
 * the DB query against a database.
46
 *
47
 * For example,
48
 *
49
 * ```php
50
 * $query = new Query;
51
 * // compose the query
52
 * $query->select('id, name')
53
 *     ->from('user')
54
 *     ->limit(10);
55
 * // build and execute the query
56
 * $rows = $query->all();
57
 * // alternatively, you can create DB command and execute it
58
 * $command = $query->createCommand();
59
 * // $command->sql returns the actual SQL
60
 * $rows = $command->queryAll();
61
 * ```
62
 *
63
 * Query internally uses the {@see QueryBuilder} class to generate the SQL statement.
64
 *
65
 * A more detailed usage guide on how to work with Query can be found in the
66
 * [guide article on Query Builder](guide:db-query-builder).
67
 *
68
 * @property string[] $tablesUsedInFrom Table names indexed by aliases. This property is read-only.
69
 */
70
class Query implements QueryInterface, ExpressionInterface
71
{
72
    use QueryTrait;
73
74
    protected array $select = [];
75
    protected ?string $selectOption = null;
76
    protected ?bool $distinct = null;
77
    protected $from;
78
    protected array $groupBy = [];
79
    protected array $join = [];
80
    protected $having;
81
    protected array $union = [];
82
    protected array $withQueries = [];
83
    protected array $params = [];
84
    private ?Dependency $queryCacheDependency = null;
85
    private ConnectionInterface $db;
86
    /** @var $queryCacheDuration bool|int|null */
0 ignored issues
show
Documentation Bug introduced by
The doc comment $queryCacheDuration at position 0 could not be parsed: Unknown type name '$queryCacheDuration' at position 0 in $queryCacheDuration.
Loading history...
87
    private $queryCacheDuration;
88
89 1875
    public function __construct(ConnectionInterface $db)
90
    {
91 1875
        $this->db = $db;
92 1875
    }
93
94
    /**
95
     * Creates a DB command that can be used to execute this query.
96
     *
97
     * If this parameter is not given, the `db` application component will be used.
98
     *
99
     * @throws Exception|InvalidConfigException
100
     *
101
     * @return Command the created DB command instance.
102
     */
103 112
    public function createCommand(): Command
104
    {
105 112
        [$sql, $params] = $this->db->getQueryBuilder()->build($this);
0 ignored issues
show
Bug introduced by
The method getQueryBuilder() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

105
        [$sql, $params] = $this->db->/** @scrutinizer ignore-call */ getQueryBuilder()->build($this);
Loading history...
106
107 112
        $command = $this->db->createCommand($sql, $params);
108
109 112
        $this->setCommandCache($command);
110
111 112
        return $command;
112
    }
113
114
    /**
115
     * Prepares for building SQL.
116
     *
117
     * This method is called by {@see QueryBuilder} when it starts to build SQL from a query object. You may override
118
     * this method to do some final preparation work when converting a query into a SQL statement.
119
     *
120
     * @param QueryBuilder $builder
121
     *
122
     * @return $this a prepared query instance which will be used by {@see QueryBuilder} to build the SQL.
123
     */
124 900
    public function prepare(QueryBuilder $builder): self
0 ignored issues
show
Unused Code introduced by
The parameter $builder is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

124
    public function prepare(/** @scrutinizer ignore-unused */ QueryBuilder $builder): self

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
125
    {
126 900
        return $this;
127
    }
128
129
    /**
130
     * Starts a batch query.
131
     *
132
     * A batch query supports fetching data in batches, which can keep the memory usage under a limit.
133
     *
134
     * This method will return a {@see BatchQueryResult} object which implements the {@see Iterator} interface and can
135
     * be traversed to retrieve the data in batches.
136
     *
137
     * For example,
138
     *
139
     * ```php
140
     * $query = (new Query)->from('user');
141
     * foreach ($query->batch() as $rows) {
142
     *     // $rows is an array of 100 or fewer rows from user table
143
     * }
144
     * ```
145
     *
146
     * @param int $batchSize the number of records to be fetched in each batch.
147
     *
148
     * @return BatchQueryResult the batch query result. It implements the {@see Iterator} interface and can be
149
     * traversed to retrieve the data in batches.
150
     */
151 30
    public function batch(int $batchSize = 100): BatchQueryResult
152
    {
153 30
        return (new BatchQueryResult())
154 30
            ->query($this)
155 30
            ->batchSize($batchSize)
156 30
            ->db($this->db)
157 30
            ->each(false);
158
    }
159
160
    /**
161
     * Starts a batch query and retrieves data row by row.
162
     *
163
     * This method is similar to {@see batch()} except that in each iteration of the result, only one row of data is
164
     * returned. For example,
165
     *
166
     * ```php
167
     * $query = (new Query)->from('user');
168
     * foreach ($query->each() as $row) {
169
     * }
170
     * ```
171
     *
172
     * @param int $batchSize the number of records to be fetched in each batch.
173
     *
174
     * @return BatchQueryResult the batch query result. It implements the {@see Iterator} interface and can be
175
     * traversed to retrieve the data in batches.
176
     */
177 10
    public function each(int $batchSize = 100): BatchQueryResult
178
    {
179 10
        return (new BatchQueryResult())
180 10
            ->query($this)
181 10
            ->batchSize($batchSize)
182 10
            ->db($this->db)
183 10
            ->each(true);
184
    }
185
186
    /**
187
     * Executes the query and returns all results as an array.
188
     *
189
     * If this parameter is not given, the `db` application component will be used.
190
     *
191
     * @throws Exception|InvalidConfigException|Throwable
192
     *
193
     * @return array the query results. If the query results in nothing, an empty array will be returned.
194
     */
195 311
    public function all(): array
196
    {
197 311
        if ($this->emulateExecution) {
198 15
            return [];
199
        }
200
201 301
        $rows = $this->createCommand()->queryAll();
202
203 301
        return $this->populate($rows);
204
    }
205
206
    /**
207
     * Converts the raw query results into the format as specified by this query.
208
     *
209
     * This method is internally used to convert the data fetched from database into the format as required by this
210
     * query.
211
     *
212
     * @param array $rows the raw query result from database.
213
     *
214
     * @return array the converted query result.
215
     */
216 520
    public function populate(array $rows): array
217
    {
218 520
        if ($this->indexBy === null) {
219 500
            return $rows;
220
        }
221
222 55
        $result = [];
223
224 55
        foreach ($rows as $row) {
225 55
            $result[ArrayHelper::getValueByPath($row, $this->indexBy)] = $row;
226
        }
227
228 55
        return $result;
229
    }
230
231
    /**
232
     * Executes the query and returns a single row of result.
233
     *
234
     * If this parameter is not given, the `db` application component will be used.
235
     *
236
     * @throws Exception|InvalidConfigException|Throwable
237
     *
238
     * @return array|bool the first row (in terms of an array) of the query result. False is returned if the query
239
     * results in nothing.
240
     */
241 424
    public function one()
242
    {
243 424
        if ($this->emulateExecution) {
244 10
            return false;
245
        }
246
247 414
        return $this->createCommand()->queryOne();
248
    }
249
250
    /**
251
     * Returns the query result as a scalar value.
252
     *
253
     * The value returned will be the first column in the first row of the query results.
254
     *
255
     * @throws Exception|InvalidConfigException|Throwable
256
     *
257
     * @return false|string|null the value of the first column in the first row of the query result. False is returned
258
     * if the query result is empty.
259
     */
260 25
    public function scalar()
261
    {
262 25
        if ($this->emulateExecution) {
263 10
            return null;
264
        }
265
266 15
        return $this->createCommand()->queryScalar();
267
    }
268
269
    /**
270
     * Executes the query and returns the first column of the result.
271
     *
272
     * If this parameter is not given, the `db` application component will be used.
273
     *
274
     * @throws Exception|InvalidConfigException|Throwable
275
     *
276
     * @return array the first column of the query result. An empty array is returned if the query results in nothing.
277
     */
278 25
    public function column(): array
279
    {
280 25
        if ($this->emulateExecution) {
281 10
            return [];
282
        }
283
284 15
        if ($this->indexBy === null) {
285 15
            return $this->createCommand()->queryColumn();
286
        }
287
288 5
        if (is_string($this->indexBy) && is_array($this->select) && count($this->select) === 1) {
289 5
            if (strpos($this->indexBy, '.') === false && count($tables = $this->getTablesUsedInFrom()) > 0) {
290 5
                $this->select[] = key($tables) . '.' . $this->indexBy;
291
            } else {
292
                $this->select[] = $this->indexBy;
293
            }
294
        }
295
296 5
        $rows = $this->createCommand()->queryAll();
297 5
        $results = [];
298 5
        foreach ($rows as $row) {
299 5
            $value = reset($row);
300
301 5
            if ($this->indexBy instanceof Closure) {
302 5
                $results[($this->indexBy)($row)] = $value;
303
            } else {
304 5
                $results[$row[$this->indexBy]] = $value;
305
            }
306
        }
307
308 5
        return $results;
309
    }
310
311
    /**
312
     * Returns the number of records.
313
     *
314
     * @param string $q the COUNT expression. Defaults to '*'.
315
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
316
     *
317
     * @throws Exception|InvalidConfigException|Throwable
318
     *
319
     * @return mixed number of records. The result may be a string depending on the underlying database engine and to
320
     * support integer values higher than a 32bit PHP integer can handle.
321
     */
322 95
    public function count(string $q = '*')
323
    {
324 95
        if ($this->emulateExecution) {
325 10
            return 0;
326
        }
327
328 95
        return $this->queryScalar("COUNT($q)");
329
    }
330
331
    /**
332
     * Returns the sum of the specified column values.
333
     *
334
     * @param string $q the column name or expression.
335
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
336
     *
337
     * @throws Exception|InvalidConfigException|Throwable
338
     *
339
     * @return mixed the sum of the specified column values.
340
     */
341 15
    public function sum(string $q)
342
    {
343 15
        if ($this->emulateExecution) {
344 10
            return 0;
345
        }
346
347 5
        return $this->queryScalar("SUM($q)");
348
    }
349
350
    /**
351
     * Returns the average of the specified column values.
352
     *
353
     * @param string $q the column name or expression.
354
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
355
     *
356
     * @throws Throwable
357
     *
358
     * @return mixed the average of the specified column values.
359
     */
360 15
    public function average(string $q)
361
    {
362 15
        if ($this->emulateExecution) {
363 10
            return 0;
364
        }
365
366 5
        return $this->queryScalar("AVG($q)");
367
    }
368
369
    /**
370
     * Returns the minimum of the specified column values.
371
     *
372
     * @param string $q the column name or expression.
373
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
374
     *
375
     * @throws Exception|InvalidConfigException|Throwable
376
     *
377
     * @return mixed the minimum of the specified column values.
378
     */
379 15
    public function min(string $q)
380
    {
381 15
        return $this->queryScalar("MIN($q)");
382
    }
383
384
    /**
385
     * Returns the maximum of the specified column values.
386
     *
387
     * @param string $q the column name or expression.
388
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
389
     *
390
     * @throws Exception|InvalidConfigException|Throwable
391
     *
392
     * @return mixed the maximum of the specified column values.
393
     */
394 15
    public function max(string $q)
395
    {
396 15
        return $this->queryScalar("MAX($q)");
397
    }
398
399
    /**
400
     * Returns a value indicating whether the query result contains any row of data.
401
     *
402
     * @throws Exception|InvalidConfigException|Throwable
403
     *
404
     * @return bool whether the query result contains any row of data.
405
     */
406 25
    public function exists(): bool
407
    {
408 25
        if ($this->emulateExecution) {
409 10
            return false;
410
        }
411
412 15
        $command = $this->createCommand();
413 15
        $params = $command->getParams();
414 15
        $command->setSql($this->db->getQueryBuilder()->selectExists($command->getSql()));
415 15
        $command->bindValues($params);
416
417 15
        return (bool) $command->queryScalar();
418
    }
419
420
    /**
421
     * Queries a scalar value by setting {@see select} first.
422
     *
423
     * Restores the value of select to make this query reusable.
424
     *
425
     * @param ExpressionInterface|string $selectExpression
426
     *
427
     * @throws Exception|InvalidConfigException|Throwable
428
     *
429
     * @return bool|string
430
     */
431 95
    protected function queryScalar($selectExpression)
432
    {
433 95
        if ($this->emulateExecution) {
434 10
            return null;
435
        }
436
437
        if (
438 95
            !$this->distinct
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->distinct of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
439 95
            && empty($this->groupBy)
440 95
            && empty($this->having)
441 95
            && empty($this->union)
442 95
            && empty($this->with)
443
        ) {
444 95
            $select = $this->select;
445 95
            $order = $this->orderBy;
446 95
            $limit = $this->limit;
447 95
            $offset = $this->offset;
448
449 95
            $this->select = [$selectExpression];
450 95
            $this->orderBy = [];
451 95
            $this->limit = null;
452 95
            $this->offset = null;
453
454
            try {
455 95
                $command = $this->createCommand();
456
            } catch (Throwable $e) {
457
                /** throw it later */
458
            }
459
460 95
            $this->select = $select;
461 95
            $this->orderBy = $order;
462 95
            $this->limit = $limit;
463 95
            $this->offset = $offset;
464
465 95
            if (isset($e)) {
466
                throw $e;
467
            }
468
469 95
            return $command->queryScalar();
470
        }
471
472 10
        $command = static::createInstance($this->db)
473 10
            ->select([$selectExpression])
474 10
            ->from(['c' => $this])
475 10
            ->createCommand();
476
477 10
        $this->setCommandCache($command);
478
479 10
        return $command->queryScalar();
480
    }
481
482
    /**
483
     * Returns table names used in {@see from} indexed by aliases.
484
     *
485
     * Both aliases and names are enclosed into {{ and }}.
486
     *
487
     * @throws InvalidArgumentException
488
     * @throws InvalidConfigException
489
     *
490
     * @return array table names indexed by aliases
491
     */
492 110
    public function getTablesUsedInFrom(): array
493
    {
494 110
        if (empty($this->from)) {
495
            return [];
496
        }
497
498 110
        if (is_array($this->from)) {
499 105
            $tableNames = $this->from;
500 5
        } elseif (is_string($this->from)) {
501
            $tableNames = preg_split('/\s*,\s*/', trim($this->from), -1, PREG_SPLIT_NO_EMPTY);
502 5
        } elseif ($this->from instanceof Expression) {
503
            $tableNames = [$this->from];
504
        } else {
505 5
            throw new InvalidConfigException(gettype($this->from) . ' in $from is not supported.');
506
        }
507
508 105
        return $this->cleanUpTableNames($tableNames);
509
    }
510
511
    /**
512
     * Clean up table names and aliases.
513
     *
514
     * Both aliases and names are enclosed into {{ and }}.
515
     *
516
     * @param array $tableNames non-empty array
517
     *
518
     * @throws InvalidArgumentException
519
     *
520
     * @return string[] table names indexed by aliases
521
     */
522 260
    protected function cleanUpTableNames(array $tableNames): array
523
    {
524 260
        $cleanedUpTableNames = [];
525 260
        foreach ($tableNames as $alias => $tableName) {
526 260
            if (is_string($tableName) && !is_string($alias)) {
527
                $pattern = <<<PATTERN
528 225
~
529
^
530
\s*
531
(
532
(?:['"`\[]|{{)
533
.*?
534
(?:['"`\]]|}})
535
|
536
\(.*?\)
537
|
538
.*?
539
)
540
(?:
541
(?:
542
    \s+
543
    (?:as)?
544
    \s*
545
)
546
(
547
   (?:['"`\[]|{{)
548
    .*?
549
    (?:['"`\]]|}})
550
    |
551
    .*?
552
)
553
)?
554
\s*
555
$
556
~iux
557
PATTERN;
558 225
                if (preg_match($pattern, $tableName, $matches)) {
559 225
                    if (isset($matches[2])) {
560 15
                        [, $tableName, $alias] = $matches;
561
                    } else {
562 220
                        $tableName = $alias = $matches[1];
563
                    }
564
                }
565
            }
566
567 260
            if ($tableName instanceof Expression) {
568 10
                if (!is_string($alias)) {
569 5
                    throw new InvalidArgumentException(
570 5
                        'To use Expression in from() method, pass it in array format with alias.'
571
                    );
572
                }
573 5
                $cleanedUpTableNames[$this->ensureNameQuoted($alias)] = $tableName;
574 250
            } elseif ($tableName instanceof self) {
575 5
                $cleanedUpTableNames[$this->ensureNameQuoted($alias)] = $tableName;
576
            } else {
577 245
                $cleanedUpTableNames[$this->ensureNameQuoted($alias)] = $this->ensureNameQuoted($tableName);
578
            }
579
        }
580
581 255
        return $cleanedUpTableNames;
582
    }
583
584
    /**
585
     * Ensures name is wrapped with {{ and }}.
586
     *
587
     * @param string $name
588
     *
589
     * @return string
590
     */
591 255
    private function ensureNameQuoted(string $name): string
592
    {
593 255
        $name = str_replace(["'", '"', '`', '[', ']'], '', $name);
594 255
        if ($name && !preg_match('/^{{.*}}$/', $name)) {
595 250
            return '{{' . $name . '}}';
596
        }
597
598 20
        return $name;
599
    }
600
601
    /**
602
     * Sets the SELECT part of the query.
603
     *
604
     * @param array|ExpressionInterface|string $columns the columns to be selected.
605
     * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
606
     * Columns can be prefixed with table names (e.g. "user.id") and/or contain column aliases
607
     * (e.g. "user.id AS user_id").
608
     *
609
     * The method will automatically quote the column names unless a column contains some parenthesis (which means the
610
     * column contains a DB expression). A DB expression may also be passed in form of an {@see ExpressionInterface}
611
     * object.
612
     *
613
     * Note that if you are selecting an expression like `CONCAT(first_name, ' ', last_name)`, you should use an array
614
     * to specify the columns. Otherwise, the expression may be incorrectly split into several parts.
615
     *
616
     * When the columns are specified as an array, you may also use array keys as the column aliases (if a column does
617
     * not need alias, do not use a string key).
618
     * @param string|null $option additional option that should be appended to the 'SELECT' keyword. For example,
619
     * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
620
     *
621
     * @return $this the query object itself.
622
     */
623 789
    public function select($columns, ?string $option = null): self
624
    {
625 789
        $this->select = $this->normalizeSelect($columns);
626 789
        $this->selectOption = $option;
627
628 789
        return $this;
629
    }
630
631
    /**
632
     * Add more columns to the SELECT part of the query.
633
     *
634
     * Note, that if {@see select} has not been specified before, you should include `*` explicitly if you want to
635
     * select all remaining columns too:
636
     *
637
     * ```php
638
     * $query->addSelect(["*", "CONCAT(first_name, ' ', last_name) AS full_name"])->one();
639
     * ```
640
     *
641
     * @param array|ExpressionInterface|string $columns the columns to add to the select. See {@see select()} for more
642
     * details about the format of this parameter.
643
     *
644
     * @return $this the query object itself.
645
     *
646
     * {@see select()}
647
     */
648 15
    public function addSelect($columns): self
649
    {
650 15
        if ($this->select === null) {
651
            return $this->select($columns);
652
        }
653
654 15
        if (!is_array($this->select)) {
0 ignored issues
show
introduced by
The condition is_array($this->select) is always true.
Loading history...
655
            $this->select = $this->normalizeSelect($this->select);
656
        }
657
658 15
        $this->select = array_merge($this->select, $this->normalizeSelect($columns));
659
660 15
        return $this;
661
    }
662
663
    /**
664
     * Normalizes the SELECT columns passed to {@see select()} or {@see addSelect()}.
665
     *
666
     * @param array|ExpressionInterface|string $columns
667
     *
668
     * @return array
669
     */
670 789
    protected function normalizeSelect($columns): array
671
    {
672 789
        if ($columns instanceof ExpressionInterface) {
673 5
            $columns = [$columns];
674 789
        } elseif (!is_array($columns)) {
675 113
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
676
        }
677
678 789
        $select = [];
679 789
        foreach ($columns as $columnAlias => $columnDefinition) {
680 348
            if (is_string($columnAlias)) {
681
                /** Already in the normalized format, good for them */
682 182
                $select[$columnAlias] = $columnDefinition;
683 182
                continue;
684
            }
685 341
            if (is_string($columnDefinition)) {
686
                if (
687 336
                    preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_.]+)$/', $columnDefinition, $matches) &&
688 336
                    !preg_match('/^\d+$/', $matches[2]) &&
689 336
                    strpos($matches[2], '.') === false
690
                ) {
691
                    /** Using "columnName as alias" or "columnName alias" syntax */
692 25
                    $select[$matches[2]] = $matches[1];
693 25
                    continue;
694
                }
695 336
                if (strpos($columnDefinition, '(') === false) {
696
                    /** Normal column name, just alias it to itself to ensure it's not selected twice */
697 280
                    $select[$columnDefinition] = $columnDefinition;
698 280
                    continue;
699
                }
700
            }
701
            /** Either a string calling a function, DB expression, or sub-query */
702 106
            $select[] = $columnDefinition;
703
        }
704
705 789
        return $select;
706
    }
707
708
    /**
709
     * Sets the value indicating whether to SELECT DISTINCT or not.
710
     *
711
     * @param bool $value whether to SELECT DISTINCT or not.
712
     *
713
     * @return $this the query object itself
714
     */
715 627
    public function distinct(?bool $value = true): self
716
    {
717 627
        $this->distinct = $value;
718
719 627
        return $this;
720
    }
721
722
    /**
723
     * Sets the FROM part of the query.
724
     *
725
     * @param array|ExpressionInterface|string $tables the table(s) to be selected from. This can be either a string
726
     * (e.g. `'user'`) or an array (e.g. `['user', 'profile']`) specifying one or several table names.
727
     *
728
     * Table names can contain schema prefixes (e.g. `'public.user'`) and/or table aliases (e.g. `'user u'`).
729
     *
730
     * The method will automatically quote the table names unless it contains some parenthesis (which means the table is
731
     * given as a sub-query or DB expression).
732
     *
733
     * When the tables are specified as an array, you may also use the array keys as the table aliases (if a table does
734
     * not need alias, do not use a string key).
735
     *
736
     * Use a Query object to represent a sub-query. In this case, the corresponding array key will be used as the alias
737
     * for the sub-query.
738
     *
739
     * To specify the `FROM` part in plain SQL, you may pass an instance of {@see ExpressionInterface}.
740
     *
741
     * Here are some examples:
742
     *
743
     * ```php
744
     * // SELECT * FROM  `user` `u`, `profile`;
745
     * $query = (new \Yiisoft\Db\Query\Query)->from(['u' => 'user', 'profile']);
746
     *
747
     * // SELECT * FROM (SELECT * FROM `user` WHERE `active` = 1) `activeusers`;
748
     * $subquery = (new \Yiisoft\Db\Query\Query)->from('user')->where(['active' => true])
749
     * $query = (new \Yiisoft\Db\Query\Query)->from(['activeusers' => $subquery]);
750
     *
751
     * // subquery can also be a string with plain SQL wrapped in parenthesis
752
     * // SELECT * FROM (SELECT * FROM `user` WHERE `active` = 1) `activeusers`;
753
     * $subquery = "(SELECT * FROM `user` WHERE `active` = 1)";
754
     * $query = (new \Yiisoft\Db\Query\Query)->from(['activeusers' => $subquery]);
755
     * ```
756
     *
757
     * @return $this the query object itself
758
     */
759 955
    public function from($tables): self
760
    {
761 955
        if ($tables instanceof ExpressionInterface) {
762 16
            $tables = [$tables];
763
        }
764 955
        if (is_string($tables)) {
765 248
            $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
766
        }
767 955
        $this->from = $tables;
768
769 955
        return $this;
770
    }
771
772
    /**
773
     * Sets the WHERE part of the query.
774
     *
775
     * The method requires a `$condition` parameter, and optionally a `$params` parameter specifying the values to be
776
     * bound to the query.
777
     *
778
     * The `$condition` parameter should be either a string (e.g. `'id=1'`) or an array.
779
     *
780
     * {@inheritdoc}
781
     *
782
     * @param array|ExpressionInterface|string $condition the conditions that should be put in the WHERE part.
783
     * @param array $params the parameters (name => value) to be bound to the query.
784
     *
785
     * @return $this the query object itself.
786
     *
787
     * {@see andWhere()}
788
     * {@see orWhere()}
789
     * {@see QueryInterface::where()}
790
     */
791 1368
    public function where($condition, array $params = []): self
792
    {
793 1368
        $this->where = $condition;
0 ignored issues
show
Documentation Bug introduced by
It seems like $condition can also be of type Yiisoft\Db\Expression\ExpressionInterface. However, the property $where is declared as type array|null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
794 1368
        $this->addParams($params);
795
796 1368
        return $this;
797
    }
798
799
    /**
800
     * Adds an additional WHERE condition to the existing one.
801
     *
802
     * The new condition and the existing one will be joined using the `AND` operator.
803
     *
804
     * @param array|ExpressionInterface|string $condition the new WHERE condition. Please refer to {@see where()} on how
805
     * to specify this parameter.
806
     * @param array $params the parameters (name => value) to be bound to the query.
807
     *
808
     * @return $this the query object itself.
809
     *
810
     * {@see where()}
811
     * {@see orWhere()}
812
     */
813 348
    public function andWhere($condition, array $params = []): self
814
    {
815 348
        if ($this->where === null) {
816 323
            $this->where = $condition;
0 ignored issues
show
Documentation Bug introduced by
It seems like $condition can also be of type Yiisoft\Db\Expression\ExpressionInterface. However, the property $where is declared as type array|null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
817 131
        } elseif (is_array($this->where) && isset($this->where[0]) && strcasecmp($this->where[0], 'and') === 0) {
818 6
            $this->where[] = $condition;
819
        } else {
820 131
            $this->where = ['and', $this->where, $condition];
821
        }
822
823 348
        $this->addParams($params);
824
825 348
        return $this;
826
    }
827
828
    /**
829
     * Adds an additional WHERE condition to the existing one.
830
     *
831
     * The new condition and the existing one will be joined using the `OR` operator.
832
     *
833
     * @param array|ExpressionInterface|string $condition the new WHERE condition. Please refer to {@see where()} on how
834
     * to specify this parameter.
835
     * @param array $params the parameters (name => value) to be bound to the query.
836
     *
837
     * @return $this the query object itself.
838
     *
839
     * {@see where()}
840
     * {@see andWhere()}
841
     */
842 6
    public function orWhere($condition, array $params = []): self
843
    {
844 6
        if ($this->where === null) {
845
            $this->where = $condition;
0 ignored issues
show
Documentation Bug introduced by
It seems like $condition can also be of type Yiisoft\Db\Expression\ExpressionInterface. However, the property $where is declared as type array|null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
846
        } else {
847 6
            $this->where = ['or', $this->where, $condition];
848
        }
849
850 6
        $this->addParams($params);
851
852 6
        return $this;
853
    }
854
855
    /**
856
     * Adds a filtering condition for a specific column and allow the user to choose a filter operator.
857
     *
858
     * It adds an additional WHERE condition for the given field and determines the comparison operator based on the
859
     * first few characters of the given value.
860
     *
861
     * The condition is added in the same way as in {@see andFilterWhere} so {@see isEmpty()|empty values} are ignored.
862
     * The new condition and the existing one will be joined using the `AND` operator.
863
     *
864
     * The comparison operator is intelligently determined based on the first few characters in the given value.
865
     * In particular, it recognizes the following operators if they appear as the leading characters in the given value:
866
     *
867
     * - `<`: the column must be less than the given value.
868
     * - `>`: the column must be greater than the given value.
869
     * - `<=`: the column must be less than or equal to the given value.
870
     * - `>=`: the column must be greater than or equal to the given value.
871
     * - `<>`: the column must not be the same as the given value.
872
     * - `=`: the column must be equal to the given value.
873
     * - If none of the above operators is detected, the `$defaultOperator` will be used.
874
     *
875
     * @param string $name the column name.
876
     * @param string|null $value the column value optionally prepended with the comparison operator.
877
     * @param string $defaultOperator The operator to use, when no operator is given in `$value`.
878
     * Defaults to `=`, performing an exact match.
879
     *
880
     * @throws NotSupportedException
881
     *
882
     * @return $this the query object itself.
883
     */
884 5
    public function andFilterCompare(string $name, ?string $value, string $defaultOperator = '='): self
885
    {
886 5
        if (preg_match('/^(<>|>=|>|<=|<|=)/', (string) $value, $matches)) {
887 5
            $operator = $matches[1];
888 5
            $value = substr($value, strlen($operator));
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type null; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

888
            $value = substr(/** @scrutinizer ignore-type */ $value, strlen($operator));
Loading history...
889
        } else {
890 5
            $operator = $defaultOperator;
891
        }
892
893 5
        return $this->andFilterWhere([$operator, $name, $value]);
894
    }
895
896
    /**
897
     * Appends a JOIN part to the query.
898
     *
899
     * The first parameter specifies what type of join it is.
900
     *
901
     * @param string $type  the type of join, such as INNER JOIN, LEFT JOIN.
902
     * @param array|string $table the table to be joined.
903
     * Use a string to represent the name of the table to be joined.
904
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
905
     * The method will automatically quote the table name unless it contains some parenthesis (which means the table is
906
     * given as a sub-query or DB expression).
907
     * Use an array to represent joining with a sub-query. The array must contain only one element.
908
     * The value must be a {@see Query} object representing the sub-query while the corresponding key represents the
909
     * alias for the sub-query.
910
     * @param array|string $on the join condition that should appear in the ON part.
911
     * Please refer to {@see where()} on how to specify this parameter.
912
     *
913
     * Note that the array format of {@see where()} is designed to match columns to values instead of columns to
914
     * columns, so the following would **not** work as expected: `['post.author_id' => 'user.id']`, it would match the
915
     * `post.author_id` column value against the string `'user.id'`.
916
     *
917
     * It is recommended to use the string syntax here which is more suited for a join:
918
     *
919
     * ```php
920
     * 'post.author_id = user.id'
921
     * ```
922
     * @param array $params the parameters (name => value) to be bound to the query.
923
     *
924
     * @return $this the query object itself.
925
     */
926 110
    public function join(string $type, $table, $on = '', array $params = []): self
927
    {
928 110
        $this->join[] = [$type, $table, $on];
929
930 110
        return $this->addParams($params);
931
    }
932
933
    /**
934
     * Appends an INNER JOIN part to the query.
935
     *
936
     * @param array|string $table the table to be joined.
937
     * Use a string to represent the name of the table to be joined.
938
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
939
     * The method will automatically quote the table name unless it contains some parenthesis (which means the table is
940
     * given as a sub-query or DB expression).
941
     * Use an array to represent joining with a sub-query. The array must contain only one element.
942
     * The value must be a {@see Query} object representing the sub-query while the corresponding key represents the
943
     * alias for the sub-query.
944
     * @param array|string $on the join condition that should appear in the ON part.
945
     * Please refer to {@see join()} on how to specify this parameter.
946
     * @param array $params the parameters (name => value) to be bound to the query.
947
     *
948
     * @return $this the query object itself.
949
     */
950 5
    public function innerJoin($table, $on = '', array $params = []): self
951
    {
952 5
        $this->join[] = ['INNER JOIN', $table, $on];
953
954 5
        return $this->addParams($params);
955
    }
956
957
    /**
958
     * Appends a LEFT OUTER JOIN part to the query.
959
     *
960
     * @param array|string $table the table to be joined.
961
     * Use a string to represent the name of the table to be joined.
962
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
963
     * The method will automatically quote the table name unless it contains some parenthesis (which means the table is
964
     * given as a sub-query or DB expression).
965
     * Use an array to represent joining with a sub-query. The array must contain only one element.
966
     * The value must be a {@see Query} object representing the sub-query while the corresponding key represents the
967
     * alias for the sub-query.
968
     * @param array|string $on the join condition that should appear in the ON part.
969
     * Please refer to {@see join()} on how to specify this parameter.
970
     * @param array $params the parameters (name => value) to be bound to the query.
971
     *
972
     * @return $this the query object itself.
973
     */
974 5
    public function leftJoin($table, $on = '', array $params = []): self
975
    {
976 5
        $this->join[] = ['LEFT JOIN', $table, $on];
977
978 5
        return $this->addParams($params);
979
    }
980
981
    /**
982
     * Appends a RIGHT OUTER JOIN part to the query.
983
     *
984
     * @param array|string $table the table to be joined.
985
     * Use a string to represent the name of the table to be joined.
986
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
987
     * The method will automatically quote the table name unless it contains some parenthesis (which means the table is
988
     * given as a sub-query or DB expression).
989
     * Use an array to represent joining with a sub-query. The array must contain only one element.
990
     * The value must be a {@see Query} object representing the sub-query while the corresponding key represents the
991
     * alias for the sub-query.
992
     * @param array|string $on the join condition that should appear in the ON part.
993
     * Please refer to {@see join()} on how to specify this parameter.
994
     * @param array $params the parameters (name => value) to be bound to the query.
995
     *
996
     * @return $this the query object itself.
997
     */
998
    public function rightJoin($table, $on = '', array $params = []): self
999
    {
1000
        $this->join[] = ['RIGHT JOIN', $table, $on];
1001
1002
        return $this->addParams($params);
1003
    }
1004
1005
    /**
1006
     * Sets the GROUP BY part of the query.
1007
     *
1008
     * @param array|ExpressionInterface|string $columns the columns to be grouped by.
1009
     * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
1010
     * The method will automatically quote the column names unless a column contains some parenthesis (which means the
1011
     * column contains a DB expression).
1012
     *
1013
     * Note that if your group-by is an expression containing commas, you should always use an array to represent the
1014
     * group-by information. Otherwise, the method will not be able to correctly determine the group-by columns.
1015
     *
1016
     * {@see ExpressionInterface} object can be passed to specify the GROUP BY part explicitly in plain SQL.
1017
     * {@see ExpressionInterface} object can be passed as well.
1018
     *
1019
     * @return $this the query object itself.
1020
     *
1021
     * {@see addGroupBy()}
1022
     */
1023 637
    public function groupBy($columns): self
1024
    {
1025 637
        if ($columns instanceof ExpressionInterface) {
1026 5
            $columns = [$columns];
1027 637
        } elseif (!is_array($columns)) {
1028 25
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
1029
        }
1030 637
        $this->groupBy = $columns;
1031
1032 637
        return $this;
1033
    }
1034
1035
    /**
1036
     * Adds additional group-by columns to the existing ones.
1037
     *
1038
     * @param array|string $columns additional columns to be grouped by.
1039
     * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
1040
     * The method will automatically quote the column names unless a column contains some parenthesis (which means the
1041
     * column contains a DB expression).
1042
     *
1043
     * Note that if your group-by is an expression containing commas, you should always use an array to represent the
1044
     * group-by information. Otherwise, the method will not be able to correctly determine the group-by columns.
1045
     *
1046
     * {@see Expression} object can be passed to specify the GROUP BY part explicitly in plain SQL.
1047
     * {@see ExpressionInterface} object can be passed as well.
1048
     *
1049
     * @return $this the query object itself
1050
     *
1051
     * {@see groupBy()}
1052
     */
1053 5
    public function addGroupBy($columns): self
1054
    {
1055 5
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
1056
            $columns = [$columns];
1057 5
        } elseif (!is_array($columns)) {
1058 5
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
1059
        }
1060 5
        if ($this->groupBy === null) {
1061
            $this->groupBy = $columns;
1062
        } else {
1063 5
            $this->groupBy = array_merge($this->groupBy, $columns);
1064
        }
1065
1066 5
        return $this;
1067
    }
1068
1069
    /**
1070
     * Sets the HAVING part of the query.
1071
     *
1072
     * @param array|ExpressionInterface|string $condition the conditions to be put after HAVING.
1073
     * Please refer to {@see where()} on how to specify this parameter.
1074
     * @param array $params the parameters (name => value) to be bound to the query.
1075
     *
1076
     * @return $this the query object itself.
1077
     *
1078
     * {@see andHaving()}
1079
     * {@see orHaving()}
1080
     */
1081 637
    public function having($condition, array $params = []): self
1082
    {
1083 637
        $this->having = $condition;
1084 637
        $this->addParams($params);
1085
1086 637
        return $this;
1087
    }
1088
1089
    /**
1090
     * Adds an additional HAVING condition to the existing one.
1091
     * The new condition and the existing one will be joined using the `AND` operator.
1092
     *
1093
     * @param array|ExpressionInterface|string $condition the new HAVING condition. Please refer to {@see where()}
1094
     * on how to specify this parameter.
1095
     * @param array $params the parameters (name => value) to be bound to the query.
1096
     *
1097
     * @return $this the query object itself.
1098
     *
1099
     * {@see having()}
1100
     * {@see orHaving()}
1101
     */
1102 5
    public function andHaving($condition, array $params = []): self
1103
    {
1104 5
        if ($this->having === null) {
1105
            $this->having = $condition;
1106
        } else {
1107 5
            $this->having = ['and', $this->having, $condition];
1108
        }
1109
1110 5
        $this->addParams($params);
1111
1112 5
        return $this;
1113
    }
1114
1115
    /**
1116
     * Adds an additional HAVING condition to the existing one.
1117
     *
1118
     * The new condition and the existing one will be joined using the `OR` operator.
1119
     *
1120
     * @param array|ExpressionInterface|string $condition the new HAVING condition. Please refer to {@see where()}
1121
     * on how to specify this parameter.
1122
     * @param array $params the parameters (name => value) to be bound to the query.
1123
     *
1124
     * @return $this the query object itself.
1125
     *
1126
     * {@see having()}
1127
     * {@see andHaving()}
1128
     */
1129 5
    public function orHaving($condition, $params = []): self
1130
    {
1131 5
        if ($this->having === null) {
1132
            $this->having = $condition;
1133
        } else {
1134 5
            $this->having = ['or', $this->having, $condition];
1135
        }
1136
1137 5
        $this->addParams($params);
1138
1139 5
        return $this;
1140
    }
1141
1142
    /**
1143
     * Sets the HAVING part of the query but ignores {@see isEmpty()|empty operands}.
1144
     *
1145
     * This method is similar to {@see having()}. The main difference is that this method will remove
1146
     * {@see isEmpty()|empty query operands}. As a result, this method is best suited for building query conditions
1147
     * based on filter values entered by users.
1148
     *
1149
     * The following code shows the difference between this method and {@see having()}:
1150
     *
1151
     * ```php
1152
     * // HAVING `age`=:age
1153
     * $query->filterHaving(['name' => null, 'age' => 20]);
1154
     * // HAVING `age`=:age
1155
     * $query->having(['age' => 20]);
1156
     * // HAVING `name` IS NULL AND `age`=:age
1157
     * $query->having(['name' => null, 'age' => 20]);
1158
     * ```
1159
     *
1160
     * Note that unlike {@see having()}, you cannot pass binding parameters to this method.
1161
     *
1162
     * @param array $condition the conditions that should be put in the HAVING part.
1163
     * See {@see having()} on how to specify this parameter.
1164
     *
1165
     * @throws NotSupportedException
1166
     *
1167
     * @return $this the query object itself.
1168
     *
1169
     * {@see having()}
1170
     * {@see andFilterHaving()}
1171
     * {@see orFilterHaving()}
1172
     */
1173 10
    public function filterHaving(array $condition): self
1174
    {
1175 10
        $condition = $this->filterCondition($condition);
1176
1177 10
        if ($condition !== []) {
1178 10
            $this->having($condition);
1179
        }
1180
1181 10
        return $this;
1182
    }
1183
1184
    /**
1185
     * Adds an additional HAVING condition to the existing one but ignores {@see isEmpty()|empty operands}.
1186
     *
1187
     * The new condition and the existing one will be joined using the `AND` operator.
1188
     *
1189
     * This method is similar to {@see andHaving()}. The main difference is that this method will remove
1190
     * {@see isEmpty()|empty query operands}. As a result, this method is best suited for building query conditions
1191
     * based on filter values entered by users.
1192
     *
1193
     * @param array $condition the new HAVING condition. Please refer to {@see having()} on how to specify this
1194
     * parameter.
1195
     *
1196
     * @throws NotSupportedException
1197
     *
1198
     * @return $this the query object itself.
1199
     *
1200
     * {@see filterHaving()}
1201
     * {@see orFilterHaving()}
1202
     */
1203 10
    public function andFilterHaving(array $condition): self
1204
    {
1205 10
        $condition = $this->filterCondition($condition);
1206
1207 10
        if ($condition !== []) {
1208
            $this->andHaving($condition);
1209
        }
1210
1211 10
        return $this;
1212
    }
1213
1214
    /**
1215
     * Adds an additional HAVING condition to the existing one but ignores {@see isEmpty()|empty operands}.
1216
     *
1217
     * The new condition and the existing one will be joined using the `OR` operator.
1218
     *
1219
     * This method is similar to {@see orHaving()}. The main difference is that this method will remove
1220
     * {@see isEmpty()|empty query operands}. As a result, this method is best suited for building query conditions
1221
     * based on filter values entered by users.
1222
     *
1223
     * @param array $condition the new HAVING condition. Please refer to {@see having()} on how to specify this
1224
     * parameter.
1225
     *
1226
     * @throws NotSupportedException
1227
     *
1228
     * @return $this the query object itself.
1229
     *
1230
     * {@see filterHaving()}
1231
     * {@see andFilterHaving()}
1232
     */
1233 10
    public function orFilterHaving(array $condition): self
1234
    {
1235 10
        $condition = $this->filterCondition($condition);
1236
1237 10
        if ($condition !== []) {
1238
            $this->orHaving($condition);
1239
        }
1240
1241 10
        return $this;
1242
    }
1243
1244
    /**
1245
     * Appends a SQL statement using UNION operator.
1246
     *
1247
     * @param Query|string $sql the SQL statement to be appended using UNION.
1248
     * @param bool $all TRUE if using UNION ALL and FALSE if using UNION.
1249
     *
1250
     * @return $this the query object itself.
1251
     */
1252 17
    public function union($sql, $all = false): self
1253
    {
1254 17
        $this->union[] = ['query' => $sql, 'all' => $all];
1255
1256 17
        return $this;
1257
    }
1258
1259
    /**
1260
     * Sets the parameters to be bound to the query.
1261
     *
1262
     * @param array $params list of query parameter values indexed by parameter placeholders.
1263
     * For example, `[':name' => 'Dan', ':age' => 31]`.
1264
     *
1265
     * @return $this the query object itself.
1266
     *
1267
     * {@see addParams()}
1268
     */
1269 640
    public function params(array $params): self
1270
    {
1271 640
        $this->params = $params;
1272
1273 640
        return $this;
1274
    }
1275
1276
    /**
1277
     * Adds additional parameters to be bound to the query.
1278
     *
1279
     * @param array $params list of query parameter values indexed by parameter placeholders.
1280
     * For example, `[':name' => 'Dan', ':age' => 31]`.
1281
     *
1282
     * @return $this the query object itself.
1283
     *
1284
     * {@see params()}
1285
     */
1286 1459
    public function addParams(array $params): self
1287
    {
1288 1459
        if (!empty($params)) {
1289 62
            if (empty($this->params)) {
1290 62
                $this->params = $params;
1291
            } else {
1292 11
                foreach ($params as $name => $value) {
1293 11
                    if (is_int($name)) {
1294
                        $this->params[] = $value;
1295
                    } else {
1296 11
                        $this->params[$name] = $value;
1297
                    }
1298
                }
1299
            }
1300
        }
1301
1302 1459
        return $this;
1303
    }
1304
1305
    /**
1306
     * Enables query cache for this Query.
1307
     *
1308
     * @param int|true $duration the number of seconds that query results can remain valid in cache.
1309
     * Use 0 to indicate that the cached data will never expire.
1310
     * Use a negative number to indicate that query cache should not be used.
1311
     * Use boolean `true` to indicate that {@see ConnectionInterface::queryCacheDuration} should be used.
1312
     * Defaults to `true`.
1313
     * @param Dependency|null $dependency the cache dependency associated with the cached result.
1314
     *
1315
     * @return $this the Query object itself.
1316
     */
1317 5
    public function cache($duration = true, ?Dependency $dependency = null): self
1318
    {
1319 5
        $this->queryCacheDuration = $duration;
1320 5
        $this->queryCacheDependency = $dependency;
1321
1322 5
        return $this;
1323
    }
1324
1325
    /**
1326
     * Disables query cache for this Query.
1327
     *
1328
     * @return $this the Query object itself.
1329
     */
1330 5
    public function noCache(): self
1331
    {
1332 5
        $this->queryCacheDuration = -1;
1333
1334 5
        return $this;
1335
    }
1336
1337
    /**
1338
     * Sets $command cache, if this query has enabled caching.
1339
     *
1340
     * @param Command $command
1341
     *
1342
     * @return Command
1343
     */
1344 664
    protected function setCommandCache(Command $command): Command
1345
    {
1346 664
        if ($this->queryCacheDuration !== null || $this->queryCacheDependency !== null) {
1347 5
            $duration = $this->queryCacheDuration === true ? null : $this->queryCacheDuration;
1348 5
            $command->cache($duration, $this->queryCacheDependency);
1349
        }
1350
1351 664
        return $command;
1352
    }
1353
1354
    /**
1355
     * Creates a new Query object and copies its property values from an existing one.
1356
     *
1357
     * The properties being copies are the ones to be used by query builders.
1358
     *
1359
     * @param ConnectionInterface $db the source query object.
1360
     * @param Query $from the source query object.
1361
     *
1362
     * @return $this the new Query object.
1363
     */
1364 622
    public static function create(ConnectionInterface $db, self $from): self
1365
    {
1366 622
        return (new self($db))
1367 622
            ->where($from->where)
1368 622
            ->limit($from->limit)
1369 622
            ->offset($from->offset)
1370 622
            ->orderBy($from->orderBy)
1371 622
            ->indexBy($from->indexBy)
1372 622
            ->select($from->select)
1373 622
            ->selectOption($from->selectOption)
1374 622
            ->distinct($from->distinct)
1375 622
            ->from($from->from)
1376 622
            ->groupBy($from->groupBy)
1377 622
            ->setJoin($from->join)
1378 622
            ->having($from->having)
1379 622
            ->setUnion($from->union)
1380 622
            ->params($from->params)
1381 622
            ->withQueries($from->withQueries);
1382
    }
1383
1384
    /**
1385
     * Returns the SQL representation of Query.
1386
     *
1387
     * @return string
1388
     */
1389
    public function __toString(): string
1390
    {
1391
        return serialize($this);
1392
    }
1393
1394 1517
    public function getDistinct(): ?bool
1395
    {
1396 1517
        return $this->distinct;
1397
    }
1398
1399 1553
    public function getFrom()
1400
    {
1401 1553
        return $this->from;
1402
    }
1403
1404 1522
    public function getGroupBy(): array
1405
    {
1406 1522
        return $this->groupBy;
1407
    }
1408
1409 1532
    public function getHaving()
1410
    {
1411 1532
        return $this->having;
1412
    }
1413
1414 1522
    public function getJoin(): array
1415
    {
1416 1522
        return $this->join;
1417
    }
1418
1419 1557
    public function getParams(): array
1420
    {
1421 1557
        return $this->params;
1422
    }
1423
1424 1537
    public function getSelect(): array
1425
    {
1426 1537
        return $this->select;
1427
    }
1428
1429 1517
    public function getSelectOption(): ?string
1430
    {
1431 1517
        return $this->selectOption;
1432
    }
1433
1434 1517
    public function getUnion(): array
1435
    {
1436 1517
        return $this->union;
1437
    }
1438
1439 1512
    public function getWithQueries(): array
1440
    {
1441 1512
        return $this->withQueries;
1442
    }
1443
1444
    /**
1445
     * Prepends a SQL statement using WITH syntax.
1446
     *
1447
     * @param Query|string $query the SQL statement to be appended using UNION.
1448
     * @param string $alias query alias in WITH construction.
1449
     * @param bool $recursive TRUE if using WITH RECURSIVE and FALSE if using WITH.
1450
     *
1451
     * @return $this the query object itself.
1452
     */
1453 15
    public function withQuery($query, string $alias, bool $recursive = false): self
1454
    {
1455 15
        $this->withQueries[] = ['query' => $query, 'alias' => $alias, 'recursive' => $recursive];
1456
1457 15
        return $this;
1458
    }
1459
1460
    /**
1461
     * This function can be overridden to customize the returned class.
1462
     *
1463
     * @param ConnectionInterface $value
1464
     *
1465
     * @return $this
1466
     */
1467 10
    protected static function createInstance(ConnectionInterface $value): self
1468
    {
1469 10
        return new self($value);
1470
    }
1471
1472 622
    public function selectOption(?string $value): self
1473
    {
1474 622
        $this->selectOption = $value;
1475
1476 622
        return $this;
1477
    }
1478
1479 622
    public function setJoin(array $value): self
1480
    {
1481 622
        $this->join = $value;
1482
1483 622
        return $this;
1484
    }
1485
1486 622
    public function setUnion(array $value): self
1487
    {
1488 622
        $this->union = $value;
1489
1490 622
        return $this;
1491
    }
1492
1493 622
    public function withQueries(array $value): self
1494
    {
1495 622
        $this->withQueries = $value;
1496
1497 622
        return $this;
1498
    }
1499
}
1500