Passed
Push — master ( a8d37b...dabdd0 )
by Wilmer
10:00
created

Command   F

Complexity

Total Complexity 130

Size/Duplication

Total Lines 1493
Duplicated Lines 0 %

Test Coverage

Coverage 90.14%

Importance

Changes 0
Metric Value
eloc 310
dl 0
loc 1493
ccs 320
cts 355
cp 0.9014
rs 2
c 0
b 0
f 0
wmc 130

67 Methods

Rating   Name   Duplication   Size   Complexity  
A setParams() 0 3 1
A getParams() 0 3 1
A setFetchMode() 0 3 1
A getPdoStatement() 0 3 1
A getFetchMode() 0 3 1
A refreshTableSchema() 0 4 2
A requireTransaction() 0 5 1
A requireTableSchemaRefresh() 0 5 1
A reset() 0 8 1
A setRetryHandler() 0 5 1
B internalExecute() 0 22 9
A cancel() 0 3 1
A getSql() 0 3 1
A noCache() 0 5 1
A logQuery() 0 14 3
A resetSequence() 0 5 1
A dropForeignKey() 0 5 1
A renameTable() 0 5 1
A queryColumn() 0 3 1
A addPrimaryKey() 0 5 1
A dropPrimaryKey() 0 5 1
A addCommentOnColumn() 0 5 1
A checkIntegrity() 0 5 1
A bindParam() 0 19 4
A dropDefaultValue() 0 5 1
A dropTable() 0 5 1
A renameColumn() 0 5 1
A createIndex() 0 5 1
A addCheck() 0 5 1
A bindPendingParams() 0 7 2
A addCommentOnTable() 0 5 1
A addDefaultValue() 0 5 1
A addUnique() 0 5 1
A __construct() 0 4 1
A createTable() 0 5 1
A dropUnique() 0 5 1
A setSql() 0 9 2
A queryAll() 0 3 1
A cache() 0 8 1
B prepare() 0 29 8
A addForeignKey() 0 20 1
A dropCommentFromColumn() 0 5 1
A execute() 0 34 6
A getCacheKey() 0 9 1
A truncateTable() 0 5 1
A batchInsert() 0 14 1
A setRawSql() 0 9 2
A bindValues() 0 24 5
A query() 0 3 1
A insert() 0 6 1
A queryScalar() 0 9 3
A addColumn() 0 5 1
A upsert() 0 5 1
A dropView() 0 5 1
A dropCommentFromTable() 0 5 1
A alterColumn() 0 5 1
A dropIndex() 0 5 1
A queryOne() 0 3 1
A executeResetSequence() 0 3 1
D queryInternal() 0 88 15
C getRawSql() 0 35 14
A update() 0 5 1
A dropCheck() 0 5 1
A bindValue() 0 11 2
A dropColumn() 0 5 1
A delete() 0 5 1
A createView() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like Command often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Command, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Command;
6
7
use JsonException;
8
use PDO;
9
use PDOException;
10
use PDOStatement;
11
use Psr\Log\LogLevel;
12
use Throwable;
13
use Yiisoft\Cache\CacheInterface;
14
use Yiisoft\Cache\Dependency\Dependency;
15
use Yiisoft\Db\Connection\ConnectionInterface;
16
use Yiisoft\Db\Data\DataReader;
17
use Yiisoft\Db\Exception\Exception;
18
use Yiisoft\Db\Expression\Expression;
19
use Yiisoft\Db\Pdo\PdoValue;
20
use Yiisoft\Db\Query\Query;
21
22
use function array_map;
23
use function call_user_func_array;
24
use function explode;
25
use function get_resource_type;
26
use function is_array;
27
use function is_bool;
28
use function is_object;
29
use function is_resource;
30
use function is_string;
31
use function stream_get_contents;
32
use function strncmp;
33
use function strtr;
34
35
/**
36
 * Command represents a SQL statement to be executed against a database.
37
 *
38
 * A command object is usually created by calling {@see ConnectionInterface::createCommand()}.
39
 *
40
 * The SQL statement it represents can be set via the {@see sql} property.
41
 *
42
 * To execute a non-query SQL (such as INSERT, DELETE, UPDATE), call {@see execute()}.
43
 * To execute a SQL statement that returns a result data set (such as SELECT), use {@see queryAll()},
44
 * {@see queryOne()}, {@see queryColumn()}, {@see queryScalar()}, or {@see query()}.
45
 *
46
 * For example,
47
 *
48
 * ```php
49
 * $users = $connectionInterface->createCommand('SELECT * FROM user')->queryAll();
50
 * ```
51
 *
52
 * Command supports SQL statement preparation and parameter binding.
53
 *
54
 * Call {@see bindValue()} to bind a value to a SQL parameter;
55
 * Call {@see bindParam()} to bind a PHP variable to a SQL parameter.
56
 *
57
 * When binding a parameter, the SQL statement is automatically prepared. You may also call {@see prepare()} explicitly
58
 * to prepare a SQL statement.
59
 *
60
 * Command also supports building SQL statements by providing methods such as {@see insert()}, {@see update()}, etc.
61
 *
62
 * For example, the following code will create and execute an INSERT SQL statement:
63
 *
64
 * ```php
65
 * $connectionInterface->createCommand()->insert('user', [
66
 *     'name' => 'Sam',
67
 *     'age' => 30,
68
 * ])->execute();
69
 * ```
70
 *
71
 * To build SELECT SQL statements, please use {@see Query} instead.
72
 *
73
 * For more details and usage information on Command, see the [guide article on Database Access Objects](guide:db-dao).
74
 *
75
 * @property string $rawSql The raw SQL with parameter values inserted into the corresponding placeholders in
76
 * {@see sql}.
77
 * @property string $sql The SQL statement to be executed.
78
 */
