Passed
Branch psalm-3 (d5d890)
by Wilmer
02:56
created

Command::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Command;
6
7
use JsonException;
8
use PDO;
9
use Psr\Log\LogLevel;
10
use Throwable;
11
use Yiisoft\Cache\CacheInterface;
12
use Yiisoft\Cache\Dependency\Dependency;
13
use Yiisoft\Db\AwareTrait\LoggerAwareTrait;
14
use Yiisoft\Db\AwareTrait\ProfilerAwareTrait;
15
use Yiisoft\Db\Cache\QueryCache;
16
use Yiisoft\Db\Connection\ConnectionInterface;
17
use Yiisoft\Db\Exception\Exception;
18
use Yiisoft\Db\Exception\InvalidArgumentException;
19
use Yiisoft\Db\Exception\InvalidConfigException;
20
use Yiisoft\Db\Exception\NotSupportedException;
21
use Yiisoft\Db\Expression\Expression;
22
use Yiisoft\Db\Pdo\PdoValue;
23
use Yiisoft\Db\Query\Data\DataReader;
24
use Yiisoft\Db\Query\QueryInterface;
25
use Yiisoft\Db\Transaction\TransactionInterface;
26
27
use function array_map;
28
use function call_user_func_array;
29
use function explode;
30
use function get_resource_type;
31
use function is_array;
32
use function is_bool;
33
use function is_object;
34
use function is_resource;
35
use function is_string;
36
use function stream_get_contents;
37
use function strncmp;
38
use function strtr;
39
40
/**
41
 * Command represents a SQL statement to be executed against a database.
42
 *
43
 * A command object is usually created by calling {@see ConnectionInterface::createCommand()}.
44
 *
45
 * The SQL statement it represents can be set via the {@see sql} property.
46
 *
47
 * To execute a non-query SQL (such as INSERT, DELETE, UPDATE), call {@see execute()}.
48
 * To execute a SQL statement that returns a result data set (such as SELECT), use {@see queryAll()},
49
 * {@see queryOne()}, {@see queryColumn()}, {@see queryScalar()}, or {@see query()}.
50
 *
51
 * For example,
52
 *
53
 * ```php
54
 * $users = $connectionInterface->createCommand('SELECT * FROM user')->queryAll();
55
 * ```
56
 *
57
 * Command supports SQL statement preparation and parameter binding.
58
 *
59
 * Call {@see bindValue()} to bind a value to a SQL parameter;
60
 * Call {@see bindParam()} to bind a PHP variable to a SQL parameter.
61
 *
62
 * When binding a parameter, the SQL statement is automatically prepared. You may also call {@see prepare()} explicitly
63
 * to prepare a SQL statement.
64
 *
65
 * Command also supports building SQL statements by providing methods such as {@see insert()}, {@see update()}, etc.
66
 *
67
 * For example, the following code will create and execute an INSERT SQL statement:
68
 *
69
 * ```php
70
 * $connectionInterface->createCommand()->insert('user', [
71
 *     'name' => 'Sam',
72
 *     'age' => 30,
73
 * ])->execute();
74
 * ```
75
 *
76
 * To build SELECT SQL statements, please use {@see QueryInterface} instead.
77
 *
78
 * For more details and usage information on Command, see the [guide article on Database Access Objects](guide:db-dao).
79
 *
80
 * @property string $rawSql The raw SQL with parameter values inserted into the corresponding placeholders in
81
 * {@see sql}.
82
 * @property string $sql The SQL statement to be executed.
83
 */
