Passed
Pull Request — master (#75)
by Wilmer
06:44
created

Schema::loadTableColumnsInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 5
c 2
b 0
f 0
dl 0
loc 9
ccs 6
cts 6
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\ConstraintFinderInterface;
13
use Yiisoft\Db\Constraint\ConstraintFinderTrait;
14
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
15
use Yiisoft\Db\Constraint\IndexConstraint;
16
use Yiisoft\Db\Exception\Exception;
17
use Yiisoft\Db\Exception\InvalidArgumentException;
18
use Yiisoft\Db\Exception\InvalidConfigException;
19
use Yiisoft\Db\Exception\NotSupportedException;
20
use Yiisoft\Db\Expression\Expression;
21
use Yiisoft\Db\Schema\ColumnSchema;
22
use Yiisoft\Db\Schema\Schema as AbstractSchema;
23
use Yiisoft\Db\Transaction\Transaction;
24
25
use function count;
26
use function explode;
27
use function preg_match;
28
use function strncasecmp;
29
use function strncmp;
30
use function strpos;
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 Transaction::READ_UNCOMMITTED} or {@see Transaction::SERIALIZABLE}.
39
 */
40
final class Schema extends AbstractSchema implements ConstraintFinderInterface
41
{
42
    use ConstraintFinderTrait;
43
44
    /**
45
     * @var array mapping from physical column types (keys) to abstract column types (values)
46
     */
47
    private array $typeMap = [
48
        'tinyint' => self::TYPE_TINYINT,
49
        'bit' => self::TYPE_SMALLINT,
50
        'boolean' => self::TYPE_BOOLEAN,
51
        'bool' => self::TYPE_BOOLEAN,
52
        'smallint' => self::TYPE_SMALLINT,
53
        'mediumint' => self::TYPE_INTEGER,
54
        'int' => self::TYPE_INTEGER,
55
        'integer' => self::TYPE_INTEGER,
56
        'bigint' => self::TYPE_BIGINT,
57
        'float' => self::TYPE_FLOAT,
58
        'double' => self::TYPE_DOUBLE,
59
        'real' => self::TYPE_FLOAT,
60
        'decimal' => self::TYPE_DECIMAL,
61
        'numeric' => self::TYPE_DECIMAL,
62
        'tinytext' => self::TYPE_TEXT,
63
        'mediumtext' => self::TYPE_TEXT,
64
        'longtext' => self::TYPE_TEXT,
65
        'text' => self::TYPE_TEXT,
66
        'varchar' => self::TYPE_STRING,
67
        'string' => self::TYPE_STRING,
68
        'char' => self::TYPE_CHAR,
69
        'blob' => self::TYPE_BINARY,
70
        'datetime' => self::TYPE_DATETIME,
71
        'year' => self::TYPE_DATE,
72
        'date' => self::TYPE_DATE,
73
        'time' => self::TYPE_TIME,
74
        'timestamp' => self::TYPE_TIMESTAMP,
75
        'enum' => self::TYPE_STRING,
76
    ];
77
78
    /**
79
     * @var string|string[] character used to quote schema, table, etc. names. An array of 2 characters can be used in
80
     * case starting and ending characters are different.
81
     */
82
    protected $tableQuoteCharacter = '`';
83
84
    /**
85
     * @var string|string[] character used to quote column names. An array of 2 characters can be used in case starting
86
     * and ending characters are different.
87
     */
88
    protected $columnQuoteCharacter = '`';
89
90
    /**
91
     * Returns all table names in the database.
92
     *
93
     * This method should be overridden by child classes in order to support this feature because the default
94
     * implementation simply throws an exception.
95
     *
96
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
97
     *
98
     * @throws Exception|InvalidConfigException|Throwable
99
     *
100
     * @return array all table names in the database. The names have NO schema name prefix.
101
     */
102 5
    protected function findTableNames(string $schema = ''): array
103
    {
104 5
        $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence' ORDER BY tbl_name";
105
106 5
        return $this->getDb()->createCommand($sql)->queryColumn();
107
    }
108
109
    /**
110
     * Loads the metadata for the specified table.
111
     *
112
     * @param string $name table name.
113
     *
114
     * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable
115
     *
116
     * @return TableSchema|null DBMS-dependent table metadata, `null` if the table does not exist.
117
     */
118 82
    protected function loadTableSchema(string $name): ?TableSchema
119
    {
120 82
        $table = new TableSchema();
121
122 82
        $table->name($name);
123 82
        $table->fullName($name);
124
125 82
        if ($this->findColumns($table)) {
126 76
            $this->findConstraints($table);
127
128 76
            return $table;
129
        }
130
131 14
        return null;
132
    }
133
134
    /**
135
     * Loads a primary key for the given table.
136
     *
137
     * @param string $tableName table name.
138
     *
139
     * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable
140
     *
141
     * @return Constraint|null primary key for the given table, `null` if the table has no primary key.
142
     */
143 30
    protected function loadTablePrimaryKey(string $tableName): ?Constraint
144
    {
145 30
        return $this->loadTableConstraints($tableName, 'primaryKey');
146
    }
147
148
    /**
149
     * Loads all foreign keys for the given table.
150
     *
151
     * @param string $tableName table name.
152
     *
153
     * @throws Exception|InvalidConfigException|Throwable
154
     *
155
     * @return ForeignKeyConstraint[] foreign keys for the given table.
156
     */
157 4
    protected function loadTableForeignKeys(string $tableName): array
158
    {
159 4
        $foreignKeys = $this->getDb()->createCommand(
160 4
            'PRAGMA FOREIGN_KEY_LIST (' . $this->quoteValue($tableName) . ')'
161 4
        )->queryAll();
162
163 4
        $foreignKeys = $this->normalizePdoRowKeyCase($foreignKeys, true);
164
165 4
        $foreignKeys = ArrayHelper::index($foreignKeys, null, 'table');
166
167 4
        ArraySorter::multisort($foreignKeys, 'seq', SORT_ASC, SORT_NUMERIC);
168
169 4
        $result = [];
170
171 4
        foreach ($foreignKeys as $table => $foreignKey) {
172 4
            $fk = (new ForeignKeyConstraint())
173 4
                ->columnNames(ArrayHelper::getColumn($foreignKey, 'from'))
174 4
                ->foreignTableName($table)
175 4
                ->foreignColumnNames(ArrayHelper::getColumn($foreignKey, 'to'))
176 4
                ->onDelete($foreignKey[0]['on_delete'] ?? null)
177 4
                ->onUpdate($foreignKey[0]['on_update'] ?? null);
178
179 4
            $result[] = $fk;
180
        }
181
182 4
        return $result;
183
    }
184
185
    /**
186
     * Loads all indexes for the given table.
187
     *
188
     * @param string $tableName table name.
189
     *
190
     * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable
191
     *
192
     * @return IndexConstraint[] indexes for the given table.
193
     */
194 11
    protected function loadTableIndexes(string $tableName): array
195
    {
196 11
        return $this->loadTableConstraints($tableName, 'indexes');
197
    }
198
199
    /**
200
     * Loads all unique constraints for the given table.
201
     *
202
     * @param string $tableName table name.
203
     *
204
     * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable
205
     *
206
     * @return Constraint[] unique constraints for the given table.
207
     */
208 14
    protected function loadTableUniques(string $tableName): array
209
    {
210 14
        return $this->loadTableConstraints($tableName, 'uniques');
211
    }
212
213
    /**
214
     * Loads all check constraints for the given table.
215
     *
216
     * @param string $tableName table name.
217
     *
218
     * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable
219
     *
220
     * @return CheckConstraint[] check constraints for the given table.
221
     */
222 12
    protected function loadTableChecks(string $tableName): array
223
    {
224 12
        $sql = $this->getDb()->createCommand('SELECT `sql` FROM `sqlite_master` WHERE name = :tableName', [
225 12
            ':tableName' => $tableName,
226 12
        ])->queryScalar();
227
228
        /** @var SqlToken[]|SqlToken[][]|SqlToken[][][] $code */
229 12
        $code = (new SqlTokenizer($sql))->tokenize();
0 ignored issues
show
Bug introduced by
It seems like $sql can also be of type false and null; however, parameter $sql of Yiisoft\Db\Sqlite\SqlTokenizer::__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

229
        $code = (new SqlTokenizer(/** @scrutinizer ignore-type */ $sql))->tokenize();
Loading history...
230
231 12
        $pattern = (new SqlTokenizer('any CREATE any TABLE any()'))->tokenize();
232
233 12
        if (!$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...
234
            return [];
235
        }
236
237 12
        $createTableToken = $code[0][$lastMatchIndex - 1];
238 12
        $result = [];
239 12
        $offset = 0;
240
241 12
        while (true) {
242 12
            $pattern = (new SqlTokenizer('any CHECK()'))->tokenize();
243
244 12
            if (!$createTableToken->matches($pattern, $offset, $firstMatchIndex, $offset)) {
245 12
                break;
246
            }
247
248 3
            $checkSql = $createTableToken[$offset - 1]->getSql();
249 3
            $name = null;
250 3
            $pattern = (new SqlTokenizer('CONSTRAINT any'))->tokenize();
251
252
            if (
253 3
                isset($createTableToken[$firstMatchIndex - 2])
254 3
                && $createTableToken->matches($pattern, $firstMatchIndex - 2)
255
            ) {
256
                $name = $createTableToken[$firstMatchIndex - 1]->getContent();
257
            }
258
259 3
            $ck = (new CheckConstraint())
260 3
                ->name($name)
261 3
                ->expression($checkSql);
262
263 3
            $result[] = $ck;
264
        }
265
266 12
        return $result;
267
    }
268
269
    /**
270
     * Loads all default value constraints for the given table.
271
     *
272
     * @param string $tableName table name.
273
     *
274
     * @throws NotSupportedException
275
     *
276
     * @return array default value constraints for the given table.
277
     */
278 12
    protected function loadTableDefaultValues(string $tableName): array
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed. ( Ignorable by Annotation )

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

278
    protected function loadTableDefaultValues(/** @scrutinizer ignore-unused */ string $tableName): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
279
    {
280 12
        throw new NotSupportedException('SQLite does not support default value constraints.');
281
    }
282
283
    /**
284
     * Creates a query builder for the MySQL database.
285
     *
286
     * This method may be overridden by child classes to create a DBMS-specific query builder.
287
     *
288
     * @return QueryBuilder query builder instance.
289
     */
290 59
    public function createQueryBuilder(): QueryBuilder
291
    {
292 59
        return new QueryBuilder($this->getDb());
293
    }
294
295
    /**
296
     * Create a column schema builder instance giving the type and value precision.
297
     *
298
     * This method may be overridden by child classes to create a DBMS-specific column schema builder.
299
     *
300
     * @param string $type type of the column. See {@see ColumnSchemaBuilder::$type}.
301
     * @param array|int|string|null $length length or precision of the column. See {@see ColumnSchemaBuilder::$length}.
302
     *
303
     * @return ColumnSchemaBuilder column schema builder instance.
304
     */
305 3
    public function createColumnSchemaBuilder(string $type, $length = null): ColumnSchemaBuilder
306
    {
307 3
        return new ColumnSchemaBuilder($type, $length);
308
    }
309
310
    /**
311
     * Collects the table column metadata.
312
     *
313
     * @param TableSchema $table the table metadata.
314
     *
315
     * @throws Exception|InvalidConfigException|Throwable
316
     *
317
     * @return bool whether the table exists in the database.
318
     */
319 82
    protected function findColumns(TableSchema $table): bool
320
    {
321 82
        $sql = 'PRAGMA table_info(' . $this->quoteSimpleTableName($table->getName()) . ')';
0 ignored issues
show
Bug introduced by
It seems like $table->getName() can also be of type null; however, parameter $name of Yiisoft\Db\Schema\Schema::quoteSimpleTableName() 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

321
        $sql = 'PRAGMA table_info(' . $this->quoteSimpleTableName(/** @scrutinizer ignore-type */ $table->getName()) . ')';
Loading history...
322 82
        $columns = $this->getDb()->createCommand($sql)->queryAll();
323
324 82
        if (empty($columns)) {
325 14
            return false;
326
        }
327
328 76
        foreach ($columns as $info) {
329 76
            $column = $this->loadColumnSchema($info);
330 76
            $table->columns($column->getName(), $column);
331 76
            if ($column->isPrimaryKey()) {
332 52
                $table->primaryKey($column->getName());
333
            }
334
        }
335
336 76
        $pk = $table->getPrimaryKey();
337 76
        if (count($pk) === 1 && !strncasecmp($table->getColumn($pk[0])->getDbType(), 'int', 3)) {
338 52
            $table->sequenceName('');
339 52
            $table->getColumn($pk[0])->autoIncrement(true);
340
        }
341
342 76
        return true;
343
    }
344
345
    /**
346
     * Collects the foreign key column details for the given table.
347
     *
348
     * @param TableSchema $table the table metadata.
349
     *
350
     * @throws Exception|InvalidConfigException|Throwable
351
     */
352 76
    protected function findConstraints(TableSchema $table): void
353
    {
354 76
        $sql = 'PRAGMA foreign_key_list(' . $this->quoteSimpleTableName($table->getName()) . ')';
0 ignored issues
show
Bug introduced by
It seems like $table->getName() can also be of type null; however, parameter $name of Yiisoft\Db\Schema\Schema::quoteSimpleTableName() 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

354
        $sql = 'PRAGMA foreign_key_list(' . $this->quoteSimpleTableName(/** @scrutinizer ignore-type */ $table->getName()) . ')';
Loading history...
355 76
        $keys = $this->getDb()->createCommand($sql)->queryAll();
356
357 76
        foreach ($keys as $key) {
358 5
            $id = (int) $key['id'];
359 5
            $fk = $table->getForeignKeys();
360 5
            if (!isset($fk[$id])) {
361 5
                $table->foreignKey($id, ([$key['table'], $key['from'] => $key['to']]));
362
            } else {
363
                /** composite FK */
364 5
                $table->compositeFK($id, $key['from'], $key['to']);
365
            }
366
        }
367 76
    }
368
369
    /**
370
     * Returns all unique indexes for the given table.
371
     *
372
     * Each array element is of the following structure:
373
     *
374
     * ```php
375
     * [
376
     *     'IndexName1' => ['col1' [, ...]],
377
     *     'IndexName2' => ['col2' [, ...]],
378
     * ]
379
     * ```
380
     *
381
     * @param TableSchema $table the table metadata.
382
     *
383
     * @throws Exception|InvalidConfigException|Throwable
384
     *
385
     * @return array all unique indexes for the given table.
386
     */
387
    public function findUniqueIndexes(TableSchema $table): array
388
    {
389
        $sql = 'PRAGMA index_list(' . $this->quoteSimpleTableName($table->getName()) . ')';
0 ignored issues
show
Bug introduced by
It seems like $table->getName() can also be of type null; however, parameter $name of Yiisoft\Db\Schema\Schema::quoteSimpleTableName() 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

389
        $sql = 'PRAGMA index_list(' . $this->quoteSimpleTableName(/** @scrutinizer ignore-type */ $table->getName()) . ')';
Loading history...
390
        $indexes = $this->getDb()->createCommand($sql)->queryAll();
391
        $uniqueIndexes = [];
392
393
        foreach ($indexes as $index) {
394
            $indexName = $index['name'];
395
            $indexInfo = $this->getDb()->createCommand(
396
                'PRAGMA index_info(' . $this->quoteValue($index['name']) . ')'
397
            )->queryAll();
398
399
            if ($index['unique']) {
400
                $uniqueIndexes[$indexName] = [];
401
                foreach ($indexInfo as $row) {
402
                    $uniqueIndexes[$indexName][] = $row['name'];
403
                }
404
            }
405
        }
406
407
        return $uniqueIndexes;
408
    }
409
410
    /**
411
     * Loads the column information into a {@see ColumnSchema} object.
412
     *
413
     * @param array $info column information.
414
     *
415
     * @return ColumnSchema the column schema object.
416
     */
417 76
    protected function loadColumnSchema(array $info): ColumnSchema
418
    {
419 76
        $column = $this->createColumnSchema();
420 76
        $column->name($info['name']);
421 76
        $column->allowNull(!$info['notnull']);
422 76
        $column->primaryKey($info['pk'] != 0);
423 76
        $column->dbType(strtolower($info['type']));
424 76
        $column->unsigned(strpos($column->getDbType(), 'unsigned') !== false);
425 76
        $column->type(self::TYPE_STRING);
426
427 76
        if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $column->getDbType(), $matches)) {
428 76
            $type = strtolower($matches[1]);
429
430 76
            if (isset($this->typeMap[$type])) {
431 76
                $column->type($this->typeMap[$type]);
432
            }
433
434 76
            if (!empty($matches[2])) {
435 71
                $values = explode(',', $matches[2]);
436 71
                $column->precision((int) $values[0]);
437 71
                $column->size((int) $values[0]);
438 71
                if (isset($values[1])) {
439 26
                    $column->scale((int) $values[1]);
440
                }
441 71
                if ($column->getSize() === 1 && ($type === 'tinyint' || $type === 'bit')) {
442 21
                    $column->type('boolean');
443 71
                } elseif ($type === 'bit') {
444
                    if ($column->getSize() > 32) {
445
                        $column->type('bigint');
446
                    } elseif ($column->getSize() === 32) {
447
                        $column->type('integer');
448
                    }
449
                }
450
            }
451
        }