79
class Command
80
{
81
    /**
82
     * @var array pending parameters to be bound to the current PDO statement.
83
     */
84
    protected array $pendingParams = [];
85
    protected array $params = [];
86
87
    /**
88
     * @var string|null the SQL statement that this command represents
89
     */
90
    private ?string $sql = null;
91
92
    /**
93
     * @var string|null name of the table, which schema, should be refreshed after command execution.
94
     */
95
    private ?string $refreshTableName = null;
96
97
    /**
98
     * @var string|null the isolation level to use for this transaction.
99
     *
100
     * See {@see Transaction::begin()} for details.
101
     */
102
    private ?string $isolationLevel = null;
103
104
    /**
105
     * @var callable a callable (e.g. anonymous function) that is called when {@see Exception} is thrown when executing
106
     * the command.
107
     */
108
    private $retryHandler;
109
110
    private ConnectionInterface $db;
111
    private ?PDOStatement $pdoStatement = null;
112
    private int $fetchMode = PDO::FETCH_ASSOC;
113
    private ?int $queryCacheDuration = null;
114
    private ?Dependency $queryCacheDependency = null;
115
116 1748
    public function __construct(ConnectionInterface $db, ?string $sql)
117
    {
118 1748
        $this->db = $db;
119 1748
        $this->sql = $sql;
120 1748
    }
121
122
    /**
123
     * Enables query cache for this command.
124
     *
125
     * @param int|null $duration the number of seconds that query result of this command can remain valid in the cache.
126
     * If this is not set, the value of {@see ConnectionInterface::queryCacheDuration} will be used instead.
127
     * Use 0 to indicate that the cached data will never expire.
128
     * @param Dependency|null $dependency the cache dependency associated with the cached query result.
129
     *
130
     * @return $this the command object itself.
131
     */
132 10
    public function cache(?int $duration = null, Dependency $dependency = null): self
133
    {
134 10
        $queryCache = $this->db->getQueryCache();
0 ignored issues
show
Bug introduced by
The method getQueryCache() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

134
        /** @scrutinizer ignore-call */ 
135
        $queryCache = $this->db->getQueryCache();
Loading history...
135
136 10
        $this->queryCacheDuration = $duration ?? $queryCache->getDuration();
137 10
        $this->queryCacheDependency = $dependency;
138
139 10
        return $this;
140
    }
141
142
    /**
143
     * Disables query cache for this command.
144
     *
145
     * @return $this the command object itself.
146
     */
147 5
    public function noCache(): self
148
    {
149 5
        $this->queryCacheDuration = -1;
150
151 5
        return $this;
152
    }
153
154
    /**
155
     * Returns the SQL statement for this command.
156
     *
157
     * @return string|null the SQL statement to be executed.
158
     */
159 1718
    public function getSql(): ?string
160
    {
161 1718
        return $this->sql;
162
    }
163
164
    /**
165
     * Specifies the SQL statement to be executed. The SQL statement will be quoted using
166
     * {@see ConnectionInterface::quoteSql()}.
167
     *
168
     * The previous SQL (if any) will be discarded, and {@see params} will be cleared as well. See {@see reset()} for
169
     * details.
170
     *
171
     * @param string $sql the SQL statement to be set.
172
     *
173
     * @return $this this command instance.
174
     *
175
     * {@see reset()}
176
     * {@see cancel()}
177
     */
178 320
    public function setSql(string $sql): self
179
    {
180 320
        if ($sql !== $this->sql) {
181 320
            $this->cancel();
182 320
            $this->reset();
183 320
            $this->sql = $this->db->quoteSql($sql);
0 ignored issues
show
Bug introduced by
The method quoteSql() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

183
            /** @scrutinizer ignore-call */ 
184
            $this->sql = $this->db->quoteSql($sql);
Loading history...
184
        }
185
186 320
        return $this;
187
    }
188
189
    /**
190
     * Specifies the SQL statement to be executed. The SQL statement will not be modified in any way.
191
     *
192
     * The previous SQL (if any) will be discarded, and {@see params} will be cleared as well. See {@see reset()}
193
     * for details.
194
     *
195
     * @param string $sql the SQL statement to be set.
196
     *
197
     * @return $this this command instance.
198
     *
199
     * {@see reset()}
200
     * {@see cancel()}
201
     */
202 41
    public function setRawSql(string $sql): self
203
    {
204 41
        if ($sql !== $this->sql) {
205 41
            $this->cancel();
206 41
            $this->reset();
207 41
            $this->sql = $sql;
208
        }
209
210 41
        return $this;
211
    }
212
213
    /**
214
     * Returns the raw SQL by inserting parameter values into the corresponding placeholders in {@see sql}.
215
     *
216
     * Note that the return value of this method should mainly be used for logging purpose.
217
     *
218
     * It is likely that this method returns an invalid SQL due to improper replacement of parameter placeholders.
219
     *
220
     * @return string the raw SQL with parameter values inserted into the corresponding placeholders in {@see sql}.
221
     */
222 1729
    public function getRawSql(): string
223
    {
224 1729
        if (empty($this->params)) {
225 1306
            return $this->sql;
226
        }
227
228 1327
        $params = [];
229
230 1327
        foreach ($this->params as $name => $value) {
231 1327
            if (is_string($name) && strncmp(':', $name, 1)) {
232 18
                $name = ':' . $name;
233
            }
234
235 1327
            if (is_string($value)) {
236 1103
                $params[$name] = $this->db->quoteValue($value);
0 ignored issues
show
Bug introduced by
The method quoteValue() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

236
                /** @scrutinizer ignore-call */ 
237
                $params[$name] = $this->db->quoteValue($value);
Loading history...
237 694
            } elseif (is_bool($value)) {
238 20
                $params[$name] = ($value ? 'TRUE' : 'FALSE');
239 685
            } elseif ($value === null) {
240 103
                $params[$name] = 'NULL';
241 626
            } elseif ((!is_object($value) && !is_resource($value)) || $value instanceof Expression) {
242 626
                $params[$name] = $value;
243
            }
244
        }
245
246 1327
        if (!isset($params[1])) {
247 1327
            return strtr($this->sql, $params);
248
        }
249
250
        $sql = '';
251
252
        foreach (explode('?', $this->sql) as $i => $part) {
253
            $sql .= ($params[$i] ?? '') . $part;
254
        }
255
256
        return $sql;
257
    }
258
259
    /**
260
     * Prepares the SQL statement to be executed.
261
     *
262
     * For complex SQL statement that is to be executed multiple times, this may improve performance. For SQL statement
263
     * with binding parameters, this method is invoked automatically.
264
     *
265
     * @param bool|null $forRead whether this method is called for a read query. If null, it means the SQL statement
266
     * should be used to determine whether it is for read or write.
267
     *
268
     * @throws Exception if there is any DB error.
269
     */
270 1704
    public function prepare(?bool $forRead = null): void
271
    {
272 1704
        if (isset($this->pdoStatement)) {
273 56
            $this->bindPendingParams();
274
275 56
            return;
276
        }
277
278 1704
        $sql = $this->getSql();
279
280 1704
        if ($this->db->getTransaction()) {
0 ignored issues
show
Bug introduced by
The method getTransaction() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

280
        if ($this->db->/** @scrutinizer ignore-call */ getTransaction()) {
Loading history...
281
            /** master is in a transaction. use the same connection. */
282 26
            $forRead = false;
283
        }
284
285 1704
        if ($forRead || ($forRead === null && $this->db->getSchema()->isReadQuery($sql))) {
286 1680
            $pdo = $this->db->getSlavePdo();
0 ignored issues
show
Bug introduced by
The method getSlavePdo() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

286
            /** @scrutinizer ignore-call */ 
287
            $pdo = $this->db->getSlavePdo();
Loading history...
287
        } else {
288 406
            $pdo = $this->db->getMasterPdo();
0 ignored issues
show
Bug introduced by
The method getMasterPdo() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

288
            /** @scrutinizer ignore-call */ 
289
            $pdo = $this->db->getMasterPdo();
Loading history...
289
        }
290
291
        try {
292 1704
            $this->pdoStatement = $pdo->prepare($sql);
293 1704
            $this->bindPendingParams();
294 3
        } catch (\Exception $e) {
295 3
            $message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
296 3
            $errorInfo = $e instanceof PDOException ? $e->errorInfo : null;
297
298 3
            throw new Exception($message, $errorInfo, $e);
299
        }
300 1704
    }
301
302
    /**
303
     * Cancels the execution of the SQL statement.
304
     *
305
     * This method mainly sets {@see pdoStatement} to be null.
306
     */
307 356
    public function cancel(): void
308
    {
309 356
        $this->pdoStatement = null;
310 356
    }
311
312
    /**
313
     * Binds a parameter to the SQL statement to be executed.
314
     *
315
     * @param int|string $name parameter identifier. For a prepared statement using named placeholders, this will be a
316
     * parameter name of the form `:name`. For a prepared statement using question mark placeholders, this will be the
317
     * 1-indexed position of the parameter.
318
     * @param mixed $value the PHP variable to bind to the SQL statement parameter (passed by reference).
319
     * @param int|null $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the
320
     * value.
321
     * @param int|null $length length of the data type.
322
     * @param mixed $driverOptions the driver-specific options.
323
     *
324
     * @throws Exception
325
     *
326
     * @return $this the current command being executed.
327
     *
328
     * {@see http://www.php.net/manual/en/function.PDOStatement-bindParam.php}
329
     */
330 2
    public function bindParam($name, &$value, ?int $dataType = null, ?int $length = null, $driverOptions = null): self
331
    {
332 2
        $this->prepare();
333
334 2
        if ($dataType === null) {
335 2
            $dataType = $this->db->getSchema()->getPdoType($value);
336
        }
337
338 2
        if ($length === null) {
339 2
            $this->pdoStatement->bindParam($name, $value, $dataType);
0 ignored issues
show
Bug introduced by
The method bindParam() does not exist on null. ( Ignorable by Annotation )

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

339
            $this->pdoStatement->/** @scrutinizer ignore-call */ 
340
                                 bindParam($name, $value, $dataType);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
340
        } elseif ($driverOptions === null) {
341
            $this->pdoStatement->bindParam($name, $value, $dataType, $length);
342
        } else {
343
            $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions);
344
        }
345
346 2
        $this->params[$name] = &$value;
347
348 2
        return $this;
349
    }
350
351
    /**
352
     * Binds pending parameters that were registered via {@see bindValue()} and {@see bindValues()}.
353
     *
354
     * Note that this method requires an active {@see pdoStatement}.
355
     */
356 1381
    protected function bindPendingParams(): void
357
    {
358 1381
        foreach ($this->pendingParams as $name => $value) {
359 998
            $this->pdoStatement->bindValue($name, $value[0], $value[1]);
360
        }
361
362 1381
        $this->pendingParams = [];
363 1381
    }
364
365
    /**
366
     * Binds a value to a parameter.
367
     *
368
     * @param int|string $name Parameter identifier. For a prepared statement using named placeholders, this will be a
369
     * parameter name of the form `:name`. For a prepared statement using question mark placeholders, this will be the
370
     * 1-indexed position of the parameter.
371
     * @param mixed $value The value to bind to the parameter.
372
     * @param int|null $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the
373
     * value.
374
     *
375
     * @return $this the current command being executed.
376
     *
377
     * {@see http://www.php.net/manual/en/function.PDOStatement-bindValue.php}
378
     */
379 7
    public function bindValue($name, $value, ?int $dataType = null): self
380
    {
381 7
        if ($dataType === null) {
382 7
            $dataType = $this->db->getSchema()->getPdoType($value);
383
        }
384
385 7
        $this->pendingParams[$name] = [$value, $dataType];
386
387 7
        $this->params[$name] = $value;
388
389 7
        return $this;
390
    }
391
392
    /**
393
     * Binds a list of values to the corresponding parameters.
394
     *
395
     * This is similar to {@see bindValue()} except that it binds multiple values at a time.
396
     *
397
     * Note that the SQL data type of each value is determined by its PHP type.
398
     *
399
     * @param array $values the values to be bound. This must be given in terms of an associative array with array keys
400
     * being the parameter names, and array values the corresponding parameter values,
401
     * e.g. `[':name' => 'John', ':age' => 25]`.
402
     * By default, the PDO type of each value is determined  by its PHP type. You may explicitly specify the PDO type by
403
     * using a {@see PdoValue} class: `new PdoValue(value, type)`,
404
     * e.g. `[':name' => 'John', ':profile' => new PdoValue($profile, \PDO::PARAM_LOB)]`.
405
     *
406
     * @return $this the current command being executed.
407
     */
408 1748
    public function bindValues(array $values): self
409
    {
410 1748
        if (empty($values)) {
411 1357
            return $this;
412
        }
413
414 1330
        $schema = $this->db->getSchema();
415
416 1330
        foreach ($values as $name => $value) {
417 1330
            if (is_array($value)) { // TODO: Drop in Yii 2.1
418
                $this->pendingParams[$name] = $value;
419
                $this->params[$name] = $value[0];
420 1330
            } elseif ($value instanceof PdoValue) {
421 1
                $this->pendingParams[$name] = [$value->getValue(), $value->getType()];
422 1
                $this->params[$name] = $value->getValue();
423
            } else {
424 1330
                $type = $schema->getPdoType($value);
425
426 1330
                $this->pendingParams[$name] = [$value, $type];
427 1330
                $this->params[$name] = $value;
428
            }
429
        }
430
431 1330
        return $this;
432
    }
433
434
    /**
435
     * Executes the SQL statement and returns query result.
436
     *
437
     * This method is for executing a SQL query that returns result set, such as `SELECT`.
438
     *
439
     * @throws Throwable
440
     * @throws Exception execution failed.
441
     *
442
     * @return DataReader the reader object for fetching the query result.
443
     */
444 35
    public function query(): DataReader
445
    {
446 35
        return $this->queryInternal('');
447
    }
448
449
    /**
450
     * Executes the SQL statement and returns ALL rows at once.
451
     *
452
     * @param int|null $fetchMode the result fetch mode.
453
     * Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) for valid fetch
454
     * modes. If this parameter is null, the value set in {@see fetchMode} will be used.
455
     *
456
     * @throws Throwable
457
     * @throws Exception execution failed.
458
     *
459
     * @return array all rows of the query result. Each array element is an array representing a row of data. An empty
460
     * array is returned if the query results in nothing.
461
     */
462 1563
    public function queryAll(?int $fetchMode = null): array
463
    {
464 1563
        return $this->queryInternal('fetchAll', $fetchMode);
465
    }
466
467
    /**
468
     * Executes the SQL statement and returns the first row of the result.
469
     *
470
     * This method is best used when only the first row of result is needed for a query.
471
     *
472
     * @param array|int|null $fetchMode the result fetch mode.
473
     *
474
     * Please refer to [PHP manual](http://php.net/manual/en/pdostatement.setfetchmode.php)
475
     * for valid fetch modes. If this parameter is null, the value set in {@see fetchMode} will be used.
476
     *
477
     * @throws Exception|Throwable execution failed.
478
     *
479
     * @return array|false the first row (in terms of an array) of the query result. False is returned if the query
480
     * results in nothing.
481
     */
482 451
    public function queryOne($fetchMode = null)
483
    {
484 451
        return $this->queryInternal('fetch', $fetchMode);
485
    }
486
487
    /**
488
     * Executes the SQL statement and returns the value of the first column in the first row of data.
489
     *
490
     * This method is best used when only a single value is needed for a query.
491
     *
492
     * @throws Exception|Throwable failed.
493
     *
494
     * @return false|string|null the value of the first column in the first row of the query result. False is returned
495
     * if there is no value.
496
     */
497 447
    public function queryScalar()
498
    {
499 447
        $result = $this->queryInternal('fetchColumn', 0);
500
501 447
        if (is_resource($result) && get_resource_type($result) === 'stream') {
502
            return stream_get_contents($result);
503
        }
504
505 447
        return $result;
506
    }
507
508
    /**
509
     * Executes the SQL statement and returns the first column of the result.
510
     *
511
     * This method is best used when only the first column of result (i.e. the first element in each row) is needed for
512
     * a query.
513
     *
514
     * @throws Exception|Throwable execution failed.
515
     *
516
     * @return array the first column of the query result. Empty array is returned if the query results in nothing.
517
     */
518 42
    public function queryColumn(): array
519
    {
520 42
        return $this->queryInternal('fetchAll', PDO::FETCH_COLUMN);
521
    }
522
523
    /**
524
     * Creates an INSERT command.
525
     *
526
     * For example,
527
     *
528
     * ```php
529
     * $connectionInterface->createCommand()->insert('user', [
530
     *     'name' => 'Sam',
531
     *     'age' => 30,
532
     * ])->execute();
533
     * ```
534
     *
535
     * The method will properly escape the column names, and bind the values to be inserted.
536
     *
537
     * Note that the created command is not executed until {@see execute()} is called.
538
     *
539
     * @param string $table the table that new rows will be inserted into.
540
     * @param array|Query $columns the column data (name => value) to be inserted into the table or instance of
541
     * {@see Query} to perform INSERT INTO ... SELECT SQL statement. Passing of {@see Query}.
542
     *
543
     * @return $this the command object itself
544
     */
545 138
    public function insert(string $table, $columns): self
546
    {
547 138
        $params = [];
548 138
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
0 ignored issues
show
Bug introduced by
The method getQueryBuilder() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

548
        $sql = $this->db->/** @scrutinizer ignore-call */ getQueryBuilder()->insert($table, $columns, $params);
Loading history...
549
550 123
        return $this->setSql($sql)->bindValues($params);
551
    }
552
553
    /**
554
     * Creates a batch INSERT command.
555
     *
556
     * For example,
557
     *
558
     * ```php
559
     * $connectionInterface->createCommand()->batchInsert('user', ['name', 'age'], [
560
     *     ['Tom', 30],
561
     *     ['Jane', 20],
562
     *     ['Linda', 25],
563
     * ])->execute();
564
     * ```
565
     *
566
     * The method will properly escape the column names, and quote the values to be inserted.
567
     *
568
     * Note that the values in each row must match the corresponding column names.
569
     *
570
     * Also note that the created command is not executed until {@see execute()} is called.
571
     *
572
     * @param string $table the table that new rows will be inserted into.
573
     * @param array $columns the column names
574
     * @param iterable $rows the rows to be batch inserted into the table.
575
     *
576
     * @return $this the command object itself.
577
     */
578 41
    public function batchInsert(string $table, array $columns, iterable $rows): self
579
    {
580 41
        $table = $this->db->quoteSql($table);
581
582 41
        $columns = array_map(fn ($column) => $this->db->quoteSql($column), $columns);
583
584 41
        $params = [];
585
586 41
        $sql = $this->db->getQueryBuilder()->batchInsert($table, $columns, $rows, $params);
587
588 41
        $this->setRawSql($sql);
589 41
        $this->bindValues($params);
590
591 41
        return $this;
592
    }
593
594
    /**
595
     * Creates a command to insert rows into a database table if they do not already exist (matching unique constraints)
596
     * or update them if they do.
597
     *
598
     * For example,
599
     *
600
     * ```php
601
     * $sql = $queryBuilder->upsert('pages', [
602
     *     'name' => 'Front page',
603
     *     'url' => 'http://example.com/', // url is unique
604
     *     'visits' => 0,
605
     * ], [
606
     *     'visits' => new \Yiisoft\Db\Expression\Expression('visits + 1'),
607
     * ], $params);
608
     * ```
609
     *
610
     * The method will properly escape the table and column names.
611
     *
612
     * @param string $table the table that new rows will be inserted into/updated in.
613
     * @param array|Query $insertColumns the column data (name => value) to be inserted into the table or instance of
614
     * {@see Query} to perform `INSERT INTO ... SELECT` SQL statement.
615
     * @param array|bool $updateColumns the column data (name => value) to be updated if they already exist.
616
     * If `true` is passed, the column data will be updated to match the insert column data.
617
     * If `false` is passed, no update will be performed if the column data already exists.
618
     * @param array $params the parameters to be bound to the command.
619
     *
620
     * @return $this the command object itself.
621
     */
622 30
    public function upsert(string $table, $insertColumns, $updateColumns = true, array $params = []): self
623
    {
624 30
        $sql = $this->db->getQueryBuilder()->upsert($table, $insertColumns, $updateColumns, $params);
625
626 30
        return $this->setSql($sql)->bindValues($params);
627
    }
628
629
    /**
630
     * Creates an UPDATE command.
631
     *
632
     * For example,
633
     *
634
     * ```php
635
     * $connectionInterface->createCommand()->update('user', ['status' => 1], 'age > 30')->execute();
636
     * ```
637
     *
638
     * or with using parameter binding for the condition:
639
     *
640
     * ```php
641
     * $minAge = 30;
642
     * $connectionInterface->createCommand()->update(
643
     *     'user',
644
     *     ['status' => 1],
645
     *     'age > :minAge',
646
     *     [':minAge' => $minAge]
647
     * )->execute();
648
     * ```
649
     *
650
     * The method will properly escape the column names and bind the values to be updated.
651
     *
652
     * Note that the created command is not executed until {@see execute()} is called.
653
     *
654
     * @param string $table the table to be updated.
655
     * @param array $columns the column data (name => value) to be updated.
656
     * @param array|string $condition the condition that will be put in the WHERE part.
657
     * Please refer to {@see Query::where()} on how to specify condition.
658
     * @param array $params the parameters to be bound to the command.
659
     *
660
     * @return $this the command object itself.
661
     */
662 77
    public function update(string $table, array $columns, $condition = '', array $params = []): self
663
    {
664 77
        $sql = $this->db->getQueryBuilder()->update($table, $columns, $condition, $params);
665
666 77
        return $this->setSql($sql)->bindValues($params);
667
    }
668
669
    /**
670
     * Creates a DELETE command.
671
     *
672
     * For example,
673
     *
674
     * ```php
675
     * $connectionInterface->createCommand()->delete('user', 'status = 0')->execute();
676
     * ```
677
     *
678
     * or with using parameter binding for the condition:
679
     *
680
     * ```php
681
     * $status = 0;
682
     * $connectionInterface->createCommand()->delete('user', 'status = :status', [':status' => $status])->execute();
683
     * ```
684
     *
685
     * The method will properly escape the table and column names.
686
     *
687
     * Note that the created command is not executed until {@see execute()} is called.
688
     *
689
     * @param string $table the table where the data will be deleted from.
690
     * @param array|string $condition the condition that will be put in the WHERE part. Please refer to
691
     * {@see Query::where()} on how to specify condition.
692
     * @param array $params the parameters to be bound to the command.
693
     *
694
     * @return $this the command object itself.
695
     */
696 36
    public function delete(string $table, $condition = '', array $params = []): self
697
    {
698 36
        $sql = $this->db->getQueryBuilder()->delete($table, $condition, $params);
699
700 36
        return $this->setSql($sql)->bindValues($params);
701
    }
702
703
    /**
704
     * Creates a SQL command for creating a new DB table.
705
     *
706
     * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'), where name
707
     * stands for a column name which will be properly quoted by the method, and definition stands for the column type
708
     * which can contain an abstract DB type.
709
     *
710
     * The method {@see QueryBuilder::getColumnType()} will be called to convert the abstract column types to physical
711
     * ones. For example, `string` will be converted as `varchar(255)`, and `string not null` becomes
712
     * `varchar(255) not null`.
713
     *
714
     * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly inserted
715
     * into the generated SQL.
716
     *
717
     * @param string $table the name of the table to be created. The name will be properly quoted by the method.
718
     * @param array $columns the columns (name => definition) in the new table.
719
     * @param string|null $options additional SQL fragment that will be appended to the generated SQL.
720
     *
721
     * @return $this the command object itself.
722
     */
723 55
    public function createTable(string $table, array $columns, ?string $options = null): self
724
    {
725 55
        $sql = $this->db->getQueryBuilder()->createTable($table, $columns, $options);
726
727 55
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
728
    }
729
730
    /**
731
     * Creates a SQL command for renaming a DB table.
732
     *
733
     * @param string $table the table to be renamed. The name will be properly quoted by the method.
734
     * @param string $newName the new table name. The name will be properly quoted by the method.
735
     *
736
     * @return $this the command object itself.
737
     */
738 10
    public function renameTable(string $table, string $newName): self
739
    {
740 10
        $sql = $this->db->getQueryBuilder()->renameTable($table, $newName);
741
742 10
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
743
    }
744
745
    /**
746
     * Creates a SQL command for dropping a DB table.
747
     *
748
     * @param string $table the table to be dropped. The name will be properly quoted by the method.
749
     *
750
     * @return $this the command object itself
751
     */
752 9
    public function dropTable(string $table): self
753
    {
754 9
        $sql = $this->db->getQueryBuilder()->dropTable($table);
755
756 9
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
757
    }
758
759
    /**
760
     * Creates a SQL command for truncating a DB table.
761
     *
762
     * @param string $table the table to be truncated. The name will be properly quoted by the method.
763
     *
764
     * @return $this the command object itself.
765
     */
766 5
    public function truncateTable(string $table): self
767
    {
768 5
        $sql = $this->db->getQueryBuilder()->truncateTable($table);
769
770 5
        return $this->setSql($sql);
771
    }
772
773
    /**
774
     * Creates a SQL command for adding a new DB column.
775
     *
776
     * @param string $table the table that the new column will be added to. The table name will be properly quoted by
777
     * the method.
778
     * @param string $column the name of the new column. The name will be properly quoted by the method.
779
     * @param string $type the column type. {@see QueryBuilder::getColumnType()} will be called to convert the give
780
     * column type to the physical one. For example, `string` will be converted as `varchar(255)`, and `string not null`
781
     * becomes `varchar(255) not null`.
782
     *
783
     * @return $this the command object itself.
784
     */
785 2
    public function addColumn(string $table, string $column, string $type): self
786
    {
787 2
        $sql = $this->db->getQueryBuilder()->addColumn($table, $column, $type);
788
789 2
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
790
    }
791
792
    /**
793
     * Creates a SQL command for dropping a DB column.
794
     *
795
     * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
796
     * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
797
     *
798
     * @return $this the command object itself.
799
     */
800
    public function dropColumn(string $table, string $column): self
801
    {
802
        $sql = $this->db->getQueryBuilder()->dropColumn($table, $column);
803
804
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
805
    }
806
807
    /**
808
     * Creates a SQL command for renaming a column.
809
     *
810
     * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
811
     * @param string $oldName the old name of the column. The name will be properly quoted by the method.
812
     * @param string $newName the new name of the column. The name will be properly quoted by the method.
813
     *
814
     * @return $this the command object itself.
815
     */
816
    public function renameColumn(string $table, string $oldName, string $newName): self
817
    {
818
        $sql = $this->db->getQueryBuilder()->renameColumn($table, $oldName, $newName);
819
820
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
821
    }
822
823
    /**
824
     * Creates a SQL command for changing the definition of a column.
825
     *
826
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the
827
     * method.
828
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
829
     * @param string $type the column type. {@see QueryBuilder::getColumnType()} will be called to
830
     * convert the give column type to the physical one. For example, `string` will be converted as `varchar(255)`, and
831
     * `string not null` becomes `varchar(255) not null`.
832
     *
833
     * @return $this the command object itself.
834
     */
835 3
    public function alterColumn(string $table, string $column, string $type): self
836
    {
837 3
        $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $type);
838
839 3
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
840
    }
