Completed
Push — 2.1 ( 481970...56545c )
by
unknown
11:50
created

QueryBuilder::buildOrderByAndLimit()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
ccs 8
cts 8
cp 1
cc 3
eloc 8
nc 4
nop 5
crap 3
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
     * @var array map of condition aliases to condition classes. For example:
62
     *
63
     * ```php
64
     * return [
65
     *     'LIKE' => yii\db\condition\LikeCondition::class,
66
     * ];
67
     * ```
68
     *
69
     * This property is used by [[createConditionFromArray]] method.
70
     * See default condition classes list in [[defaultConditionClasses()]] method.
71
     *
72
     * In case you want to add custom conditions support, use the [[setConditionClasses()]] method.
73
     *
74
     * @see setConditonClasses()
75
     * @see defaultConditionClasses()
76
     * @since 2.0.14
77
     */
78
    protected $conditionClasses = [];
79
    /**
80
     * @var string[]|ExpressionBuilderInterface[] maps expression class to expression builder class.
81
     * For example:
82
     *
83
     * ```php
84
     * [
85
     *    yii\db\Expression::class => yii\db\ExpressionBuilder::class
86
     * ]
87
     * ```
88
     * This property is mainly used by [[buildExpression()]] to build SQL expressions form expression objects.
89
     * See default values in [[defaultExpressionBuilders()]] method.
90
     *
91
     *
92
     * To override existing builders or add custom, use [[setExpressionBuilder()]] method. New items will be added
93
     * to the end of this array.
94
     *
95
     * To find a builder, [[buildExpression()]] will check the expression class for its exact presence in this map.
96
     * In case it is NOT present, the array will be iterated in reverse direction, checking whether the expression
97
     * extends the class, defined in this map.
98
     *
99
     * @see setExpressionBuilders()
100
     * @see defaultExpressionBuilders()
101
     * @since 2.0.14
102
     */
103
    protected $expressionBuilders = [];
104
105
106
    /**
107
     * Constructor.
108
     * @param Connection $connection the database connection.
109
     * @param array $config name-value pairs that will be used to initialize the object properties
110
     */
111 1438
    public function __construct($connection, $config = [])
112
    {
113 1438
        $this->db = $connection;
114 1438
        parent::__construct($config);
115 1438
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120 1438
    public function init()
121
    {
122 1438
        parent::init();
123
124 1438
        $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<string|integer,str...nsCondition":"string"}> 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...
125 1438
        $this->conditionClasses = array_merge($this->defaultConditionClasses(), $this->conditionClasses);
126 1438
    }
127
128
    /**
129
     * Contains array of default condition classes. Extend this method, if you want to change
130
     * default condition classes for the query builder. See [[conditionClasses]] docs for details.
131
     *
132
     * @return array
133
     * @see conditionClasses
134
     * @since 2.0.14
135
     */
136 1438
    protected function defaultConditionClasses()
137
    {
138
        return [
139 1438
            'NOT' => 'yii\db\conditions\NotCondition',
140
            'AND' => 'yii\db\conditions\AndCondition',
141
            'OR' => 'yii\db\conditions\OrCondition',
142
            'BETWEEN' => 'yii\db\conditions\BetweenCondition',
143
            'NOT BETWEEN' => 'yii\db\conditions\BetweenCondition',
144
            'IN' => 'yii\db\conditions\InCondition',
145
            'NOT IN' => 'yii\db\conditions\InCondition',
146
            'LIKE' => 'yii\db\conditions\LikeCondition',
147
            'NOT LIKE' => 'yii\db\conditions\LikeCondition',
148
            'OR LIKE' => 'yii\db\conditions\LikeCondition',
149
            'OR NOT LIKE' => 'yii\db\conditions\LikeCondition',
150
            'EXISTS' => 'yii\db\conditions\ExistsCondition',
151
            'NOT EXISTS' => 'yii\db\conditions\ExistsCondition',
152
        ];
153
    }
154
155
    /**
156
     * Contains array of default expression builders. Extend this method and override it, if you want to change
157
     * default expression builders for this query builder. See [[expressionBuilders]] docs for details.
158
     *
159
     * @return array
160
     * @see $expressionBuilders
161
     * @since 2.0.14
162
     */
163 1438
    protected function defaultExpressionBuilders()
164
    {
165
        return [
166 1438
            'yii\db\Query' => 'yii\db\QueryExpressionBuilder',
167
            'yii\db\PdoValue' => 'yii\db\PdoValueBuilder',
168
            'yii\db\Expression' => 'yii\db\ExpressionBuilder',
169
            'yii\db\conditions\ConjunctionCondition' => 'yii\db\conditions\ConjunctionConditionBuilder',
170
            'yii\db\conditions\NotCondition' => 'yii\db\conditions\NotConditionBuilder',
171
            'yii\db\conditions\AndCondition' => 'yii\db\conditions\ConjunctionConditionBuilder',
172
            'yii\db\conditions\OrCondition' => 'yii\db\conditions\ConjunctionConditionBuilder',
173
            'yii\db\conditions\BetweenCondition' => 'yii\db\conditions\BetweenConditionBuilder',
174
            'yii\db\conditions\InCondition' => 'yii\db\conditions\InConditionBuilder',
175
            'yii\db\conditions\LikeCondition' => 'yii\db\conditions\LikeConditionBuilder',
176
            'yii\db\conditions\ExistsCondition' => 'yii\db\conditions\ExistsConditionBuilder',
177
            'yii\db\conditions\SimpleCondition' => 'yii\db\conditions\SimpleConditionBuilder',
178
            'yii\db\conditions\HashCondition' => 'yii\db\conditions\HashConditionBuilder',
179
            'yii\db\conditions\BetweenColumnsCondition' => 'yii\db\conditions\BetweenColumnsConditionBuilder',
180
        ];
181
    }
182
183
    /**
184
     * Setter for [[expressionBuilders]] property.
185
     *
186
     * @param string[] $builders array of builders that should be merged with the pre-defined ones
187
     * in [[expressionBuilders]] property.
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
     * Generates a SELECT SQL statement from a [[Query]] object.
198
     *
199
     * @param Query $query the [[Query]] object from which the SQL statement will be generated.
200
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
201
     * be included in the result with the additional parameters generated during the query building process.
202
     * @return array the generated SQL statement (the first array element) and the corresponding
203
     * parameters to be bound to the SQL statement (the second array element). The parameters returned
204
     * include those provided in `$params`.
205
     */
206 819
    public function build($query, $params = [])
207
    {
208 819
        $query = $query->prepare($this);
209
210 819
        $params = empty($params) ? $query->params : array_merge($params, $query->params);
211
212
        $clauses = [
213 819
            $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
214 819
            $this->buildFrom($query->from, $params),
215 819
            $this->buildJoin($query->join, $params),
216 819
            $this->buildWhere($query->where, $params),
217 819
            $this->buildGroupBy($query->groupBy, $params),
218 819
            $this->buildHaving($query->having, $params),
219
        ];
220
221 819
        $sql = implode($this->separator, array_filter($clauses));
222 819
        $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset, $params);
223
224 819
        $union = $this->buildUnion($query->union, $params);
225 819
        if ($union !== '') {
226 8
            $sql = "($sql){$this->separator}$union";
227
        }
228
229 819
        return [$sql, $params];
230
    }
231
232
    /**
233
     * Builds given $expression
234
     *
235
     * @param ExpressionInterface $expression the expression to be built
236
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
237
     * be included in the result with the additional parameters generated during the expression building process.
238
     * @return string the SQL statement that will not be neither quoted nor encoded before passing to DBMS
239
     * @see ExpressionInterface
240
     * @see ExpressionBuilderInterface
241
     * @see expressionBuilders
242
     * @since 2.0.14
243
     * @throws InvalidArgumentException when $expression building is not supported by this QueryBuilder.
244
     */
