Passed
Push — master ( 385fe1...5adb55 )
by Alexander
10:01
created

framework/db/Command.php (2 issues)

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;
11
use yii\base\Component;
12
use yii\base\NotSupportedException;
13
14
/**
15
 * Command represents a SQL statement to be executed against a database.
16
 *
17
 * A command object is usually created by calling [[Connection::createCommand()]].
18
 * The SQL statement it represents can be set via the [[sql]] property.
19
 *
20
 * To execute a non-query SQL (such as INSERT, DELETE, UPDATE), call [[execute()]].
21
 * To execute a SQL statement that returns a result data set (such as SELECT),
22
 * use [[queryAll()]], [[queryOne()]], [[queryColumn()]], [[queryScalar()]], or [[query()]].
23
 *
24
 * For example,
25
 *
26
 * ```php
27
 * $users = $connection->createCommand('SELECT * FROM user')->queryAll();
28
 * ```
29
 *
30
 * Command supports SQL statement preparation and parameter binding.
31
 * Call [[bindValue()]] to bind a value to a SQL parameter;
32
 * Call [[bindParam()]] to bind a PHP variable to a SQL parameter.
33
 * When binding a parameter, the SQL statement is automatically prepared.
34
 * You may also call [[prepare()]] explicitly to prepare a SQL statement.
35
 *
36
 * Command also supports building SQL statements by providing methods such as [[insert()]],
37
 * [[update()]], etc. For example, the following code will create and execute an INSERT SQL statement:
38
 *
39
 * ```php
40
 * $connection->createCommand()->insert('user', [
41
 *     'name' => 'Sam',
42
 *     'age' => 30,
43
 * ])->execute();
44
 * ```
45
 *
46
 * To build SELECT SQL statements, please use [[Query]] instead.
47
 *
48
 * For more details and usage information on Command, see the [guide article on Database Access Objects](guide:db-dao).
49
 *
50
 * @property string $rawSql The raw SQL with parameter values inserted into the corresponding placeholders in
51
 * [[sql]].
52
 * @property string $sql The SQL statement to be executed.
53
 *
54
 * @author Qiang Xue <[email protected]>
55
 * @since 2.0
56
 */