452
453 76
        $column->phpType($this->getColumnPhpType($column));
454
455 76
        if (!$column->isPrimaryKey()) {
456 74
            if ($info['dflt_value'] === 'null' || $info['dflt_value'] === '' || $info['dflt_value'] === null) {
457 72
                $column->defaultValue(null);
458 61
            } elseif ($column->getType() === 'timestamp' && $info['dflt_value'] === 'CURRENT_TIMESTAMP') {
459 21
                $column->defaultValue(new Expression('CURRENT_TIMESTAMP'));
460
            } else {
461 61
                $value = trim($info['dflt_value'], "'\"");
462 61
                $column->defaultValue($column->phpTypecast($value));
463
            }
464
        }
465
466 76
        return $column;
467
    }
468
469
    /**
470
     * Sets the isolation level of the current transaction.
471
     *
472
     * @param string $level The transaction isolation level to use for this transaction. This can be either
473
     * {@see Transaction::READ_UNCOMMITTED} or {@see Transaction::SERIALIZABLE}.
474
     *
475
     * @throws Exception|InvalidConfigException|NotSupportedException|Throwable when unsupported isolation levels are
476
     * used. SQLite only supports SERIALIZABLE and READ UNCOMMITTED.
477
     *
478
     * {@see http://www.sqlite.org/pragma.html#pragma_read_uncommitted}
479
     */
