Completed
Pull Request — 2.1 (#15718)
by Alex
17:00
created

QueryBuilder::truncateTable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db;
9
10
use yii\base\InvalidArgumentException;
11
use yii\base\NotSupportedException;
12
use yii\db\conditions\ConditionInterface;
13
use yii\db\conditions\HashCondition;
14
use yii\helpers\StringHelper;
15
16
/**
17
 * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[Query]] object.
18
 *
19
 * SQL statements are created from [[Query]] objects using the [[build()]]-method.
20
 *
21
 * QueryBuilder is also used by [[Command]] to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE.
22
 *
23
 * For more details and usage information on QueryBuilder, see the [guide article on query builders](guide:db-query-builder).
24
 *
25
 * @property string[] $expressionBuilders Array of builders that should be merged with the pre-defined ones in
26
 * [[expressionBuilders]] property. This property is write-only.
27
 *
28
 * @author Qiang Xue <[email protected]>
29
 * @since 2.0
30
 */
31
class QueryBuilder extends \yii\base\BaseObject
32
{
33
    /**
34
     * The prefix for automatically generated query binding parameters.
35
     */
36
    const PARAM_PREFIX = ':qp';
37
38
    /**
39
     * @var Connection the database connection.
40
     */
41
    public $db;
42
    /**
43
     * @var string the separator between different fragments of a SQL statement.
44
     * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
45
     */
46
    public $separator = ' ';
47
    /**
48
     * @var array the abstract column types mapped to physical column types.
49
     * This is mainly used to support creating/modifying tables using DB-independent data type specifications.
50
     * Child classes should override this property to declare supported type mappings.
51
     */
52
    public $typeMap = [];
53
54
    /**
55
     * @var array map of query condition to builder methods.
56
     * These methods are used by [[buildCondition]] to build SQL conditions from array syntax.
57
     * @deprecated since 2.0.14. Is not used, will be dropped in 2.1.0.
58
     */
59
    protected $conditionBuilders = [];
60
61
    /**
62
     * @var array map of condition aliases to condition classes. For example:
63
     *
64
     * ```php
65
     * return [
66
     *     'LIKE' => yii\db\condition\LikeCondition::class,
67
     * ];
68
     * ```
69
     *
70
     * This property is used by [[createConditionFromArray]] method.
71
     * See default condition classes list in [[defaultConditionClasses()]] method.
72
     *
73
     * In case you want to add custom conditions support, use the [[setConditionClasses()]] method.
74
     *
75
     * @see setConditonClasses()
76
     * @see defaultConditionClasses()
77
     * @since 2.0.14
78
     */
79
    protected $conditionClasses = [];
80
81
    /**
82
     * @var string[]|ExpressionBuilderInterface[] maps expression class to expression builder class.
83
     * For example:
84
     *
85
     * ```php
86
     * [
87
     *    yii\db\Expression::class => yii\db\ExpressionBuilder::class
88
     * ]
89
     * ```
90
     * This property is mainly used by [[buildExpression()]] to build SQL expressions form expression objects.
91
     * See default values in [[defaultExpressionBuilders()]] method.
92
     *
93
     *
94
     * To override existing builders or add custom, use [[setExpressionBuilder()]] method. New items will be added
95
     * to the end of this array.
96
     *
97
     * To find a builder, [[buildExpression()]] will check the expression class for its exact presence in this map.
98
     * In case it is NOT present, the array will be iterated in reverse direction, checking whether the expression
99
     * extends the class, defined in this map.
100
     *
101
     * @see setExpressionBuilders()
102
     * @see defaultExpressionBuilders()
103
     * @since 2.0.14
104
     */
105
    protected $expressionBuilders = [];
106
107
    /**
108
     * Constructor.
109
     * @param Connection $connection the database connection.
110
     * @param array $config name-value pairs that will be used to initialize the object properties
111
     */
112 1418
    public function __construct($connection, $config = [])
113
    {
114 1418
        $this->db = $connection;
115 1418
        parent::__construct($config);
116 1418
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121 1418
    public function init()
122
    {
123 1418
        parent::init();
124
125 1418
        $this->expressionBuilders = array_merge($this->defaultExpressionBuilders(), $this->expressionBuilders);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($this->defau...is->expressionBuilders) of type array is incompatible with the declared type array<integer,string|obj...ssionBuilderInterface>> of property $expressionBuilders.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
126 1418
        $this->conditionClasses = array_merge($this->defaultConditionClasses(), $this->conditionClasses);
127 1418
    }
128
129
    /**
130
     * Contains array of default condition classes. Extend this method, if you want to change
131
     * default condition classes for the query builder. See [[conditionClasses]] docs for details.
132
     *
133
     * @return array
134
     * @see conditionClasses
135
     * @since 2.0.14
136
     */
137 1418
    protected function defaultConditionClasses()
138
    {
139
        return [
140 1418
            'NOT' => conditions\NotCondition::class,
141
            'AND' => conditions\AndCondition::class,
142
            'OR' => conditions\OrCondition::class,
143
            'BETWEEN' => conditions\BetweenCondition::class,
144
            'NOT BETWEEN' => conditions\BetweenCondition::class,
145
            'IN' => conditions\InCondition::class,
146
            'NOT IN' => conditions\InCondition::class,
147
            'LIKE' => conditions\LikeCondition::class,
148
            'NOT LIKE' => conditions\LikeCondition::class,
149
            'OR LIKE' => conditions\LikeCondition::class,
150
            'OR NOT LIKE' => conditions\LikeCondition::class,
151
            'EXISTS' => conditions\ExistsCondition::class,
152
            'NOT EXISTS' => conditions\ExistsCondition::class,
153
        ];
154
    }
155
156
    /**
157
     * Contains array of default expression builders. Extend this method and override it, if you want to change
158
     * default expression builders for this query builder. See [[expressionBuilders]] docs for details.
159
     *
160
     * @return array
161
     * @see $expressionBuilders
162
     * @since 2.0.14
163
     */
164 1418
    protected function defaultExpressionBuilders()
165
    {
166
        return [
167 1418
            Query::class => QueryExpressionBuilder::class,
168
            PdoValue::class => PdoValueBuilder::class,
169
            Expression::class => ExpressionBuilder::class,
170
            conditions\ConjunctionCondition::class => conditions\ConjunctionConditionBuilder::class,
171
            conditions\NotCondition::class => conditions\NotConditionBuilder::class,
172
            conditions\AndCondition::class => conditions\ConjunctionConditionBuilder::class,
173
            conditions\OrCondition::class => conditions\ConjunctionConditionBuilder::class,
174
            conditions\BetweenCondition::class => conditions\BetweenConditionBuilder::class,
175
            conditions\InCondition::class => conditions\InConditionBuilder::class,
176
            conditions\LikeCondition::class => conditions\LikeConditionBuilder::class,
177
            conditions\ExistsCondition::class => conditions\ExistsConditionBuilder::class,
178
            conditions\SimpleCondition::class => conditions\SimpleConditionBuilder::class,
179
            conditions\HashCondition::class => conditions\HashConditionBuilder::class,
180
            conditions\BetweenColumnsCondition::class => conditions\BetweenColumnsConditionBuilder::class,
181
        ];
182
    }
183
184
    /**
185
     * Setter for [[expressionBuilders]] property.
186
     *
187
     * @param string[] $builders array of builder that should be merged with [[expressionBuilders]]
188
     * @since 2.0.14
189
     * @see expressionBuilders
190
     */
191
    public function setExpressionBuilders($builders)
192
    {
193
        $this->expressionBuilders = array_merge($this->expressionBuilders, $builders);
194
    }
195
196
    /**
197
     * Setter for [[conditionClasses]] property.
198
     *
199
     * @param string[] $classes map of condition aliases to condition classes. For example:
200
     *
201
     * ```php
202
     * ['LIKE' => yii\db\condition\LikeCondition::class]
203
     * ```
204
     *
205
     * @since 2.0.14.2
206
     * @see conditionClasses
207
     */
208
    public function setConditionClasses($classes)
209
    {
210
        $this->conditionClasses = array_merge($this->conditionClasses, $classes);
211
    }
212
213
    /**
214
     * Generates a SELECT SQL statement from a [[Query]] object.
215
     *
216
     * @param Query $query the [[Query]] object from which the SQL statement will be generated.
217
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
218
     * be included in the result with the additional parameters generated during the query building process.
219
     * @return array the generated SQL statement (the first array element) and the corresponding
220
     * parameters to be bound to the SQL statement (the second array element). The parameters returned
221
     * include those provided in `$params`.
222
     */
223 815
    public function build($query, $params = [])
224
    {
225 815
        $query = $query->prepare($this);
226
227 815
        $params = empty($params) ? $query->params : array_merge($params, $query->params);
228
229
        $clauses = [
230 815
            $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
231 815
            $this->buildFrom($query->from, $params),
232 815
            $this->buildJoin($query->join, $params),
233 815
            $this->buildWhere($query->where, $params),
234 815
            $this->buildGroupBy($query->groupBy, $params),
235 815
            $this->buildHaving($query->having, $params),
236
        ];
237
238 815
        $sql = implode($this->separator, array_filter($clauses));
239 815
        $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset, $params);
240
241 815
        $union = $this->buildUnion($query->union, $params);
242 815
        if ($union !== '') {
243 8
            $sql = "($sql){$this->separator}$union";
244
        }
245
246 815
        return [$sql, $params];
247
    }
248
249
    /**
250
     * Builds given $expression.
251
     *
252
     * @param ExpressionInterface $expression the expression to be built
253
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
254
     * be included in the result with the additional parameters generated during the expression building process.
255
     * @return string the SQL statement that will not be neither quoted nor encoded before passing to DBMS
256
     * @see ExpressionInterface
257
     * @see ExpressionBuilderInterface
258
     * @see expressionBuilders
259
     * @since 2.0.14
260
     * @throws InvalidArgumentException when $expression building is not supported by this QueryBuilder.
261
     */
262 1047
    public function buildExpression(ExpressionInterface $expression, &$params = [])
263
    {
264 1047
        $builder = $this->getExpressionBuilder($expression);
265
266 1047
        return $builder->build($expression, $params);
267
    }
268
269
    /**
270
     * Gets object of [[ExpressionBuilderInterface]] that is suitable for $expression.
271
     * Uses [[expressionBuilders]] array to find a suitable builder class.
272
     *
273
     * @param ExpressionInterface $expression
274
     * @return ExpressionBuilderInterface
275
     * @see expressionBuilders
276
     * @since 2.0.14
277
     * @throws InvalidArgumentException when $expression building is not supported by this QueryBuilder.
278
     */
279 1047
    public function getExpressionBuilder(ExpressionInterface $expression)
280
    {
281 1047
        $className = get_class($expression);
282
283 1047
        if (!isset($this->expressionBuilders[$className])) {
284
            foreach (array_reverse($this->expressionBuilders) as $expressionClass => $builderClass) {
285
                if (is_subclass_of($expression, $expressionClass)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $expressionClass can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
286
                    $this->expressionBuilders[$className] = $builderClass;
287
                    break;
288
                }
289
            }
290
291
            if (!isset($this->expressionBuilders[$className])) {
292
                throw new InvalidArgumentException('Expression of class ' . $className . ' can not be built in ' . get_class($this));
293
            }
294
        }
295
296 1047
        if ($this->expressionBuilders[$className] === __CLASS__) {
297
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (yii\db\QueryBuilder) is incompatible with the return type documented by yii\db\QueryBuilder::getExpressionBuilder of type yii\db\ExpressionBuilderInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
298
        }
299
300 1047
        if (!is_object($this->expressionBuilders[$className])) {
301 1003
            $this->expressionBuilders[$className] = new $this->expressionBuilders[$className]($this);
302
        }
303
304 1047
        return $this->expressionBuilders[$className];
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->expressionBuilders[$className]; of type string|object adds the type string to the return on line 304 which is incompatible with the return type documented by yii\db\QueryBuilder::getExpressionBuilder of type yii\db\ExpressionBuilderInterface.
Loading history...
305
    }
306
307
    /**
308
     * Creates an INSERT SQL statement.
309
     * For example,.
310
     *
311
     * ```php
312
     * $sql = $queryBuilder->insert('user', [
313
     *     'name' => 'Sam',
314
     *     'age' => 30,
315
     * ], $params);
316
     * ```
317
     *
318
     * The method will properly escape the table and column names.
319
     *
320
     * @param string $table the table that new rows will be inserted into.
321
     * @param array|Query $columns the column data (name => value) to be inserted into the table or instance
322
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
323
     * Passing of [[yii\db\Query|Query]] is available since version 2.0.11.
324
     * @param array $params the binding parameters that will be generated by this method.
325
     * They should be bound to the DB command later.
326
     * @return string the INSERT SQL
327
     */
328 534
    public function insert($table, $columns, &$params)
329
    {
330 534
        [$names, $placeholders, $values, $params] = $this->prepareInsertValues($table, $columns, $params);
0 ignored issues
show
Bug introduced by
The variable $names does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $placeholders does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $values does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
331 525
        return 'INSERT INTO ' . $this->db->quoteTableName($table)
332 525
            . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '')
333 525
            . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : $values);
334
    }
