Test Failed
Pull Request — master (#119)
by Def
34:37 queued 22:21
created

Schema::resolveTableName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 8
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Sqlite;
6
7
use Throwable;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Arrays\ArraySorter;
10
use Yiisoft\Db\Connection\ConnectionInterface;
11
use Yiisoft\Db\Constraint\CheckConstraint;
12
use Yiisoft\Db\Constraint\Constraint;
13
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
14
use Yiisoft\Db\Constraint\IndexConstraint;
15
use Yiisoft\Db\Exception\Exception;
16
use Yiisoft\Db\Exception\InvalidArgumentException;
17
use Yiisoft\Db\Exception\InvalidConfigException;
18
use Yiisoft\Db\Exception\NotSupportedException;
19
use Yiisoft\Db\Expression\Expression;
20
use Yiisoft\Db\Schema\ColumnSchema;
21
use Yiisoft\Db\Schema\ColumnSchemaInterface;
22
use Yiisoft\Db\Schema\Schema as AbstractSchema;
23
use Yiisoft\Db\Schema\TableNameInterface;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Db\Schema\TableNameInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
24
use Yiisoft\Db\Schema\TableSchemaInterface;
25
use Yiisoft\Db\Transaction\TransactionInterface;
26
27
use function count;
28
use function explode;
29
use function preg_match;
30
use function strncasecmp;
31
use function strtolower;
32
use function trim;
33
34
/**
35
 * Schema is the class for retrieving metadata from a SQLite (2/3) database.
36
 *
37
 * @property string $transactionIsolationLevel The transaction isolation level to use for this transaction. This can be
38
 * either {@see TransactionInterface::READ_UNCOMMITTED} or {@see TransactionInterface::SERIALIZABLE}.
39
 *
40
 * @psalm-type Column = array<array-key, array{seqno:string, cid:string, name:string}>
41
 *
42
 * @psalm-type NormalizePragmaForeignKeyList = array<
43
 *   string,
44
 *   array<
45
 *     array-key,
46
 *     array{
47
 *       id:string,
48
 *       cid:string,
49
 *       seq:string,
50
 *       table:string,
51
 *       from:string,
52
 *       to:string,
53
 *       on_update:string,
54
 *       on_delete:string
55
 *     }
56
 *   >
57
 * >
58
 *
59
 * @psalm-type PragmaForeignKeyList = array<
60
 *   string,
61
 *   array{
62
 *     id:string,
63
 *     cid:string,
64
 *     seq:string,
65
 *     table:string,
66
 *     from:string,
67
 *     to:string,
68
 *     on_update:string,
69
 *     on_delete:string
70
 *   }
71
 * >
72
 *
73
 * @psalm-type PragmaIndexInfo = array<array-key, array{seqno:string, cid:string, name:string}>
74
 *
75
 * @psalm-type PragmaIndexList = array<
76
 *   array-key,
77
 *   array{seq:string, name:string, unique:string, origin:string, partial:string}
78
 * >
79
 *
80
 * @psalm-type PragmaTableInfo = array<
81
 *   array-key,
82
 *   array{cid:string, name:string, type:string, notnull:string, dflt_value:string|null, pk:string}
83
 * >
84
 */
85
final class Schema extends AbstractSchema
86
{
87
    /**
88
     * @var array mapping from physical column types (keys) to abstract column types (values)
89
     *
90
     * @psalm-var array<array-key, string> $typeMap
91
     */
92
    private array $typeMap = [
93
        'tinyint' => self::TYPE_TINYINT,
94
        'bit' => self::TYPE_SMALLINT,
95
        'boolean' => self::TYPE_BOOLEAN,
96
        'bool' => self::TYPE_BOOLEAN,
97
        'smallint' => self::TYPE_SMALLINT,
98
        'mediumint' => self::TYPE_INTEGER,
99
        'int' => self::TYPE_INTEGER,
100
        'integer' => self::TYPE_INTEGER,
101
        'bigint' => self::TYPE_BIGINT,
102
        'float' => self::TYPE_FLOAT,
103
        'double' => self::TYPE_DOUBLE,
104
        'real' => self::TYPE_FLOAT,
105
        'decimal' => self::TYPE_DECIMAL,
106
        'numeric' => self::TYPE_DECIMAL,
107
        'tinytext' => self::TYPE_TEXT,
108
        'mediumtext' => self::TYPE_TEXT,
109
        'longtext' => self::TYPE_TEXT,
110
        'text' => self::TYPE_TEXT,
111
        'varchar' => self::TYPE_STRING,
112
        'string' => self::TYPE_STRING,
113
        'char' => self::TYPE_CHAR,
114
        'blob' => self::TYPE_BINARY,
115
        'datetime' => self::TYPE_DATETIME,
116
        'year' => self::TYPE_DATE,
117
        'date' => self::TYPE_DATE,
118
        'time' => self::TYPE_TIME,
119
        'timestamp' => self::TYPE_TIMESTAMP,
120
        'enum' => self::TYPE_STRING,
121
    ];
122
123 353
    /**
124
     * Returns all table names in the database.
125 353
     *
126
     * This method should be overridden by child classes in order to support this feature because the default
127
     * implementation simply throws an exception.
128
     *
129
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
130
     *
131
     * @throws Exception|InvalidConfigException|Throwable
132
     *
133
     * @return array All table names in the database. The names have NO schema name prefix.
134
     */
135
    protected function findTableNames(string $schema = ''): array
136
    {
137
        return $this->db
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Yiisoft\Db\Sqlite\Schema. Did you maybe forget to declare it?
Loading history...
138
           ->createCommand(
139
               "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence' ORDER BY tbl_name"
140 10
           )
141
           ->queryColumn();
142 10
    }
143
144 10
    /**
145
     * Loads the metadata for the specified table.
146 10
     *
147
     * @param TableNameInterface $name table name.
148
     *
149
     * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable
150 10
     *
151
     * @return TableSchemaInterface|null DBMS-dependent table metadata, `null` if the table does not exist.
152
     */
153
    protected function loadTableSchema(TableNameInterface $name): ?TableSchemaInterface
154
    {
155
        $table = $this->resolveTableName($name);
156
157
        if ($this->findColumns($table)) {
158
            $this->findConstraints($table);
159
160
            return $table;
161
        }
162 86
163
        return null;
164 86
    }
165
166 86
    /**
167 86
     * Loads a primary key for the given table.
168
     *
169 86
     * @param TableNameInterface $tableName table name.
170 82
     *
171
     * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable
172 82
     *
173
     * @return Constraint|null primary key for the given table, `null` if the table has no primary key.
174
     */
175 12
    protected function loadTablePrimaryKey(TableNameInterface $tableName): ?Constraint
176
    {
177
        $tablePrimaryKey = $this->loadTableConstraints($tableName, self::PRIMARY_KEY);
178
179
        return $tablePrimaryKey instanceof Constraint ? $tablePrimaryKey : null;
180
    }
181
182
    /**
183
     * Loads all foreign keys for the given table.
184
     *
185
     * @param TableNameInterface $tableName table name.
186
     *
187 31
     * @throws Exception|InvalidConfigException|Throwable
188
     *
189 31
     * @return ForeignKeyConstraint[] foreign keys for the given table.
190
     */
191 31
    protected function loadTableForeignKeys(TableNameInterface $tableName): array
192
    {
193
        $result = [];
194
        /** @psalm-var PragmaForeignKeyList */
195
        $foreignKeysList = $this->getPragmaForeignKeyList((string) $tableName);
196
        /** @psalm-var NormalizePragmaForeignKeyList */
197
        $foreignKeysList = $this->normalizeRowKeyCase($foreignKeysList, true);
198
        /** @psalm-var NormalizePragmaForeignKeyList */
199
        $foreignKeysList = ArrayHelper::index($foreignKeysList, null, 'table');
200
        ArraySorter::multisort($foreignKeysList, 'seq', SORT_ASC, SORT_NUMERIC);
201
202
        /** @psalm-var NormalizePragmaForeignKeyList $foreignKeysList */
203 5
        foreach ($foreignKeysList as $table => $foreignKey) {
204
            $fk = (new ForeignKeyConstraint())
205 5
                ->columnNames(ArrayHelper::getColumn($foreignKey, 'from'))
206
                ->foreignTableName($table)
207 5
                ->foreignColumnNames(ArrayHelper::getColumn($foreignKey, 'to'))
208
                ->onDelete($foreignKey[0]['on_delete'] ?? null)
209 5
                ->onUpdate($foreignKey[0]['on_update'] ?? null);
210
211 5
            $result[] = $fk;
212 5
        }
213
214
        return $result;
215 5
    }
216 5
217 5
    /**
218 5
     * Loads all indexes for the given table.
219 5
     *
220 5
     * @param TableNameInterface $tableName table name.
221 5
     *
222
     * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable
223 5
     *
224
     * @return array indexes for the given table.
225
     *
226 5
     * @psalm-return array|IndexConstraint[]
227
     */
228
    protected function loadTableIndexes(TableNameInterface $tableName): array
229
    {
230
        $tableIndexes = $this->loadTableConstraints($tableName, self::INDEXES);
231
232
        return is_array($tableIndexes) ? $tableIndexes : [];
233
    }
234
235
    /**
236
     * Loads all unique constraints for the given table.
237
     *
238
     * @param TableNameInterface $tableName table name.
239
     *
240 11
     * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable
241
     *
242 11
     * @return array unique constraints for the given table.
243
     *
244 11
     * @psalm-return array|Constraint[]
245
     */
246
    protected function loadTableUniques(TableNameInterface $tableName): array
247
    {
248
        $tableUniques = $this->loadTableConstraints($tableName, self::UNIQUES);
249
250
        return is_array($tableUniques) ? $tableUniques : [];
251
    }
252
253
    /**
254
     * Loads all check constraints for the given table.
255
     *
256
     * @param TableNameInterface $tableName table name.
257
     *
258 13
     * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable
259
     *
260 13
     * @return CheckConstraint[] check constraints for the given table.
261
     */
262 13
    protected function loadTableChecks(TableNameInterface $tableName): array
263
    {
264
        $sql = $this->db->createCommand(
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Yiisoft\Db\Sqlite\Schema. Did you maybe forget to declare it?
Loading history...
265
            'SELECT `sql` FROM `sqlite_master` WHERE name = :tableName',
266
            [':tableName' => $tableName],
267
        )->queryScalar();
268
269
        $sql = ($sql === false || $sql === null) ? '' : (string) $sql;
270
271
        /** @var SqlToken[]|SqlToken[][]|SqlToken[][][] $code */
272
        $code = (new SqlTokenizer($sql))->tokenize();
273
        $pattern = (new SqlTokenizer('any CREATE any TABLE any()'))->tokenize();
274 13
        $result = [];
275
276 13
        if ($code[0] instanceof SqlToken && $code[0]->matches($pattern, 0, $firstMatchIndex, $lastMatchIndex)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lastMatchIndex seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $firstMatchIndex seems to be never defined.
Loading history...
277
            $offset = 0;
278 13
            $createTableToken = $code[0][(int) $lastMatchIndex - 1];
279 13
            $sqlTokenizerAnyCheck = new SqlTokenizer('any CHECK()');
280
281 13
            while (
282
                $createTableToken instanceof SqlToken &&
283
                $createTableToken->matches($sqlTokenizerAnyCheck->tokenize(), (int) $offset, $firstMatchIndex, $offset)
284 13
            ) {
285 13
                $name = null;
286 13
                $checkSql = (string) $createTableToken[(int) $offset - 1];
287
                $pattern = (new SqlTokenizer('CONSTRAINT any'))->tokenize();
288 13
289 13
                if (
290 13
                    isset($createTableToken[(int) $firstMatchIndex - 2])
291 13
                    && $createTableToken->matches($pattern, (int) $firstMatchIndex - 2)
292
                ) {
293
                    $sqlToken = $createTableToken[(int) $firstMatchIndex - 1];
294 13
                    $name = $sqlToken?->getContent();
295 13
                }
296
297 4
                $result[] = (new CheckConstraint())->name($name)->expression($checkSql);
298 4
            }
299 4
        }
300
301
        return $result;
302 4
    }
303 4
304
    /**
305
     * Loads all default value constraints for the given table.
306
     *
307
     * @param TableNameInterface $tableName table name.
308
     *
309 4
     * @throws NotSupportedException
310
     *
311
     * @return array default value constraints for the given table.
312
     */
313 13
    protected function loadTableDefaultValues(TableNameInterface $tableName): array
314
    {
315
        throw new NotSupportedException('SQLite does not support default value constraints.');
316
    }
317
318
    /**
319
     * Create a column schema builder instance giving the type and value precision.
320
     *
321
     * This method may be overridden by child classes to create a DBMS-specific column schema builder.
322
     *
323
     * @param string $type type of the column. See {@see ColumnSchemaBuilder::$type}.
324
     * @param array|int|string|null $length length or precision of the column. See {@see ColumnSchemaBuilder::$length}.
325 12
     *
326
     * @return ColumnSchemaBuilder column schema builder instance.
327 12
     *
328
     * @psalm-param array<array-key, string>|int|null|string $length
329
     */
330
    public function createColumnSchemaBuilder(string $type, array|int|string $length = null): ColumnSchemaBuilder
331
    {
332
        return new ColumnSchemaBuilder($type, $length);
333
    }
334
335
    /**
336
     * Collects the table column metadata.
337
     *
338
     * @param TableSchemaInterface $table the table metadata.
339
     *
340
     * @throws Exception|InvalidConfigException|Throwable
341
     *
342 3
     * @return bool whether the table exists in the database.
343
     */
344 3
    protected function findColumns(TableSchemaInterface $table): bool
345
    {
346
        /** @psalm-var PragmaTableInfo */
347
        $columns = $this->getPragmaTableInfo($table->getName());
348
349
        foreach ($columns as $info) {
350
            $column = $this->loadColumnSchema($info);
351
            $table->columns($column->getName(), $column);
352
353
            if ($column->isPrimaryKey()) {
354
                $table->primaryKey($column->getName());
355
            }
356 86
        }
357
358
        $column = count($table->getPrimaryKey()) === 1 ? $table->getColumn($table->getPrimaryKey()[0]) : null;
359 86
360
        if ($column !== null && !strncasecmp($column->getDbType(), 'int', 3)) {
361 86
            $table->sequenceName('');
362 82
            $column->autoIncrement(true);
363 82
        }
364
365 82
        return !empty($columns);
366 57
    }
367
368
    /**
369
     * Collects the foreign key column details for the given table.
370 86
     *
371
     * @param TableSchemaInterface $table the table metadata.
372 86
     *
373 57
     * @throws Exception|InvalidConfigException|Throwable
374 57
     */
375
    protected function findConstraints(TableSchemaInterface $table): void
376
    {
377 86
        /** @psalm-var PragmaForeignKeyList */
378
        $foreignKeysList = $this->getPragmaForeignKeyList($table->getName());
379
380
        foreach ($foreignKeysList as $foreignKey) {
381
            $id = (int) $foreignKey['id'];
382
            $fk = $table->getForeignKeys();
383
384
            if (!isset($fk[$id])) {
385
                $table->foreignKey($id, ([$foreignKey['table'], $foreignKey['from'] => $foreignKey['to']]));
386
            } else {
387 82
                /** composite FK */
388
                $table->compositeFK($id, $foreignKey['from'], $foreignKey['to']);
389
            }
390 82
        }
391
    }
392 82
393 5
    /**
394 5
     * Returns all unique indexes for the given table.
395
     *
396 5
     * Each array element is of the following structure:
397 5
     *
398
     * ```php
399
     * [
400 5
     *     'IndexName1' => ['col1' [, ...]],
401
     *     'IndexName2' => ['col2' [, ...]],
402
     * ]
403
     * ```
404
     *
405
     * @param TableSchemaInterface $table the table metadata.
406
     *
407
     * @throws Exception|InvalidConfigException|Throwable
408
     *
409
     * @return array all unique indexes for the given table.
410
     */
411
    public function findUniqueIndexes(TableSchemaInterface $table): array
412
    {
413
        /** @psalm-var PragmaIndexList */
414
        $indexList = $this->getPragmaIndexList($table->getName());
415
        $uniqueIndexes = [];
416
417
        foreach ($indexList as $index) {
418
            $indexName = $index['name'];
419
            /** @psalm-var PragmaIndexInfo */
420
            $indexInfo = $this->getPragmaIndexInfo($index['name']);
421
422
            if ($index['unique']) {
423 1
                $uniqueIndexes[$indexName] = [];
424
                foreach ($indexInfo as $row) {
425
                    $uniqueIndexes[$indexName][] = $row['name'];
426 1
                }
427 1
            }
428
        }
429 1
430 1
        return $uniqueIndexes;
431
    }
432 1
433
    /**
434 1
     * Loads the column information into a {@see ColumnSchemaInterface} object.
435 1
     *
436 1
     * @param array $info column information.
437 1
     *
438
     * @return ColumnSchemaInterface the column schema object.
439
     *
440
     * @psalm-param array{cid:string, name:string, type:string, notnull:string, dflt_value:string|null, pk:string} $info
441
     */
442 1
    protected function loadColumnSchema(array $info): ColumnSchemaInterface
443
    {
444
        $column = $this->createColumnSchema();
445
        $column->name($info['name']);
446
        $column->allowNull(!$info['notnull']);
447
        $column->primaryKey($info['pk'] != '0');
448
        $column->dbType(strtolower($info['type']));
449
        $column->unsigned(str_contains($column->getDbType(), 'unsigned'));
450
        $column->type(self::TYPE_STRING);
451
452
        if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $column->getDbType(), $matches)) {
453
            $type = strtolower($matches[1]);
454 82
455
            if (isset($this->typeMap[$type])) {
456 82
                $column->type($this->typeMap[$type]);
457 82
            }
458 82
459 82
            if (!empty($matches[2])) {
460 82
                $values = explode(',', $matches[2]);
461 82
                $column->precision((int) $values[0]);
462 82
                $column->size((int) $values[0]);
463
464 82
                if (isset($values[1])) {
465 82
                    $column->scale((int) $values[1]);
466
                }
467 82
468 82
                if ($column->getSize() === 1 && ($type === 'tinyint' || $type === 'bit')) {
469
                    $column->type(self::TYPE_BOOLEAN);
470
                } elseif ($type === 'bit') {
471 82
                    if ($column->getSize() > 32) {
472 77
                        $column->type(self::TYPE_BIGINT);
473 77
                    } elseif ($column->getSize() === 32) {
474 77
                        $column->type(self::TYPE_INTEGER);
475
                    }
476 77
                }
477 27
            }
478
        }
479
480 77
        $column->phpType($this->getColumnPhpType($column));
481 22
482 77
        if (!$column->isPrimaryKey()) {
483
            if ($info['dflt_value'] === 'null' || $info['dflt_value'] === '' || $info['dflt_value'] === null) {
484
                $column->defaultValue(null);
485
            } elseif ($column->getType() === 'timestamp' && $info['dflt_value'] === 'CURRENT_TIMESTAMP') {
486
                $column->defaultValue(new Expression('CURRENT_TIMESTAMP'));
487
            } else {
488
                $value = trim($info['dflt_value'], "'\"");
489
                $column->defaultValue($column->phpTypecast($value));
490
            }
491
        }
492 82
493
        return $column;
494 82
    }