245 1066
    public function buildExpression(ExpressionInterface $expression, &$params = [])
246
    {
247 1066
        $builder = $this->getExpressionBuilder($expression);
248
249 1066
        return $builder->build($expression, $params);
250
    }
251
252
    /**
253
     * Gets object of [[ExpressionBuilderInterface]] that is suitable for $expression.
254
     * Uses [[expressionBuilders]] array to find a suitable builder class.
255
     *
256
     * @param ExpressionInterface $expression
257
     * @return ExpressionBuilderInterface
258
     * @see expressionBuilders
259
     * @since 2.0.14
260
     * @throws InvalidArgumentException when $expression building is not supported by this QueryBuilder.
261
     */
262 1066
    public function getExpressionBuilder(ExpressionInterface $expression)
263
    {
264 1066
        $className = get_class($expression);
265
266 1066
        if (!isset($this->expressionBuilders[$className])) {
267
            foreach (array_reverse($this->expressionBuilders) as $expressionClass => $builderClass) {
268
                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...
269
                    $this->expressionBuilders[$className] = $builderClass;
270
                    break;
271
                }
272
            }
273
274
            if (!isset($this->expressionBuilders[$className])) {
275
                throw new InvalidArgumentException('Expression of class ' . $className . ' can not be built in ' . get_class($this));
276
            }
277
        }
278
279 1066
        if ($this->expressionBuilders[$className] === __CLASS__) {
280
            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...
281
        }
282
283 1066
        if (!is_object($this->expressionBuilders[$className])) {
284 1022
            $this->expressionBuilders[$className] = new $this->expressionBuilders[$className]($this);
285
        }
286
287 1066
        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 287 which is incompatible with the return type documented by yii\db\QueryBuilder::getExpressionBuilder of type yii\db\ExpressionBuilderInterface.
Loading history...
288
    }
289
290
    /**
291
     * Creates an INSERT SQL statement.
292
     * For example,
293
     *
294
     * ```php
295
     * $sql = $queryBuilder->insert('user', [
296
     *     'name' => 'Sam',
297
     *     'age' => 30,
298
     * ], $params);
299
     * ```
300
     *
301
     * The method will properly escape the table and column names.
302
     *
303
     * @param string $table the table that new rows will be inserted into.
304
     * @param array|Query $columns the column data (name => value) to be inserted into the table or instance
305
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
306
     * Passing of [[yii\db\Query|Query]] is available since version 2.0.11.
307
     * @param array $params the binding parameters that will be generated by this method.
308
     * They should be bound to the DB command later.
309
     * @return string the INSERT SQL
310
     */
311 534
    public function insert($table, $columns, &$params)
312
    {
313 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...
314 525
        return 'INSERT INTO ' . $this->db->quoteTableName($table)
315 525
            . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '')
316 525
            . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : $values);
317
    }
318
319
    /**
320
     * Prepares a `VALUES` part for an `INSERT` SQL statement.
321
     *
322
     * @param string $table the table that new rows will be inserted into.
323
     * @param array|Query $columns the column data (name => value) to be inserted into the table or instance
324
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
325
     * @param array $params the binding parameters that will be generated by this method.
326
     * They should be bound to the DB command later.
327
     * @return array array of column names, placeholders, values and params.
328
     * @since 2.0.14
329
     */
330 548
    protected function prepareInsertValues($table, $columns, $params = [])
331
    {
332 548
        $schema = $this->db->getSchema();
333 548
        $tableSchema = $schema->getTableSchema($table);
334 548
        $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
335 548
        $names = [];
336 548
        $placeholders = [];
337 548
        $values = ' DEFAULT VALUES';
338 548
        if ($columns instanceof Query) {
339 42
            [$names, $values, $params] = $this->prepareInsertSelectSubQuery($columns, $schema, $params);
340
        } else {
341 512
            foreach ($columns as $name => $value) {
342 508
                $names[] = $schema->quoteColumnName($name);
343 508
                $value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
344
345 508
                if ($value instanceof ExpressionInterface) {
346 118
                    $placeholders[] = $this->buildExpression($value, $params);
347 501
                } elseif ($value instanceof \yii\db\Query) {
348
                    [$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...
349
                    $placeholders[] = "($sql)";
350
                } else {
351 508
                    $placeholders[] = $this->bindParam($value, $params);
352
                }
353
            }
354
        }
355 539
        return [$names, $placeholders, $values, $params];
356
    }
357
358
    /**
359
     * Prepare select-subquery and field names for INSERT INTO ... SELECT SQL statement.
360
     *
361
     * @param Query $columns Object, which represents select query.
362
     * @param \yii\db\Schema $schema Schema object to quote column name.
363
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
364
     * be included in the result with the additional parameters generated during the query building process.
365
     * @return array array of column names, values and params.
366
     * @throws InvalidArgumentException if query's select does not contain named parameters only.
367
     * @since 2.0.11
368
     */
369 42
    protected function prepareInsertSelectSubQuery($columns, $schema, $params = [])
370
    {
371 42
        if (!is_array($columns->select) || empty($columns->select) || in_array('*', $columns->select)) {
372 9
            throw new InvalidArgumentException('Expected select query object with enumerated (named) parameters');
373
        }
374
375 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...
376 33
        $names = [];
377 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...
378 33
        foreach ($columns->select as $title => $field) {
379 33
            if (is_string($title)) {
380 27
                $names[] = $schema->quoteColumnName($title);
381 24
            } elseif (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $field, $matches)) {
382 3
                $names[] = $schema->quoteColumnName($matches[2]);
383
            } else {
384 33
                $names[] = $schema->quoteColumnName($field);
385
            }
386
        }
387
388 33
        return [$names, $values, $params];
389
    }
390
391
    /**
392
     * Generates a batch INSERT SQL statement.
393
     *
394
     * For example,
395
     *
396
     * ```php
397
     * $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [
398
     *     ['Tom', 30],
399
     *     ['Jane', 20],
400
     *     ['Linda', 25],
401
     * ]);
402
     * ```
403
     *
404
     * Note that the values in each row must match the corresponding column names.
405
     *
406
     * The method will properly escape the column names, and quote the values to be inserted.
407
     *
408
     * @param string $table the table that new rows will be inserted into.
409
     * @param array $columns the column names
410
     * @param array|\Generator $rows the rows to be batch inserted into the table
411
     * @param array $params the binding parameters. This parameter exists since 2.0.14
412
     * @return string the batch INSERT SQL statement
413
     */
414 27
    public function batchInsert($table, $columns, $rows, &$params = [])
415
    {
416 27
        if (empty($rows)) {
417 2
            return '';
418
        }
419
420 26
        $schema = $this->db->getSchema();
421 26
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
422 20
            $columnSchemas = $tableSchema->columns;
423
        } else {
424 6
            $columnSchemas = [];
425
        }
426
427 26
        $values = [];
428 26
        foreach ($rows as $row) {
429 24
            $vs = [];
430 24
            foreach ($row as $i => $value) {
431 24
                if (isset($columns[$i], $columnSchemas[$columns[$i]])) {
432 15
                    $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
433
                }
434 24
                if (is_string($value)) {
435 16
                    $value = $schema->quoteValue($value);
436 15
                } elseif (is_float($value)) {
437
                    // ensure type cast always has . as decimal separator in all locales
438 2
                    $value = StringHelper::floatToString($value);
439 15
                } elseif ($value === false) {
440 4
                    $value = 0;
441 15
                } elseif ($value === null) {
442 8
                    $value = 'NULL';
443 10
                } elseif ($value instanceof ExpressionInterface) {
444 6
                    $value = $this->buildExpression($value, $params);
445
                }
446 24
                $vs[] = $value;
447
            }
448 24
            $values[] = '(' . implode(', ', $vs) . ')';
449
        }