335
336
    /**
337
     * Prepares a `VALUES` part for an `INSERT` SQL statement.
338
     *
339
     * @param string $table the table that new rows will be inserted into.
340
     * @param array|Query $columns the column data (name => value) to be inserted into the table or instance
341
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
342
     * @param array $params the binding parameters that will be generated by this method.
343
     * They should be bound to the DB command later.
344
     * @return array array of column names, placeholders, values and params.
345
     * @since 2.0.14
346
     */
347 548
    protected function prepareInsertValues($table, $columns, $params = [])
348
    {
349 548
        $schema = $this->db->getSchema();
350 548
        $tableSchema = $schema->getTableSchema($table);
351 548
        $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
352 548
        $names = [];
353 548
        $placeholders = [];
354 548
        $values = ' DEFAULT VALUES';
355 548
        if ($columns instanceof Query) {
356 42
            [$names, $values, $params] = $this->prepareInsertSelectSubQuery($columns, $schema, $params);
357
        } else {
358 512
            foreach ($columns as $name => $value) {
359 508
                $names[] = $schema->quoteColumnName($name);
360 508
                $value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
361
362 508
                if ($value instanceof ExpressionInterface) {
363 118
                    $placeholders[] = $this->buildExpression($value, $params);
364 501
                } elseif ($value instanceof \yii\db\Query) {
365
                    [$sql, $params] = $this->build($value, $params);
0 ignored issues
show
Bug introduced by
The variable $sql does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
366
                    $placeholders[] = "($sql)";
367
                } else {
368 508
                    $placeholders[] = $this->bindParam($value, $params);
369
                }
370
            }
371
        }
372 539
        return [$names, $placeholders, $values, $params];
373
    }
374
375
    /**
376
     * Prepare select-subquery and field names for INSERT INTO ... SELECT SQL statement.
377
     *
378
     * @param Query $columns Object, which represents select query.
379
     * @param \yii\db\Schema $schema Schema object to quote column name.
380
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
381
     * be included in the result with the additional parameters generated during the query building process.
382
     * @return array array of column names, values and params.
383
     * @throws InvalidArgumentException if query's select does not contain named parameters only.
384
     * @since 2.0.11
385
     */
386 42
    protected function prepareInsertSelectSubQuery($columns, $schema, $params = [])
387
    {
388 42
        if (!is_array($columns->select) || empty($columns->select) || in_array('*', $columns->select)) {
389 9
            throw new InvalidArgumentException('Expected select query object with enumerated (named) parameters');
390
        }
391
392 33
        [$values, $params] = $this->build($columns, $params);
0 ignored issues
show
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
393 33
        $names = [];
394 33
        $values = ' ' . $values;
0 ignored issues
show
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
395 33
        foreach ($columns->select as $title => $field) {
396 33
            if (is_string($title)) {
397 27
                $names[] = $schema->quoteColumnName($title);
398 24
            } elseif (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $field, $matches)) {
399 3
                $names[] = $schema->quoteColumnName($matches[2]);
400
            } else {
401 33
                $names[] = $schema->quoteColumnName($field);
402
            }
403
        }
404
405 33
        return [$names, $values, $params];
406
    }