495 80
496 78
    /**
497 64
     * Returns table columns info.
498 22
     *
499
     * @param string $tableName table name.
500 64
     *
501 64
     * @throws Exception|InvalidConfigException|Throwable
502
     *
503
     * @return array
504
     */
505 82
    private function loadTableColumnsInfo(string $tableName): array
506
    {
507
        $tableColumns = $this->getPragmaTableInfo($tableName);
508
        /** @psalm-var PragmaTableInfo */
509
        $tableColumns = $this->normalizeRowKeyCase($tableColumns, true);
510
511
        return ArrayHelper::index($tableColumns, 'cid');
512
    }
513
514
    /**
515
     * Loads multiple types of constraints and returns the specified ones.
516
     *
517 31
     * @param TableNameInterface $tableName table name.
518
     * @param string $returnType return type: (primaryKey, indexes, uniques).
519 31
     *
520
     * @throws Exception|InvalidConfigException|Throwable
521 31
     *
522
     * @return array|Constraint|null
523 31
     *
524
     * @psalm-return (Constraint|IndexConstraint)[]|Constraint|null
525
     */
526
    private function loadTableConstraints(TableNameInterface $tableName, string $returnType): Constraint|array|null
527
    {
528
        $indexList = $this->getPragmaIndexList((string) $tableName);
529
        /** @psalm-var PragmaIndexList $indexes */
530
        $indexes = $this->normalizeRowKeyCase($indexList, true);
531
        $result = [
532
            self::PRIMARY_KEY => null,
533
            self::INDEXES => [],
534
            self::UNIQUES => [],
535
        ];
536
537
        foreach ($indexes as $index) {
538 55
            /** @psalm-var Column $columns */
539
            $columns = $this->getPragmaIndexInfo($index['name']);
540 55
541
            if ($index['origin'] === 'pk') {
542 55
                $result[self::PRIMARY_KEY] = (new Constraint())
543 55
                    ->columnNames(ArrayHelper::getColumn($columns, 'name'));
544
            }
545 55
546 55
            if ($index['origin'] === 'u') {
547
                $result[self::UNIQUES][] = (new Constraint())
548
                    ->name($index['name'])
549 55
                    ->columnNames(ArrayHelper::getColumn($columns, 'name'));
550
            }
551 45
552
            $result[self::INDEXES][] = (new IndexConstraint())
553 45
                ->primary($index['origin'] === 'pk')
554 27
                ->unique((bool) $index['unique'])
555 27
                ->name($index['name'])
556
                ->columnNames(ArrayHelper::getColumn($columns, 'name'));
557
        }
558 45
559 44
        if (!isset($result[self::PRIMARY_KEY])) {
560 44
            /**
561 44
             * Additional check for PK in case of INTEGER PRIMARY KEY with ROWID.
562
             *
563
             * {@See https://www.sqlite.org/lang_createtable.html#primkeyconst}
564 45
             *
565 45
             * @psalm-var PragmaTableInfo
566 45
             */
567 45
            $tableColumns = $this->loadTableColumnsInfo((string) $tableName);
568 45
569
            foreach ($tableColumns as $tableColumn) {
570
                if ($tableColumn['pk'] > 0) {
571 55
                    $result[self::PRIMARY_KEY] = (new Constraint())->columnNames([$tableColumn['name']]);
572
                    break;
573
                }
574
            }
575
        }
576
577
        foreach ($result as $type => $data) {
578
            $this->setTableMetadata($tableName, $type, $data);
579 31
        }
580
581 31
        return $result[$returnType];
582 31
    }