841
842
    /**
843
     * Creates a SQL command for adding a primary key constraint to an existing table.
844
     *
845
     * The method will properly quote the table and column names.
846
     *
847
     * @param string $name the name of the primary key constraint.
848
     * @param string $table the table that the primary key constraint will be added to.
849
     * @param array|string $columns comma separated string or array of columns that the primary key will consist of.
850
     *
851
     * @return $this the command object itself.
852
     */
853 3
    public function addPrimaryKey(string $name, string $table, $columns): self
854
    {
855 3
        $sql = $this->db->getQueryBuilder()->addPrimaryKey($name, $table, $columns);
856
857 3
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
858
    }
859
860
    /**
861
     * Creates a SQL command for removing a primary key constraint to an existing table.
862
     *
863
     * @param string $name the name of the primary key constraint to be removed.
864
     * @param string $table the table that the primary key constraint will be removed from.
865
     *
866
     * @return $this the command object itself.
867
     */
868 3
    public function dropPrimaryKey(string $name, string $table): self
869
    {
870 3
        $sql = $this->db->getQueryBuilder()->dropPrimaryKey($name, $table);
871
872 3
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
873
    }
874
875
    /**
876
     * Creates a SQL command for adding a foreign key constraint to an existing table.
877
     *
878
     * The method will properly quote the table and column names.
879
     *
880
     * @param string $name the name of the foreign key constraint.
881
     * @param string $table the table that the foreign key constraint will be added to.
882
     * @param array|string $columns the name of the column to that the constraint will be added on. If there are
883
     * multiple columns, separate them with commas.
884
     * @param string $refTable the table that the foreign key references to.
885
     * @param array|string $refColumns the name of the column that the foreign key references to. If there are multiple
886
     * columns, separate them with commas.
887
     * @param string|null $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION,
888
     * SET DEFAULT, SET NULL.
889
     * @param string|null $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION,
890
     * SET DEFAULT, SET NULL.
891
     *
892
     * @return $this the command object itself.
893
     */