407
408
    /**
409
     * Generates a batch INSERT SQL statement.
410
     *
411
     * For example,
412
     *
413
     * ```php
414
     * $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [
415
     *     ['Tom', 30],
416
     *     ['Jane', 20],
417
     *     ['Linda', 25],
418
     * ]);
419
     * ```
420
     *
421
     * Note that the values in each row must match the corresponding column names.
422
     *
423
     * The method will properly escape the column names, and quote the values to be inserted.
424
     *
425
     * @param string $table the table that new rows will be inserted into.
426
     * @param array $columns the column names
427
     * @param array|\Generator $rows the rows to be batch inserted into the table
428
     * @return string the batch INSERT SQL statement
429
     */
430 26
    public function batchInsert($table, $columns, $rows)
431
    {
432 26
        if (empty($rows)) {
433 2
            return '';
434
        }
435
436 25
        $schema = $this->db->getSchema();
437 25
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
438 19
            $columnSchemas = $tableSchema->columns;
439
        } else {
440 6
            $columnSchemas = [];
441
        }
442
443 25
        $values = [];
444 25
        foreach ($rows as $row) {
445 23
            $vs = [];
446 23
            foreach ($row as $i => $value) {
447 23
                if (isset($columns[$i], $columnSchemas[$columns[$i]])) {
448 14
                    $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
449
                }
450 23
                if (is_string($value)) {
451 17
                    $value = $schema->quoteValue($value);
452 13
                } elseif (is_float($value)) {
453
                    // ensure type cast always has . as decimal separator in all locales
454 2
                    $value = StringHelper::floatToString($value);
455 13
                } elseif ($value === false) {
456 4
                    $value = 0;
457 13
                } elseif ($value === null) {
458 8
                    $value = 'NULL';
459
                }
460 23
                $vs[] = $value;
461
            }
462 23
            $values[] = '(' . implode(', ', $vs) . ')';
463
        }
464 25
        if (empty($values)) {
465 2
            return '';
466
        }
467
468 23
        foreach ($columns as $i => $name) {
469 21
            $columns[$i] = $schema->quoteColumnName($name);
470
        }
471
472 23
        return 'INSERT INTO ' . $schema->quoteTableName($table)
473 23
        . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
474
    }
475
476
    /**
477
     * Creates an SQL statement to insert rows into a database table if
478
     * they do not already exist (matching unique constraints),
479
     * or update them if they do.
480
     *
481
     * For example,
482
     *
483
     * ```php
484
     * $sql = $queryBuilder->upsert('pages', [
485
     *     'name' => 'Front page',
486
     *     'url' => 'http://example.com/', // url is unique
487
     *     'visits' => 0,
488
     * ], [
489
     *     'visits' => new \yii\db\Expression('visits + 1'),
490
     * ], $params);
491
     * ```
492
     *
493
     * The method will properly escape the table and column names.
494
     *
495
     * @param string $table the table that new rows will be inserted into/updated in.
496
     * @param array|Query $insertColumns the column data (name => value) to be inserted into the table or instance
497
     * of [[Query]] to perform `INSERT INTO ... SELECT` SQL statement.
498
     * @param array|bool $updateColumns the column data (name => value) to be updated if they already exist.
499
     * If `true` is passed, the column data will be updated to match the insert column data.
500
     * If `false` is passed, no update will be performed if the column data already exists.
501
     * @param array $params the binding parameters that will be generated by this method.
502
     * They should be bound to the DB command later.
503
     * @return string the resulting SQL.
504
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
505
     * @since 2.0.14
506
     */
507
    public function upsert($table, $insertColumns, $updateColumns, &$params)
508
    {
509
        throw new NotSupportedException($this->db->getDriverName() . ' does not support upsert statements.');
510
    }
511
512
    /**
513
     * @param string $table
514
     * @param array|Query $insertColumns
515
     * @param array|bool $updateColumns
516
     * @param Constraint[] $constraints this parameter recieves a matched constraint list.
517
     * The constraints will be unique by their column names.
518
     * @return array
519
     * @since 2.0.14
520
     */
521 66
    protected function prepareUpsertColumns($table, $insertColumns, $updateColumns, &$constraints = [])
522
    {
523 66
        if ($insertColumns instanceof Query) {
524 24
            [$insertNames] = $this->prepareInsertSelectSubQuery($insertColumns, $this->db->getSchema());
0 ignored issues
show
Bug introduced by
The variable $insertNames seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
525
        } else {
526 42
            $insertNames = array_map([$this->db, 'quoteColumnName'], array_keys($insertColumns));
527
        }
528 66
        $uniqueNames = $this->getTableUniqueColumnNames($table, $insertNames, $constraints);
0 ignored issues
show
Bug introduced by
The variable $insertNames does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
529 66
        $uniqueNames = array_map([$this->db, 'quoteColumnName'], $uniqueNames);
530 66
        if ($updateColumns !== true) {
531 36
            return [$uniqueNames, $insertNames, null];
532
        }
533
534 30
        return [$uniqueNames, $insertNames, array_diff($insertNames, $uniqueNames)];
535
    }
536
537
    /**
538
     * Returns all column names belonging to constraints enforcing uniqueness (`PRIMARY KEY`, `UNIQUE INDEX`, etc.)
539
     * for the named table removing constraints which did not cover the specified column list.
540
     * The column list will be unique by column names.
541
     *
542
     * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
543
     * @param string[] $columns source column list.
544
     * @param Constraint[] $constraints this parameter optionally recieves a matched constraint list.
545
     * The constraints will be unique by their column names.
546
     * @return string[] column list.
547
     */
548 66
    private function getTableUniqueColumnNames($name, $columns, &$constraints = [])
549
    {
550 66
        $schema = $this->db->getSchema();
551 66
        if (!$schema instanceof ConstraintFinderInterface) {
552
            return [];
553
        }
554
555 66
        $constraints = [];
556 66
        $primaryKey = $schema->getTablePrimaryKey($name);
557 66
        if ($primaryKey !== null) {
558 49
            $constraints[] = $primaryKey;
559
        }
560 66
        foreach ($schema->getTableIndexes($name) as $constraint) {
561 66
            if ($constraint->isUnique) {
562 66
                $constraints[] = $constraint;
563
            }
564
        }
565 66
        $constraints = array_merge($constraints, $schema->getTableUniques($name));
566
        // Remove duplicates
567 66
        $constraints = array_combine(array_map(function (Constraint $constraint) {
568 66
            $columns = $constraint->columnNames;
569 66
            sort($columns, SORT_STRING);
570 66
            return json_encode($columns);
571 66
        }, $constraints), $constraints);
572 66
        $columnNames = [];
573
        // Remove all constraints which do not cover the specified column list
574 66
        $constraints = array_values(array_filter($constraints, function (Constraint $constraint) use ($schema, $columns, &$columnNames) {
575 66
            $constraintColumnNames = array_map([$schema, 'quoteColumnName'], $constraint->columnNames);
576 66
            $result = !array_diff($constraintColumnNames, $columns);
577 66
            if ($result) {
578 57
                $columnNames = array_merge($columnNames, $constraintColumnNames);
579
            }
580 66
            return $result;
581 66
        }));
582 66
        return array_unique($columnNames);
583
    }
584
585
    /**
586
     * Creates an UPDATE SQL statement.
587
     *
588
     * For example,
589
     *
590
     * ```php
591
     * $params = [];
592
     * $sql = $queryBuilder->update('user', ['status' => 1], 'age > 30', $params);
593
     * ```
594
     *
595
     * The method will properly escape the table and column names.
596
     *
597
     * @param string $table the table to be updated.
598
     * @param array $columns the column data (name => value) to be updated.
599
     * @param array|string $condition the condition that will be put in the WHERE part. Please
600
     * refer to [[Query::where()]] on how to specify condition.
601
     * @param array $params the binding parameters that will be modified by this method
602
     * so that they can be bound to the DB command later.
603
     * @return string the UPDATE SQL
604
     */
605 134
    public function update($table, $columns, $condition, &$params)
606
    {
607 134
        [$lines, $params] = $this->prepareUpdateSets($table, $columns, $params);
0 ignored issues
show
Bug introduced by
The variable $lines does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
608 134
        $sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
609 134
        $where = $this->buildWhere($condition, $params);
610 134
        return $where === '' ? $sql : $sql . ' ' . $where;
611
    }