583 21
584 21
    /**
585
     * Creates a column schema for the database.
586
     *
587
     * This method may be overridden by child classes to create a DBMS-specific column schema.
588
     *
589 55
     * @return ColumnSchemaInterface column schema instance.
590 55
     */
591
    private function createColumnSchema(): ColumnSchemaInterface
592
    {
593 55
        return new ColumnSchema();
594
    }
595
596
    /**
597
     * @throws Exception|InvalidConfigException|Throwable
598
     */
599
    private function getPragmaForeignKeyList(string $tableName): array
600
    {
601
        return $this->db->createCommand(
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Yiisoft\Db\Sqlite\Schema. Did you maybe forget to declare it?
Loading history...
602
            'PRAGMA FOREIGN_KEY_LIST(' . $this->db->getQuoter()->quoteSimpleTableName(($tableName)) . ')'
603 82
        )->queryAll();
604
    }
605 82
606
    /**
607
     * @throws Exception|InvalidConfigException|Throwable
608
     */
609
    private function getPragmaIndexInfo(string $name): array
610
    {
611 87
        $column = $this->db
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Yiisoft\Db\Sqlite\Schema. Did you maybe forget to declare it?
Loading history...
612
            ->createCommand('PRAGMA INDEX_INFO(' . (string) $this->db->getQuoter()->quoteValue($name) . ')')
613 87
            ->queryAll();
614 87
        /** @psalm-var Column */
615 87
        $column = $this->normalizeRowKeyCase($column, true);
616
        ArraySorter::multisort($column, 'seqno', SORT_ASC, SORT_NUMERIC);
617
618
        return $column;
619
    }