57
class Command extends Component
58
{
59
    /**
60
     * @var Connection the DB connection that this command is associated with
61
     */
62
    public $db;
63
    /**
64
     * @var \PDOStatement the PDOStatement object that this command is associated with
65
     */
66
    public $pdoStatement;
67
    /**
68
     * @var int the default fetch mode for this command.
69
     * @see https://secure.php.net/manual/en/pdostatement.setfetchmode.php
70
     */
71
    public $fetchMode = \PDO::FETCH_ASSOC;
72
    /**
73
     * @var array the parameters (name => value) that are bound to the current PDO statement.
74
     * This property is maintained by methods such as [[bindValue()]]. It is mainly provided for logging purpose
75
     * and is used to generate [[rawSql]]. Do not modify it directly.
76
     */
77
    public $params = [];
78
    /**
79
     * @var int the default number of seconds that query results can remain valid in cache.
80
     * Use 0 to indicate that the cached data will never expire. And use a negative number to indicate
81
     * query cache should not be used.
82
     * @see cache()
83
     */
84
    public $queryCacheDuration;
85
    /**
86
     * @var \yii\caching\Dependency the dependency to be associated with the cached query result for this command
87
     * @see cache()
88
     */
89
    public $queryCacheDependency;
90
91
    /**
92
     * @var array pending parameters to be bound to the current PDO statement.
93
     * @since 2.0.33
94
     */
95
    protected $pendingParams = [];
96
97
    /**
98
     * @var string the SQL statement that this command represents
99
     */
100
    private $_sql;
101
    /**
102
     * @var string name of the table, which schema, should be refreshed after command execution.
103
     */
104
    private $_refreshTableName;
105
    /**
106
     * @var string|false|null the isolation level to use for this transaction.
107
     * See [[Transaction::begin()]] for details.
108
     */
109
    private $_isolationLevel = false;
110
    /**
111
     * @var callable a callable (e.g. anonymous function) that is called when [[\yii\db\Exception]] is thrown
112
     * when executing the command.
113
     */
114
    private $_retryHandler;
115
116
117
    /**
118
     * Enables query cache for this command.
119
     * @param int $duration the number of seconds that query result of this command can remain valid in the cache.
120
     * If this is not set, the value of [[Connection::queryCacheDuration]] will be used instead.
121
     * Use 0 to indicate that the cached data will never expire.
122
     * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query result.
123
     * @return $this the command object itself
124
     */
125 6
    public function cache($duration = null, $dependency = null)
126
    {
127 6
        $this->queryCacheDuration = $duration === null ? $this->db->queryCacheDuration : $duration;
128 6
        $this->queryCacheDependency = $dependency;
129 6
        return $this;
130
    }
131
132
    /**
133
     * Disables query cache for this command.
134
     * @return $this the command object itself
135
     */
136 3
    public function noCache()
137
    {
138 3
        $this->queryCacheDuration = -1;
139 3
        return $this;
140
    }
141
142
    /**
143
     * Returns the SQL statement for this command.
144
     * @return string the SQL statement to be executed
145
     */
146 1569
    public function getSql()
147
    {
148 1569
        return $this->_sql;
149
    }
150
151
    /**
152
     * Specifies the SQL statement to be executed. The SQL statement will be quoted using [[Connection::quoteSql()]].
153
     * The previous SQL (if any) will be discarded, and [[params]] will be cleared as well. See [[reset()]]
154
     * for details.
155
     *
156
     * @param string $sql the SQL statement to be set.
157
     * @return $this this command instance
158
     * @see reset()
159
     * @see cancel()
160
     */
161 1596
    public function setSql($sql)
162
    {
163 1596
        if ($sql !== $this->_sql) {
164 1596
            $this->cancel();
165 1596
            $this->reset();
166 1596
            $this->_sql = $this->db->quoteSql($sql);
167
        }
168
169 1596
        return $this;
170
    }
171
172
    /**
173
     * Specifies the SQL statement to be executed. The SQL statement will not be modified in any way.
174
     * The previous SQL (if any) will be discarded, and [[params]] will be cleared as well. See [[reset()]]
175
     * for details.
176
     *
177
     * @param string $sql the SQL statement to be set.
178
     * @return $this this command instance
179
     * @since 2.0.13
180
     * @see reset()
181
     * @see cancel()
182
     */
183 31
    public function setRawSql($sql)
184
    {
185 31
        if ($sql !== $this->_sql) {
186 31
            $this->cancel();
187 31
            $this->reset();
188 31
            $this->_sql = $sql;
189
        }
190
191 31
        return $this;
192
    }
193
194
    /**
195
     * Returns the raw SQL by inserting parameter values into the corresponding placeholders in [[sql]].
196
     * Note that the return value of this method should mainly be used for logging purpose.
197
     * It is likely that this method returns an invalid SQL due to improper replacement of parameter placeholders.
198
     * @return string the raw SQL with parameter values inserted into the corresponding placeholders in [[sql]].
199
     */
200 1575
    public function getRawSql()
201
    {
202 1575
        if (empty($this->params)) {
203 1395
            return $this->_sql;
204
        }
205 1128
        $params = [];
206 1128
        foreach ($this->params as $name => $value) {
207 1128
            if (is_string($name) && strncmp(':', $name, 1)) {
208 15
                $name = ':' . $name;
209
            }
210 1128
            if (is_string($value) || $value instanceof Expression) {
211 887
                $params[$name] = $this->db->quoteValue((string)$value);
212 881
            } elseif (is_bool($value)) {
213 25
                $params[$name] = ($value ? 'TRUE' : 'FALSE');
214 874
            } elseif ($value === null) {
215 329
                $params[$name] = 'NULL';
216 817
            } elseif (!is_object($value) && !is_resource($value)) {
217 1128
                $params[$name] = $value;
218
            }
219
        }
220 1128
        if (!isset($params[1])) {
221 1128
            return strtr($this->_sql, $params);
222
        }
223
        $sql = '';
224
        foreach (explode('?', $this->_sql) as $i => $part) {
225
            $sql .= (isset($params[$i]) ? $params[$i] : '') . $part;
226
        }
227
228
        return $sql;
229
    }
230
231
    /**
232
     * Prepares the SQL statement to be executed.
233
     * For complex SQL statement that is to be executed multiple times,
234
     * this may improve performance.
235
     * For SQL statement with binding parameters, this method is invoked
236
     * automatically.
237
     * @param bool $forRead whether this method is called for a read query. If null, it means
238
     * the SQL statement should be used to determine whether it is for read or write.
239
     * @throws Exception if there is any DB error
240
     */
241 1557
    public function prepare($forRead = null)
242
    {
243 1557
        if ($this->pdoStatement) {
244 60
            $this->bindPendingParams();
245 60
            return;
246
        }
247
248 1557
        $sql = $this->getSql();
249
250 1557
        if ($this->db->getTransaction()) {
251
            // primary is in a transaction. use the same connection.
252 21
            $forRead = false;
253
        }
254 1557
        if ($forRead || $forRead === null && $this->db->getSchema()->isReadQuery($sql)) {
255 1507
            $pdo = $this->db->getSlavePdo();
0 ignored issues
show
Deprecated Code introduced by
The function yii\db\Connection::getSlavePdo() has been deprecated: since 2.0.36. Use [[getReplicaPdo()]] instead. ( Ignorable by Annotation )

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

255
            $pdo = /** @scrutinizer ignore-deprecated */ $this->db->getSlavePdo();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
256
        } else {
257 720
            $pdo = $this->db->getMasterPdo();
0 ignored issues
show
Deprecated Code introduced by
The function yii\db\Connection::getMasterPdo() has been deprecated: since 2.0.36. Use [[getPrimaryPdo()]] instead. ( Ignorable by Annotation )

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

257
            $pdo = /** @scrutinizer ignore-deprecated */ $this->db->getMasterPdo();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
258
        }
259
260
        try {
261 1557
            $this->pdoStatement = $pdo->prepare($sql);
262 1556
            $this->bindPendingParams();
263 5
        } catch (\Exception $e) {
264 5
            $message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
265 5
            $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
266 5
            throw new Exception($message, $errorInfo, (int) $e->getCode(), $e);
267
        }
268 1556
    }
269
270
    /**
271
     * Cancels the execution of the SQL statement.
272
     * This method mainly sets [[pdoStatement]] to be null.
273
     */
274 1596
    public function cancel()
275
    {
276 1596
        $this->pdoStatement = null;
277 1596
    }
278
279
    /**
280
     * Binds a parameter to the SQL statement to be executed.
281
     * @param string|int $name parameter identifier. For a prepared statement
282
     * using named placeholders, this will be a parameter name of
283
     * the form `:name`. For a prepared statement using question mark
284
     * placeholders, this will be the 1-indexed position of the parameter.
285
     * @param mixed $value the PHP variable to bind to the SQL statement parameter (passed by reference)
286
     * @param int $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
287
     * @param int $length length of the data type
288
     * @param mixed $driverOptions the driver-specific options
289
     * @return $this the current command being executed
290
     * @see https://secure.php.net/manual/en/function.PDOStatement-bindParam.php
291
     */
292 3
    public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null)
293
    {
294 3
        $this->prepare();
295
296 3
        if ($dataType === null) {
297 3
            $dataType = $this->db->getSchema()->getPdoType($value);
298
        }
299 3
        if ($length === null) {
300 3
            $this->pdoStatement->bindParam($name, $value, $dataType);
301
        } elseif ($driverOptions === null) {
302
            $this->pdoStatement->bindParam($name, $value, $dataType, $length);
303
        } else {
304
            $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions);
305
        }
306 3
        $this->params[$name] = &$value;
307
308 3
        return $this;
309
    }
310
311
    /**
312
     * Binds pending parameters that were registered via [[bindValue()]] and [[bindValues()]].
313
     * Note that this method requires an active [[pdoStatement]].
314
     */