612
613
    /**
614
     * Prepares a `SET` parts for an `UPDATE` SQL statement.
615
     * @param string $table the table to be updated.
616
     * @param array $columns the column data (name => value) to be updated.
617
     * @param array $params the binding parameters that will be modified by this method
618
     * so that they can be bound to the DB command later.
619
     * @return array an array `SET` parts for an `UPDATE` SQL statement (the first array element) and params (the second array element).
620
     * @since 2.0.14
621
     */
622 166
    protected function prepareUpdateSets($table, $columns, $params = [])
623
    {
624 166
        $tableSchema = $this->db->getTableSchema($table);
625 166
        $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
626 166
        $sets = [];
627 166
        foreach ($columns as $name => $value) {
628 166
            $value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
629 166
            if ($value instanceof ExpressionInterface) {
630 91
                $placeholder = $this->buildExpression($value, $params);
631
            } else {
632 127
                $placeholder = $this->bindParam($value, $params);
633
            }
634
635 166
            $sets[] = $this->db->quoteColumnName($name) . '=' . $placeholder;
636
        }
637 166
        return [$sets, $params];
638
    }
639
640
    /**
641
     * Creates a DELETE SQL statement.
642
     *
643
     * For example,
644
     *
645
     * ```php
646
     * $sql = $queryBuilder->delete('user', 'status = 0');
647
     * ```
648
     *
649
     * The method will properly escape the table and column names.
650
     *
651
     * @param string $table the table where the data will be deleted from.
652
     * @param array|string $condition the condition that will be put in the WHERE part. Please
653
     * refer to [[Query::where()]] on how to specify condition.
654
     * @param array $params the binding parameters that will be modified by this method
655
     * so that they can be bound to the DB command later.
656
     * @return string the DELETE SQL
657
     */
658 348
    public function delete($table, $condition, &$params)
659
    {
660 348
        $sql = 'DELETE FROM ' . $this->db->quoteTableName($table);
661 348
        $where = $this->buildWhere($condition, $params);
662
663 348
        return $where === '' ? $sql : $sql . ' ' . $where;
664
    }
665
666
    /**
667
     * Builds a SQL statement for creating a new DB table.
668
     *
669
     * The columns in the new  table should be specified as name-definition pairs (e.g. 'name' => 'string'),
670
     * where name stands for a column name which will be properly quoted by the method, and definition
671
     * stands for the column type which can contain an abstract DB type.
672
     * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
673
     *
674
     * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
675
     * inserted into the generated SQL.
676
     *
677
     * For example,
678
     *
679
     * ```php
680
     * $sql = $queryBuilder->createTable('user', [
681
     *  'id' => 'pk',
682
     *  'name' => 'string',
683
     *  'age' => 'integer',
684
     * ]);
685
     * ```
686
     *
687
     * @param string $table the name of the table to be created. The name will be properly quoted by the method.
688
     * @param array $columns the columns (name => definition) in the new table.
689
     * @param string $options additional SQL fragment that will be appended to the generated SQL.
690
     * @return string the SQL statement for creating a new DB table.
691
     */
692 137
    public function createTable($table, $columns, $options = null)
693
    {
694 137
        $cols = [];
695 137
        foreach ($columns as $name => $type) {
696 137
            if (is_string($name)) {
697 137
                $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type);
698
            } else {
699 137
                $cols[] = "\t" . $type;
700
            }
701
        }
702 137
        $sql = 'CREATE TABLE ' . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
703
704 137
        return $options === null ? $sql : $sql . ' ' . $options;
705
    }
706
707
    /**
708
     * Builds a SQL statement for renaming a DB table.
709
     * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
710
     * @param string $newName the new table name. The name will be properly quoted by the method.
711
     * @return string the SQL statement for renaming a DB table.
712
     */
713 1
    public function renameTable($oldName, $newName)
714
    {
715 1
        return 'RENAME TABLE ' . $this->db->quoteTableName($oldName) . ' TO ' . $this->db->quoteTableName($newName);
716
    }
717
718
    /**
719
     * Builds a SQL statement for dropping a DB table.
720
     * @param string $table the table to be dropped. The name will be properly quoted by the method.
721
     * @return string the SQL statement for dropping a DB table.
722
     */
723 39
    public function dropTable($table)
724
    {
725 39
        return 'DROP TABLE ' . $this->db->quoteTableName($table);
726
    }
727
728
    /**
729
     * Builds a SQL statement for adding a primary key constraint to an existing table.
730
     * @param string $name the name of the primary key constraint.
731
     * @param string $table the table that the primary key constraint will be added to.
732
     * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
733
     * @return string the SQL statement for adding a primary key constraint to an existing table.
734
     */
735 6
    public function addPrimaryKey($name, $table, $columns)
736
    {
737 6
        if (is_string($columns)) {
738 4
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
739
        }
740
741 6
        foreach ($columns as $i => $col) {
742 6
            $columns[$i] = $this->db->quoteColumnName($col);
743
        }
744
745 6
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
746 6
            . $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
747 6
            . implode(', ', $columns) . ')';
748
    }
749
750
    /**
751
     * Builds a SQL statement for removing a primary key constraint to an existing table.
752
     * @param string $name the name of the primary key constraint to be removed.
753
     * @param string $table the table that the primary key constraint will be removed from.
754
     * @return string the SQL statement for removing a primary key constraint from an existing table.
755
     */
756 2
    public function dropPrimaryKey($name, $table)
757
    {
758 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
759 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
760
    }
761
762
    /**
763
     * Builds a SQL statement for truncating a DB table.
764
     * @param string $table the table to be truncated. The name will be properly quoted by the method.
765
     * @return string the SQL statement for truncating a DB table.
766
     */
767 11
    public function truncateTable($table)
768
    {
769 11
        return 'TRUNCATE TABLE ' . $this->db->quoteTableName($table);
770
    }
771
772
    /**
773
     * Builds a SQL statement for adding a new DB column.
774
     * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
775
     * @param string $column the name of the new column. The name will be properly quoted by the method.
776
     * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
777
     * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
778
     * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
779
     * @return string the SQL statement for adding a new column.
780
     */
781 4
    public function addColumn($table, $column, $type)
782
    {
783 4
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
784 4
            . ' ADD ' . $this->db->quoteColumnName($column) . ' '
785 4
            . $this->getColumnType($type);
786
    }
787
788
    /**
789
     * Builds a SQL statement for dropping a DB column.
790
     * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
791
     * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
792
     * @return string the SQL statement for dropping a DB column.
793
     */
794
    public function dropColumn($table, $column)
795
    {
796
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
797
            . ' DROP COLUMN ' . $this->db->quoteColumnName($column);
798
    }
799
800
    /**
801
     * Builds a SQL statement for renaming a column.
802
     * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
803
     * @param string $oldName the old name of the column. The name will be properly quoted by the method.
804
     * @param string $newName the new name of the column. The name will be properly quoted by the method.
805
     * @return string the SQL statement for renaming a DB column.
806
     */
807
    public function renameColumn($table, $oldName, $newName)
808
    {
809
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
810
            . ' RENAME COLUMN ' . $this->db->quoteColumnName($oldName)
811
            . ' TO ' . $this->db->quoteColumnName($newName);
812
    }
813
814
    /**
815
     * Builds a SQL statement for changing the definition of a column.
816
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
817
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
818
     * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
819
     * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
820
     * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
821
     * will become 'varchar(255) not null'.
822
     * @return string the SQL statement for changing the definition of a column.
823
     */
824 1
    public function alterColumn($table, $column, $type)
825
    {
826 1
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' CHANGE '
827 1
            . $this->db->quoteColumnName($column) . ' '
828 1
            . $this->db->quoteColumnName($column) . ' '
829 1
            . $this->getColumnType($type);
830
    }
831
832
    /**
833
     * Builds a SQL statement for adding a foreign key constraint to an existing table.
834
     * The method will properly quote the table and column names.
835
     * @param string $name the name of the foreign key constraint.
836
     * @param string $table the table that the foreign key constraint will be added to.
837
     * @param string|array $columns the name of the column to that the constraint will be added on.
838
     * If there are multiple columns, separate them with commas or use an array to represent them.
839
     * @param string $refTable the table that the foreign key references to.
840
     * @param string|array $refColumns the name of the column that the foreign key references to.
841
     * If there are multiple columns, separate them with commas or use an array to represent them.
842
     * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
843
     * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
844
     * @return string the SQL statement for adding a foreign key constraint to an existing table.
845
     */
