Test Failed
Pull Request — master (#120)
by Def
26:53 queued 23:31
created

Schema::getPragmaIndexList()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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