Completed
Push — master ( b139e8...786a73 )
by Dmitry
11:12
created

QueryBuilder::buildExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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