620
621 46
    /**
622
     * @throws Exception|InvalidConfigException|Throwable
623 46
     */
624 46
    private function getPragmaIndexList(string $tableName): array
625 46
    {
626
        return $this->db
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Yiisoft\Db\Sqlite\Schema. Did you maybe forget to declare it?
Loading history...
627 46
            ->createCommand('PRAGMA INDEX_LIST(' . (string) $this->db->getQuoter()->quoteValue($tableName) . ')')
628 46
            ->queryAll();
629
    }
630 46
631
    /**
632
     * @throws Exception|InvalidConfigException|Throwable
633
     */
634
    private function getPragmaTableInfo(string $tableName): array
635
    {
636 56
        return $this->db->createCommand(
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Yiisoft\Db\Sqlite\Schema. Did you maybe forget to declare it?
Loading history...
637
            'PRAGMA TABLE_INFO(' . $this->db->getQuoter()->quoteSimpleTableName($tableName) . ')'
638 56
        )->queryAll();
639 56
    }
640 56
641
    /**
642
     * Returns the actual name of a given table name.
643
     *
644
     * This method will strip off curly brackets from the given table name and replace the percentage character '%' with
645
     * {@see ConnectionInterface::tablePrefix}.
646 98
     *
647
     * @param string|TableNameInterface $name the table name to be converted.
648 98
     *
649 98
     * @return string the real name of the given table name.
650 98
     */