846 8
    public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
847
    {
848 8
        $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
849 8
            . ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name)
850 8
            . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')'
851 8
            . ' REFERENCES ' . $this->db->quoteTableName($refTable)
852 8
            . ' (' . $this->buildColumns($refColumns) . ')';
853 8
        if ($delete !== null) {
854 4
            $sql .= ' ON DELETE ' . $delete;
855
        }
856 8
        if ($update !== null) {
857 4
            $sql .= ' ON UPDATE ' . $update;
858
        }
859
860 8
        return $sql;
861
    }
862
863
    /**
864
     * Builds a SQL statement for dropping a foreign key constraint.
865
     * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
866
     * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
867
     * @return string the SQL statement for dropping a foreign key constraint.
868
     */
869 3
    public function dropForeignKey($name, $table)
870
    {
871 3
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
872 3
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
873
    }
874
875
    /**
876
     * Builds a SQL statement for creating a new index.
877
     * @param string $name the name of the index. The name will be properly quoted by the method.
878
     * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
879
     * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
880
     * separate them with commas or use an array to represent them. Each column name will be properly quoted
881
     * by the method, unless a parenthesis is found in the name.
882
     * @param bool $unique whether to add UNIQUE constraint on the created index.
883
     * @return string the SQL statement for creating a new index.
884
     */
885 6
    public function createIndex($name, $table, $columns, $unique = false)
886
    {
887 6
        return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
888 6
            . $this->db->quoteTableName($name) . ' ON '
889 6
            . $this->db->quoteTableName($table)
890 6
            . ' (' . $this->buildColumns($columns) . ')';
891
    }
892
893
    /**
894
     * Builds a SQL statement for dropping an index.
895
     * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
896
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
897
     * @return string the SQL statement for dropping an index.
898
     */
899 4
    public function dropIndex($name, $table)
900
    {
901 4
        return 'DROP INDEX ' . $this->db->quoteTableName($name) . ' ON ' . $this->db->quoteTableName($table);
902
    }
903
904
    /**
905
     * Creates a SQL command for adding an unique constraint to an existing table.
906
     * @param string $name the name of the unique constraint.
907
     * The name will be properly quoted by the method.
908
     * @param string $table the table that the unique constraint will be added to.
909
     * The name will be properly quoted by the method.
910
     * @param string|array $columns the name of the column to that the constraint will be added on.
911
     * If there are multiple columns, separate them with commas.
912
     * The name will be properly quoted by the method.
913
     * @return string the SQL statement for adding an unique constraint to an existing table.
914
     * @since 2.0.13
915
     */
916 6
    public function addUnique($name, $table, $columns)
917
    {
918 6
        if (is_string($columns)) {
919 4
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
920
        }
921 6
        foreach ($columns as $i => $col) {
922 6
            $columns[$i] = $this->db->quoteColumnName($col);
923
        }
924
925 6
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
926 6
            . $this->db->quoteColumnName($name) . ' UNIQUE ('
927 6
            . implode(', ', $columns) . ')';
928
    }
929
930
    /**
931
     * Creates a SQL command for dropping an unique constraint.
932
     * @param string $name the name of the unique constraint to be dropped.
933
     * The name will be properly quoted by the method.
934
     * @param string $table the table whose unique constraint is to be dropped.
935
     * The name will be properly quoted by the method.
936
     * @return string the SQL statement for dropping an unique constraint.
937
     * @since 2.0.13
938
     */
939 2
    public function dropUnique($name, $table)
940
    {
941 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
942 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
943
    }
944
945
    /**
946
     * Creates a SQL command for adding a check constraint to an existing table.
947
     * @param string $name the name of the check constraint.
948
     * The name will be properly quoted by the method.
949
     * @param string $table the table that the check constraint will be added to.
950
     * The name will be properly quoted by the method.
951
     * @param string $expression the SQL of the `CHECK` constraint.
952
     * @return string the SQL statement for adding a check constraint to an existing table.
953
     * @since 2.0.13
954
     */
955 2
    public function addCheck($name, $table, $expression)
956
    {
957 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
958 2
            . $this->db->quoteColumnName($name) . ' CHECK (' . $this->db->quoteSql($expression) . ')';
959
    }
960
961
    /**
962
     * Creates a SQL command for dropping a check constraint.
963
     * @param string $name the name of the check constraint to be dropped.
964
     * The name will be properly quoted by the method.
965
     * @param string $table the table whose check constraint is to be dropped.
966
     * The name will be properly quoted by the method.
967
     * @return string the SQL statement for dropping a check constraint.
968
     * @since 2.0.13
969
     */
970 2
    public function dropCheck($name, $table)
971
    {
972 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
973 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
974
    }
975
976
    /**
977
     * Creates a SQL command for adding a default value constraint to an existing table.
978
     * @param string $name the name of the default value constraint.
979
     * The name will be properly quoted by the method.
980
     * @param string $table the table that the default value constraint will be added to.
981
     * The name will be properly quoted by the method.
982
     * @param string $column the name of the column to that the constraint will be added on.
983
     * The name will be properly quoted by the method.
984
     * @param mixed $value default value.
985
     * @return string the SQL statement for adding a default value constraint to an existing table.
986
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
987
     * @since 2.0.13
988
     */
989
    public function addDefaultValue($name, $table, $column, $value)
990
    {
991
        throw new NotSupportedException($this->db->getDriverName() . ' does not support adding default value constraints.');
992
    }
993
994
    /**
995
     * Creates a SQL command for dropping a default value constraint.
996
     * @param string $name the name of the default value constraint to be dropped.
997
     * The name will be properly quoted by the method.
998
     * @param string $table the table whose default value constraint is to be dropped.
999
     * The name will be properly quoted by the method.
1000
     * @return string the SQL statement for dropping a default value constraint.
1001
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
1002
     * @since 2.0.13
1003
     */
1004
    public function dropDefaultValue($name, $table)
1005
    {
1006
        throw new NotSupportedException($this->db->getDriverName() . ' does not support dropping default value constraints.');
1007
    }
1008
1009
    /**
1010
     * Creates a SQL statement for resetting the sequence value of a table's primary key.
1011
     * The sequence will be reset such that the primary key of the next new row inserted
1012
     * will have the specified value or 1.
1013
     * @param string $table the name of the table whose primary key sequence will be reset
1014
     * @param array|string $value the value for the primary key of the next new row inserted. If this is not set,
1015
     * the next new row's primary key will have a value 1.
1016
     * @return string the SQL statement for resetting sequence
1017
     * @throws NotSupportedException if this is not supported by the underlying DBMS
1018
     */
1019
    public function resetSequence($table, $value = null)
1020
    {
1021
        throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.');
1022
    }
1023
1024
    /**
1025
     * Builds a SQL statement for enabling or disabling integrity check.
1026
     * @param bool $check whether to turn on or off the integrity check.
1027
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
1028
     * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
1029
     * @return string the SQL statement for checking integrity
1030
     * @throws NotSupportedException if this is not supported by the underlying DBMS
1031
     */
1032
    public function checkIntegrity($check = true, $schema = '', $table = '')
1033
    {
1034
        throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.');
1035
    }
1036
1037
    /**
1038
     * Builds a SQL command for adding comment to column.
1039
     *
1040
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1041
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
1042
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
1043
     * @return string the SQL statement for adding comment on column
1044
     * @since 2.0.8
1045
     */
1046 2
    public function addCommentOnColumn($table, $column, $comment)
1047
    {
1048 2
        return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS ' . $this->db->quoteValue($comment);
1049
    }
1050
1051
    /**
1052
     * Builds a SQL command for adding comment to table.
1053
     *
1054
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1055
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
1056
     * @return string the SQL statement for adding comment on table
1057
     * @since 2.0.8
1058
     */
1059 1
    public function addCommentOnTable($table, $comment)
1060
    {
1061 1
        return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS ' . $this->db->quoteValue($comment);
1062
    }