894 7
    public function addForeignKey(
895
        string $name,
896
        string $table,
897
        $columns,
898
        string $refTable,
899
        $refColumns,
900
        ?string $delete = null,
901
        ?string $update = null
902
    ): self {
903 7
        $sql = $this->db->getQueryBuilder()->addForeignKey(
904 7
            $name,
905
            $table,
906
            $columns,
907
            $refTable,
908
            $refColumns,
909
            $delete,
910
            $update
911
        );
912
913 7
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
914
    }
915
916
    /**
917
     * Creates a SQL command for dropping a foreign key constraint.
918
     *
919
     * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by
920
     * the method.
921
     * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
922
     *
923
     * @return $this the command object itself.
924
     */
925 6
    public function dropForeignKey(string $name, string $table): self
926
    {
927 6
        $sql = $this->db->getQueryBuilder()->dropForeignKey($name, $table);
928
929 6
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
930
    }
931
932
    /**
933
     * Creates a SQL command for creating a new index.
934
     *
935
     * @param string $name the name of the index. The name will be properly quoted by the method.
936
     * @param string $table the table that the new index will be created for. The table name will be properly quoted by
937
     * the method.
938
     * @param array|string $columns the column(s) that should be included in the index. If there are multiple columns,
939
     * please separate them by commas. The column names will be properly quoted by the method.
940
     * @param bool $unique whether to add UNIQUE constraint on the created index.
941
     *
942
     * @return $this the command object itself.
943
     */