84
abstract class Command implements CommandInterface
85
{
86
    use CommandPdoTrait;
87
    use LoggerAwareTrait;
88
    use ProfilerAwareTrait;
89
90
    protected ?string $isolationLevel = null;
91
92
    protected ?string $refreshTableName = null;
93
    /** @var callable|null */
94
    protected $retryHandler = null;
95
    private int $fetchMode = PDO::FETCH_ASSOC;
96
    private ?int $queryCacheDuration = null;
97
    private ?string $sql = null;
98
    private ?Dependency $queryCacheDependency = null;
99
100
    public function __construct(private QueryCache $queryCache)
101
    {
102
    }
103
104
    /**
105
     * Returns the cache key for the query.
106
     *
107
     * @param string $method method of PDOStatement to be called.
108
     * @param array|int|null $fetchMode the result fetch mode.
109
     * Please refer to [PHP manual](https://secure.php.net/manual/en/function.PDOStatement-setFetchMode.php) for valid
110
     * fetch modes.
111
     * @param string $rawSql the raw SQL with parameter values inserted into the corresponding placeholders.
112
     *
113
     * @throws JsonException
114
     *
115
     * @return array the cache key.
116
     */
117
    abstract protected function getCacheKey(string $method, array|int|null $fetchMode, string $rawSql): array;
118
119
    /**
120
     * Executes a prepared statement.
121
     *
122
     * It's a wrapper around {@see PDOStatement::execute()} to support transactions and retry handlers.
123
     *
124
     * @param string|null $rawSql the rawSql if it has been created.
125
     *
126
     * @throws Exception|Throwable
127
     */
128
    abstract protected function internalExecute(?string $rawSql): void;
129
130
    public function addCheck(string $name, string $table, string $expression): self
131
    {
132
        $sql = $this->queryBuilder()->addCheck($name, $table, $expression);
133
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
134
    }
135
136
    public function addColumn(string $table, string $column, string $type): self
137
    {
138
        $sql = $this->queryBuilder()->addColumn($table, $column, $type);
139
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
140
    }
141
142
    /**
143
     * @throws \Exception
144
     */
145
    public function addCommentOnColumn(string $table, string $column, string $comment): self
146
    {
147
        $sql = $this->queryBuilder()->addCommentOnColumn($table, $column, $comment);
148
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
149
    }
150
151
    /**
152
     * @throws \Exception
153
     */
154
    public function addCommentOnTable(string $table, string $comment): self
155
    {
156
        $sql = $this->queryBuilder()->addCommentOnTable($table, $comment);
157
        return $this->setSql($sql);
158
    }
159
160
    /**
161
     * @throws Exception|NotSupportedException
162
     */
163
    public function addDefaultValue(string $name, string $table, string $column, mixed $value): self
164
    {
165
        $sql = $this->queryBuilder()->addDefaultValue($name, $table, $column, $value);
166
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
167
    }
168
169
    /**
170
     * @throws Exception|InvalidArgumentException
171
     */
172
    public function addForeignKey(
173
        string $name,
174
        string $table,
175
        array|string $columns,
176
        string $refTable,
177
        array|string $refColumns,
178
        ?string $delete = null,
179
        ?string $update = null
180
    ): self {
181
        $sql = $this->queryBuilder()->addForeignKey(
182
            $name,
183
            $table,
184
            $columns,
185
            $refTable,
186
            $refColumns,
187
            $delete,
188
            $update
189
        );
190
191
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
192
    }
193
194
    public function addPrimaryKey(string $name, string $table, array|string $columns): self
195
    {
196
        $sql = $this->queryBuilder()->addPrimaryKey($name, $table, $columns);
197
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
198
    }
199
200
    public function addUnique(string $name, string $table, array|string $columns): self
201
    {
202
        $sql = $this->queryBuilder()->addUnique($name, $table, $columns);
203
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
204
    }
205
206
    public function alterColumn(string $table, string $column, string $type): self
207
    {
208
        $sql = $this->queryBuilder()->alterColumn($table, $column, $type);
209
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
210
    }
211
212
    /**
213
     * @throws Exception|InvalidArgumentException
214
     */
215
    public function batchInsert(string $table, array $columns, iterable $rows): self
216
    {
217
        $table = $this->queryBuilder()->quoter()->quoteSql($table);
218
        $columns = array_map(fn ($column) => $this->queryBuilder()->quoter()->quoteSql($column), $columns);
219
        $params = [];
220
        $sql = $this->queryBuilder()->batchInsert($table, $columns, $rows, $params);
221
222
        $this->setRawSql($sql);
223
        $this->bindValues($params);
224
225
        return $this;
226
    }
227
228
    public function bindValue(int|string $name, mixed $value, ?int $dataType = null): self
229
    {
230
        if ($dataType === null) {
231
            $dataType = $this->queryBuilder()->schema()->getPdoType($value);
232
        }
233
234
        $this->params[$name] = new Param($name, $value, $dataType);
235
236
        return $this;
237
    }
238
239
    public function bindValues(array $values): self
240
    {
241
        if (empty($values)) {
242
            return $this;
243
        }
244
245
        foreach ($values as $name => $value) {
246
            if ($value instanceof ParamInterface) {
247
                $this->params[$value->getName()] = $value;
248
            } elseif (is_array($value)) { // TODO: Drop in Yii 2.1
249
                $this->params[$name] = new Param($name, ...$value);
250
            } elseif ($value instanceof PdoValue && is_int($value->getType())) {
251
                $this->params[$name] = new Param($name, $value->getValue(), $value->getType());
252
            } else {
253
                $type = $this->queryBuilder()->schema()->getPdoType($value);
254
                $this->params[$name] = new Param($name, $value, $type);
255
            }
256
        }
257
258
        return $this;
259
    }
260
261
    public function cache(?int $duration = null, Dependency $dependency = null): self
262
    {
263
        $this->queryCacheDuration = $duration ?? $this->queryCache->getDuration();
264
        $this->queryCacheDependency = $dependency;
265
        return $this;
266
    }
267
268
    /**
269
     * @throws Exception|NotSupportedException
270
     */
271
    public function checkIntegrity(string $schema, string $table, bool $check = true): self
272
    {
273
        $sql = $this->queryBuilder()->checkIntegrity($schema, $table, $check);
274
        return $this->setSql($sql);
275
    }
276
277
    /**
278
     * @throws Exception|InvalidArgumentException
279
     */
280
    public function createIndex(string $name, string $table, array|string $columns, bool $unique = false): self
281
    {
282
        $sql = $this->queryBuilder()->createIndex($name, $table, $columns, $unique);
283
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
284
    }
285
286
    public function createTable(string $table, array $columns, ?string $options = null): self
287
    {
288
        $sql = $this->queryBuilder()->createTable($table, $columns, $options);
289
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
290
    }
291
292
    /**
293
     * @throws Exception|InvalidConfigException|NotSupportedException
294
     */
295
    public function createView(string $viewName, QueryInterface|string $subquery): self
296
    {
297
        $sql = $this->queryBuilder()->createView($viewName, $subquery);
298
        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
299
    }
300
301
    /**
302
     * @throws Exception|InvalidArgumentException
303
     */
304
    public function delete(string $table, array|string $condition = '', array $params = []): self
305
    {
306
        $sql = $this->queryBuilder()->delete($table, $condition, $params);
307
        return $this->setSql($sql)->bindValues($params);
308
    }
309
310
    public function dropCheck(string $name, string $table): self
311
    {
312
        $sql = $this->queryBuilder()->dropCheck($name, $table);
313
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
314
    }
315
316
    public function dropColumn(string $table, string $column): self
317
    {
318
        $sql = $this->queryBuilder()->dropColumn($table, $column);
319
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
320
    }
321
322
    public function dropCommentFromColumn(string $table, string $column): self
323
    {
324
        $sql = $this->queryBuilder()->dropCommentFromColumn($table, $column);
325
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
326
    }
327
328
    public function dropCommentFromTable(string $table): self
329
    {
330
        $sql = $this->queryBuilder()->dropCommentFromTable($table);
331
        return $this->setSql($sql);
332
    }
333
334
    /**
335
     * @throws Exception|NotSupportedException
336
     */
337
    public function dropDefaultValue(string $name, string $table): self
338
    {
339
        $sql = $this->queryBuilder()->dropDefaultValue($name, $table);
340
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
341
    }
342
343
    public function dropForeignKey(string $name, string $table): self
344
    {
345
        $sql = $this->queryBuilder()->dropForeignKey($name, $table);
346
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
347
    }
348
349
    public function dropIndex(string $name, string $table): self
350
    {
351
        $sql = $this->queryBuilder()->dropIndex($name, $table);
352
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
353
    }
354
355
    public function dropPrimaryKey(string $name, string $table): self
356
    {
357
        $sql = $this->queryBuilder()->dropPrimaryKey($name, $table);
358
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
359
    }
360
361
    public function dropTable(string $table): self
362
    {
363
        $sql = $this->queryBuilder()->dropTable($table);
364
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
365
    }
366
367
    public function dropUnique(string $name, string $table): self
368
    {
369
        $sql = $this->queryBuilder()->dropUnique($name, $table);
370
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
371
    }
372
373
    public function dropView(string $viewName): self
374
    {
375
        $sql = $this->queryBuilder()->dropView($viewName);
376
        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
377
    }
378
379
    /**
380
     * Executes the SQL statement.
381
     *
382
     * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
383
     * No result set will be returned.
384
     *
385
     * @throws Throwable
386
     * @throws Exception execution failed.
387
     *
388
     * @return int number of rows affected by the execution.
389
     */
390
    public function execute(): int
391
    {
392
        $sql = $this->getSql();
393
394
        [, $rawSql] = $this->logQuery(__METHOD__);
395
396
        if ($sql === '') {
397
            return 0;
398
        }
399
400
        $this->prepare(false);
401
402
        try {
403
            $this->profiler?->begin((string)$rawSql, [__METHOD__]);
404
405
            $this->internalExecute($rawSql);
406
            $n = $this->pdoStatement?->rowCount();
407
408
            $this->profiler?->end((string)$rawSql, [__METHOD__]);
409
410
            $this->refreshTableSchema();
411
412
            return $n ?? 0;
413
        } catch (Exception $e) {
414
            $this->profiler?->end((string)$rawSql, [__METHOD__]);
415
            throw $e;
416
        }
417
    }
418
419
    /**
420
     * @throws Exception|NotSupportedException
421
     */
422
    public function executeResetSequence(string $table, mixed $value = null): self
423
    {
424
        return $this->resetSequence($table, $value);
425
    }
426
427
    public function getFetchMode(): int
428
    {
429
        return $this->fetchMode;
430
    }
431
432
    public function getParams(): array
433
    {
434
        return array_map(static fn (mixed $value): mixed => $value->getValue(), $this->params);
435
    }
436
437
    /**
438
     * @throws \Exception
439
     */
440
    public function getRawSql(): string
441
    {
442
        if (empty($this->params)) {
443
            return $this->sql;
444
        }
445
446
        $params = [];
447
448
        foreach ($this->params as $name => $value) {
449
            if (is_string($name) && strncmp(':', $name, 1)) {
450
                $name = ':' . $name;
451
            }
452
453
            if ($value instanceof ParamInterface) {
454
                $value = $value->getValue();
455
            }
456
457
            if (is_string($value)) {
458
                $params[$name] = $this->queryBuilder()->quoter()->quoteValue($value);
459
            } elseif (is_bool($value)) {
460
                $params[$name] = ($value ? 'TRUE' : 'FALSE');
461
            } elseif ($value === null) {
462
                $params[$name] = 'NULL';
463
            } elseif ((!is_object($value) && !is_resource($value)) || $value instanceof Expression) {
464
                $params[$name] = $value;
465
            }
466
        }
467
468
        if (!isset($params[1])) {
469
            return strtr($this->sql, $params);
470
        }
471
472
        $sql = '';
473
474
        foreach (explode('?', $this->sql) as $i => $part) {
475
            $sql .= ($params[$i] ?? '') . $part;
476
        }
477
478
        return $sql;
479
    }
480
481
    public function getSql(): ?string
482
    {
483
        return $this->sql;
484
    }
485
486
    /**
487
     * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException
488
     */
489
    public function insert(string $table, QueryInterface|array $columns): self
490
    {
491
        $params = [];
492
        $sql = $this->queryBuilder()->insert($table, $columns, $params);
493
        return $this->setSql($sql)->bindValues($params);
494
    }
495
496
    abstract public function insertEx(string $table, array $columns): bool|array;
497
498
    public function noCache(): self
499
    {
500
        $this->queryCacheDuration = -1;
501
        return $this;
502
    }
503
504
    public function query(): DataReader
505
    {
506
        return $this->queryInternal('');
507
    }
508
509
    public function queryAll(?int $fetchMode = null): array
510
    {
511
        return $this->queryInternal('fetchAll', $fetchMode);
512
    }
513
514
    public function queryColumn(): array
515
    {
516
        return $this->queryInternal('fetchAll', PDO::FETCH_COLUMN);
517
    }
518
519
    public function queryOne(array|int $fetchMode = null): mixed
520
    {
521
        return $this->queryInternal('fetch', $fetchMode);
522
    }
523
524
    public function queryScalar(): bool|string|null|int
525
    {
526
        $result = $this->queryInternal('fetchColumn', 0);
527
528
        if (is_resource($result) && get_resource_type($result) === 'stream') {
529
            return stream_get_contents($result);
530
        }
531
532
        return $result;
533
    }
534
535
    public function renameColumn(string $table, string $oldName, string $newName): self
536
    {
537
        $sql = $this->queryBuilder()->renameColumn($table, $oldName, $newName);
538
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
539
    }
540
541
    public function renameTable(string $table, string $newName): self
542
    {
543
        $sql = $this->queryBuilder()->renameTable($table, $newName);
544
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
545
    }
546
547
    /**
548
     * @throws Exception|NotSupportedException
549
     */
550
    public function resetSequence(string $table, mixed $value = null): self
551
    {
552
        $sql = $this->queryBuilder()->resetSequence($table, $value);
553
        return $this->setSql($sql);
554
    }
555
556
    public function setFetchMode(int $value): void
557
    {
558
        $this->fetchMode = $value;
559
    }
560
561
    public function setParams(array $value): void
562
    {
563
        $this->params = $value;
564
    }
565
566
    public function setRawSql(string $sql): self
567
    {
568
        if ($sql !== $this->sql) {
569
            $this->cancel();
570
            $this->reset();
571
            $this->sql = $sql;
572
        }
573
574
        return $this;
575
    }
576
577
    public function setSql(string $sql): self
578
    {
579
        $this->cancel();
580
        $this->reset();
581
        $this->sql = $this->queryBuilder()->quoter()->quoteSql($sql);
582
583
        return $this;
584
    }
585
586
    public function truncateTable(string $table): self
587
    {
588
        $sql = $this->queryBuilder()->truncateTable($table);
589
        return $this->setSql($sql);
590
    }
591
592
    /**
593
     * @throws Exception|InvalidArgumentException
594
     */
595
    public function update(string $table, array $columns, array|string $condition = '', array $params = []): self
596
    {
597
        $sql = $this->queryBuilder()->update($table, $columns, $condition, $params);
598
        return $this->setSql($sql)->bindValues($params);
599
    }
600
601
    /**
602
     * @throws Exception|InvalidConfigException|JsonException|NotSupportedException
603
     */
604
    public function upsert(
605
        string $table,
606
        QueryInterface|array $insertColumns,
607
        bool|array $updateColumns = true,
608
        array $params = []
609
    ): self {
610
        $sql = $this->queryBuilder()->upsert($table, $insertColumns, $updateColumns, $params);
611
        return $this->setSql($sql)->bindValues($params);
612
    }
613
614
    /**
615
     * Logs the current database query if query logging is enabled and returns the profiling token if profiling is
616
     * enabled.
617
     *
618
     * @param string $category The log category.
619
     *
620
     * @throws \Exception
621
     *
622
     * @return array Two elements, the first is boolean of whether profiling is enabled or not. The second is
623
     * the rawSql if it has been created.
624
     */
625
    protected function logQuery(string $category): array
626
    {
627
        if ($this->logger !== null) {
628
            $rawSql = $this->getRawSql();
629
            $this->logger->log(LogLevel::INFO, $rawSql, [$category]);
630
        }
631
632
        if ($this->profiler === null) {
633
            return [false, $rawSql ?? null];
634
        }
635
636
        return [true, $rawSql ?? $this->getRawSql()];
637
    }
638
639
    /**
640
     * Performs the actual DB query of a SQL statement.
641
     *
642
     * @param string $method Method of PDOStatement to be called.
643
     * @param array|int|null $fetchMode The result fetch mode.
644
     *
645
     * Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) for valid fetch
646
     * modes. If this parameter is null, the value set in {@see fetchMode} will be used.
647
     *
648
     * @throws Exception|Throwable If the query causes any problem.
649
     *
650
     * @return mixed The method execution result.
651
     */
652
    protected function queryInternal(string $method, array|int $fetchMode = null): mixed
653
    {
654
        [, $rawSql] = $this->logQuery(__CLASS__ . '::query');
655
656
        if ($method !== '') {
657
            $info = $this->queryCache->info($this->queryCacheDuration, $this->queryCacheDependency);
658
659
            if (is_array($info)) {
660
                /* @var $cache CacheInterface */
661
                $cache = $info[0];
662
                $rawSql = $rawSql ?: $this->getRawSql();
663
                $cacheKey = $this->getCacheKey($method, $fetchMode, $rawSql);
664
                $result = $cache->getOrSet(
665
                    $cacheKey,
666
                    static fn () => null,
667
                );
668
669
                if (is_array($result) && isset($result[0])) {
670
                    $this->logger?->log(LogLevel::DEBUG, 'Query result served from cache', [__CLASS__ . '::query']);
671
672
                    return $result[0];
673
                }
674
            }
675
        }
676
677
        $this->prepare(true);
678
679
        try {
680
            $this->profiler?->begin((string)$rawSql, [__CLASS__ . '::query']);
681
682
            $this->internalExecute($rawSql);
683
684
            if ($method === '') {
685
                $result = new DataReader($this);
686
            } else {
687
                if ($fetchMode === null) {
688
                    $fetchMode = $this->fetchMode;
689
                }
690
691
                $result = call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode);
692
693
                $this->pdoStatement?->closeCursor();
694
            }
695
696
            $this->profiler?->end((string)$rawSql, [__CLASS__ . '::query']);
697
        } catch (Exception $e) {
698
            $this->profiler?->end((string)$rawSql, [__CLASS__ . '::query']);
699
            throw $e;
700
        }
701
702
        if (isset($cache, $cacheKey, $info)) {
703
            $cache->getOrSet(
704
                $cacheKey,
705
                static fn (): array => [$result],
706
                $info[1],
707
                $info[2]
708
            );
709
710
            $this->logger?->log(LogLevel::DEBUG, 'Saved query result in cache', [__CLASS__ . '::query']);
711
        }
712
713
        return $result;
714
    }