1063
1064
    /**
1065
     * Builds a SQL command for adding comment to column.
1066
     *
1067
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1068
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
1069
     * @return string the SQL statement for adding comment on column
1070
     * @since 2.0.8
1071
     */
1072 2
    public function dropCommentFromColumn($table, $column)
1073
    {
1074 2
        return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS NULL';
1075
    }
1076
1077
    /**
1078
     * Builds a SQL command for adding comment to table.
1079
     *
1080
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1081
     * @return string the SQL statement for adding comment on column
1082
     * @since 2.0.8
1083
     */
1084 1
    public function dropCommentFromTable($table)
1085
    {
1086 1
        return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS NULL';
1087
    }
1088
1089
    /**
1090
     * Creates a SQL View.
1091
     *
1092
     * @param string $viewName the name of the view to be created.
1093
     * @param string|Query $subQuery the select statement which defines the view.
1094
     * This can be either a string or a [[Query]] object.
1095
     * @return string the `CREATE VIEW` SQL statement.
1096
     * @since 2.0.14
1097
     */
1098 3
    public function createView($viewName, $subQuery)
1099
    {
1100 3
        if ($subQuery instanceof Query) {
1101 3
            [$rawQuery, $params] = $this->build($subQuery);
0 ignored issues
show
Bug introduced by
The variable $rawQuery does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $params seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1102 3
            array_walk(
1103 3
                $params,
1104 3
                function (&$param) {
1105 3
                    $param = $this->db->quoteValue($param);
1106 3
                }
1107
            );
1108 3
            $subQuery = strtr($rawQuery, $params);
1109
        }
1110
1111 3
        return 'CREATE VIEW ' . $this->db->quoteTableName($viewName) . ' AS ' . $subQuery;
1112
    }
1113
1114
    /**
1115
     * Drops a SQL View.
1116
     *
1117
     * @param string $viewName the name of the view to be dropped.
1118
     * @return string the `DROP VIEW` SQL statement.
1119
     * @since 2.0.14
1120
     */
1121 3
    public function dropView($viewName)
1122
    {
1123 3
        return 'DROP VIEW ' . $this->db->quoteTableName($viewName);
1124
    }
1125
1126
    /**
1127
     * Converts an abstract column type into a physical column type.
1128
     *
1129
     * The conversion is done using the type map specified in [[typeMap]].
1130
     * The following abstract column types are supported (using MySQL as an example to explain the corresponding
1131
     * physical types):
1132
     *
1133
     * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
1134
     * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
1135
     * - `upk`: an unsigned auto-incremental primary key type, will be converted into "int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY"
1136
     * - `char`: char type, will be converted into "char(1)"
1137
     * - `string`: string type, will be converted into "varchar(255)"
1138
     * - `text`: a long string type, will be converted into "text"
1139
     * - `smallint`: a small integer type, will be converted into "smallint(6)"
1140
     * - `integer`: integer type, will be converted into "int(11)"
1141
     * - `bigint`: a big integer type, will be converted into "bigint(20)"
1142
     * - `boolean`: boolean type, will be converted into "tinyint(1)"
1143
     * - `float``: float number type, will be converted into "float"
1144
     * - `decimal`: decimal number type, will be converted into "decimal"
1145
     * - `datetime`: datetime type, will be converted into "datetime"
1146
     * - `timestamp`: timestamp type, will be converted into "timestamp"
1147
     * - `time`: time type, will be converted into "time"
1148
     * - `date`: date type, will be converted into "date"
1149
     * - `money`: money type, will be converted into "decimal(19,4)"
1150
     * - `binary`: binary data type, will be converted into "blob"
1151
     *
1152
     * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
1153
     * the first part will be converted, and the rest of the parts will be appended to the converted result.
1154
     * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
1155
     *
1156
     * For some of the abstract types you can also specify a length or precision constraint
1157
     * by appending it in round brackets directly to the type.
1158
     * For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
1159
     * If the underlying DBMS does not support these kind of constraints for a type it will
1160
     * be ignored.
1161
     *
1162
     * If a type cannot be found in [[typeMap]], it will be returned without any change.
1163
     * @param string|ColumnSchemaBuilder $type abstract column type
1164
     * @return string physical column type.
1165
     */
1166 141
    public function getColumnType($type)
1167
    {
1168 141
        if ($type instanceof ColumnSchemaBuilder) {
1169 33
            $type = $type->__toString();
1170
        }
1171
1172 141
        if (isset($this->typeMap[$type])) {
1173 128
            return $this->typeMap[$type];
1174 77
        } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
1175 39
            if (isset($this->typeMap[$matches[1]])) {
1176 39
                return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
1177
            }
1178 52
        } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
1179 49
            if (isset($this->typeMap[$matches[1]])) {
1180 49
                return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
1181
            }
1182
        }
1183
1184 32
        return $type;
1185
    }
1186
1187
    /**
1188
     * @param array $columns
1189
     * @param array $params the binding parameters to be populated
1190
     * @param bool $distinct
1191
     * @param string $selectOption
1192
     * @return string the SELECT clause built from [[Query::$select]].
1193
     */
1194 1154
    public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
1195
    {
1196 1154
        $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
1197 1154
        if ($selectOption !== null) {
1198
            $select .= ' ' . $selectOption;
1199
        }
1200
1201 1154
        if (empty($columns)) {
1202 889
            return $select . ' *';
1203
        }
1204
1205 546
        foreach ($columns as $i => $column) {
1206 546
            if ($column instanceof ExpressionInterface) {
1207 42
                if (is_int($i)) {
1208 6
                    $columns[$i] = $this->buildExpression($column, $params);
1209
                } else {
1210 42
                    $columns[$i] = $this->buildExpression($column, $params) . ' AS ' . $this->db->quoteColumnName($i);
1211
                }
1212 537
            } elseif ($column instanceof Query) {
1213
                [$sql, $params] = $this->build($column, $params);
0 ignored issues
show
Bug introduced by
The variable $sql does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1214
                $columns[$i] = "($sql) AS " . $this->db->quoteColumnName($i);
1215 537
            } elseif (is_string($i)) {
1216 23
                if (strpos($column, '(') === false) {
1217 23
                    $column = $this->db->quoteColumnName($column);
1218
                }
1219 23
                $columns[$i] = "$column AS " . $this->db->quoteColumnName($i);
1220 532
            } elseif (strpos($column, '(') === false) {
1221 448
                if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
1222 6
                    $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
1223
                } else {
1224 546
                    $columns[$i] = $this->db->quoteColumnName($column);
1225
                }
1226
            }
1227
        }
1228
1229 546
        return $select . ' ' . implode(', ', $columns);
1230
    }
1231
1232
    /**
1233
     * @param array $tables
1234
     * @param array $params the binding parameters to be populated
1235
     * @return string the FROM clause built from [[Query::$from]].
1236
     */
1237 1154
    public function buildFrom($tables, &$params)
1238
    {
1239 1154
        if (empty($tables)) {
1240 349
            return '';
1241
        }
1242
1243 842
        $tables = $this->quoteTableNames($tables, $params);
1244
1245 842
        return 'FROM ' . implode(', ', $tables);
1246
    }
1247
1248
    /**
1249
     * @param array $joins
1250
     * @param array $params the binding parameters to be populated
1251
     * @return string the JOIN clause built from [[Query::$join]].
1252
     * @throws Exception if the $joins parameter is not in proper format
1253
     */
1254 1154
    public function buildJoin($joins, &$params)
1255
    {
1256 1154
        if (empty($joins)) {
1257 1142
            return '';
1258
        }
1259
1260 54
        foreach ($joins as $i => $join) {
1261 54
            if (!is_array($join) || !isset($join[0], $join[1])) {
1262
                throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
1263
            }
1264
            // 0:join type, 1:join table, 2:on-condition (optional)
1265 54
            [$joinType, $table] = $join;
0 ignored issues
show
Bug introduced by
The variable $joinType does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $table does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1266 54
            $tables = $this->quoteTableNames((array) $table, $params);
1267 54
            $table = reset($tables);
1268 54
            $joins[$i] = "$joinType $table";
1269 54
            if (isset($join[2])) {
1270 54
                $condition = $this->buildCondition($join[2], $params);
1271 54
                if ($condition !== '') {
1272 54
                    $joins[$i] .= ' ON ' . $condition;
1273
                }
1274
            }
1275
        }
1276
1277 54
        return implode($this->separator, $joins);
1278
    }