944 6
    public function createIndex(string $name, string $table, $columns, bool $unique = false): self
945
    {
946 6
        $sql = $this->db->getQueryBuilder()->createIndex($name, $table, $columns, $unique);
947
948 6
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
949
    }
950
951
    /**
952
     * Creates a SQL command for dropping an index.
953
     *
954
     * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
955
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
956
     *
957
     * @return $this the command object itself.
958
     */
959 5
    public function dropIndex(string $name, string $table): self
960
    {
961 5
        $sql = $this->db->getQueryBuilder()->dropIndex($name, $table);
962
963 5
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
964
    }
965
966
    /**
967
     * Creates a SQL command for adding an unique constraint to an existing table.
968
     *
969
     * @param string $name the name of the unique constraint. The name will be properly quoted by the method.
970
     * @param string $table the table that the unique constraint will be added to. The name will be properly quoted by
971
     * the method.
972
     * @param array|string $columns the name of the column to that the constraint will be added on. If there are
973
     * multiple columns, separate them with commas. The name will be properly quoted by the method.
974
     *
975
     * @return $this the command object itself.
976
     */
977 5
    public function addUnique(string $name, string $table, $columns): self
978
    {
979 5
        $sql = $this->db->getQueryBuilder()->addUnique($name, $table, $columns);
980
981 5
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
982
    }