315 1556
    protected function bindPendingParams()
316
    {
317 1556
        foreach ($this->pendingParams as $name => $value) {
318 1111
            $this->pdoStatement->bindValue($name, $value[0], $value[1]);
319
        }
320 1556
        $this->pendingParams = [];
321 1556
    }
322
323
    /**
324
     * Binds a value to a parameter.
325
     * @param string|int $name Parameter identifier. For a prepared statement
326
     * using named placeholders, this will be a parameter name of
327
     * the form `:name`. For a prepared statement using question mark
328
     * placeholders, this will be the 1-indexed position of the parameter.
329
     * @param mixed $value The value to bind to the parameter
330
     * @param int $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
331
     * @return $this the current command being executed
332
     * @see https://secure.php.net/manual/en/function.PDOStatement-bindValue.php
333
     */
334 6
    public function bindValue($name, $value, $dataType = null)
335
    {
336 6
        if ($dataType === null) {
337 6
            $dataType = $this->db->getSchema()->getPdoType($value);
338
        }
339 6
        $this->pendingParams[$name] = [$value, $dataType];
340 6
        $this->params[$name] = $value;
341
342 6
        return $this;
343
    }
344
345
    /**
346
     * Binds a list of values to the corresponding parameters.
347
     * This is similar to [[bindValue()]] except that it binds multiple values at a time.
348
     * Note that the SQL data type of each value is determined by its PHP type.
349
     * @param array $values the values to be bound. This must be given in terms of an associative
350
     * array with array keys being the parameter names, and array values the corresponding parameter values,
351
     * e.g. `[':name' => 'John', ':age' => 25]`. By default, the PDO type of each value is determined
352
     * by its PHP type. You may explicitly specify the PDO type by using a [[yii\db\PdoValue]] class: `new PdoValue(value, type)`,
353
     * e.g. `[':name' => 'John', ':profile' => new PdoValue($profile, \PDO::PARAM_LOB)]`.
354
     * @return $this the current command being executed
355
     */
356 1596
    public function bindValues($values)
357
    {
358 1596
        if (empty($values)) {
359 1419
            return $this;
360
        }
361
362 1136
        $schema = $this->db->getSchema();
363 1136
        foreach ($values as $name => $value) {
364 1136
            if (is_array($value)) { // TODO: Drop in Yii 2.1
365 3
                $this->pendingParams[$name] = $value;
366 3
                $this->params[$name] = $value[0];
367 1133
            } elseif ($value instanceof PdoValue) {
368 105
                $this->pendingParams[$name] = [$value->getValue(), $value->getType()];
369 105
                $this->params[$name] = $value->getValue();
370
            } else {
371 1133
                $type = $schema->getPdoType($value);
372 1133
                $this->pendingParams[$name] = [$value, $type];
373 1136
                $this->params[$name] = $value;
374
            }
375
        }
376
377 1136
        return $this;
378
    }
379
380
    /**
381
     * Executes the SQL statement and returns query result.
382
     * This method is for executing a SQL query that returns result set, such as `SELECT`.
383
     * @return DataReader the reader object for fetching the query result
384
     * @throws Exception execution failed
385
     */
386 15
    public function query()
387
    {
388 15
        return $this->queryInternal('');
389
    }
390
391
    /**
392
     * Executes the SQL statement and returns ALL rows at once.
393
     * @param int $fetchMode the result fetch mode. Please refer to [PHP manual](https://secure.php.net/manual/en/function.PDOStatement-setFetchMode.php)
394
     * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
395
     * @return array all rows of the query result. Each array element is an array representing a row of data.
396
     * An empty array is returned if the query results in nothing.
397
     * @throws Exception execution failed
398
     */
399 1370
    public function queryAll($fetchMode = null)
400
    {
401 1370
        return $this->queryInternal('fetchAll', $fetchMode);
402
    }
403
404
    /**
405
     * Executes the SQL statement and returns the first row of the result.
406
     * This method is best used when only the first row of result is needed for a query.
407
     * @param int $fetchMode the result fetch mode. Please refer to [PHP manual](https://secure.php.net/manual/en/pdostatement.setfetchmode.php)
408
     * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
409
     * @return array|false the first row (in terms of an array) of the query result. False is returned if the query
410
     * results in nothing.
411
     * @throws Exception execution failed
412
     */
413 501
    public function queryOne($fetchMode = null)
414
    {
415 501
        return $this->queryInternal('fetch', $fetchMode);
416
    }
417
418
    /**
419
     * Executes the SQL statement and returns the value of the first column in the first row of data.
420
     * This method is best used when only a single value is needed for a query.
421
     * @return string|null|false the value of the first column in the first row of the query result.
422
     * False is returned if there is no value.
423
     * @throws Exception execution failed
424
     */
425 332
    public function queryScalar()
426
    {
427 332
        $result = $this->queryInternal('fetchColumn', 0);
428 329
        if (is_resource($result) && get_resource_type($result) === 'stream') {
429 20
            return stream_get_contents($result);
430
        }
431
432 321
        return $result;
433
    }
434
435
    /**
436
     * Executes the SQL statement and returns the first column of the result.
437
     * This method is best used when only the first column of result (i.e. the first element in each row)
438
     * is needed for a query.
439
     * @return array the first column of the query result. Empty array is returned if the query results in nothing.
440
     * @throws Exception execution failed
441
     */
442 87
    public function queryColumn()
443
    {
444 87
        return $this->queryInternal('fetchAll', \PDO::FETCH_COLUMN);
445
    }
446
447
    /**
448
     * Creates an INSERT command.
449
     *
450
     * For example,
451
     *
452
     * ```php
453
     * $connection->createCommand()->insert('user', [
454
     *     'name' => 'Sam',
455
     *     'age' => 30,
456
     * ])->execute();
457
     * ```
458
     *
459
     * The method will properly escape the column names, and bind the values to be inserted.
460
     *
461
     * Note that the created command is not executed until [[execute()]] is called.
462
     *
463
     * @param string $table the table that new rows will be inserted into.
464
     * @param array|\yii\db\Query $columns the column data (name => value) to be inserted into the table or instance
465
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
466
     * Passing of [[yii\db\Query|Query]] is available since version 2.0.11.
467
     * @return $this the command object itself
468
     */