1279
1280
    /**
1281
     * Quotes table names passed.
1282
     *
1283
     * @param array $tables
1284
     * @param array $params
1285
     * @return array
1286
     */
1287 842
    private function quoteTableNames($tables, &$params)
1288
    {
1289 842
        foreach ($tables as $i => $table) {
1290 842
            if ($table instanceof Query) {
1291 10
                [$sql, $params] = $this->build($table, $params);
0 ignored issues
show
Bug introduced by
The variable $sql does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1292 10
                $tables[$i] = "($sql) " . $this->db->quoteTableName($i);
1293 842
            } elseif (is_string($i)) {
1294 79
                if (strpos($table, '(') === false) {
1295 70
                    $table = $this->db->quoteTableName($table);
1296
                }
1297 79
                $tables[$i] = "$table " . $this->db->quoteTableName($i);
1298 821
            } elseif (strpos($table, '(') === false) {
1299 814
                if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
1300 21
                    $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
1301
                } else {
1302 842
                    $tables[$i] = $this->db->quoteTableName($table);
1303
                }
1304
            }
1305
        }
1306
1307 842
        return $tables;
1308
    }
1309
1310
    /**
1311
     * @param string|array $condition
1312
     * @param array $params the binding parameters to be populated
1313
     * @return string the WHERE clause built from [[Query::$where]].
1314
     */
1315 1228
    public function buildWhere($condition, &$params)
1316
    {
1317 1228
        $where = $this->buildCondition($condition, $params);
1318
1319 1228
        return $where === '' ? '' : 'WHERE ' . $where;
1320
    }
1321
1322
    /**
1323
     * @param array $columns
1324
     * @param array $params the binding parameters to be populated
1325
     * @return string the GROUP BY clause
1326
     */
1327 1154
    public function buildGroupBy($columns, &$params)
1328
    {
1329 1154
        if (empty($columns)) {
1330 1148
            return '';
1331
        }
1332 21
        foreach ($columns as $i => $column) {
1333 21
            if ($column instanceof ExpressionInterface) {
1334 3
                $columns[$i] = $this->buildExpression($column);
1335 3
                $params = array_merge($params, $column->params);
0 ignored issues
show
Bug introduced by
Accessing params on the interface yii\db\ExpressionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1336 21
            } elseif (strpos($column, '(') === false) {
1337 21
                $columns[$i] = $this->db->quoteColumnName($column);
1338
            }
1339
        }
1340
1341 21
        return 'GROUP BY ' . implode(', ', $columns);
1342
    }
1343
1344
    /**
1345
     * @param string|array $condition
1346
     * @param array $params the binding parameters to be populated
1347
     * @return string the HAVING clause built from [[Query::$having]].
1348
     */
1349 1154
    public function buildHaving($condition, &$params)
1350
    {
1351 1154
        $having = $this->buildCondition($condition, $params);
1352
1353 1154
        return $having === '' ? '' : 'HAVING ' . $having;
1354
    }
1355
1356
    /**
1357
     * Builds the ORDER BY and LIMIT/OFFSET clauses and appends them to the given SQL.
1358
     * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
1359
     * @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter.
1360
     * @param int $limit the limit number. See [[Query::limit]] for more details.
1361
     * @param int $offset the offset number. See [[Query::offset]] for more details.
1362
     * @param array $params the binding parameters to be populated
1363
     * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
1364
     */
1365 1154
    public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset, &$params)
1366
    {
1367 1154
        $orderBy = $this->buildOrderBy($orderBy, $params);
1368 1154
        if ($orderBy !== '') {
1369 180
            $sql .= $this->separator . $orderBy;
1370
        }
1371 1154
        $limit = $this->buildLimit($limit, $offset);
1372 1154
        if ($limit !== '') {
1373 91
            $sql .= $this->separator . $limit;
1374
        }
1375
1376 1154
        return $sql;
1377
    }
1378
1379
    /**
1380
     * @param array $columns
1381
     * @param array $params the binding parameters to be populated
1382
     * @return string the ORDER BY clause built from [[Query::$orderBy]].
1383
     */
1384 1154
    public function buildOrderBy($columns, &$params)
1385
    {
1386 1154
        if (empty($columns)) {
1387 1119
            return '';
1388
        }
1389 180
        $orders = [];
1390 180
        foreach ($columns as $name => $direction) {
1391 180
            if ($direction instanceof ExpressionInterface) {
1392 3
                $orders[] = $this->buildExpression($direction);
1393 3
                $params = array_merge($params, $direction->params);
0 ignored issues
show
Bug introduced by
Accessing params on the interface yii\db\ExpressionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1394
            } else {
1395 180
                $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
1396
            }
1397
        }
1398
1399 180
        return 'ORDER BY ' . implode(', ', $orders);
1400
    }
1401
1402
    /**
1403
     * @param int $limit
1404
     * @param int $offset
1405
     * @return string the LIMIT and OFFSET clauses
1406
     */
1407 418
    public function buildLimit($limit, $offset)
1408
    {
1409 418
        $sql = '';
1410 418
        if ($this->hasLimit($limit)) {
1411 25
            $sql = 'LIMIT ' . $limit;
1412
        }
1413 418
        if ($this->hasOffset($offset)) {
1414 3
            $sql .= ' OFFSET ' . $offset;
1415
        }
1416
1417 418
        return ltrim($sql);
1418
    }
1419
1420
    /**
1421
     * Checks to see if the given limit is effective.
1422
     * @param mixed $limit the given limit
1423
     * @return bool whether the limit is effective
1424
     */
1425 757
    protected function hasLimit($limit)
1426
    {
1427 757
        return ($limit instanceof ExpressionInterface) || ctype_digit((string) $limit);
1428
    }
1429
1430
    /**
1431
     * Checks to see if the given offset is effective.
1432
     * @param mixed $offset the given offset
1433
     * @return bool whether the offset is effective
1434
     */
1435 757
    protected function hasOffset($offset)
1436
    {
1437 757
        return ($offset instanceof ExpressionInterface) || ctype_digit((string) $offset) && (string) $offset !== '0';
1438
    }
1439
1440
    /**
1441
     * @param array $unions
1442
     * @param array $params the binding parameters to be populated
1443
     * @return string the UNION clause built from [[Query::$union]].
1444
     */
1445 815
    public function buildUnion($unions, &$params)
1446
    {
1447 815
        if (empty($unions)) {
1448 815
            return '';
1449
        }
1450
1451 8
        $result = '';
1452
1453 8
        foreach ($unions as $i => $union) {
1454 8
            $query = $union['query'];
1455 8
            if ($query instanceof Query) {
1456 8
                [$unions[$i]['query'], $params] = $this->build($query, $params);
1457
            }
1458
1459 8
            $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) ';
1460
        }
1461
1462 8
        return trim($result);
1463
    }
1464
1465
    /**
1466
     * Processes columns and properly quotes them if necessary.
1467
     * It will join all columns into a string with comma as separators.
1468
     * @param string|array $columns the columns to be processed
1469
     * @return string the processing result
1470
     */
1471 32
    public function buildColumns($columns)
1472
    {
1473 32
        if (!is_array($columns)) {
1474 27
            if (strpos($columns, '(') !== false) {
1475
                return $columns;
1476
            }
1477
1478 27
            $rawColumns = $columns;
1479 27
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
1480 27
            if ($columns === false) {
1481
                throw new InvalidArgumentException("$rawColumns is not valid columns.");
1482
            }
1483
        }
1484 32
        foreach ($columns as $i => $column) {
1485 32
            if ($column instanceof ExpressionInterface) {
1486
                $columns[$i] = $this->buildExpression($column);
1487 32
            } elseif (strpos($column, '(') === false) {
1488 32
                $columns[$i] = $this->db->quoteColumnName($column);
1489
            }
1490
        }
1491
1492 32
        return implode(', ', $columns);
1493
    }