651
    public function getRawTableName(string|TableNameInterface $name): string
652
    {
653
        if ($name instanceof TableNameInterface) {
654
            return (string)$name;
655
        }
656
657
        if (!str_contains($name, '{{')) {
658
            return $name;
659
        }
660
661
        $name = preg_replace('/{{(.*?)}}/', '\1', $name);
662
        return str_replace('%', $this->db->getTablePrefix(), $name);
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Yiisoft\Db\Sqlite\Schema. Did you maybe forget to declare it?
Loading history...
663 151
    }
664
665 151
    /**
666 23
     * Returns the cache key for the specified table name.
667
     *
668 23
     * @param string $name the table name.
669
     *
670
     * @return array the cache key.
671 151
     */
672
    protected function getCacheKey(string $name): array
673
    {
674
        return array_merge([__CLASS__], $this->db->getCacheKey(), [$name]);
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Yiisoft\Db\Sqlite\Schema. Did you maybe forget to declare it?
Loading history...
675
    }
676
677
    /**
678
     * Returns the cache tag name.
679
     *
680
     * This allows {@see refresh()} to invalidate all cached table schemas.
681 151
     *
682
     * @return string the cache tag name.
683 151
     */
684
    protected function getCacheTag(): string
685
    {
686
        return md5(serialize(array_merge([__CLASS__], $this->db->getCacheKey())));
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Yiisoft\Db\Sqlite\Schema. Did you maybe forget to declare it?
Loading history...
687
    }