469 437
    public function insert($table, $columns)
470
    {
471 437
        $params = [];
472 437
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
473
474 428
        return $this->setSql($sql)->bindValues($params);
475
    }
476
477
    /**
478
     * Creates a batch INSERT command.
479
     *
480
     * For example,
481
     *
482
     * ```php
483
     * $connection->createCommand()->batchInsert('user', ['name', 'age'], [
484
     *     ['Tom', 30],
485
     *     ['Jane', 20],
486
     *     ['Linda', 25],
487
     * ])->execute();
488
     * ```
489
     *
490
     * The method will properly escape the column names, and quote the values to be inserted.
491
     *
492
     * Note that the values in each row must match the corresponding column names.
493
     *
494
     * Also note that the created command is not executed until [[execute()]] is called.
495
     *
496
     * @param string $table the table that new rows will be inserted into.
497
     * @param array $columns the column names
498
     * @param array|\Generator $rows the rows to be batch inserted into the table
499
     * @return $this the command object itself
500
     */
501 31
    public function batchInsert($table, $columns, $rows)
502
    {
503 31
        $table = $this->db->quoteSql($table);
504
        $columns = array_map(function ($column) {
505 31
            return $this->db->quoteSql($column);
506 31
        }, $columns);
507
508 31
        $params = [];
509 31
        $sql = $this->db->getQueryBuilder()->batchInsert($table, $columns, $rows, $params);
510
511 31
        $this->setRawSql($sql);
512 31
        $this->bindValues($params);
513
514 31
        return $this;
515
    }
516
517
    /**
518
     * Creates a command to insert rows into a database table if
519
     * they do not already exist (matching unique constraints),
520
     * or update them if they do.
521
     *
522
     * For example,
523
     *
524
     * ```php
525
     * $sql = $queryBuilder->upsert('pages', [
526
     *     'name' => 'Front page',
527
     *     'url' => 'http://example.com/', // url is unique
528
     *     'visits' => 0,
529
     * ], [
530
     *     'visits' => new \yii\db\Expression('visits + 1'),
531
     * ], $params);
532
     * ```
533
     *
534
     * The method will properly escape the table and column names.
535
     *
536
     * @param string $table the table that new rows will be inserted into/updated in.
537
     * @param array|Query $insertColumns the column data (name => value) to be inserted into the table or instance
538
     * of [[Query]] to perform `INSERT INTO ... SELECT` SQL statement.
539
     * @param array|bool $updateColumns the column data (name => value) to be updated if they already exist.
540
     * If `true` is passed, the column data will be updated to match the insert column data.
541
     * If `false` is passed, no update will be performed if the column data already exists.
542
     * @param array $params the parameters to be bound to the command.
543
     * @return $this the command object itself.
544
     * @since 2.0.14
545
     */
546 68
    public function upsert($table, $insertColumns, $updateColumns = true, $params = [])
547
    {
548 68
        $sql = $this->db->getQueryBuilder()->upsert($table, $insertColumns, $updateColumns, $params);
549
550 68
        return $this->setSql($sql)->bindValues($params);
551
    }
552
553
    /**
554
     * Creates an UPDATE command.
555
     *
556
     * For example,
557
     *
558
     * ```php
559
     * $connection->createCommand()->update('user', ['status' => 1], 'age > 30')->execute();
560
     * ```
561
     *
562
     * or with using parameter binding for the condition:
563
     *
564
     * ```php
565
     * $minAge = 30;
566
     * $connection->createCommand()->update('user', ['status' => 1], 'age > :minAge', [':minAge' => $minAge])->execute();
567
     * ```
568
     *
569
     * The method will properly escape the column names and bind the values to be updated.
570
     *
571
     * Note that the created command is not executed until [[execute()]] is called.
572
     *
573
     * @param string $table the table to be updated.
574
     * @param array $columns the column data (name => value) to be updated.
575
     * @param string|array $condition the condition that will be put in the WHERE part. Please
576
     * refer to [[Query::where()]] on how to specify condition.
577
     * @param array $params the parameters to be bound to the command
578
     * @return $this the command object itself
579
     */
580 93
    public function update($table, $columns, $condition = '', $params = [])
581
    {
582 93
        $sql = $this->db->getQueryBuilder()->update($table, $columns, $condition, $params);
583
584 93
        return $this->setSql($sql)->bindValues($params);
585
    }
586
587
    /**
588
     * Creates a DELETE command.
589
     *
590
     * For example,
591
     *
592
     * ```php
593
     * $connection->createCommand()->delete('user', 'status = 0')->execute();
594
     * ```
595
     *
596
     * or with using parameter binding for the condition:
597
     *
598
     * ```php
599
     * $status = 0;
600
     * $connection->createCommand()->delete('user', 'status = :status', [':status' => $status])->execute();
601
     * ```
602
     *
603
     * The method will properly escape the table and column names.
604
     *
605
     * Note that the created command is not executed until [[execute()]] is called.
606
     *
607
     * @param string $table the table where the data will be deleted from.
608
     * @param string|array $condition the condition that will be put in the WHERE part. Please
609
     * refer to [[Query::where()]] on how to specify condition.
610
     * @param array $params the parameters to be bound to the command
611
     * @return $this the command object itself
612
     */
613 369
    public function delete($table, $condition = '', $params = [])
614
    {
615 369
        $sql = $this->db->getQueryBuilder()->delete($table, $condition, $params);
616
617 369
        return $this->setSql($sql)->bindValues($params);
618
    }
619
620
    /**
621
     * Creates a SQL command for creating a new DB table.
622
     *
623
     * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
624
     * where name stands for a column name which will be properly quoted by the method, and definition
625
     * stands for the column type which can contain an abstract DB type.
626
     * The method [[QueryBuilder::getColumnType()]] will be called
627
     * to convert the abstract column types to physical ones. For example, `string` will be converted
628
     * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
629
     *
630
     * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
631
     * inserted into the generated SQL.
632
     *
633
     * @param string $table the name of the table to be created. The name will be properly quoted by the method.
634
     * @param array $columns the columns (name => definition) in the new table.
635
     * @param string $options additional SQL fragment that will be appended to the generated SQL.
636
     * @return $this the command object itself
637
     */