1494
1495
    /**
1496
     * Parses the condition specification and generates the corresponding SQL expression.
1497
     * @param string|array|ExpressionInterface $condition the condition specification. Please refer to [[Query::where()]]
1498
     * on how to specify a condition.
1499
     * @param array $params the binding parameters to be populated
1500
     * @return string the generated SQL expression
1501
     */
1502 1228
    public function buildCondition($condition, &$params)
1503
    {
1504 1228
        if (is_array($condition)) {
1505 974
            if (empty($condition)) {
1506 3
                return '';
1507
            }
1508
1509 974
            $condition = $this->createConditionFromArray($condition);
1510
        }
1511
1512 1228
        if ($condition instanceof ExpressionInterface) {
1513 995
            return $this->buildExpression($condition, $params);
1514
        }
1515
1516 1211
        return (string) $condition;
1517
    }
1518
1519
    /**
1520
     * Transforms $condition defined in array format (as described in [[Query::where()]]
1521
     * to instance of [[yii\db\condition\ConditionInterface|ConditionInterface]] according to
1522
     * [[conditionClasses]] map.
1523
     *
1524
     * @param string|array $condition
1525
     * @see conditionClasses
1526
     * @return ConditionInterface
1527
     * @since 2.0.14
1528
     */
1529 974
    public function createConditionFromArray($condition)
1530
    {
1531 974
        if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
1532 586
            $operator = strtoupper(array_shift($condition));
1533 586
            if (isset($this->conditionClasses[$operator])) {
1534 508
                $className = $this->conditionClasses[$operator];
1535
            } else {
1536 84
                $className = 'yii\db\conditions\SimpleCondition';
1537
            }
1538
            /** @var ConditionInterface $className */
1539 586
            return $className::fromArrayDefinition($operator, $condition);
1540
        }
1541
1542
        // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
1543 685
        return new HashCondition($condition);
0 ignored issues
show
Bug introduced by
It seems like $condition defined by parameter $condition on line 1529 can also be of type string; however, yii\db\conditions\HashCondition::__construct() does only seem to accept array|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1544
    }
1545
1546
    /**
1547
     * Creates a condition based on column-value pairs.
1548
     * @param array $condition the condition specification.
1549
     * @param array $params the binding parameters to be populated
1550
     * @return string the generated SQL expression
1551
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1552
     */
1553
    public function buildHashCondition($condition, &$params)
1554
    {
1555
        return $this->buildCondition(new HashCondition($condition), $params);
1556
    }
1557
1558
    /**
1559
     * Connects two or more SQL expressions with the `AND` or `OR` operator.
1560
     * @param string $operator the operator to use for connecting the given operands
1561
     * @param array $operands the SQL expressions to connect.
1562
     * @param array $params the binding parameters to be populated
1563
     * @return string the generated SQL expression
1564
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1565
     */
1566
    public function buildAndCondition($operator, $operands, &$params)
1567
    {
1568
        array_unshift($operands, $operator);
1569
        return $this->buildCondition($operands, $params);
1570
    }
1571
1572
    /**
1573
     * Inverts an SQL expressions with `NOT` operator.
1574
     * @param string $operator the operator to use for connecting the given operands
1575
     * @param array $operands the SQL expressions to connect.
1576
     * @param array $params the binding parameters to be populated
1577
     * @return string the generated SQL expression
1578
     * @throws InvalidArgumentException if wrong number of operands have been given.
1579
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1580
     */
1581
    public function buildNotCondition($operator, $operands, &$params)
1582
    {
1583
        array_unshift($operands, $operator);
1584
        return $this->buildCondition($operands, $params);
1585
    }
1586
1587
    /**
1588
     * Creates an SQL expressions with the `BETWEEN` operator.
1589
     * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
1590
     * @param array $operands the first operand is the column name. The second and third operands
1591
     * describe the interval that column value should be in.
1592
     * @param array $params the binding parameters to be populated
1593
     * @return string the generated SQL expression
1594
     * @throws InvalidArgumentException if wrong number of operands have been given.
1595
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1596
     */
1597
    public function buildBetweenCondition($operator, $operands, &$params)
1598
    {
1599
        array_unshift($operands, $operator);
1600
        return $this->buildCondition($operands, $params);
1601
    }
1602
1603
    /**
1604
     * Creates an SQL expressions with the `IN` operator.
1605
     * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
1606
     * @param array $operands the first operand is the column name. If it is an array
1607
     * a composite IN condition will be generated.
1608
     * The second operand is an array of values that column value should be among.
1609
     * If it is an empty array the generated expression will be a `false` value if
1610
     * operator is `IN` and empty if operator is `NOT IN`.
1611
     * @param array $params the binding parameters to be populated
1612
     * @return string the generated SQL expression
1613
     * @throws Exception if wrong number of operands have been given.
1614
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1615
     */
1616
    public function buildInCondition($operator, $operands, &$params)
1617
    {
1618
        array_unshift($operands, $operator);
1619
        return $this->buildCondition($operands, $params);
1620
    }
1621
1622
    /**
1623
     * Creates an SQL expressions with the `LIKE` operator.
1624
     * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
1625
     * @param array $operands an array of two or three operands
1626
     *
1627
     * - The first operand is the column name.
1628
     * - The second operand is a single value or an array of values that column value
1629
     *   should be compared with. If it is an empty array the generated expression will
1630
     *   be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
1631
     *   is `NOT LIKE` or `OR NOT LIKE`.
1632
     * - An optional third operand can also be provided to specify how to escape special characters
1633
     *   in the value(s). The operand should be an array of mappings from the special characters to their
1634
     *   escaped counterparts. If this operand is not provided, a default escape mapping will be used.
1635
     *   You may use `false` or an empty array to indicate the values are already escaped and no escape
1636
     *   should be applied. Note that when using an escape mapping (or the third operand is not provided),
1637
     *   the values will be automatically enclosed within a pair of percentage characters.
1638
     * @param array $params the binding parameters to be populated
1639
     * @return string the generated SQL expression
1640
     * @throws InvalidArgumentException if wrong number of operands have been given.
1641
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1642
     */
1643
    public function buildLikeCondition($operator, $operands, &$params)
1644
    {
1645
        array_unshift($operands, $operator);
1646
        return $this->buildCondition($operands, $params);
1647
    }
1648
1649
    /**
1650
     * Creates an SQL expressions with the `EXISTS` operator.
1651
     * @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
1652
     * @param array $operands contains only one element which is a [[Query]] object representing the sub-query.
1653
     * @param array $params the binding parameters to be populated
1654
     * @return string the generated SQL expression
1655
     * @throws InvalidArgumentException if the operand is not a [[Query]] object.
1656
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1657
     */
1658
    public function buildExistsCondition($operator, $operands, &$params)
1659
    {
1660
        array_unshift($operands, $operator);
1661
        return $this->buildCondition($operands, $params);
1662
    }
1663
1664
    /**
1665
     * Creates an SQL expressions like `"column" operator value`.
1666
     * @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc.
1667
     * @param array $operands contains two column names.
1668
     * @param array $params the binding parameters to be populated
1669
     * @return string the generated SQL expression
1670
     * @throws InvalidArgumentException if wrong number of operands have been given.
1671
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1672
     */
1673
    public function buildSimpleCondition($operator, $operands, &$params)
1674
    {
1675
        array_unshift($operands, $operator);
1676
        return $this->buildCondition($operands, $params);
1677
    }
1678
1679
    /**
1680
     * Creates a SELECT EXISTS() SQL statement.
1681
     * @param string $rawSql the subquery in a raw form to select from.
1682
     * @return string the SELECT EXISTS() SQL statement.
1683
     * @since 2.0.8
1684
     */
1685 67
    public function selectExists($rawSql)
1686
    {
1687 67
        return 'SELECT EXISTS(' . $rawSql . ')';
1688
    }
1689
1690
    /**
1691
     * Helper method to add $value to $params array using [[PARAM_PREFIX]].
1692
     *
1693
     * @param string|null $value
1694
     * @param array $params passed by reference
1695
     * @return string the placeholder name in $params array
1696
     *
1697
     * @since 2.0.14
1698
     */
1699 1021
    public function bindParam($value, &$params)
1700
    {
1701 1021
        $phName = self::PARAM_PREFIX . count($params);
1702 1021
        $params[$phName] = $value;
1703
1704 1021
        return $phName;
1705
    }
1706
}
1707