688
689
    /**
690
     * Changes row's array key case to lower.
691
     *
692
     * @param array $row row's array or an array of row's arrays.
693 151
     * @param bool $multiple whether multiple rows or a single row passed.
694
     *
695 151
     * @return array normalized row or rows.
696
     */
697
    protected function normalizeRowKeyCase(array $row, bool $multiple): array
698
    {
699
        if ($multiple) {
700
            return array_map(static function (array $row) {
701
                return array_change_key_case($row, CASE_LOWER);
702
            }, $row);
703
        }
704
705
        return array_change_key_case($row, CASE_LOWER);
706 61
    }
707
708 61
    /**
709 61
     * Resolves the table name and schema name (if any).
710 61
     *
711
     * @param TableNameInterface $name the table name.
712
     *
713
     * {@see TableSchemaInterface}
714
     *
715
     * @return TableSchemaInterface
716
     */
717
    protected function resolveTableName(TableNameInterface $name): TableSchemaInterface
718
    {
719
        $resolvedName = new TableSchema();
720 5
721
        $resolvedName->name($name->getTableName());
722 5
        $resolvedName->fullName((string) $name);
723
724
        return $resolvedName;
725
    }
726
727
    /**
728
     * @return bool whether this DBMS supports [savepoint](http://en.wikipedia.org/wiki/Savepoint).
729
     */
730
    public function supportsSavepoint(): bool
731
    {
732
        return $this->db->isSavepointEnabled();
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Yiisoft\Db\Sqlite\Schema. Did you maybe forget to declare it?
Loading history...
733
    }
734
735
    /**
736
     * @inheritDoc
737
     */
738
    public function getLastInsertID(?string $sequenceName = null): string
739
    {
740
        return $this->db->getLastInsertID($sequenceName);
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Yiisoft\Db\Sqlite\Schema. Did you maybe forget to declare it?
Loading history...
741
    }
742
}
743