638 144
    public function createTable($table, $columns, $options = null)
639
    {
640 144
        $sql = $this->db->getQueryBuilder()->createTable($table, $columns, $options);
641
642 144
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
643
    }
644
645
    /**
646
     * Creates a SQL command for renaming a DB table.
647
     * @param string $table the table to be renamed. The name will be properly quoted by the method.
648
     * @param string $newName the new table name. The name will be properly quoted by the method.
649
     * @return $this the command object itself
650
     */
651 6
    public function renameTable($table, $newName)
652
    {
653 6
        $sql = $this->db->getQueryBuilder()->renameTable($table, $newName);
654
655 6
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
656
    }
657
658
    /**
659
     * Creates a SQL command for dropping a DB table.
660
     * @param string $table the table to be dropped. The name will be properly quoted by the method.
661
     * @return $this the command object itself
662
     */
663 43
    public function dropTable($table)
664
    {
665 43
        $sql = $this->db->getQueryBuilder()->dropTable($table);
666
667 43
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
668
    }
669
670
    /**
671
     * Creates a SQL command for truncating a DB table.
672
     * @param string $table the table to be truncated. The name will be properly quoted by the method.
673
     * @return $this the command object itself
674
     */
675 13
    public function truncateTable($table)
676
    {
677 13
        $sql = $this->db->getQueryBuilder()->truncateTable($table);
678
679 13
        return $this->setSql($sql);
680
    }
681
682
    /**
683
     * Creates a SQL command for adding a new DB column.
684
     * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
685
     * @param string $column the name of the new column. The name will be properly quoted by the method.
686
     * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
687
     * to convert the give column type to the physical one. For example, `string` will be converted
688
     * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
689
     * @return $this the command object itself
690
     */
691 7
    public function addColumn($table, $column, $type)
692
    {
693 7
        $sql = $this->db->getQueryBuilder()->addColumn($table, $column, $type);
694
695 7
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
696
    }
697
698
    /**
699
     * Creates a SQL command for dropping a DB column.
700
     * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
701
     * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
702
     * @return $this the command object itself
703
     */
704
    public function dropColumn($table, $column)
705
    {
706
        $sql = $this->db->getQueryBuilder()->dropColumn($table, $column);
707
708
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
709
    }
710
711
    /**
712
     * Creates a SQL command for renaming a column.
713
     * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
714
     * @param string $oldName the old name of the column. The name will be properly quoted by the method.
715
     * @param string $newName the new name of the column. The name will be properly quoted by the method.
716
     * @return $this the command object itself
717
     */
718
    public function renameColumn($table, $oldName, $newName)
719
    {
720
        $sql = $this->db->getQueryBuilder()->renameColumn($table, $oldName, $newName);
721
722
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
723
    }
724
725
    /**
726
     * Creates a SQL command for changing the definition of a column.
727
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
728
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
729
     * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
730
     * to convert the give column type to the physical one. For example, `string` will be converted
731
     * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
732
     * @return $this the command object itself
733
     */
734 2
    public function alterColumn($table, $column, $type)
735
    {
736 2
        $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $type);
737
738 2
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
739
    }
740
741
    /**
742
     * Creates a SQL command for adding a primary key constraint to an existing table.
743
     * The method will properly quote the table and column names.
744
     * @param string $name the name of the primary key constraint.
745
     * @param string $table the table that the primary key constraint will be added to.
746
     * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
747
     * @return $this the command object itself.
748
     */
749 2
    public function addPrimaryKey($name, $table, $columns)
750
    {
751 2
        $sql = $this->db->getQueryBuilder()->addPrimaryKey($name, $table, $columns);
752
753 2
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
754
    }
755
756
    /**
757
     * Creates a SQL command for removing a primary key constraint to an existing table.
758
     * @param string $name the name of the primary key constraint to be removed.
759
     * @param string $table the table that the primary key constraint will be removed from.
760
     * @return $this the command object itself
761
     */
762 2
    public function dropPrimaryKey($name, $table)
763
    {
764 2
        $sql = $this->db->getQueryBuilder()->dropPrimaryKey($name, $table);
765
766 2
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
767
    }
768
769
    /**
770
     * Creates a SQL command for adding a foreign key constraint to an existing table.
771
     * The method will properly quote the table and column names.
772
     * @param string $name the name of the foreign key constraint.
773
     * @param string $table the table that the foreign key constraint will be added to.
774
     * @param string|array $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
775
     * @param string $refTable the table that the foreign key references to.
776
     * @param string|array $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
777
     * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
778
     * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
779
     * @return $this the command object itself
780
     */
781 4
    public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
782
    {
783 4
        $sql = $this->db->getQueryBuilder()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update);
784
785 4
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
786
    }
787
788
    /**
789
     * Creates a SQL command for dropping a foreign key constraint.
790
     * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
791
     * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
792
     * @return $this the command object itself
793
     */
794 5
    public function dropForeignKey($name, $table)
795
    {
796 5
        $sql = $this->db->getQueryBuilder()->dropForeignKey($name, $table);
797
798 5
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
799
    }
800
801
    /**
802
     * Creates a SQL command for creating a new index.
803
     * @param string $name the name of the index. The name will be properly quoted by the method.
804
     * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
805
     * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
806
     * by commas. The column names will be properly quoted by the method.
807
     * @param bool $unique whether to add UNIQUE constraint on the created index.
808
     * @return $this the command object itself
809
     */
810 12
    public function createIndex($name, $table, $columns, $unique = false)
811
    {
812 12
        $sql = $this->db->getQueryBuilder()->createIndex($name, $table, $columns, $unique);
813
814 12
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
815
    }
816
817
    /**
818
     * Creates a SQL command for dropping an index.
819
     * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
820
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
821
     * @return $this the command object itself
822
     */
