Completed
Push — master ( bd642b...7a44ed )
by Dmitry
12:29
created

QueryBuilder::setConditionClasses()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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