983
984
    /**
985
     * Creates a SQL command for dropping an unique constraint.
986
     *
987
     * @param string $name the name of the unique constraint to be dropped. The name will be properly quoted by the
988
     * method.
989
     * @param string $table the table whose unique constraint is to be dropped. The name will be properly quoted by
990
     * the method.
991
     *
992
     * @return $this the command object itself.
993
     */
994 5
    public function dropUnique(string $name, string $table): self
995
    {
996 5
        $sql = $this->db->getQueryBuilder()->dropUnique($name, $table);
997
998 5
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
999
    }
1000
1001
    /**
1002
     * Creates a SQL command for adding a check constraint to an existing table.
1003
     *
1004
     * @param string $name the name of the check constraint. The name will be properly quoted by the method.
1005
     * @param string $table the table that the check constraint will be added to. The name will be properly quoted by
1006
     * the method.
1007
     * @param string $expression the SQL of the `CHECK` constraint.
1008
     *
1009
     * @return $this the command object itself.
1010
     */
1011 3
    public function addCheck(string $name, string $table, string $expression): self
1012
    {
1013 3
        $sql = $this->db->getQueryBuilder()->addCheck($name, $table, $expression);
1014
1015 3
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
1016
    }
1017
1018
    /**
1019
     * Creates a SQL command for dropping a check constraint.
1020
     *
1021
     * @param string $name the name of the check constraint to be dropped. The name will be properly quoted by the
1022
     * method.
1023
     * @param string $table the table whose check constraint is to be dropped. The name will be properly quoted by the
1024
     * method.
1025
     *
1026
     * @return $this the command object itself.
1027
     */
1028 3
    public function dropCheck(string $name, string $table): self
1029
    {
1030 3
        $sql = $this->db->getQueryBuilder()->dropCheck($name, $table);
1031
1032 3
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
1033
    }
1034
1035
    /**
1036
     * Creates a SQL command for adding a default value constraint to an existing table.
1037
     *
1038
     * @param string $name the name of the default value constraint. The name will be properly quoted by the method.
1039
     * @param string $table the table that the default value constraint will be added to. The name will be properly
1040
     * quoted by the method.
1041
     * @param string $column the name of the column to that the constraint will be added on. The name will be properly
1042
     * quoted by the method.
1043
     * @param mixed $value default value.
1044
     *
1045
     * @return $this the command object itself.
1046
     */
1047 1
    public function addDefaultValue(string $name, string $table, string $column, $value): self
1048
    {
1049 1
        $sql = $this->db->getQueryBuilder()->addDefaultValue($name, $table, $column, $value);
1050
1051 1
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
1052
    }
1053
1054
    /**
1055
     * Creates a SQL command for dropping a default value constraint.
1056
     *
1057
     * @param string $name the name of the default value constraint to be dropped. The name will be properly quoted by
1058
     * the method.
1059
     * @param string $table the table whose default value constraint is to be dropped. The name will be properly quoted
1060
     * by the method.
1061
     *
1062
     * @return $this the command object itself.
1063
     */
1064 1
    public function dropDefaultValue(string $name, string $table): self
1065
    {
1066 1
        $sql = $this->db->getQueryBuilder()->dropDefaultValue($name, $table);
1067
1068 1
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
1069
    }
1070
1071
    /**
1072
     * Creates a SQL command for resetting the sequence value of a table's primary key.
1073
     *
1074
     * The sequence will be reset such that the primary key of the next new row inserted will have the specified value
1075
     * or 1.
1076
     *
1077
     * @param string $table the name of the table whose primary key sequence will be reset.
1078
     * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, the next new
1079
     * row's primary key will have a value 1.
1080
     *
1081
     * @return $this the command object itself.
1082
     */
1083
    public function resetSequence(string $table, $value = null): self
1084
    {
1085
        $sql = $this->db->getQueryBuilder()->resetSequence($table, $value);
1086
1087
        return $this->setSql($sql);
1088
    }
1089
1090
    /**
1091
     * Executes a db command resetting the sequence value of a table's primary key.
1092
     *
1093
     * Reason for execute is that some databases (Oracle) need several queries to do so.
1094
     *
1095
     * The sequence is reset such that the primary key of the next new row inserted will have the specified value or the
1096
     * maximum existing value +1.
1097
     *
1098
     * @param string $table the name of the table whose primary key sequence is reset.
1099
     * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, the next new
1100
     * row's primary key will have the maximum existing value +1.
1101
     *
1102
     * @return $this
1103
     */
1104
    public function executeResetSequence(string $table, $value = null): self
1105
    {
1106
        return $this->resetSequence($table, $value);
1107
    }
1108
1109
    /**
1110
     * Builds a SQL command for enabling or disabling integrity check.
1111
     *
1112
     * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current or default
1113
     * schema.
1114
     * @param string $table the table name.
1115
     * @param bool $check whether to turn on or off the integrity check.
1116
     *
1117
     * @return $this the command object itself
1118
     */
1119
    public function checkIntegrity(string $schema, string $table, bool $check = true): self
1120
    {
1121
        $sql = $this->db->getQueryBuilder()->checkIntegrity($schema, $table, $check);
1122
1123
        return $this->setSql($sql);
1124
    }
1125
1126
    /**
1127
     * Builds a SQL command for adding comment to column.
1128
     *
1129
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the
1130
     * method.
1131
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the
1132
     * method.
1133
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
1134
     *
1135
     * @return $this the command object itself.
1136
     */
1137 1
    public function addCommentOnColumn(string $table, string $column, string $comment): self
1138
    {
1139 1
        $sql = $this->db->getQueryBuilder()->addCommentOnColumn($table, $column, $comment);
1140
1141 1
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
1142
    }
1143
1144
    /**
1145
     * Builds a SQL command for adding comment to table.
1146
     *
1147
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the
1148
     * method.
1149
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
1150
     *
1151
     * @return $this the command object itself.
1152
     */
1153
    public function addCommentOnTable(string $table, string $comment): self
1154
    {
1155
        $sql = $this->db->getQueryBuilder()->addCommentOnTable($table, $comment);
1156
1157
        return $this->setSql($sql);
1158
    }
1159
1160
    /**
1161
     * Builds a SQL command for dropping comment from column.
1162
     *
1163
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the
1164
     * method.
1165
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the
1166
     * method.
1167
     *
1168
     * @return $this the command object itself.
1169
     */
1170 1
    public function dropCommentFromColumn(string $table, string $column): self
1171
    {
1172 1
        $sql = $this->db->getQueryBuilder()->dropCommentFromColumn($table, $column);
1173
1174 1
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
1175
    }
1176
1177
    /**
1178
     * Builds a SQL command for dropping comment from table.
1179
     *
1180
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the
1181
     * method.
1182
     *
1183
     * @return $this the command object itself.
1184
     */