480 2
    public function setTransactionIsolationLevel(string $level): void
481
    {
482
        switch ($level) {
483 2
            case Transaction::SERIALIZABLE:
484 1
                $this->getDb()->createCommand('PRAGMA read_uncommitted = False;')->execute();
485 1
                break;
486 2
            case Transaction::READ_UNCOMMITTED:
487 2
                $this->getDb()->createCommand('PRAGMA read_uncommitted = True;')->execute();
488 2
                break;
489
            default:
490
                throw new NotSupportedException(
491
                    self::class . ' only supports transaction isolation levels READ UNCOMMITTED and SERIALIZABLE.'
492
                );
493
        }
494 2
    }
495
496
    /**
497
     * Returns table columns info.
498
     *
499
     * @param string $tableName table name.
500
     *
501
     * @throws Exception|InvalidConfigException|Throwable
502
     *
503
     * @return array
504
     */
505 30
    private function loadTableColumnsInfo(string $tableName): array
506
    {
507 30
        $tableColumns = $this->getDb()->createCommand(
508 30
            'PRAGMA TABLE_INFO (' . $this->quoteValue($tableName) . ')'
509 30
        )->queryAll();
510
511 30
        $tableColumns = $this->normalizePdoRowKeyCase($tableColumns, true);
512
513 30
        return ArrayHelper::index($tableColumns, 'cid');
514
    }