715
716
    /**
717
     * Refreshes table schema, which was marked by {@see requireTableSchemaRefresh()}.
718
     */
719
    protected function refreshTableSchema(): void
720
    {
721
        if ($this->refreshTableName !== null) {
722
            $this->queryBuilder()->schema()->refreshTableSchema($this->refreshTableName);
723
        }
724
    }
725
726
    /**
727
     * Marks a specified table schema to be refreshed after command execution.
728
     *
729
     * @param string $name Name of the table, which schema should be refreshed.
730
     *
731
     * @return static
732
     */
733
    protected function requireTableSchemaRefresh(string $name): self
734
    {
735
        $this->refreshTableName = $name;
736
        return $this;
737
    }
738
739
    /**
740
     * Marks the command to be executed in transaction.
741
     *
742
     * @param string|null $isolationLevel The isolation level to use for this transaction.
743
     *
744
     * {@see TransactionInterface::begin()} for details.
745
     *
746
     * @return static
747
     */
748
    protected function requireTransaction(?string $isolationLevel = null): self
749
    {
750
        $this->isolationLevel = $isolationLevel;
751
        return $this;
752
    }
753
754
    protected function reset(): void
755
    {
756
        $this->sql = null;
757
        $this->params = [];
758
        $this->refreshTableName = null;
759
        $this->isolationLevel = null;
760
        $this->retryHandler = null;
761
    }
762
763
    /**
764
     * Sets a callable (e.g. anonymous function) that is called when {@see Exception} is thrown when executing the
765
     * command. The signature of the callable should be:.
766
     *
767
     * ```php
768
     * function (Exceptions $e, $attempt)
769
     * {
770
     *     // return true or false (whether to retry the command or rethrow $e)
771
     * }
772
     * ```
773
     *
774
     * The callable will receive a database exception thrown and a current attempt (to execute the command) number
775
     * starting from 1.
776
     *
777
     * @param callable|null $handler A PHP callback to handle database exceptions.
778
     *
779
     * @return static
780
     */
781
    protected function setRetryHandler(?callable $handler): self
782
    {
783
        $this->retryHandler = $handler;
784
        return $this;
785
    }
786
}
787