1185
    public function dropCommentFromTable(string $table): self
1186
    {
1187
        $sql = $this->db->getQueryBuilder()->dropCommentFromTable($table);
1188
1189
        return $this->setSql($sql);
1190
    }
1191
1192
    /**
1193
     * Creates a SQL View.
1194
     *
1195
     * @param string $viewName the name of the view to be created.
1196
     * @param Query|string $subquery the select statement which defines the view. This can be either a string or a
1197
     * {@see Query} object.
1198
     *
1199
     * @return $this the command object itself.
1200
     */
1201 5
    public function createView(string $viewName, $subquery): self
1202
    {
1203 5
        $sql = $this->db->getQueryBuilder()->createView($viewName, $subquery);
1204
1205 5
        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
1206
    }
1207
1208
    /**
1209
     * Drops a SQL View.
1210
     *
1211
     * @param string $viewName the name of the view to be dropped.
1212
     *
1213
     * @return $this the command object itself.
1214
     */
1215 5
    public function dropView(string $viewName): self
1216
    {
1217 5
        $sql = $this->db->getQueryBuilder()->dropView($viewName);
1218
1219 5
        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
1220
    }
1221
1222
    /**
1223
     * Executes the SQL statement.
1224
     *
1225
     * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
1226
     * No result set will be returned.
1227
     *
1228
     * @throws Throwable
1229
     * @throws Exception execution failed.
1230
     *
1231
     * @return int number of rows affected by the execution.
1232
     */
1233 381
    public function execute(): int