515
516
    /**
517
     * Loads multiple types of constraints and returns the specified ones.
518
     *
519
     * @param string $tableName table name.
520
     * @param string $returnType return type: (primaryKey, indexes, uniques).
521
     *
522
     * @throws Exception|InvalidConfigException|Throwable
523
     *
524
     * @return mixed constraints.
525
     */
526 54
    private function loadTableConstraints(string $tableName, string $returnType)
527
    {
528 54
        $tableColumns = null;
529 54
        $indexList = $this->getDb()->createCommand(
530 54
            'PRAGMA INDEX_LIST (' . $this->quoteValue($tableName) . ')'
531 54
        )->queryAll();
532 54
        $indexes = $this->normalizePdoRowKeyCase($indexList, true);
533
534 54
        if (!empty($indexes) && !isset($indexes[0]['origin'])) {
535
            /**
536
             * SQLite may not have an "origin" column in INDEX_LIST.
537
             *
538
             * {See https://www.sqlite.org/src/info/2743846cdba572f6}
539
             */
540
            $tableColumns = $this->loadTableColumnsInfo($tableName);
541
        }
542
543 54
        $result = [
544
            'primaryKey' => null,
545
            'indexes' => [],
546
            'uniques' => [],
547
        ];
548
549 54
        foreach ($indexes as $index) {
550 44
            $columns = $this->getPragmaIndexInfo($index['name']);
551
552 44
            if ($tableColumns !== null) {
553
                /** SQLite may not have an "origin" column in INDEX_LIST */
554
                $index['origin'] = 'c';
555
556
                if (!empty($columns) && $tableColumns[$columns[0]['cid']]['pk'] > 0) {
557
                    $index['origin'] = 'pk';
558
                }
559
            }
560
561 44
            $ic = (new IndexConstraint())
562 44
                ->primary($index['origin'] === 'pk')
563 44
                ->unique((bool) $index['unique'])
564 44
                ->name($index['name'])
565 44
                ->columnNames(ArrayHelper::getColumn($columns, 'name'));
566
567 44
            $result['indexes'][] = $ic;
568
569 44
            if ($index['origin'] === 'pk') {
570 24
                $ct = (new Constraint())
571 24
                    ->columnNames(ArrayHelper::getColumn($columns, 'name'));
572
573 24
                $result['primaryKey'] = $ct;
574 44
            } elseif ($index['unique']) {
575 44
                $ct = (new Constraint())
576 44
                    ->name($index['name'])
577 44
                    ->columnNames(ArrayHelper::getColumn($columns, 'name'));
578
579 44
                $result['uniques'][] = $ct;
580
            }
581
        }
582
583 54
        if ($result['primaryKey'] === null) {
584
            /**
585
             * Additional check for PK in case of INTEGER PRIMARY KEY with ROWID.
586
             *
587
             * {@See https://www.sqlite.org/lang_createtable.html#primkeyconst}
588
             */
589
590 30
            if ($tableColumns === null) {
591 30
                $tableColumns = $this->loadTableColumnsInfo($tableName);
592
            }
593
594 30
            foreach ($tableColumns as $tableColumn) {
595 30
                if ($tableColumn['pk'] > 0) {
596 18
                    $ct = (new Constraint())
597 18
                        ->columnNames([$tableColumn['name']]);
598
599 18
                    $result['primaryKey'] = $ct;
600 18
                    break;
601
                }
602
            }
603
        }
604
605 54
        foreach ($result as $type => $data) {
606 54
            $this->setTableMetadata($tableName, $type, $data);
607
        }
608
609 54
        return $result[$returnType];
610
    }