823 3
    public function dropIndex($name, $table)
824
    {
825 3
        $sql = $this->db->getQueryBuilder()->dropIndex($name, $table);
826
827 3
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
828
    }
829
830
    /**
831
     * Creates a SQL command for adding an unique constraint to an existing table.
832
     * @param string $name the name of the unique constraint.
833
     * The name will be properly quoted by the method.
834
     * @param string $table the table that the unique constraint will be added to.
835
     * The name will be properly quoted by the method.
836
     * @param string|array $columns the name of the column to that the constraint will be added on.
837
     * If there are multiple columns, separate them with commas.
838
     * The name will be properly quoted by the method.
839
     * @return $this the command object itself.
840
     * @since 2.0.13
841
     */
842 2
    public function addUnique($name, $table, $columns)
843
    {
844 2
        $sql = $this->db->getQueryBuilder()->addUnique($name, $table, $columns);
845
846 2
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
847
    }
848
849
    /**
850
     * Creates a SQL command for dropping an unique constraint.
851
     * @param string $name the name of the unique constraint to be dropped.
852
     * The name will be properly quoted by the method.
853
     * @param string $table the table whose unique constraint is to be dropped.
854
     * The name will be properly quoted by the method.
855
     * @return $this the command object itself.
856
     * @since 2.0.13
857
     */
858 2
    public function dropUnique($name, $table)
859
    {
860 2
        $sql = $this->db->getQueryBuilder()->dropUnique($name, $table);
861
862 2
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
863
    }
864
865
    /**
866
     * Creates a SQL command for adding a check constraint to an existing table.
867
     * @param string $name the name of the check constraint.
868
     * The name will be properly quoted by the method.
869
     * @param string $table the table that the check constraint will be added to.
870
     * The name will be properly quoted by the method.
871
     * @param string $expression the SQL of the `CHECK` constraint.
872
     * @return $this the command object itself.
873
     * @since 2.0.13
874
     */
875 1
    public function addCheck($name, $table, $expression)
876
    {
877 1
        $sql = $this->db->getQueryBuilder()->addCheck($name, $table, $expression);
878
879 1
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
880
    }
881
882
    /**
883
     * Creates a SQL command for dropping a check constraint.
884
     * @param string $name the name of the check constraint to be dropped.
885
     * The name will be properly quoted by the method.
886
     * @param string $table the table whose check constraint is to be dropped.
887
     * The name will be properly quoted by the method.
888
     * @return $this the command object itself.
889
     * @since 2.0.13
890
     */
891 1
    public function dropCheck($name, $table)
892
    {
893 1
        $sql = $this->db->getQueryBuilder()->dropCheck($name, $table);
894
895 1
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
896
    }
897
898
    /**
899
     * Creates a SQL command for adding a default value constraint to an existing table.
900
     * @param string $name the name of the default value constraint.
901
     * The name will be properly quoted by the method.
902
     * @param string $table the table that the default value constraint will be added to.
903
     * The name will be properly quoted by the method.
904
     * @param string $column the name of the column to that the constraint will be added on.
905
     * The name will be properly quoted by the method.
906
     * @param mixed $value default value.
907
     * @return $this the command object itself.
908
     * @since 2.0.13
909
     */
910
    public function addDefaultValue($name, $table, $column, $value)
911
    {
912
        $sql = $this->db->getQueryBuilder()->addDefaultValue($name, $table, $column, $value);
913
914
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
915
    }
916
917
    /**
918
     * Creates a SQL command for dropping a default value constraint.
919
     * @param string $name the name of the default value constraint to be dropped.
920
     * The name will be properly quoted by the method.
921
     * @param string $table the table whose default value constraint is to be dropped.
922
     * The name will be properly quoted by the method.
923
     * @return $this the command object itself.
924
     * @since 2.0.13
925
     */
926
    public function dropDefaultValue($name, $table)
927
    {
928
        $sql = $this->db->getQueryBuilder()->dropDefaultValue($name, $table);
929
930
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
931
    }
932
933
    /**
934
     * Creates a SQL command for resetting the sequence value of a table's primary key.
935
     * The sequence will be reset such that the primary key of the next new row inserted
936
     * will have the specified value or the maximum existing value +1.
937
     * @param string $table the name of the table whose primary key sequence will be reset
938
     * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
939
     * the next new row's primary key will have the maximum existing value +1.
940
     * @return $this the command object itself
941
     * @throws NotSupportedException if this is not supported by the underlying DBMS
942
     */
943 25
    public function resetSequence($table, $value = null)
944
    {
945 25
        $sql = $this->db->getQueryBuilder()->resetSequence($table, $value);
946
947 25
        return $this->setSql($sql);
948
    }
949
950
    /**
951
     * Executes a db command resetting the sequence value of a table's primary key.
952
     * Reason for execute is that some databases (Oracle) need several queries to do so.
953
     * The sequence is reset such that the primary key of the next new row inserted
954
     * will have the specified value or the maximum existing value +1.
955
     * @param string $table the name of the table whose primary key sequence is reset
956
     * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
957
     * the next new row's primary key will have the maximum existing value +1.
958
     * @throws NotSupportedException if this is not supported by the underlying DBMS
959
     * @since 2.0.16
960
     */
961 25
    public function executeResetSequence($table, $value = null)
962
    {
963 25
        return $this->db->getQueryBuilder()->executeResetSequence($table, $value);
964
    }
965
966
    /**
967
     * Builds a SQL command for enabling or disabling integrity check.
968
     * @param bool $check whether to turn on or off the integrity check.
969
     * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current
970
     * or default schema.
971
     * @param string $table the table name.
972
     * @return $this the command object itself
973
     * @throws NotSupportedException if this is not supported by the underlying DBMS
974
     */
975 4
    public function checkIntegrity($check = true, $schema = '', $table = '')
976
    {
977 4
        $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema, $table);
978
979 4
        return $this->setSql($sql);
980
    }