1234
    {
1235 381
        $profiler = $this->db->getProfiler();
0 ignored issues
show
Bug introduced by
The method getProfiler() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

1235
        /** @scrutinizer ignore-call */ 
1236
        $profiler = $this->db->getProfiler();
Loading history...
1236 381
        $sql = $this->getSql();
1237
1238 381
        [$profile, $rawSql] = $this->logQuery(__METHOD__);
1239
1240 381
        if ($sql === '') {
1241 10
            return 0;
1242
        }
1243
1244 376
        $this->prepare(false);
1245
1246
        try {
1247 376
            if ($this->db->isProfilingEnabled()) {
0 ignored issues
show
Bug introduced by
The method isProfilingEnabled() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

1247
            if ($this->db->/** @scrutinizer ignore-call */ isProfilingEnabled()) {
Loading history...
1248 376
                $profiler->begin((string) $rawSql, [__METHOD__]);
1249
            }
1250
1251 376
            $this->internalExecute($rawSql);
1252 371
            $n = $this->pdoStatement->rowCount();
1253
1254 371
            if ($this->db->isProfilingEnabled()) {
1255 371
                $profiler->end((string) $rawSql, [__METHOD__]);
1256
            }
1257
1258 371
            $this->refreshTableSchema();
1259
1260 371
            return $n;
1261 20
        } catch (Exception $e) {
1262 20
            if ($this->db->isProfilingEnabled()) {
1263 20
                $profiler->end((string) $rawSql, [__METHOD__]);
1264
            }
1265
1266 20
            throw $e;
1267
        }
1268
    }
1269
1270
    /**
1271
     * Logs the current database query if query logging is enabled and returns the profiling token if profiling is
1272
     * enabled.
1273
     *
1274
     * @param string $category the log category.
1275
     *
1276
     * @return array array of two elements, the first is boolean of whether profiling is enabled or not. The second is
1277
     * the rawSql if it has been created.
1278
     */
1279 1699
    protected function logQuery(string $category): array
1280
    {
1281 1699
        $logger = $this->db->getLogger();
0 ignored issues
show
Bug introduced by
The method getLogger() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

1281
        /** @scrutinizer ignore-call */ 
1282
        $logger = $this->db->getLogger();
Loading history...
1282
1283 1699
        if ($this->db->isLoggingEnabled()) {
0 ignored issues
show
Bug introduced by
The method isLoggingEnabled() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

1283
        if ($this->db->/** @scrutinizer ignore-call */ isLoggingEnabled()) {
Loading history...
1284 1699
            $rawSql = $this->getRawSql();
1285 1699
            $logger->log(LogLevel::INFO, $rawSql, [$category]);
1286
        }
1287
1288 1699
        if (!$this->db->isProfilingEnabled()) {
1289 10
            return [false, $rawSql ?? null];
1290
        }
1291
1292 1699
        return [true, $rawSql ?? $this->getRawSql()];
1293
    }
1294
1295
    /**
1296
     * Performs the actual DB query of a SQL statement.
1297
     *
1298
     * @param string $method method of PDOStatement to be called.
1299
     * @param array|int|null $fetchMode the result fetch mode.
1300
     *
1301
     * Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) for valid fetch
1302
     * modes. If this parameter is null, the value set in {@see fetchMode} will be used.
1303
     *
1304
     * @throws Exception|Throwable if the query causes any problem.
1305
     *
1306
     * @return mixed the method execution result.
1307
     */
1308 1680
    protected function queryInternal(string $method, $fetchMode = null)
1309
    {
1310 1680
        $logger = $this->db->getLogger();
1311 1680
        $profiler = $this->db->getProfiler();
1312 1680
        $queryCache = $this->db->getqueryCache();
0 ignored issues
show
Bug introduced by
The method getqueryCache() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

1312
        /** @scrutinizer ignore-call */ 
1313
        $queryCache = $this->db->getqueryCache();
Loading history...
1313
1314 1680
        [, $rawSql] = $this->logQuery(__CLASS__ . '::query');
1315
1316 1680
        if ($method !== '') {
1317 1665
            $info = $queryCache->info(
1318 1665
                $this->queryCacheDuration,
1319 1665
                $this->queryCacheDependency
1320
            );
1321
1322 1665
            if (is_array($info)) {
1323
                /* @var $cache CacheInterface */
1324 10
                $cache = $info[0];
1325 10
                $rawSql = $rawSql ?: $this->getRawSql();
1326 10
                $cacheKey = $this->getCacheKey($method, $fetchMode, $rawSql);
0 ignored issues
show
Bug introduced by
It seems like $fetchMode can also be of type array; however, parameter $fetchMode of Yiisoft\Db\Command\Command::getCacheKey() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

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

1326
                $cacheKey = $this->getCacheKey($method, /** @scrutinizer ignore-type */ $fetchMode, $rawSql);
Loading history...
1327 10
                $result = $cache->getOrSet(
1328
                    $cacheKey,
1329 10
                    static fn () => null,
1330
                );
1331
1332 10
                if (is_array($result) && isset($result[0])) {
1333 10
                    if ($this->db->isLoggingEnabled()) {
1334 10
                        $logger->log(
1335 10
                            LogLevel::DEBUG,
1336 10
                            'Query result served from cache',
1337 10
                            [__CLASS__ . '::query']
1338
                        );
1339
                    }
1340
1341 10
                    return $result[0];
1342
                }
1343
            }
1344
        }
1345
1346 1680
        $this->prepare(true);
1347
1348
        try {
1349 1680
            if ($this->db->isProfilingEnabled()) {
1350 1680
                $profiler->begin((string) $rawSql, [__CLASS__ . '::query']);
1351
            }
1352
1353 1680
            $this->internalExecute($rawSql);
1354
1355 1678
            if ($method === '') {
1356 35
                $result = new DataReader($this);
1357
            } else {
1358 1663
                if ($fetchMode === null) {
1359 1579
                    $fetchMode = $this->fetchMode;
1360
                }
1361
1362 1663
                $result = call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode);
1363
1364 1663
                $this->pdoStatement->closeCursor();
1365
            }
1366
1367 1678
            if ($this->db->isProfilingEnabled()) {
1368 1678
                $profiler->end((string) $rawSql, [__CLASS__ . '::query']);
1369
            }
1370 28
        } catch (Exception $e) {
1371 28
            if ($this->db->isProfilingEnabled()) {
1372 28
                $profiler->end((string) $rawSql, [__CLASS__ . '::query']);
1373
            }
1374
1375 28
            throw $e;
1376
        }
1377
1378 1678
        if (isset($cache, $cacheKey, $info)) {
1379 10
            $cache->getOrSet(
1380
                $cacheKey,
1381 10
                static fn (): array => [$result],
1382 10
                $info[1],
1383 10
                $info[2]
1384
            );
1385
1386 10
            if ($this->db->isLoggingEnabled()) {
1387 10
                $logger->log(
1388 10
                    LogLevel::DEBUG,
1389 10
                    'Saved query result in cache',
1390 10
                    [__CLASS__ . '::query']
1391
                );
1392
            }
1393
        }
1394
1395 1678
        return $result;
1396
    }
1397
1398
    /**
1399
     * Returns the cache key for the query.
1400
     *
1401
     * @param string $method method of PDOStatement to be called.
1402
     * @param int|null $fetchMode the result fetch mode.
1403
     * Please refer to [PHP manual](https://secure.php.net/manual/en/function.PDOStatement-setFetchMode.php) for valid
1404
     * fetch modes.
1405
     * @param string $rawSql the raw SQL with parameter values inserted into the corresponding placeholders.
1406
     *
1407
     * @throws JsonException
1408
     *
1409
     * @return array the cache key.
1410
     */
1411 10
    protected function getCacheKey(string $method, ?int $fetchMode, string $rawSql): array
1412
    {
1413
        return [
1414 10
            __CLASS__,
1415 10
            $method,
1416 10
            $fetchMode,
1417 10
            $this->db->getDsn(),
1418 10
            $this->db->getUsername(),
0 ignored issues
show
Bug introduced by
The method getUsername() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

1418
            $this->db->/** @scrutinizer ignore-call */ 
1419
                       getUsername(),
Loading history...
1419 10
            $rawSql,
1420
        ];
1421
    }
1422
1423
    /**
1424
     * Marks a specified table schema to be refreshed after command execution.
1425
     *
1426
     * @param string $name name of the table, which schema should be refreshed.
1427
     *
1428
     * @return $this this command instance.
1429
     */
1430 75
    protected function requireTableSchemaRefresh(string $name): self
1431
    {
1432 75
        $this->refreshTableName = $name;
1433
1434 75
        return $this;
1435
    }
1436
1437
    /**
1438
     * Refreshes table schema, which was marked by {@see requireTableSchemaRefresh()}.
1439
     */
1440 371
    protected function refreshTableSchema(): void
1441
    {
1442 371
        if ($this->refreshTableName !== null) {
1443 70
            $this->db->getSchema()->refreshTableSchema($this->refreshTableName);
1444
        }
1445 371
    }
1446
1447
    /**
1448
     * Marks the command to be executed in transaction.
1449
     *
1450
     * @param string|null $isolationLevel The isolation level to use for this transaction.
1451
     *
1452
     * See {@see Transaction::begin()} for details.
1453
     *
1454
     * @return $this this command instance.
1455
     */
1456 5
    protected function requireTransaction(?string $isolationLevel = null): self
1457
    {
1458 5
        $this->isolationLevel = $isolationLevel;
1459
1460 5
        return $this;
1461
    }
1462
1463
    /**
1464
     * Sets a callable (e.g. anonymous function) that is called when {@see Exception} is thrown when executing the
1465
     * command. The signature of the callable should be:.
1466
     *
1467
     * ```php
1468
     * function (Exceptions $e, $attempt)
1469
     * {
1470
     *     // return true or false (whether to retry the command or rethrow $e)
1471
     * }
1472
     * ```
1473
     *
1474
     * The callable will receive a database exception thrown and a current attempt (to execute the command) number
1475
     * starting from 1.
1476
     *
1477
     * @param callable $handler a PHP callback to handle database exceptions.
1478
     *
1479
     * @return $this this command instance.
1480
     */
1481 5
    protected function setRetryHandler(callable $handler): self
1482
    {
1483 5
        $this->retryHandler = $handler;
1484
1485 5
        return $this;
1486
    }
1487
1488
    /**
1489
     * Executes a prepared statement.
1490
     *
1491
     * It's a wrapper around {@see PDOStatement::execute()} to support transactions and retry handlers.
1492
     *
1493
     * @param string|null $rawSql the rawSql if it has been created.
1494
     *
1495
     * @throws Exception|Throwable
1496
     */
1497 1699
    protected function internalExecute(?string $rawSql): void
1498
    {
1499 1699
        $attempt = 0;
1500
1501 1699
        while (true) {
1502
            try {
1503
                if (
1504 1699
                    ++$attempt === 1
1505 1699
                    && $this->isolationLevel !== null
1506 1699
                    && $this->db->getTransaction() === null
1507
                ) {
1508
                    $this->db->transaction(fn ($rawSql) => $this->internalExecute($rawSql), $this->isolationLevel);
0 ignored issues
show
Bug introduced by
The method transaction() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

1508
                    $this->db->/** @scrutinizer ignore-call */ 
1509
                               transaction(fn ($rawSql) => $this->internalExecute($rawSql), $this->isolationLevel);
Loading history...
1509
                } else {
1510 1699
                    $this->pdoStatement->execute();
1511
                }
1512 1696
                break;
1513 44
            } catch (\Exception $e) {
1514 44
                $rawSql = $rawSql ?: $this->getRawSql();
1515 44
                $e = $this->db->getSchema()->convertException($e, $rawSql);
1516
1517 44
                if ($this->retryHandler === null || !($this->retryHandler)($e, $attempt)) {
1518 44
                    throw $e;
1519
                }
1520
            }
1521
        }
1522 1696
    }
1523
1524 351
    protected function reset(): void
1525
    {
1526 351
        $this->sql = null;
1527 351
        $this->pendingParams = [];
1528 351
        $this->params = [];
1529 351
        $this->refreshTableName = null;
1530 351
        $this->isolationLevel = null;
1531 351
        $this->retryHandler = null;
1532 351
    }
1533
1534
    public function getFetchMode(): int
1535
    {
1536
        return $this->fetchMode;
1537
    }
1538
1539 33
    public function getParams(): array
1540
    {
1541 33
        return $this->params;
1542
    }
1543
1544 393
    public function getPdoStatement(): ?PDOStatement
1545
    {
1546 393
        return $this->pdoStatement;
1547
    }
1548
1549
    /**
1550
     * The default fetch mode for this command.
1551
     *
1552
     * @param int $value
1553
     *
1554
     * {@see http://www.php.net/manual/en/pdostatement.setfetchmode.php}
1555
     */
1556 5
    public function setFetchMode(int $value): void
1557
    {
1558 5
        $this->fetchMode = $value;
1559 5
    }
1560
1561
    /**
1562
     * The parameters (name => value) that are bound to the current PDO statement.
1563
     *
1564
     * This property is maintained by methods such as {@see bindValue()}. It is mainly provided for logging purpose and
1565
     * is used to generate {@see rawSql}. Do not modify it directly.
1566
     *
1567
     * @param $value
1568
     */
1569
    public function setParams(array $value): void
1570
    {
1571
        $this->params = $value;
1572
    }
1573
}
1574