450 26
        if (empty($values)) {
451 2
            return '';
452
        }
453
454 24
        foreach ($columns as $i => $name) {
455 22
            $columns[$i] = $schema->quoteColumnName($name);
456
        }
457
458 24
        return 'INSERT INTO ' . $schema->quoteTableName($table)
459 24
        . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
460
    }
461
462
    /**
463
     * Creates an SQL statement to insert rows into a database table if
464
     * they do not already exist (matching unique constraints),
465
     * or update them if they do.
466
     *
467
     * For example,
468
     *
469
     * ```php
470
     * $sql = $queryBuilder->upsert('pages', [
471
     *     'name' => 'Front page',
472
     *     'url' => 'http://example.com/', // url is unique
473
     *     'visits' => 0,
474
     * ], [
475
     *     'visits' => new \yii\db\Expression('visits + 1'),
476
     * ], $params);
477
     * ```
478
     *
479
     * The method will properly escape the table and column names.
480
     *
481
     * @param string $table the table that new rows will be inserted into/updated in.
482
     * @param array|Query $insertColumns the column data (name => value) to be inserted into the table or instance
483
     * of [[Query]] to perform `INSERT INTO ... SELECT` SQL statement.
484
     * @param array|bool $updateColumns the column data (name => value) to be updated if they already exist.
485
     * If `true` is passed, the column data will be updated to match the insert column data.
486
     * If `false` is passed, no update will be performed if the column data already exists.
487
     * @param array $params the binding parameters that will be generated by this method.
488
     * They should be bound to the DB command later.
489
     * @return string the resulting SQL.
490
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
491
     * @since 2.0.14
492
     */
493
    public function upsert($table, $insertColumns, $updateColumns, &$params)
494
    {
495
        throw new NotSupportedException($this->db->getDriverName() . ' does not support upsert statements.');
496
    }
497
498
    /**
499
     * @param string $table
500
     * @param array|Query $insertColumns
501
     * @param array|bool $updateColumns
502
     * @param Constraint[] $constraints this parameter recieves a matched constraint list.
503
     * The constraints will be unique by their column names.
504
     * @return array
505
     * @since 2.0.14
506
     */
507 66
    protected function prepareUpsertColumns($table, $insertColumns, $updateColumns, &$constraints = [])
508
    {
509 66
        if ($insertColumns instanceof Query) {
510 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...
511
        } else {
512 42
            $insertNames = array_map([$this->db, 'quoteColumnName'], array_keys($insertColumns));
513
        }
514 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...
515 66
        $uniqueNames = array_map([$this->db, 'quoteColumnName'], $uniqueNames);
516 66
        if ($updateColumns !== true) {
517 36
            return [$uniqueNames, $insertNames, null];
518
        }
519
520 30
        return [$uniqueNames, $insertNames, array_diff($insertNames, $uniqueNames)];
521
    }
522
523
    /**
524
     * Returns all column names belonging to constraints enforcing uniqueness (`PRIMARY KEY`, `UNIQUE INDEX`, etc.)
525
     * for the named table removing constraints which did not cover the specified column list.
526
     * The column list will be unique by column names.
527
     *
528
     * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
529
     * @param string[] $columns source column list.
530
     * @param Constraint[] $constraints this parameter optionally recieves a matched constraint list.
531
     * The constraints will be unique by their column names.
532
     * @return string[] column list.
533
     */
534 66
    private function getTableUniqueColumnNames($name, $columns, &$constraints = [])
535
    {
536 66
        $schema = $this->db->getSchema();
537 66
        if (!$schema instanceof ConstraintFinderInterface) {
538
            return [];
539
        }
540
541 66
        $constraints = [];
542 66
        $primaryKey = $schema->getTablePrimaryKey($name);
543 66
        if ($primaryKey !== null) {
544 49
            $constraints[] = $primaryKey;
545
        }
546 66
        foreach ($schema->getTableIndexes($name) as $constraint) {
547 66
            if ($constraint->isUnique) {
548 66
                $constraints[] = $constraint;
549
            }
550
        }
551 66
        $constraints = array_merge($constraints, $schema->getTableUniques($name));
552
        // Remove duplicates
553 66
        $constraints = array_combine(array_map(function (Constraint $constraint) {
554 66
            $columns = $constraint->columnNames;
555 66
            sort($columns, SORT_STRING);
556 66
            return json_encode($columns);
557 66
        }, $constraints), $constraints);
558 66
        $columnNames = [];
559
        // Remove all constraints which do not cover the specified column list
560 66
        $constraints = array_values(array_filter($constraints, function (Constraint $constraint) use ($schema, $columns, &$columnNames) {
561 66
            $constraintColumnNames = array_map([$schema, 'quoteColumnName'], $constraint->columnNames);
562 66
            $result = !array_diff($constraintColumnNames, $columns);
563 66
            if ($result) {
564 57
                $columnNames = array_merge($columnNames, $constraintColumnNames);
565
            }
566 66
            return $result;
567 66
        }));
568 66
        return array_unique($columnNames);
569
    }
570
571
    /**
572
     * Creates an UPDATE SQL statement.
573
     *
574
     * For example,
575
     *
576
     * ```php
577
     * $params = [];
578
     * $sql = $queryBuilder->update('user', ['status' => 1], 'age > 30', $params);
579
     * ```
580
     *
581
     * The method will properly escape the table and column names.
582
     *
583
     * @param string $table the table to be updated.
584
     * @param array $columns the column data (name => value) to be updated.
585
     * @param array|string $condition the condition that will be put in the WHERE part. Please
586
     * refer to [[Query::where()]] on how to specify condition.
587
     * @param array $params the binding parameters that will be modified by this method
588
     * so that they can be bound to the DB command later.
589
     * @return string the UPDATE SQL
590
     */
591 136
    public function update($table, $columns, $condition, &$params)