611
612
    /**
613
     * Return whether the specified identifier is a SQLite system identifier.
614
     *
615
     * @param string $identifier
616
     *
617
     * @return bool
618
     *
619
     * {@see https://www.sqlite.org/src/artifact/74108007d286232f}
620
     */
621
    private function isSystemIdentifier(string $identifier): bool
0 ignored issues
show
Unused Code introduced by
The method isSystemIdentifier() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
622
    {
623
        return strncmp($identifier, 'sqlite_', 7) === 0;
624
    }
625
626
    /**
627
     * Creates a column schema for the database.
628
     *
629
     * This method may be overridden by child classes to create a DBMS-specific column schema.
630
     *
631
     * @return ColumnSchema column schema instance.
632
     */
633 76
    private function createColumnSchema(): ColumnSchema
634
    {
635 76
        return new ColumnSchema();
636
    }
637
638
    /**
639
     * @throws Exception|InvalidConfigException|Throwable
640
     */
641 44
    private function getPragmaIndexInfo(string $name): array
642
    {
643 44
        $column = $this->getDb()->createCommand('PRAGMA INDEX_INFO (' . $this->quoteValue($name) . ')')->queryAll();
644 44
        $columns = $this->normalizePdoRowKeyCase($column, true);
645 44
        ArraySorter::multisort($columns, 'seqno', SORT_ASC, SORT_NUMERIC);
646
647 44
        return $columns;
648
    }
649
}
650