Passed
Pull Request — master (#268)
by
unknown
03:43
created

Schema::loadTablePrimaryKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 2
c 2
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Sqlite;
6
7
use Throwable;
8
use Yiisoft\Db\Constraint\CheckConstraint;
9
use Yiisoft\Db\Constraint\Constraint;
10
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
11
use Yiisoft\Db\Constraint\IndexConstraint;
12
use Yiisoft\Db\Driver\Pdo\AbstractPdoSchema;
13
use Yiisoft\Db\Exception\Exception;
14
use Yiisoft\Db\Exception\InvalidArgumentException;
15
use Yiisoft\Db\Exception\InvalidConfigException;
16
use Yiisoft\Db\Exception\NotSupportedException;
17
use Yiisoft\Db\Expression\Expression;
18
use Yiisoft\Db\Helper\DbArrayHelper;
19
use Yiisoft\Db\Schema\Builder\ColumnInterface;
20
use Yiisoft\Db\Schema\ColumnSchemaInterface;
21
use Yiisoft\Db\Schema\TableSchemaInterface;
22
23
use function array_column;
24
use function array_merge;
25
use function count;
26
use function explode;
27
use function md5;
28
use function preg_match;
29
use function preg_replace;
30
use function serialize;
31
use function strncasecmp;
32
use function strtolower;
33
34
/**
35
 * Implements the SQLite Server specific schema, supporting SQLite 3.3.0 or higher.
36
 *
37
 * @psalm-type Column = array<array-key, array{seqno:string, cid:string, name:string}>
38
 * @psalm-type NormalizePragmaForeignKeyList = array<
39
 *   string,
40
 *   array<
41
 *     array-key,
42
 *     array{
43
 *       id:string,
44
 *       cid:string,
45
 *       seq:string,
46
 *       table:string,
47
 *       from:string,
48
 *       to:string|null,
49
 *       on_update:string,
50
 *       on_delete:string
51
 *     }
52
 *   >
53
 * >
54
 * @psalm-type PragmaForeignKeyList = array<
55
 *   string,
56
 *   array{
57
 *     id:string,
58
 *     cid:string,
59
 *     seq:string,
60
 *     table:string,
61
 *     from:string,
62
 *     to:string|null,
63
 *     on_update:string,
64
 *     on_delete:string
65
 *   }
66
 * >
67
 * @psalm-type PragmaIndexInfo = array<array-key, array{seqno:string, cid:string, name:string}>
68
 * @psalm-type PragmaIndexList = array<
69
 *   array-key,
70
 *   array{seq:string, name:string, unique:string, origin:string, partial:string}
71
 * >
72
 * @psalm-type PragmaTableInfo = array<
73
 *   array-key,
74
 *   array{cid:string, name:string, type:string, notnull:string, dflt_value:string|null, pk:string}
75
 * >
76
 */
77
final class Schema extends AbstractPdoSchema
78
{
79
    /**
80
     * @var array Mapping from physical column types (keys) to abstract column types (values).
81
     *
82
     * @psalm-var array<array-key, string> $typeMap
83
     */
84
    private array $typeMap = [
85
        'tinyint' => self::TYPE_TINYINT,
86
        'bit' => self::TYPE_SMALLINT,
87
        'boolean' => self::TYPE_BOOLEAN,
88
        'bool' => self::TYPE_BOOLEAN,
89
        'smallint' => self::TYPE_SMALLINT,
90
        'mediumint' => self::TYPE_INTEGER,
91
        'int' => self::TYPE_INTEGER,
92
        'integer' => self::TYPE_INTEGER,
93
        'bigint' => self::TYPE_BIGINT,
94
        'float' => self::TYPE_FLOAT,
95
        'double' => self::TYPE_DOUBLE,
96
        'real' => self::TYPE_FLOAT,
97
        'decimal' => self::TYPE_DECIMAL,
98
        'numeric' => self::TYPE_DECIMAL,
99
        'tinytext' => self::TYPE_TEXT,
100
        'mediumtext' => self::TYPE_TEXT,
101
        'longtext' => self::TYPE_TEXT,
102
        'text' => self::TYPE_TEXT,
103
        'varchar' => self::TYPE_STRING,
104
        'string' => self::TYPE_STRING,
105
        'char' => self::TYPE_CHAR,
106
        'blob' => self::TYPE_BINARY,
107
        'datetime' => self::TYPE_DATETIME,
108
        'year' => self::TYPE_DATE,
109
        'date' => self::TYPE_DATE,
110
        'time' => self::TYPE_TIME,
111
        'timestamp' => self::TYPE_TIMESTAMP,
112
        'enum' => self::TYPE_STRING,
113
    ];
114
115 15
    public function createColumn(string $type, array|int|string $length = null): ColumnInterface
116
    {
117 15
        return new Column($type, $length);
118
    }
119
120
    /**
121
     * Returns all table names in the database.
122
     *
123
     * This method should be overridden by child classes to support this feature because the default implementation
124
     * simply throws an exception.
125
     *
126
     * @param string $schema The schema of the tables.
127
     * Defaults to empty string, meaning the current or default schema.
128
     *
129
     * @throws Exception
130
     * @throws InvalidConfigException
131
     * @throws Throwable
132
     *
133
     * @return array All tables name in the database. The names have NO schema name prefix.
134
     */
135 10
    protected function findTableNames(string $schema = ''): array
136
    {
137 10
        return $this->db
138 10
           ->createCommand(
139 10
               "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence' ORDER BY tbl_name"
140 10
           )
141 10
           ->queryColumn();
142
    }
143
144
    /**
145
     * Loads the metadata for the specified table.
146
     *
147
     * @param string $name The table name.
148
     *
149
     * @throws Exception
150
     * @throws InvalidArgumentException
151
     * @throws InvalidConfigException
152
     * @throws Throwable
153
     *
154
     * @return TableSchemaInterface|null DBMS-dependent table metadata, `null` if the table doesn't exist.
155
     */
156 157
    protected function loadTableSchema(string $name): TableSchemaInterface|null
157
    {
158 157
        $table = new TableSchema();
159
160 157
        $table->name($name);
161 157
        $table->fullName($name);
162
163 157
        if ($this->findColumns($table)) {
164 114
            $this->findConstraints($table);
165
166 114
            return $table;
167
        }
168
169 65
        return null;
170
    }
171
172
    /**
173
     * Loads a primary key for the given table.
174
     *
175
     * @param string $tableName The table name.
176
     *
177
     * @throws Exception
178
     * @throws InvalidArgumentException
179
     * @throws InvalidConfigException
180
     * @throws Throwable
181
     *
182
     * @return Constraint|null Primary key for the given table, `null` if the table has no primary key.
183
     */
184 54
    protected function loadTablePrimaryKey(string $tableName): Constraint|null
185
    {
186 54
        $tablePrimaryKey = $this->loadTableConstraints($tableName, self::PRIMARY_KEY);
187
188 54
        return $tablePrimaryKey instanceof Constraint ? $tablePrimaryKey : null;
189
    }
190
191
    /**
192
     * Loads all foreign keys for the given table.
193
     *
194
     * @param string $tableName The table name.
195
     *
196
     * @throws Exception
197
     * @throws InvalidConfigException
198
     * @throws Throwable
199
     *
200
     * @return ForeignKeyConstraint[] Foreign keys for the given table.
201
     */
202 123
    protected function loadTableForeignKeys(string $tableName): array
203
    {
204 123
        $result = [];
205
        /** @psalm-var PragmaForeignKeyList $foreignKeysList */
206 123
        $foreignKeysList = $this->getPragmaForeignKeyList($tableName);
207
        /** @psalm-var NormalizePragmaForeignKeyList $foreignKeysList */
208 123
        $foreignKeysList = $this->normalizeRowKeyCase($foreignKeysList, true);
209
        /** @psalm-var NormalizePragmaForeignKeyList $foreignKeysList */
210 123
        $foreignKeysList = DbArrayHelper::index($foreignKeysList, null, ['table']);
211 123
        DbArrayHelper::multisort($foreignKeysList, 'seq');
212
213
        /** @psalm-var NormalizePragmaForeignKeyList $foreignKeysList */
214 123
        foreach ($foreignKeysList as $table => $foreignKeys) {
215 11
            $foreignKeysById = DbArrayHelper::index($foreignKeys, null, ['id']);
216
217
            /** @psalm-var NormalizePragmaForeignKeyList $foreignKeysById */
218 11
            foreach ($foreignKeysById as $id => $foreignKey) {
219 11
                if ($foreignKey[0]['to'] === null) {
220 5
                    $primaryKey = $this->getTablePrimaryKey($table);
221
222 5
                    if ($primaryKey !== null) {
223
                        /** @psalm-var string $primaryKeyColumnName */
224 5
                        foreach ((array) $primaryKey->getColumnNames() as $i => $primaryKeyColumnName) {
225 5
                            $foreignKey[$i]['to'] = $primaryKeyColumnName;
226
                        }
227
                    }
228
                }
229
230 11
                $fk = (new ForeignKeyConstraint())
231 11
                    ->name((string) $id)
232 11
                    ->columnNames(array_column($foreignKey, 'from'))
233 11
                    ->foreignTableName($table)
234 11
                    ->foreignColumnNames(array_column($foreignKey, 'to'))
235 11
                    ->onDelete($foreignKey[0]['on_delete'])
236 11
                    ->onUpdate($foreignKey[0]['on_update']);
237
238 11
                $result[] = $fk;
239
            }
240
        }
241
242 123
        return $result;
243
    }
244
245
    /**
246
     * Loads all indexes for the given table.
247
     *
248
     * @param string $tableName The table name.
249
     *
250
     * @throws Exception
251
     * @throws InvalidArgumentException
252
     * @throws InvalidConfigException
253
     * @throws Throwable
254
     *
255
     * @return array Indexes for the given table.
256
     *
257
     * @psalm-return array|IndexConstraint[]
258
     */
259 14
    protected function loadTableIndexes(string $tableName): array
260
    {
261 14
        $tableIndexes = $this->loadTableConstraints($tableName, self::INDEXES);
262
263 14
        return is_array($tableIndexes) ? $tableIndexes : [];
264
    }
265
266
    /**
267
     * Loads all unique constraints for the given table.
268
     *
269
     * @param string $tableName The table name.
270
     *
271
     * @throws Exception
272
     * @throws InvalidArgumentException
273
     * @throws InvalidConfigException
274
     * @throws Throwable
275
     *
276
     * @return array Unique constraints for the given table.
277
     *
278
     * @psalm-return array|Constraint[]
279
     */
280 15
    protected function loadTableUniques(string $tableName): array
281
    {
282 15
        $tableUniques = $this->loadTableConstraints($tableName, self::UNIQUES);
283
284 15
        return is_array($tableUniques) ? $tableUniques : [];
285
    }
286
287
    /**
288
     * Loads all check constraints for the given table.
289
     *
290
     * @param string $tableName The table name.
291
     *
292
     * @throws Exception
293
     * @throws InvalidArgumentException
294
     * @throws InvalidConfigException
295
     * @throws Throwable
296
     *
297
     * @return CheckConstraint[] Check constraints for the given table.
298
     */
299 15
    protected function loadTableChecks(string $tableName): array
300
    {
301 15
        $sql = $this->db->createCommand(
302 15
            'SELECT `sql` FROM `sqlite_master` WHERE name = :tableName',
303 15
            [':tableName' => $tableName],
304 15
        )->queryScalar();
305
306 15
        $sql = ($sql === false || $sql === null) ? '' : (string) $sql;
307
308
        /** @psalm-var SqlToken[]|SqlToken[][]|SqlToken[][][] $code */
309 15
        $code = (new SqlTokenizer($sql))->tokenize();
310 15
        $pattern = (new SqlTokenizer('any CREATE any TABLE any()'))->tokenize();
311 15
        $result = [];
312
313 15
        if ($code[0] instanceof SqlToken && $code[0]->matches($pattern, 0, $firstMatchIndex, $lastMatchIndex)) {
0 ignored issues
show
Bug introduced by
The method matches() 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

313
        if ($code[0] instanceof SqlToken && $code[0]->/** @scrutinizer ignore-call */ matches($pattern, 0, $firstMatchIndex, $lastMatchIndex)) {

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...
314 15
            $offset = 0;
315 15
            $createTableToken = $code[0][(int) $lastMatchIndex - 1];
316 15
            $sqlTokenizerAnyCheck = new SqlTokenizer('any CHECK()');
317
318
            while (
319 15
                $createTableToken instanceof SqlToken &&
320 15
                $createTableToken->matches($sqlTokenizerAnyCheck->tokenize(), (int) $offset, $firstMatchIndex, $offset)
321
            ) {
322 4
                $name = null;
323 4
                $checkSql = (string) $createTableToken[(int) $offset - 1];
324 4
                $pattern = (new SqlTokenizer('CONSTRAINT any'))->tokenize();
325
326
                if (
327 4
                    isset($createTableToken[(int) $firstMatchIndex - 2])
328 4
                    && $createTableToken->matches($pattern, (int) $firstMatchIndex - 2)
329
                ) {
330 1
                    $sqlToken = $createTableToken[(int) $firstMatchIndex - 1];
331 1
                    $name = $sqlToken?->getContent();
332
                }
333
334 4
                $result[] = (new CheckConstraint())->name($name)->expression($checkSql);
335
            }
336
        }
337
338 15
        return $result;
339
    }
340
341
    /**
342
     * Loads all default value constraints for the given table.
343
     *
344
     * @param string $tableName The table name.
345
     *
346
     * @throws NotSupportedException
347
     *
348
     * @return array Default value constraints for the given table.
349
     */
350 13
    protected function loadTableDefaultValues(string $tableName): array
351
    {
352 13
        throw new NotSupportedException('SQLite does not support default value constraints.');
353
    }
354
355
    /**
356
     * Collects the table column metadata.
357
     *
358
     * @param TableSchemaInterface $table The table metadata.
359
     *
360
     * @throws Exception
361
     * @throws InvalidConfigException
362
     * @throws Throwable
363
     *
364
     * @return bool Whether the table exists in the database.
365
     */
366 157
    protected function findColumns(TableSchemaInterface $table): bool
367
    {
368
        /** @psalm-var PragmaTableInfo $columns */
369 157
        $columns = $this->getPragmaTableInfo($table->getName());
370
371 157
        foreach ($columns as $info) {
372 114
            $column = $this->loadColumnSchema($info);
373 114
            $table->column($column->getName(), $column);
374
375 114
            if ($column->isPrimaryKey()) {
376 75
                $table->primaryKey($column->getName());
377
            }
378
        }
379
380 157
        $column = count($table->getPrimaryKey()) === 1 ? $table->getColumn($table->getPrimaryKey()[0]) : null;
381
382 157
        if ($column !== null && !strncasecmp($column->getDbType() ?? '', 'int', 3)) {
383 70
            $table->sequenceName('');
384 70
            $column->autoIncrement(true);
385
        }
386
387 157
        return !empty($columns);
388
    }
389
390
    /**
391
     * Collects the foreign key column details for the given table.
392
     *
393
     * @param TableSchemaInterface $table The table metadata.
394
     *
395
     * @throws Exception
396
     * @throws InvalidConfigException
397
     * @throws Throwable
398
     */
399 114
    protected function findConstraints(TableSchemaInterface $table): void
400
    {
401
        /** @psalm-var ForeignKeyConstraint[] $foreignKeysList */
402 114
        $foreignKeysList = $this->getTableForeignKeys($table->getName(), true);
403
404 114
        foreach ($foreignKeysList as $foreignKey) {
405
            /** @var array<string> $columnNames */
406 6
            $columnNames = (array) $foreignKey->getColumnNames();
407 6
            $columnNames = array_combine($columnNames, $foreignKey->getForeignColumnNames());
408
409 6
            $foreignReference = array_merge([$foreignKey->getForeignTableName()], $columnNames);
410
411
            /** @psalm-suppress InvalidCast */
412 6
            $table->foreignKey((int) $foreignKey->getName(), $foreignReference);
413
        }
414
    }
415
416
    /**
417
     * Returns all unique indexes for the given table.
418
     *
419
     * Each array element is of the following structure:
420
     *
421
     * ```php
422
     * [
423
     *     'IndexName1' => ['col1' [, ...]],
424
     *     'IndexName2' => ['col2' [, ...]],
425
     * ]
426
     * ```
427
     *
428
     * @param TableSchemaInterface $table The table metadata.
429
     *
430
     * @throws Exception
431
     * @throws InvalidConfigException
432
     * @throws Throwable
433
     *
434
     * @return array All unique indexes for the given table.
435
     */
436 1
    public function findUniqueIndexes(TableSchemaInterface $table): array
437
    {
438
        /** @psalm-var PragmaIndexList $indexList */
439 1
        $indexList = $this->getPragmaIndexList($table->getName());
440 1
        $uniqueIndexes = [];
441
442 1
        foreach ($indexList as $index) {
443 1
            $indexName = $index['name'];
444
            /** @psalm-var PragmaIndexInfo $indexInfo */
445 1
            $indexInfo = $this->getPragmaIndexInfo($index['name']);
446
447 1
            if ($index['unique']) {
448 1
                $uniqueIndexes[$indexName] = [];
449 1
                foreach ($indexInfo as $row) {
450 1
                    $uniqueIndexes[$indexName][] = $row['name'];
451
                }
452
            }
453
        }
454
455 1
        return $uniqueIndexes;
456
    }
457
458
    /**
459
     * @throws NotSupportedException
460
     */
461 1
    public function getSchemaDefaultValues(string $schema = '', bool $refresh = false): array
462
    {
463 1
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
464
    }
465
466
    /**
467
     * Loads the column information into a {@see ColumnSchemaInterface} object.
468
     *
469
     * @param array $info The column information.
470
     *
471
     * @return ColumnSchemaInterface The column schema object.
472
     *
473
     * @psalm-param array{cid:string, name:string, type:string, notnull:string, dflt_value:string|null, pk:string} $info
474
     */
475 114
    protected function loadColumnSchema(array $info): ColumnSchemaInterface
476
    {
477 114
        $column = $this->createColumnSchema($info['name']);
478 114
        $column->allowNull(!$info['notnull']);
479 114
        $column->primaryKey($info['pk'] != '0');
480 114
        $column->dbType(strtolower($info['type']));
481 114
        $column->unsigned(str_contains($column->getDbType() ?? '', 'unsigned'));
482 114
        $column->type(self::TYPE_STRING);
483
484 114
        if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $column->getDbType() ?? '', $matches)) {
485 114
            $type = strtolower($matches[1]);
486
487 114
            if (isset($this->typeMap[$type])) {
488 114
                $column->type($this->typeMap[$type]);
489
            }
490
491 114
            if (!empty($matches[2])) {
492 99
                $values = explode(',', $matches[2]);
493 99
                $column->precision((int) $values[0]);
494 99
                $column->size((int) $values[0]);
495
496 99
                if (isset($values[1])) {
497 31
                    $column->scale((int) $values[1]);
498
                }
499
500 99
                if (($type === 'tinyint' || $type === 'bit') && $column->getSize() === 1) {
501 25
                    $column->type(self::TYPE_BOOLEAN);
502 99
                } elseif ($type === 'bit') {
503 4
                    if ($column->getSize() > 32) {
504 4
                        $column->type(self::TYPE_BIGINT);
505 4
                    } elseif ($column->getSize() === 32) {
506 4
                        $column->type(self::TYPE_INTEGER);
507
                    }
508
                }
509
            }
510
        }
511
512 114
        $column->phpType($this->getColumnPhpType($column));
513 114
        $column->defaultValue($this->normalizeDefaultValue($info['dflt_value'], $column));
514
515 114
        return $column;
516
    }
517
518
    /**
519
     * Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database.
520
     *
521
     * @param string|null $defaultValue The default value retrieved from the database.
522
     * @param ColumnSchemaInterface $column The column schema object.
523
     *
524
     * @return mixed The normalized default value.
525
     */
526 114
    private function normalizeDefaultValue(string|null $defaultValue, ColumnSchemaInterface $column): mixed
527
    {
528 114
        if ($column->isPrimaryKey() || in_array($defaultValue, [null, '', 'null', 'NULL'], true)) {
529 110
            return null;
530
        }
531
532 76
        if (in_array($defaultValue, ['CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME'], true)) {
533 25
            return new Expression($defaultValue);
0 ignored issues
show
Bug introduced by
It seems like $defaultValue can also be of type null; however, parameter $expression of Yiisoft\Db\Expression\Expression::__construct() does only seem to accept string, 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

533
            return new Expression(/** @scrutinizer ignore-type */ $defaultValue);
Loading history...
534
        }
535
536 76
        $value = preg_replace('/^([\'"])(.*)\1$/s', '$2', $defaultValue);
537
538 76
        return $column->phpTypecast($value);
539
    }
540
541
    /**
542
     * Returns table columns info.
543
     *
544
     * @param string $tableName The table name.
545
     *
546
     * @throws Exception
547
     * @throws InvalidConfigException
548
     * @throws Throwable
549
     *
550
     * @return array The table columns info.
551
     */
552 54
    private function loadTableColumnsInfo(string $tableName): array
553
    {
554 54
        $tableColumns = $this->getPragmaTableInfo($tableName);
555
        /** @psalm-var PragmaTableInfo $tableColumns */
556 54
        $tableColumns = $this->normalizeRowKeyCase($tableColumns, true);
557
558 54
        return DbArrayHelper::index($tableColumns, 'cid');
559
    }
560
561
    /**
562
     * Loads multiple types of constraints and returns the specified ones.
563
     *
564
     * @param string $tableName The table name.
565
     * @param string $returnType Return type: (primaryKey, indexes, uniques).
566
     *
567
     * @throws Exception
568
     * @throws InvalidConfigException
569
     * @throws Throwable
570
     *
571
     * @psalm-return Constraint[]|IndexConstraint[]|Constraint|null
572
     */
573 83
    private function loadTableConstraints(string $tableName, string $returnType): Constraint|array|null
574
    {
575 83
        $indexList = $this->getPragmaIndexList($tableName);
576
        /** @psalm-var PragmaIndexList $indexes */
577 83
        $indexes = $this->normalizeRowKeyCase($indexList, true);
578 83
        $result = [
579 83
            self::PRIMARY_KEY => null,
580 83
            self::INDEXES => [],
581 83
            self::UNIQUES => [],
582 83
        ];
583
584 83
        foreach ($indexes as $index) {
585
            /** @psalm-var Column $columns */
586 64
            $columns = $this->getPragmaIndexInfo($index['name']);
587
588 64
            if ($index['origin'] === 'pk') {
589 29
                $result[self::PRIMARY_KEY] = (new Constraint())
590 29
                    ->columnNames(DbArrayHelper::getColumn($columns, 'name'));
591
            }
592
593 64
            if ($index['origin'] === 'u') {
594 59
                $result[self::UNIQUES][] = (new Constraint())
595 59
                    ->name($index['name'])
596 59
                    ->columnNames(DbArrayHelper::getColumn($columns, 'name'));
597
            }
598
599 64
            $result[self::INDEXES][] = (new IndexConstraint())
600 64
                ->primary($index['origin'] === 'pk')
601 64
                ->unique((bool) $index['unique'])
602 64
                ->name($index['name'])
603 64
                ->columnNames(DbArrayHelper::getColumn($columns, 'name'));
604
        }
605
606 83
        if (!isset($result[self::PRIMARY_KEY])) {
607
            /**
608
             * Extra check for PK in case of `INTEGER PRIMARY KEY` with ROWID.
609
             *
610
             * @link https://www.sqlite.org/lang_createtable.html#primkeyconst
611
             *
612
             * @psalm-var PragmaTableInfo $tableColumns
613
             */
614 54
            $tableColumns = $this->loadTableColumnsInfo($tableName);
615
616 54
            foreach ($tableColumns as $tableColumn) {
617 54
                if ($tableColumn['pk'] > 0) {
618 34
                    $result[self::PRIMARY_KEY] = (new Constraint())->columnNames([$tableColumn['name']]);
619 34
                    break;
620
                }
621
            }
622
        }
623
624 83
        foreach ($result as $type => $data) {
625 83
            $this->setTableMetadata($tableName, $type, $data);
626
        }
627
628 83
        return $result[$returnType];
629
    }
630
631
    /**
632
     * Creates a column schema for the database.
633
     *
634
     * This method may be overridden by child classes to create a DBMS-specific column schema.
635
     *
636
     * @param string $name Name of the column.
637
     */
638 114
    private function createColumnSchema(string $name): ColumnSchemaInterface
639
    {
640 114
        return new ColumnSchema($name);
641
    }
642
643
    /**
644
     * @throws Exception
645
     * @throws InvalidConfigException
646
     * @throws Throwable
647
     */
648 123
    private function getPragmaForeignKeyList(string $tableName): array
649
    {
650 123
        return $this->db->createCommand(
651 123
            'PRAGMA FOREIGN_KEY_LIST(' . $this->db->getQuoter()->quoteSimpleTableName($tableName) . ')'
652 123
        )->queryAll();
653
    }
654
655
    /**
656
     * @throws Exception
657
     * @throws InvalidConfigException
658
     * @throws Throwable
659
     */
660 65
    private function getPragmaIndexInfo(string $name): array
661
    {
662 65
        $column = $this->db
663 65
            ->createCommand('PRAGMA INDEX_INFO(' . (string) $this->db->getQuoter()->quoteValue($name) . ')')
664 65
            ->queryAll();
665
        /** @psalm-var Column $column */
666 65
        $column = $this->normalizeRowKeyCase($column, true);
667 65
        DbArrayHelper::multisort($column, 'seqno');
668
669 65
        return $column;
670
    }
671
672
    /**
673
     * @throws Exception
674
     * @throws InvalidConfigException
675
     * @throws Throwable
676
     */
677 84
    private function getPragmaIndexList(string $tableName): array
678
    {
679 84
        return $this->db
680 84
            ->createCommand('PRAGMA INDEX_LIST(' . (string) $this->db->getQuoter()->quoteValue($tableName) . ')')
681 84
            ->queryAll();
682
    }
683
684
    /**
685
     * @throws Exception
686
     * @throws InvalidConfigException
687
     * @throws Throwable
688
     */
689 166
    private function getPragmaTableInfo(string $tableName): array
690
    {
691 166
        return $this->db->createCommand(
692 166
            'PRAGMA TABLE_INFO(' . $this->db->getQuoter()->quoteSimpleTableName($tableName) . ')'
693 166
        )->queryAll();
694
    }
695
696
    /**
697
     * @throws Exception
698
     * @throws InvalidConfigException
699
     * @throws Throwable
700
     */
701 1
    protected function findViewNames(string $schema = ''): array
702
    {
703
        /** @psalm-var string[][] $views */
704 1
        $views = $this->db->createCommand(
705 1
            <<<SQL
706
            SELECT name as view FROM sqlite_master WHERE type = 'view' AND name NOT LIKE 'sqlite_%'
707 1
            SQL,
708 1
        )->queryAll();
709
710 1
        foreach ($views as $key => $view) {
711 1
            $views[$key] = $view['view'];
712
        }
713
714 1
        return $views;
715
    }
716
717
    /**
718
     * Returns the cache key for the specified table name.
719
     *
720
     * @param string $name the table name.
721
     *
722
     * @return array The cache key.
723
     */
724 224
    protected function getCacheKey(string $name): array
725
    {
726 224
        return array_merge([self::class], $this->generateCacheKey(), [$this->getRawTableName($name)]);
727
    }
728
729
    /**
730
     * Returns the cache tag name.
731
     *
732
     * This allows {@see refresh()} to invalidate all cached table schemas.
733
     *
734
     * @return string The cache tag name.
735
     */
736 208
    protected function getCacheTag(): string
737
    {
738 208
        return md5(serialize(array_merge([self::class], $this->generateCacheKey())));
739
    }
740
}
741