592
    {
593 136
        [$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...
594 136
        $sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
595 136
        $where = $this->buildWhere($condition, $params);
596 136
        return $where === '' ? $sql : $sql . ' ' . $where;
597
    }
598
599
    /**
600
     * Prepares a `SET` parts for an `UPDATE` SQL statement.
601
     * @param string $table the table to be updated.
602
     * @param array $columns the column data (name => value) to be updated.
603
     * @param array $params the binding parameters that will be modified by this method
604
     * so that they can be bound to the DB command later.
605
     * @return array an array `SET` parts for an `UPDATE` SQL statement (the first array element) and params (the second array element).
606
     * @since 2.0.14
607
     */
608 168
    protected function prepareUpdateSets($table, $columns, $params = [])
609
    {
610 168
        $tableSchema = $this->db->getTableSchema($table);
611 168
        $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
612 168
        $sets = [];
613 168
        foreach ($columns as $name => $value) {
614
615 168
            $value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
616 168
            if ($value instanceof ExpressionInterface) {
617 93
                $placeholder = $this->buildExpression($value, $params);
618
            } else {
619 127
                $placeholder = $this->bindParam($value, $params);
620
            }
621
622 168
            $sets[] = $this->db->quoteColumnName($name) . '=' . $placeholder;
623
        }
624 168
        return [$sets, $params];
625
    }
626
627
    /**
628
     * Creates a DELETE SQL statement.
629
     *
630
     * For example,
631
     *
632
     * ```php
633
     * $sql = $queryBuilder->delete('user', 'status = 0');
634
     * ```
635
     *
636
     * The method will properly escape the table and column names.
637
     *
638
     * @param string $table the table where the data will be deleted from.
639
     * @param array|string $condition the condition that will be put in the WHERE part. Please
640
     * refer to [[Query::where()]] on how to specify condition.
641
     * @param array $params the binding parameters that will be modified by this method
642
     * so that they can be bound to the DB command later.
643
     * @return string the DELETE SQL
644
     */
645 358
    public function delete($table, $condition, &$params)
646
    {
647 358
        $sql = 'DELETE FROM ' . $this->db->quoteTableName($table);
648 358
        $where = $this->buildWhere($condition, $params);
649
650 358
        return $where === '' ? $sql : $sql . ' ' . $where;
651
    }
652
653
    /**
654
     * Builds a SQL statement for creating a new DB table.
655
     *
656
     * The columns in the new  table should be specified as name-definition pairs (e.g. 'name' => 'string'),
657
     * where name stands for a column name which will be properly quoted by the method, and definition
658
     * stands for the column type which can contain an abstract DB type.
659
     * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
660
     *
661
     * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
662
     * inserted into the generated SQL.
663
     *
664
     * For example,
665
     *
666
     * ```php
667
     * $sql = $queryBuilder->createTable('user', [
668
     *  'id' => 'pk',
669
     *  'name' => 'string',
670
     *  'age' => 'integer',
671
     * ]);
672
     * ```
673
     *
674
     * @param string $table the name of the table to be created. The name will be properly quoted by the method.
675
     * @param array $columns the columns (name => definition) in the new table.
676
     * @param string $options additional SQL fragment that will be appended to the generated SQL.
677
     * @return string the SQL statement for creating a new DB table.
678
     */
679 137
    public function createTable($table, $columns, $options = null)
680
    {
681 137
        $cols = [];
682 137
        foreach ($columns as $name => $type) {
683 137
            if (is_string($name)) {
684 137
                $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type);
685
            } else {
686 137
                $cols[] = "\t" . $type;
687
            }
688
        }
689 137
        $sql = 'CREATE TABLE ' . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
690
691 137
        return $options === null ? $sql : $sql . ' ' . $options;
692
    }
693
694
    /**
695
     * Builds a SQL statement for renaming a DB table.
696
     * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
697
     * @param string $newName the new table name. The name will be properly quoted by the method.
698
     * @return string the SQL statement for renaming a DB table.
699
     */
700 1
    public function renameTable($oldName, $newName)
701
    {
702 1
        return 'RENAME TABLE ' . $this->db->quoteTableName($oldName) . ' TO ' . $this->db->quoteTableName($newName);
703
    }
704
705
    /**
706
     * Builds a SQL statement for dropping a DB table.
707
     * @param string $table the table to be dropped. The name will be properly quoted by the method.
708
     * @return string the SQL statement for dropping a DB table.
709
     */
710 39
    public function dropTable($table)
711
    {
712 39
        return 'DROP TABLE ' . $this->db->quoteTableName($table);
713
    }
714
715
    /**
716
     * Builds a SQL statement for adding a primary key constraint to an existing table.
717
     * @param string $name the name of the primary key constraint.
718
     * @param string $table the table that the primary key constraint will be added to.
719
     * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
720
     * @return string the SQL statement for adding a primary key constraint to an existing table.
721
     */
722 6
    public function addPrimaryKey($name, $table, $columns)
723
    {
724 6
        if (is_string($columns)) {
725 4
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
726
        }
727
728 6
        foreach ($columns as $i => $col) {
729 6
            $columns[$i] = $this->db->quoteColumnName($col);
730
        }
731
732 6
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
733 6
            . $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
734 6
            . implode(', ', $columns) . ')';
735
    }
736
737
    /**
738
     * Builds a SQL statement for removing a primary key constraint to an existing table.
739
     * @param string $name the name of the primary key constraint to be removed.
740
     * @param string $table the table that the primary key constraint will be removed from.
741
     * @return string the SQL statement for removing a primary key constraint from an existing table.
742
     */
743 2
    public function dropPrimaryKey($name, $table)
744
    {
745 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
746 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
747
    }
748
749
    /**
750
     * Builds a SQL statement for truncating a DB table.
751
     * @param string $table the table to be truncated. The name will be properly quoted by the method.
752
     * @return string the SQL statement for truncating a DB table.
753
     */
754 11
    public function truncateTable($table)
755
    {
756 11
        return 'TRUNCATE TABLE ' . $this->db->quoteTableName($table);
757
    }
758
759
    /**
760
     * Builds a SQL statement for adding a new DB column.
761
     * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
762
     * @param string $column the name of the new column. The name will be properly quoted by the method.
763
     * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
764
     * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
765
     * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
766
     * @return string the SQL statement for adding a new column.
767
     */
768 4
    public function addColumn($table, $column, $type)
769
    {
770 4
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
771 4
            . ' ADD ' . $this->db->quoteColumnName($column) . ' '
772 4
            . $this->getColumnType($type);
773
    }
774
775
    /**
776
     * Builds a SQL statement for dropping a DB column.
777
     * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
778
     * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
779
     * @return string the SQL statement for dropping a DB column.
780
     */
781
    public function dropColumn($table, $column)
782
    {
783
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
784
            . ' DROP COLUMN ' . $this->db->quoteColumnName($column);
785
    }
786
787
    /**
788
     * Builds a SQL statement for renaming a column.
789
     * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
790
     * @param string $oldName the old name of the column. The name will be properly quoted by the method.
791
     * @param string $newName the new name of the column. The name will be properly quoted by the method.
792
     * @return string the SQL statement for renaming a DB column.
793
     */
794
    public function renameColumn($table, $oldName, $newName)
795
    {
796
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
797
            . ' RENAME COLUMN ' . $this->db->quoteColumnName($oldName)
798
            . ' TO ' . $this->db->quoteColumnName($newName);
799
    }
800
801
    /**
802
     * Builds a SQL statement for changing the definition of a column.
803
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
804
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
805
     * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
806
     * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
807
     * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
808
     * will become 'varchar(255) not null'.
809
     * @return string the SQL statement for changing the definition of a column.
810
     */
811 1
    public function alterColumn($table, $column, $type)
812
    {
813 1
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' CHANGE '
814 1
            . $this->db->quoteColumnName($column) . ' '
815 1
            . $this->db->quoteColumnName($column) . ' '
816 1
            . $this->getColumnType($type);
817
    }
818
819
    /**
820
     * Builds a SQL statement for adding a foreign key constraint to an existing table.
821
     * The method will properly quote the table and column names.
822
     * @param string $name the name of the foreign key constraint.
823
     * @param string $table the table that the foreign key constraint will be added to.
824
     * @param string|array $columns the name of the column to that the constraint will be added on.
825
     * If there are multiple columns, separate them with commas or use an array to represent them.
826
     * @param string $refTable the table that the foreign key references to.
827
     * @param string|array $refColumns the name of the column that the foreign key references to.
828
     * If there are multiple columns, separate them with commas or use an array to represent them.
829
     * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
830
     * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
831
     * @return string the SQL statement for adding a foreign key constraint to an existing table.
832
     */
833 8
    public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
834
    {
835 8
        $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
836 8
            . ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name)
837 8
            . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')'
838 8
            . ' REFERENCES ' . $this->db->quoteTableName($refTable)
839 8
            . ' (' . $this->buildColumns($refColumns) . ')';
840 8
        if ($delete !== null) {
841 4
            $sql .= ' ON DELETE ' . $delete;
842
        }
843 8
        if ($update !== null) {
844 4
            $sql .= ' ON UPDATE ' . $update;
845
        }
846
847 8
        return $sql;
848
    }
849
850
    /**
851
     * Builds a SQL statement for dropping a foreign key constraint.
852
     * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
853
     * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
854
     * @return string the SQL statement for dropping a foreign key constraint.
855
     */
856 3
    public function dropForeignKey($name, $table)