981
982
    /**
983
     * Builds a SQL command for adding comment to column.
984
     *
985
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
986
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
987
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
988
     * @return $this the command object itself
989
     * @since 2.0.8
990
     */
991 2
    public function addCommentOnColumn($table, $column, $comment)
992
    {
993 2
        $sql = $this->db->getQueryBuilder()->addCommentOnColumn($table, $column, $comment);
994
995 2
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
996
    }
997
998
    /**
999
     * Builds a SQL command for adding comment to table.
1000
     *
1001
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1002
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
1003
     * @return $this the command object itself
1004
     * @since 2.0.8
1005
     */
1006
    public function addCommentOnTable($table, $comment)
1007
    {
1008
        $sql = $this->db->getQueryBuilder()->addCommentOnTable($table, $comment);
1009
1010
        return $this->setSql($sql);
1011
    }
1012
1013
    /**
1014
     * Builds a SQL command for dropping comment from column.
1015
     *
1016
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1017
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
1018
     * @return $this the command object itself
1019
     * @since 2.0.8
1020
     */
1021 2
    public function dropCommentFromColumn($table, $column)
1022
    {
1023 2
        $sql = $this->db->getQueryBuilder()->dropCommentFromColumn($table, $column);
1024
1025 2
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
1026
    }
1027
1028
    /**
1029
     * Builds a SQL command for dropping comment from table.
1030
     *
1031
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1032
     * @return $this the command object itself
1033
     * @since 2.0.8
1034
     */
1035
    public function dropCommentFromTable($table)
1036
    {
1037
        $sql = $this->db->getQueryBuilder()->dropCommentFromTable($table);
1038
1039
        return $this->setSql($sql);
1040
    }
1041
1042
    /**
1043
     * Creates a SQL View.
1044
     *
1045
     * @param string $viewName the name of the view to be created.
1046
     * @param string|Query $subquery the select statement which defines the view.
1047
     * This can be either a string or a [[Query]] object.
1048
     * @return $this the command object itself.
1049
     * @since 2.0.14
1050
     */
1051 3
    public function createView($viewName, $subquery)
1052
    {
1053 3
        $sql = $this->db->getQueryBuilder()->createView($viewName, $subquery);
1054
1055 3
        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
1056
    }
1057
1058
    /**
1059
     * Drops a SQL View.
1060
     *
1061
     * @param string $viewName the name of the view to be dropped.
1062
     * @return $this the command object itself.
1063
     * @since 2.0.14
1064
     */
1065 5
    public function dropView($viewName)
1066
    {
1067 5
        $sql = $this->db->getQueryBuilder()->dropView($viewName);
1068
1069 5
        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
1070
    }
1071
1072
    /**
1073
     * Executes the SQL statement.
1074
     * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
1075
     * No result set will be returned.
1076
     * @return int number of rows affected by the execution.
1077
     * @throws Exception execution failed
1078
     */
1079 693
    public function execute()
1080
    {
1081 693
        $sql = $this->getSql();
1082 693
        list($profile, $rawSql) = $this->logQuery(__METHOD__);
1083
1084 693
        if ($sql == '') {
1085 6
            return 0;
1086
        }
1087
1088 690
        $this->prepare(false);
1089
1090
        try {
1091 690
            $profile and Yii::beginProfile($rawSql, __METHOD__);
1092
1093 690
            $this->internalExecute($rawSql);
1094 687
            $n = $this->pdoStatement->rowCount();
1095
1096 687
            $profile and Yii::endProfile($rawSql, __METHOD__);
1097
1098 687
            $this->refreshTableSchema();
1099
1100 687
            return $n;
1101 18
        } catch (Exception $e) {
1102 18
            $profile and Yii::endProfile($rawSql, __METHOD__);
1103 18
            throw $e;
1104
        }
1105
    }
1106
1107
    /**
1108
     * Logs the current database query if query logging is enabled and returns
1109
     * the profiling token if profiling is enabled.
1110
     * @param string $category the log category.
1111
     * @return array array of two elements, the first is boolean of whether profiling is enabled or not.
1112
     * The second is the rawSql if it has been created.
1113
     */
1114 1554
    protected function logQuery($category)
1115
    {
1116 1554
        if ($this->db->enableLogging) {
1117 1554
            $rawSql = $this->getRawSql();
1118 1554
            Yii::info($rawSql, $category);
1119
        }
1120 1554
        if (!$this->db->enableProfiling) {
1121 7
            return [false, isset($rawSql) ? $rawSql : null];
1122
        }
1123
1124 1554
        return [true, isset($rawSql) ? $rawSql : $this->getRawSql()];
1125
    }
1126
1127
    /**
1128
     * Performs the actual DB query of a SQL statement.
1129
     * @param string $method method of PDOStatement to be called
1130
     * @param int $fetchMode the result fetch mode. Please refer to [PHP manual](https://secure.php.net/manual/en/function.PDOStatement-setFetchMode.php)
1131
     * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
1132
     * @return mixed the method execution result
1133
     * @throws Exception if the query causes any problem
1134
     * @since 2.0.1 this method is protected (was private before).
1135
     */
1136 1508
    protected function queryInternal($method, $fetchMode = null)
1137
    {
1138 1508
        list($profile, $rawSql) = $this->logQuery('yii\db\Command::query');
1139
1140 1508
        if ($method !== '') {
1141 1505
            $info = $this->db->getQueryCacheInfo($this->queryCacheDuration, $this->queryCacheDependency);
1142 1505
            if (is_array($info)) {
1143
                /* @var $cache \yii\caching\CacheInterface */
1144 6
                $cache = $info[0];
1145 6
                $cacheKey = $this->getCacheKey($method, $fetchMode, '');
1146 6
                $result = $cache->get($cacheKey);
1147 6
                if (is_array($result) && isset($result[0])) {
1148 6
                    Yii::debug('Query result served from cache', 'yii\db\Command::query');
1149 6
                    return $result[0];
1150
                }
1151
            }
1152
        }
1153
1154 1508
        $this->prepare(true);
1155
1156
        try {
1157 1507
            $profile and Yii::beginProfile($rawSql, 'yii\db\Command::query');
1158
1159 1507
            $this->internalExecute($rawSql);
1160
1161 1504
            if ($method === '') {
1162 15
                $result = new DataReader($this);
1163
            } else {
1164 1501
                if ($fetchMode === null) {
1165 1389
                    $fetchMode = $this->fetchMode;
1166
                }
1167 1501
                $result = call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode);
1168 1501
                $this->pdoStatement->closeCursor();
1169
            }
1170
1171 1504
            $profile and Yii::endProfile($rawSql, 'yii\db\Command::query');
1172 22
        } catch (Exception $e) {
1173 22
            $profile and Yii::endProfile($rawSql, 'yii\db\Command::query');
1174 22
            throw $e;
1175
        }