857
    {
858 3
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
859 3
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
860
    }
861
862
    /**
863
     * Builds a SQL statement for creating a new index.
864
     * @param string $name the name of the index. The name will be properly quoted by the method.
865
     * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
866
     * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
867
     * separate them with commas or use an array to represent them. Each column name will be properly quoted
868
     * by the method, unless a parenthesis is found in the name.
869
     * @param bool $unique whether to add UNIQUE constraint on the created index.
870
     * @return string the SQL statement for creating a new index.
871
     */
872 6
    public function createIndex($name, $table, $columns, $unique = false)
873
    {
874 6
        return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
875 6
            . $this->db->quoteTableName($name) . ' ON '
876 6
            . $this->db->quoteTableName($table)
877 6
            . ' (' . $this->buildColumns($columns) . ')';
878
    }
879
880
    /**
881
     * Builds a SQL statement for dropping an index.
882
     * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
883
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
884
     * @return string the SQL statement for dropping an index.
885
     */
886 4
    public function dropIndex($name, $table)
887
    {
888 4
        return 'DROP INDEX ' . $this->db->quoteTableName($name) . ' ON ' . $this->db->quoteTableName($table);
889
    }
890
891
    /**
892
     * Creates a SQL command for adding an unique constraint to an existing table.
893
     * @param string $name the name of the unique constraint.
894
     * The name will be properly quoted by the method.
895
     * @param string $table the table that the unique constraint will be added to.
896
     * The name will be properly quoted by the method.
897
     * @param string|array $columns the name of the column to that the constraint will be added on.
898
     * If there are multiple columns, separate them with commas.
899
     * The name will be properly quoted by the method.
900
     * @return string the SQL statement for adding an unique constraint to an existing table.
901
     * @since 2.0.13
902
     */
903 6
    public function addUnique($name, $table, $columns)
904
    {
905 6
        if (is_string($columns)) {
906 4
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
907
        }
908 6
        foreach ($columns as $i => $col) {
909 6
            $columns[$i] = $this->db->quoteColumnName($col);
910
        }
911
912 6
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
913 6
            . $this->db->quoteColumnName($name) . ' UNIQUE ('
914 6
            . implode(', ', $columns) . ')';
915
    }
916
917
    /**
918
     * Creates a SQL command for dropping an unique constraint.
919
     * @param string $name the name of the unique constraint to be dropped.
920
     * The name will be properly quoted by the method.
921
     * @param string $table the table whose unique constraint is to be dropped.
922
     * The name will be properly quoted by the method.
923
     * @return string the SQL statement for dropping an unique constraint.
924
     * @since 2.0.13
925
     */
926 2
    public function dropUnique($name, $table)
927
    {
928 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
929 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
930
    }
931
932
    /**
933
     * Creates a SQL command for adding a check constraint to an existing table.
934
     * @param string $name the name of the check constraint.
935
     * The name will be properly quoted by the method.
936
     * @param string $table the table that the check constraint will be added to.
937
     * The name will be properly quoted by the method.
938
     * @param string $expression the SQL of the `CHECK` constraint.
939
     * @return string the SQL statement for adding a check constraint to an existing table.
940
     * @since 2.0.13
941
     */
942 2
    public function addCheck($name, $table, $expression)
943
    {
944 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
945 2
            . $this->db->quoteColumnName($name) . ' CHECK (' . $this->db->quoteSql($expression) . ')';
946
    }
947
948
    /**
949
     * Creates a SQL command for dropping a check constraint.
950
     * @param string $name the name of the check constraint to be dropped.
951
     * The name will be properly quoted by the method.
952
     * @param string $table the table whose check constraint is to be dropped.
953
     * The name will be properly quoted by the method.
954
     * @return string the SQL statement for dropping a check constraint.
955
     * @since 2.0.13
956
     */
957 2
    public function dropCheck($name, $table)
958
    {
959 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
960 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
961
    }
962
963
    /**
964
     * Creates a SQL command for adding a default value constraint to an existing table.
965
     * @param string $name the name of the default value constraint.
966
     * The name will be properly quoted by the method.
967
     * @param string $table the table that the default value constraint will be added to.
968
     * The name will be properly quoted by the method.
969
     * @param string $column the name of the column to that the constraint will be added on.
970
     * The name will be properly quoted by the method.
971
     * @param mixed $value default value.
972
     * @return string the SQL statement for adding a default value constraint to an existing table.
973
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
974
     * @since 2.0.13
975
     */
976
    public function addDefaultValue($name, $table, $column, $value)
977
    {
978
        throw new NotSupportedException($this->db->getDriverName() . ' does not support adding default value constraints.');
979
    }
980
981
    /**
982
     * Creates a SQL command for dropping a default value constraint.
983
     * @param string $name the name of the default value constraint to be dropped.
984
     * The name will be properly quoted by the method.
985
     * @param string $table the table whose default value constraint is to be dropped.
986
     * The name will be properly quoted by the method.
987
     * @return string the SQL statement for dropping a default value constraint.
988
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
989
     * @since 2.0.13
990
     */
991
    public function dropDefaultValue($name, $table)
992
    {
993
        throw new NotSupportedException($this->db->getDriverName() . ' does not support dropping default value constraints.');
994
    }
995
996
    /**
997
     * Creates a SQL statement for resetting the sequence value of a table's primary key.
998
     * The sequence will be reset such that the primary key of the next new row inserted
999
     * will have the specified value or 1.
1000
     * @param string $table the name of the table whose primary key sequence will be reset
1001
     * @param array|string $value the value for the primary key of the next new row inserted. If this is not set,
1002
     * the next new row's primary key will have a value 1.
1003
     * @return string the SQL statement for resetting sequence
1004
     * @throws NotSupportedException if this is not supported by the underlying DBMS
1005
     */
1006
    public function resetSequence($table, $value = null)
1007
    {
1008
        throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.');
1009
    }
1010
1011
    /**
1012
     * Builds a SQL statement for enabling or disabling integrity check.
1013
     * @param bool $check whether to turn on or off the integrity check.
1014
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
1015
     * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
1016
     * @return string the SQL statement for checking integrity
1017
     * @throws NotSupportedException if this is not supported by the underlying DBMS
1018
     */
1019
    public function checkIntegrity($check = true, $schema = '', $table = '')
1020
    {
1021
        throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.');
1022
    }
1023
1024
    /**
1025
     * Builds a SQL command for adding comment to column.
1026
     *
1027
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1028
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
1029
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
1030
     * @return string the SQL statement for adding comment on column
1031
     * @since 2.0.8
1032
     */
1033 2
    public function addCommentOnColumn($table, $column, $comment)
1034
    {
1035 2
        return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS ' . $this->db->quoteValue($comment);
1036
    }
1037
1038
    /**
1039
     * Builds a SQL command for adding comment to table.
1040
     *
1041
     * @param string $table the table whose column is to be commented. The table 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 table
1044
     * @since 2.0.8
1045
     */
1046 1
    public function addCommentOnTable($table, $comment)
1047
    {
1048 1
        return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS ' . $this->db->quoteValue($comment);
1049
    }
1050
1051
    /**
1052
     * Builds a SQL command for adding comment to column.
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 $column the name of the column to be commented. The column name will be properly quoted by the method.
1056
     * @return string the SQL statement for adding comment on column
1057
     * @since 2.0.8
1058
     */
1059 2
    public function dropCommentFromColumn($table, $column)
1060
    {
1061 2
        return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS NULL';
1062
    }
1063
1064
    /**
1065
     * Builds a SQL command for adding comment to table.
1066
     *
1067
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1068
     * @return string the SQL statement for adding comment on column
1069
     * @since 2.0.8
1070
     */
1071 1
    public function dropCommentFromTable($table)
1072
    {
1073 1
        return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS NULL';
1074
    }
1075
1076
    /**
1077
     * Creates a SQL View.
1078
     *
1079
     * @param string $viewName the name of the view to be created.
1080
     * @param string|Query $subQuery the select statement which defines the view.
1081
     * This can be either a string or a [[Query]] object.
1082
     * @return string the `CREATE VIEW` SQL statement.
1083
     * @since 2.0.14
1084
     */
1085 3
    public function createView($viewName, $subQuery)
1086
    {
1087 3
        if ($subQuery instanceof Query) {
1088 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...
1089 3
            array_walk(
1090 3
                $params,
1091 3
                function(&$param) {
1092 3
                    $param = $this->db->quoteValue($param);
1093 3
                }
1094
            );
1095 3
            $subQuery = strtr($rawQuery, $params);
1096
        }
1097
1098 3
        return 'CREATE VIEW ' . $this->db->quoteTableName($viewName) . ' AS ' . $subQuery;
1099
    }
1100
1101
    /**
1102
     * Drops a SQL View.
1103
     *
1104
     * @param string $viewName the name of the view to be dropped.
1105
     * @return string the `DROP VIEW` SQL statement.
1106
     * @since 2.0.14
1107
     */
1108 3
    public function dropView($viewName)
1109
    {
1110 3
        return 'DROP VIEW ' . $this->db->quoteTableName($viewName);
1111
    }
1112
1113
    /**
1114
     * Converts an abstract column type into a physical column type.
1115
     *
1116
     * The conversion is done using the type map specified in [[typeMap]].
1117
     * The following abstract column types are supported (using MySQL as an example to explain the corresponding
1118
     * physical types):
1119
     *
1120
     * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
1121
     * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
1122
     * - `upk`: an unsigned auto-incremental primary key type, will be converted into "int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY"
1123
     * - `char`: char type, will be converted into "char(1)"
1124
     * - `string`: string type, will be converted into "varchar(255)"
1125
     * - `text`: a long string type, will be converted into "text"
1126
     * - `smallint`: a small integer type, will be converted into "smallint(6)"
1127
     * - `integer`: integer type, will be converted into "int(11)"
1128
     * - `bigint`: a big integer type, will be converted into "bigint(20)"
1129
     * - `boolean`: boolean type, will be converted into "tinyint(1)"
1130
     * - `float``: float number type, will be converted into "float"
1131
     * - `decimal`: decimal number type, will be converted into "decimal"
1132
     * - `datetime`: datetime type, will be converted into "datetime"
1133
     * - `timestamp`: timestamp type, will be converted into "timestamp"
1134
     * - `time`: time type, will be converted into "time"
1135
     * - `date`: date type, will be converted into "date"
1136
     * - `money`: money type, will be converted into "decimal(19,4)"
1137
     * - `binary`: binary data type, will be converted into "blob"
1138
     *
1139
     * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
1140
     * the first part will be converted, and the rest of the parts will be appended to the converted result.
1141
     * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
1142
     *
1143
     * For some of the abstract types you can also specify a length or precision constraint
1144
     * by appending it in round brackets directly to the type.
1145
     * For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
1146
     * If the underlying DBMS does not support these kind of constraints for a type it will
1147
     * be ignored.
1148
     *
1149
     * If a type cannot be found in [[typeMap]], it will be returned without any change.
1150
     * @param string|ColumnSchemaBuilder $type abstract column type
1151
     * @return string physical column type.
1152
     */
1153 141
    public function getColumnType($type)
1154
    {
1155 141
        if ($type instanceof ColumnSchemaBuilder) {
1156 33
            $type = $type->__toString();
1157
        }
1158
1159 141
        if (isset($this->typeMap[$type])) {
1160 128
            return $this->typeMap[$type];
1161 77
        } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
1162 39
            if (isset($this->typeMap[$matches[1]])) {
1163 39
                return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
1164
            }
1165 52
        } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
1166 49
            if (isset($this->typeMap[$matches[1]])) {
1167 49
                return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
1168
            }
1169
        }
1170
1171 32
        return $type;
1172
    }
1173
1174
    /**
1175
     * @param array $columns
1176
     * @param array $params the binding parameters to be populated
1177
     * @param bool $distinct
1178
     * @param string $selectOption
1179
     * @return string the SELECT clause built from [[Query::$select]].
1180
     */
1181 1160
    public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
1182
    {
1183 1160
        $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
1184 1160
        if ($selectOption !== null) {
1185
            $select .= ' ' . $selectOption;
1186
        }
1187
1188 1160
        if (empty($columns)) {
1189 895
            return $select . ' *';
1190
        }
1191
1192 546
        foreach ($columns as $i => $column) {
1193 546
            if ($column instanceof ExpressionInterface) {
1194 42
                if (is_int($i)) {
1195 6
                    $columns[$i] = $this->buildExpression($column, $params);
1196
                } else {
1197 42
                    $columns[$i] = $this->buildExpression($column, $params) . ' AS ' . $this->db->quoteColumnName($i);
1198
                }
1199 537
            } elseif ($column instanceof Query) {
1200
                [$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...
1201
                $columns[$i] = "($sql) AS " . $this->db->quoteColumnName($i);
1202 537
            } elseif (is_string($i)) {
1203 23
                if (strpos($column, '(') === false) {
1204 23
                    $column = $this->db->quoteColumnName($column);
1205
                }
1206 23
                $columns[$i] = "$column AS " . $this->db->quoteColumnName($i);
1207 532
            } elseif (strpos($column, '(') === false) {
1208 448
                if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
1209 6
                    $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
1210
                } else {
1211 546
                    $columns[$i] = $this->db->quoteColumnName($column);
1212
                }
1213
            }
1214
        }
1215
1216 546
        return $select . ' ' . implode(', ', $columns);
1217
    }
1218
1219
    /**
1220
     * @param array $tables
1221
     * @param array $params the binding parameters to be populated
1222
     * @return string the FROM clause built from [[Query::$from]].
1223
     */
1224 1160
    public function buildFrom($tables, &$params)
1225
    {
1226 1160
        if (empty($tables)) {
1227 349
            return '';
1228
        }
1229
1230 848
        $tables = $this->quoteTableNames($tables, $params);
1231
1232 848
        return 'FROM ' . implode(', ', $tables);
1233
    }
1234
1235
    /**
1236
     * @param array $joins
1237
     * @param array $params the binding parameters to be populated
1238
     * @return string the JOIN clause built from [[Query::$join]].
1239
     * @throws Exception if the $joins parameter is not in proper format
1240
     */
1241 1160
    public function buildJoin($joins, &$params)
1242
    {
1243 1160
        if (empty($joins)) {
1244 1148
            return '';
1245
        }
1246
1247 54
        foreach ($joins as $i => $join) {
1248 54
            if (!is_array($join) || !isset($join[0], $join[1])) {
1249
                throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
1250
            }
1251
            // 0:join type, 1:join table, 2:on-condition (optional)
1252 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...
1253 54
            $tables = $this->quoteTableNames((array) $table, $params);
1254 54
            $table = reset($tables);
1255 54
            $joins[$i] = "$joinType $table";
1256 54
            if (isset($join[2])) {
1257 54
                $condition = $this->buildCondition($join[2], $params);
1258 54
                if ($condition !== '') {
1259 54
                    $joins[$i] .= ' ON ' . $condition;
1260
                }
1261
            }
1262
        }
1263
1264 54
        return implode($this->separator, $joins);
1265
    }
1266
1267
    /**
1268
     * Quotes table names passed.
1269
     *
1270
     * @param array $tables
1271
     * @param array $params
1272
     * @return array
1273
     */
1274 848
    private function quoteTableNames($tables, &$params)