1176
1177 1504
        if (isset($cache, $cacheKey, $info)) {
1178 6
            $cache->set($cacheKey, [$result], $info[1], $info[2]);
1179 6
            Yii::debug('Saved query result in cache', 'yii\db\Command::query');
1180
        }
1181
1182 1504
        return $result;
1183
    }
1184
1185
    /**
1186
     * Returns the cache key for the query.
1187
     *
1188
     * @param string $method method of PDOStatement to be called
1189
     * @param int $fetchMode the result fetch mode. Please refer to [PHP manual](https://secure.php.net/manual/en/function.PDOStatement-setFetchMode.php)
1190
     * for valid fetch modes.
1191
     * @return array the cache key
1192
     * @since 2.0.16
1193
     */
1194 6
    protected function getCacheKey($method, $fetchMode, $rawSql)
1195
    {
1196 6
        $params = $this->params;
1197 6
        ksort($params);
1198
        return [
1199 6
            __CLASS__,
1200 6
            $method,
1201 6
            $fetchMode,
1202 6
            $this->db->dsn,
1203 6
            $this->db->username,
1204 6
            $this->getSql(),
1205 6
            json_encode($params),
1206
        ];
1207
    }
1208
1209
    /**
1210
     * Marks a specified table schema to be refreshed after command execution.
1211
     * @param string $name name of the table, which schema should be refreshed.
1212
     * @return $this this command instance
1213
     * @since 2.0.6
1214
     */
1215 156
    protected function requireTableSchemaRefresh($name)
1216
    {
1217 156
        $this->_refreshTableName = $name;
1218 156
        return $this;
1219
    }
1220
1221
    /**
1222
     * Refreshes table schema, which was marked by [[requireTableSchemaRefresh()]].
1223
     * @since 2.0.6
1224
     */
1225 687
    protected function refreshTableSchema()
1226
    {
1227 687
        if ($this->_refreshTableName !== null) {
1228 153
            $this->db->getSchema()->refreshTableSchema($this->_refreshTableName);
1229
        }
1230 687
    }
1231
1232
    /**
1233
     * Marks the command to be executed in transaction.
1234
     * @param string|null $isolationLevel The isolation level to use for this transaction.
1235
     * See [[Transaction::begin()]] for details.
1236
     * @return $this this command instance.
1237
     * @since 2.0.14
1238
     */
1239 3
    protected function requireTransaction($isolationLevel = null)
1240
    {
1241 3
        $this->_isolationLevel = $isolationLevel;
1242 3
        return $this;
1243
    }
1244
1245
    /**
1246
     * Sets a callable (e.g. anonymous function) that is called when [[Exception]] is thrown
1247
     * when executing the command. The signature of the callable should be:
1248
     *
1249
     * ```php
1250
     * function (\yii\db\Exception $e, $attempt)
1251
     * {
1252
     *     // return true or false (whether to retry the command or rethrow $e)
1253
     * }
1254
     * ```
1255
     *
1256
     * The callable will recieve a database exception thrown and a current attempt
1257
     * (to execute the command) number starting from 1.
1258
     *
1259
     * @param callable $handler a PHP callback to handle database exceptions.
1260
     * @return $this this command instance.
1261
     * @since 2.0.14
1262
     */
1263 3
    protected function setRetryHandler(callable $handler)
1264
    {
1265 3
        $this->_retryHandler = $handler;
1266 3
        return $this;
1267
    }
1268
1269
    /**
1270
     * Executes a prepared statement.
1271
     *
1272
     * It's a wrapper around [[\PDOStatement::execute()]] to support transactions
1273
     * and retry handlers.
1274
     *
1275
     * @param string|null $rawSql the rawSql if it has been created.
1276
     * @throws Exception if execution failed.
1277
     * @since 2.0.14
1278
     */
1279 1553
    protected function internalExecute($rawSql)
1280
    {
1281 1553
        $attempt = 0;
1282 1553
        while (true) {
1283
            try {
1284
                if (
1285 1553
                    ++$attempt === 1
1286 1553
                    && $this->_isolationLevel !== false
1287 1553
                    && $this->db->getTransaction() === null
1288
                ) {
1289 3
                    $this->db->transaction(function () use ($rawSql) {
1290 3
                        $this->internalExecute($rawSql);
1291 3
                    }, $this->_isolationLevel);
1292
                } else {
1293 1553
                    $this->pdoStatement->execute();
1294
                }
1295 1550
                break;
1296 36
            } catch (\Exception $e) {
1297 36
                $rawSql = $rawSql ?: $this->getRawSql();
1298 36
                $e = $this->db->getSchema()->convertException($e, $rawSql);
1299 36
                if ($this->_retryHandler === null || !call_user_func($this->_retryHandler, $e, $attempt)) {
1300 36
                    throw $e;
1301
                }
1302
            }
1303
        }
1304 1550
    }
1305
1306
    /**
1307
     * Resets command properties to their initial state.
1308
     *
1309
     * @since 2.0.13
1310
     */
1311 1596
    protected function reset()
1312
    {
1313 1596
        $this->_sql = null;
1314 1596
        $this->pendingParams = [];
1315 1596
        $this->params = [];
1316 1596
        $this->_refreshTableName = null;
1317 1596
        $this->_isolationLevel = false;
1318 1596
        $this->_retryHandler = null;
1319 1596
    }
1320
}
1321