1275
    {
1276 848
        foreach ($tables as $i => $table) {
1277 848
            if ($table instanceof Query) {
1278 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...
1279 10
                $tables[$i] = "($sql) " . $this->db->quoteTableName($i);
1280 848
            } elseif (is_string($i)) {
1281 79
                if (strpos($table, '(') === false) {
1282 70
                    $table = $this->db->quoteTableName($table);
1283
                }
1284 79
                $tables[$i] = "$table " . $this->db->quoteTableName($i);
1285 827
            } elseif (strpos($table, '(') === false) {
1286 820
                if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
1287 21
                    $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
1288
                } else {
1289 848
                    $tables[$i] = $this->db->quoteTableName($table);
1290
                }
1291
            }
1292
        }
1293
1294 848
        return $tables;
1295
    }
1296
1297
    /**
1298
     * @param string|array $condition
1299
     * @param array $params the binding parameters to be populated
1300
     * @return string the WHERE clause built from [[Query::$where]].
1301
     */
1302 1246
    public function buildWhere($condition, &$params)
1303
    {
1304 1246
        $where = $this->buildCondition($condition, $params);
1305
1306 1246
        return $where === '' ? '' : 'WHERE ' . $where;
1307
    }
1308
1309
    /**
1310
     * @param array $columns
1311
     * @param array $params the binding parameters to be populated
1312
     * @return string the GROUP BY clause
1313
     */
1314 1160
    public function buildGroupBy($columns, &$params)
1315
    {
1316 1160
        if (empty($columns)) {
1317 1154
            return '';
1318
        }
1319 21
        foreach ($columns as $i => $column) {
1320 21
            if ($column instanceof ExpressionInterface) {
1321 3
                $columns[$i] = $this->buildExpression($column);
1322 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...
1323 21
            } elseif (strpos($column, '(') === false) {
1324 21
                $columns[$i] = $this->db->quoteColumnName($column);
1325
            }
1326
        }
1327
1328 21
        return 'GROUP BY ' . implode(', ', $columns);
1329
    }
1330
1331
    /**
1332
     * @param string|array $condition
1333
     * @param array $params the binding parameters to be populated
1334
     * @return string the HAVING clause built from [[Query::$having]].
1335
     */
1336 1160
    public function buildHaving($condition, &$params)
1337
    {
1338 1160
        $having = $this->buildCondition($condition, $params);
1339
1340 1160
        return $having === '' ? '' : 'HAVING ' . $having;
1341
    }
1342
1343
    /**
1344
     * Builds the ORDER BY and LIMIT/OFFSET clauses and appends them to the given SQL.
1345
     * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
1346
     * @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter.
1347
     * @param int $limit the limit number. See [[Query::limit]] for more details.
1348
     * @param int $offset the offset number. See [[Query::offset]] for more details.
1349
     * @param array $params the binding parameters to be populated
1350
     * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
1351
     */
1352 1160
    public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset, &$params)
1353
    {
1354 1160
        $orderBy = $this->buildOrderBy($orderBy, $params);
1355 1160
        if ($orderBy !== '') {
1356 180
            $sql .= $this->separator . $orderBy;
1357
        }
1358 1160
        $limit = $this->buildLimit($limit, $offset);
1359 1160
        if ($limit !== '') {
1360 91
            $sql .= $this->separator . $limit;
1361
        }
1362
1363 1160
        return $sql;
1364
    }
1365
1366
    /**
1367
     * @param array $columns
1368
     * @param array $params the binding parameters to be populated
1369
     * @return string the ORDER BY clause built from [[Query::$orderBy]].
1370
     */
1371 1160
    public function buildOrderBy($columns, &$params)
1372
    {
1373 1160
        if (empty($columns)) {
1374 1125
            return '';
1375
        }
1376 180
        $orders = [];
1377 180
        foreach ($columns as $name => $direction) {
1378 180
            if ($direction instanceof ExpressionInterface) {
1379 3
                $orders[] = $this->buildExpression($direction);
1380 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...
1381
            } else {
1382 180
                $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
1383
            }
1384
        }
1385
1386 180
        return 'ORDER BY ' . implode(', ', $orders);
1387
    }
1388
1389
    /**
1390
     * @param int $limit
1391
     * @param int $offset
1392
     * @return string the LIMIT and OFFSET clauses
1393
     */
1394 420
    public function buildLimit($limit, $offset)
1395
    {
1396 420
        $sql = '';
1397 420
        if ($this->hasLimit($limit)) {
1398 25
            $sql = 'LIMIT ' . $limit;
1399
        }
1400 420
        if ($this->hasOffset($offset)) {
1401 3
            $sql .= ' OFFSET ' . $offset;
1402
        }
1403
1404 420
        return ltrim($sql);
1405
    }
1406
1407
    /**
1408
     * Checks to see if the given limit is effective.
1409
     * @param mixed $limit the given limit
1410
     * @return bool whether the limit is effective
1411
     */
1412 761
    protected function hasLimit($limit)
1413
    {
1414 761
        return ($limit instanceof ExpressionInterface) || ctype_digit((string) $limit);
1415
    }
1416
1417
    /**
1418
     * Checks to see if the given offset is effective.
1419
     * @param mixed $offset the given offset
1420
     * @return bool whether the offset is effective
1421
     */
1422 761
    protected function hasOffset($offset)
1423
    {
1424 761
        return ($offset instanceof ExpressionInterface) || ctype_digit((string) $offset) && (string) $offset !== '0';
1425
    }
1426
1427
    /**
1428
     * @param array $unions
1429
     * @param array $params the binding parameters to be populated
1430
     * @return string the UNION clause built from [[Query::$union]].
1431
     */
1432 819
    public function buildUnion($unions, &$params)
1433
    {
1434 819
        if (empty($unions)) {
1435 819
            return '';
1436
        }
1437
1438 8
        $result = '';
1439
1440 8
        foreach ($unions as $i => $union) {
1441 8
            $query = $union['query'];
1442 8
            if ($query instanceof Query) {
1443 8
                [$unions[$i]['query'], $params] = $this->build($query, $params);
1444
            }
1445
1446 8
            $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) ';
1447
        }
1448
1449 8
        return trim($result);
1450
    }
1451
1452
    /**
1453
     * Processes columns and properly quotes them if necessary.
1454
     * It will join all columns into a string with comma as separators.
1455
     * @param string|array $columns the columns to be processed
1456
     * @return string the processing result
1457
     */
1458 32
    public function buildColumns($columns)
1459
    {
1460 32
        if (!is_array($columns)) {
1461 27
            if (strpos($columns, '(') !== false) {
1462
                return $columns;
1463
            }
1464
1465 27
            $rawColumns = $columns;
1466 27
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
1467 27
            if ($columns === false) {
1468
                throw new InvalidArgumentException("$rawColumns is not valid columns.");
1469
            }
1470
        }
1471 32
        foreach ($columns as $i => $column) {
1472 32
            if ($column instanceof ExpressionInterface) {
1473
                $columns[$i] = $this->buildExpression($column);
1474 32
            } elseif (strpos($column, '(') === false) {
1475 32
                $columns[$i] = $this->db->quoteColumnName($column);
1476
            }
1477
        }
1478
1479 32
        return implode(', ', $columns);
1480
    }
1481
1482
    /**
1483
     * Parses the condition specification and generates the corresponding SQL expression.
1484
     * @param string|array|ExpressionInterface $condition the condition specification. Please refer to [[Query::where()]]
1485
     * on how to specify a condition.
1486
     * @param array $params the binding parameters to be populated
1487
     * @return string the generated SQL expression
1488
     */
1489 1246
    public function buildCondition($condition, &$params)
1490
    {
1491 1246
        if (is_array($condition)) {
1492 982
            if (empty($condition)) {
1493 3
                return '';
1494
            }
1495
1496 982
            $condition = $this->createConditionFromArray($condition);
1497
        }
1498
1499 1246
        if ($condition instanceof ExpressionInterface) {
1500 1003
            return $this->buildExpression($condition, $params);
1501
        }
1502
1503 1227
        return (string) $condition;
1504
    }
1505
1506
    /**
1507
     * Transforms $condition defined in array format (as described in [[Query::where()]]
1508
     * to instance of [[yii\db\condition\ConditionInterface|ConditionInterface]] according to
1509
     * [[conditionClasses]] map.
1510
     *
1511
     * @param string|array $condition
1512
     * @see conditionClasses
1513
     * @return ConditionInterface
1514
     * @since 2.0.14
1515
     */
1516 982
    public function createConditionFromArray($condition)
1517
    {
1518 982
        if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
1519 589
            $operator = strtoupper(array_shift($condition));
1520 589
            if (isset($this->conditionClasses[$operator])) {
1521 511
                $className = $this->conditionClasses[$operator];
1522
            } else {
1523 84
                $className = 'yii\db\conditions\SimpleCondition';
1524
            }
1525
            /** @var ConditionInterface $className */
1526 589
            return $className::fromArrayDefinition($operator, $condition);
1527
        }
1528
1529
        // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
1530 693
        return new HashCondition($condition);
0 ignored issues
show
Bug introduced by
It seems like $condition defined by parameter $condition on line 1516 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...
1531
    }
1532
1533
    /**
1534
     * Creates a condition based on column-value pairs.
1535
     * @param array $condition the condition specification.
1536
     * @param array $params the binding parameters to be populated
1537
     * @return string the generated SQL expression
1538
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1539
     */
1540
    public function buildHashCondition($condition, &$params)
1541
    {
1542
        return $this->buildCondition(new HashCondition($condition), $params);
1543
    }
1544
1545
    /**
1546
     * Connects two or more SQL expressions with the `AND` or `OR` operator.
1547
     * @param string $operator the operator to use for connecting the given operands
1548
     * @param array $operands the SQL expressions to connect.
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 buildAndCondition($operator, $operands, &$params)
1554
    {
1555
        array_unshift($operands, $operator);
1556
        return $this->buildCondition($operands, $params);
1557
    }
1558
1559
    /**
1560
     * Inverts an SQL expressions with `NOT` operator.
1561
     * @param string $operator the operator to use for connecting the given operands
1562
     * @param array $operands the SQL expressions to connect.
1563
     * @param array $params the binding parameters to be populated
1564
     * @return string the generated SQL expression
1565
     * @throws InvalidArgumentException if wrong number of operands have been given.
1566
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1567
     */
1568
    public function buildNotCondition($operator, $operands, &$params)
1569
    {
1570
        array_unshift($operands, $operator);
1571
        return $this->buildCondition($operands, $params);
1572
    }
1573
1574
    /**
1575
     * Creates an SQL expressions with the `BETWEEN` operator.
1576
     * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
1577
     * @param array $operands the first operand is the column name. The second and third operands
1578
     * describe the interval that column value should be in.
1579
     * @param array $params the binding parameters to be populated
1580
     * @return string the generated SQL expression
1581
     * @throws InvalidArgumentException if wrong number of operands have been given.
1582
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1583
     */
1584
    public function buildBetweenCondition($operator, $operands, &$params)
1585
    {
1586
        array_unshift($operands, $operator);
1587
        return $this->buildCondition($operands, $params);
1588
    }
1589
1590
    /**
1591
     * Creates an SQL expressions with the `IN` operator.
1592
     * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
1593
     * @param array $operands the first operand is the column name. If it is an array
1594
     * a composite IN condition will be generated.
1595
     * The second operand is an array of values that column value should be among.
1596
     * If it is an empty array the generated expression will be a `false` value if
1597
     * operator is `IN` and empty if operator is `NOT IN`.
1598
     * @param array $params the binding parameters to be populated
1599
     * @return string the generated SQL expression
1600
     * @throws Exception if wrong number of operands have been given.
1601
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1602
     */
1603
    public function buildInCondition($operator, $operands, &$params)
1604
    {
1605
        array_unshift($operands, $operator);
1606
        return $this->buildCondition($operands, $params);
1607
    }
1608
1609
    /**
1610
     * Creates an SQL expressions with the `LIKE` operator.
1611
     * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
1612
     * @param array $operands an array of two or three operands
1613
     *
1614
     * - The first operand is the column name.
1615
     * - The second operand is a single value or an array of values that column value
1616
     *   should be compared with. If it is an empty array the generated expression will
1617
     *   be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
1618
     *   is `NOT LIKE` or `OR NOT LIKE`.
1619
     * - An optional third operand can also be provided to specify how to escape special characters
1620
     *   in the value(s). The operand should be an array of mappings from the special characters to their
1621
     *   escaped counterparts. If this operand is not provided, a default escape mapping will be used.
1622
     *   You may use `false` or an empty array to indicate the values are already escaped and no escape
1623
     *   should be applied. Note that when using an escape mapping (or the third operand is not provided),
1624
     *   the values will be automatically enclosed within a pair of percentage characters.
1625
     * @param array $params the binding parameters to be populated
1626
     * @return string the generated SQL expression
1627
     * @throws InvalidArgumentException if wrong number of operands have been given.
1628
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1629
     */
1630
    public function buildLikeCondition($operator, $operands, &$params)
1631
    {
1632
        array_unshift($operands, $operator);
1633
        return $this->buildCondition($operands, $params);
1634
    }
1635
1636
    /**
1637
     * Creates an SQL expressions with the `EXISTS` operator.
1638
     * @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
1639
     * @param array $operands contains only one element which is a [[Query]] object representing the sub-query.
1640
     * @param array $params the binding parameters to be populated
1641
     * @return string the generated SQL expression
1642
     * @throws InvalidArgumentException if the operand is not a [[Query]] object.
1643
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1644
     */
1645
    public function buildExistsCondition($operator, $operands, &$params)
1646
    {
1647
        array_unshift($operands, $operator);
1648
        return $this->buildCondition($operands, $params);
1649
    }
1650
1651
    /**
1652
     * Creates an SQL expressions like `"column" operator value`.
1653
     * @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc.
1654
     * @param array $operands contains two column names.
1655
     * @param array $params the binding parameters to be populated
1656
     * @return string the generated SQL expression
1657
     * @throws InvalidArgumentException if wrong number of operands have been given.
1658
     * @deprecated since 2.0.14. Use `buildCondition()` instead.
1659
     */
1660
    public function buildSimpleCondition($operator, $operands, &$params)
1661
    {
1662
        array_unshift($operands, $operator);
1663
        return $this->buildCondition($operands, $params);
1664
    }
1665
1666
    /**
1667
     * Creates a SELECT EXISTS() SQL statement.
1668
     * @param string $rawSql the subquery in a raw form to select from.
1669
     * @return string the SELECT EXISTS() SQL statement.
1670
     * @since 2.0.8
1671
     */
1672 70
    public function selectExists($rawSql)
1673
    {
1674 70
        return 'SELECT EXISTS(' . $rawSql . ')';
1675
    }
1676
1677
    /**
1678
     * Helper method to add $value to $params array using [[PARAM_PREFIX]].
1679
     *
1680
     * @param string|null $value
1681
     * @param array $params passed by reference
1682
     * @return string the placeholder name in $params array
1683
     *
1684
     * @since 2.0.14
1685
     */
1686 1031
    public function bindParam($value, &$params)
1687
    {
1688 1031
        $phName = self::PARAM_PREFIX . count($params);
1689 1031
        $params[$phName] = $value;
1690
1691 1031
        return $phName;
1692
    }
1693
}
1694