GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( ecf3ef...78a151 )
by Robert
11:40
created

Schema::quoteSimpleTableName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
cc 2
eloc 2
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db\sqlite;
9
10
use yii\base\NotSupportedException;
11
use yii\db\CheckConstraint;
12
use yii\db\ColumnSchema;
13
use yii\db\Constraint;
14
use yii\db\ConstraintFinderInterface;
15
use yii\db\ConstraintFinderTrait;
16
use yii\db\Expression;
17
use yii\db\ForeignKeyConstraint;
18
use yii\db\IndexConstraint;
19
use yii\db\SqlToken;
20
use yii\db\TableSchema;
21
use yii\db\Transaction;
22
use yii\helpers\ArrayHelper;
23
24
/**
25
 * Schema is the class for retrieving metadata from a SQLite (2/3) database.
26
 *
27
 * @property string $transactionIsolationLevel The transaction isolation level to use for this transaction.
28
 * This can be either [[Transaction::READ_UNCOMMITTED]] or [[Transaction::SERIALIZABLE]].
29
 *
30
 * @author Qiang Xue <[email protected]>
31
 * @since 2.0
32
 */
33
class Schema extends \yii\db\Schema implements ConstraintFinderInterface
34
{
35
    use ConstraintFinderTrait;
36
37
    /**
38
     * @var array mapping from physical column types (keys) to abstract column types (values)
39
     */
40
    public $typeMap = [
41
        'tinyint' => self::TYPE_SMALLINT,
42
        'bit' => self::TYPE_SMALLINT,
43
        'boolean' => self::TYPE_BOOLEAN,
44
        'bool' => self::TYPE_BOOLEAN,
45
        'smallint' => self::TYPE_SMALLINT,
46
        'mediumint' => self::TYPE_INTEGER,
47
        'int' => self::TYPE_INTEGER,
48
        'integer' => self::TYPE_INTEGER,
49
        'bigint' => self::TYPE_BIGINT,
50
        'float' => self::TYPE_FLOAT,
51
        'double' => self::TYPE_DOUBLE,
52
        'real' => self::TYPE_FLOAT,
53
        'decimal' => self::TYPE_DECIMAL,
54
        'numeric' => self::TYPE_DECIMAL,
55
        'tinytext' => self::TYPE_TEXT,
56
        'mediumtext' => self::TYPE_TEXT,
57
        'longtext' => self::TYPE_TEXT,
58
        'text' => self::TYPE_TEXT,
59
        'varchar' => self::TYPE_STRING,
60
        'string' => self::TYPE_STRING,
61
        'char' => self::TYPE_CHAR,
62
        'blob' => self::TYPE_BINARY,
63
        'datetime' => self::TYPE_DATETIME,
64
        'year' => self::TYPE_DATE,
65
        'date' => self::TYPE_DATE,
66
        'time' => self::TYPE_TIME,
67
        'timestamp' => self::TYPE_TIMESTAMP,
68
        'enum' => self::TYPE_STRING,
69
    ];
70
71
    /**
72
     * @inheritDoc
73
     */
74
    protected $tableQuoteCharacter = '`';
75
    /**
76
     * @inheritDoc
77
     */
78
    protected $columnQuoteCharacter = '`';
79
80
81
    /**
82
     * @inheritDoc
83
     */
84 6
    protected function findTableNames($schema = '')
85
    {
86 6
        $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence' ORDER BY tbl_name";
87 6
        return $this->db->createCommand($sql)->queryColumn();
88
    }
89
90
    /**
91
     * @inheritDoc
92
     */
93 266
    protected function loadTableSchema($name)
94
    {
95 266
        $table = new TableSchema();
96 266
        $table->name = $name;
97 266
        $table->fullName = $name;
98
99 266
        if ($this->findColumns($table)) {
100 244
            $this->findConstraints($table);
101 244
            return $table;
102
        }
103
104 57
        return null;
105
    }
106
107
    /**
108
     * @inheritDoc
109
     */
110 34
    protected function loadTablePrimaryKey($tableName)
111
    {
112 34
        return $this->loadTableConstraints($tableName, 'primaryKey');
113
    }
114
115
    /**
116
     * @inheritDoc
117
     */
118 3
    protected function loadTableForeignKeys($tableName)
119
    {
120 3
        $foreignKeys = $this->db->createCommand('PRAGMA FOREIGN_KEY_LIST (' . $this->quoteValue($tableName) . ')')->queryAll();
121 3
        $foreignKeys = $this->normalizePdoRowKeyCase($foreignKeys, true);
122 3
        $foreignKeys = ArrayHelper::index($foreignKeys, null, 'table');
123 3
        ArrayHelper::multisort($foreignKeys, 'seq', SORT_ASC, SORT_NUMERIC);
124 3
        $result = [];
125 3
        foreach ($foreignKeys as $table => $foreignKey) {
126 3
            $result[] = new ForeignKeyConstraint([
127 3
                'columnNames' => ArrayHelper::getColumn($foreignKey, 'from'),
128 3
                'foreignTableName' => $table,
129 3
                'foreignColumnNames' => ArrayHelper::getColumn($foreignKey, 'to'),
130 3
                'onDelete' => isset($foreignKey[0]['on_delete']) ? $foreignKey[0]['on_delete'] : null,
131 3
                'onUpdate' => isset($foreignKey[0]['on_update']) ? $foreignKey[0]['on_update'] : null,
132
            ]);
133
        }
134
135 3
        return $result;
136
    }
137
138
    /**
139
     * @inheritDoc
140
     */
141 10
    protected function loadTableIndexes($tableName)
142
    {
143 10
        return $this->loadTableConstraints($tableName, 'indexes');
144
    }
145
146
    /**
147
     * @inheritDoc
148
     */
149 12
    protected function loadTableUniques($tableName)
150
    {
151 12
        return $this->loadTableConstraints($tableName, 'uniques');
152
    }
153
154
    /**
155
     * @inheritDoc
156
     */
157 12
    protected function loadTableChecks($tableName)
158
    {
159 12
        $sql = $this->db->createCommand('SELECT `sql` FROM `sqlite_master` WHERE name = :tableName', [
160 12
            ':tableName' => $tableName,
161 12
        ])->queryScalar();
162
        /** @var $code SqlToken[]|SqlToken[][]|SqlToken[][][] */
163 12
        $code = (new SqlTokenizer($sql))->tokenize();
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->db->createCommand...leName))->queryScalar() on line 159 can also be of type false or null; however, yii\db\SqlTokenizer::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
164 12
        $pattern = (new SqlTokenizer('any CREATE any TABLE any()'))->tokenize();
165 12
        if (!$code[0]->matches($pattern, 0, $firstMatchIndex, $lastMatchIndex)) {
0 ignored issues
show
Bug introduced by
The variable $firstMatchIndex does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $lastMatchIndex does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
166
            return [];
167
        }
168
169 12
        $createTableToken = $code[0][$lastMatchIndex - 1];
170 12
        $result = [];
171 12
        $offset = 0;
172 12
        while (true) {
173 12
            $pattern = (new SqlTokenizer('any CHECK()'))->tokenize();
174 12
            if (!$createTableToken->matches($pattern, $offset, $firstMatchIndex, $offset)) {
175 12
                break;
176
            }
177
178 3
            $checkSql = $createTableToken[$offset - 1]->getSql();
179 3
            $name = null;
180 3
            $pattern = (new SqlTokenizer('CONSTRAINT any'))->tokenize();
181 3
            if (isset($createTableToken[$firstMatchIndex - 2]) && $createTableToken->matches($pattern, $firstMatchIndex - 2)) {
182
                $name = $createTableToken[$firstMatchIndex - 1]->content;
183
            }
184 3
            $result[] = new CheckConstraint([
185 3
                'name' => $name,
186 3
                'expression' => $checkSql,
187
            ]);
188
        }
189
190 12
        return $result;
191
    }
192
193
    /**
194
     * @inheritDoc
195
     * @throws NotSupportedException if this method is called.
196
     */
197 12
    protected function loadTableDefaultValues($tableName)
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed.

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

Loading history...
198
    {
199 12
        throw new NotSupportedException('SQLite does not support default value constraints.');
200
    }
201
202
    /**
203
     * Creates a query builder for the MySQL database.
204
     * This method may be overridden by child classes to create a DBMS-specific query builder.
205
     * @return QueryBuilder query builder instance
206
     */
207 248
    public function createQueryBuilder()
208
    {
209 248
        return new QueryBuilder($this->db);
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     * @return ColumnSchemaBuilder column schema builder instance
215
     */
216 10
    public function createColumnSchemaBuilder($type, $length = null)
217
    {
218 10
        return new ColumnSchemaBuilder($type, $length);
219
    }
220
221
    /**
222
     * Collects the table column metadata.
223
     * @param TableSchema $table the table metadata
224
     * @return bool whether the table exists in the database
225
     */
226 266
    protected function findColumns($table)
227
    {
228 266
        $sql = 'PRAGMA table_info(' . $this->quoteSimpleTableName($table->name) . ')';
229 266
        $columns = $this->db->createCommand($sql)->queryAll();
230 266
        if (empty($columns)) {
231 57
            return false;
232
        }
233
234 244
        foreach ($columns as $info) {
235 244
            $column = $this->loadColumnSchema($info);
236 244
            $table->columns[$column->name] = $column;
237 244
            if ($column->isPrimaryKey) {
238 244
                $table->primaryKey[] = $column->name;
239
            }
240
        }
241 244
        if (count($table->primaryKey) === 1 && !strncasecmp($table->columns[$table->primaryKey[0]]->dbType, 'int', 3)) {
242 185
            $table->sequenceName = '';
243 185
            $table->columns[$table->primaryKey[0]]->autoIncrement = true;
244
        }
245
246 244
        return true;
247
    }
248
249
    /**
250
     * Collects the foreign key column details for the given table.
251
     * @param TableSchema $table the table metadata
252
     */
253 244
    protected function findConstraints($table)
254
    {
255 244
        $sql = 'PRAGMA foreign_key_list(' . $this->quoteSimpleTableName($table->name) . ')';
256 244
        $keys = $this->db->createCommand($sql)->queryAll();
257 244
        foreach ($keys as $key) {
258 7
            $id = (int) $key['id'];
259 7
            if (!isset($table->foreignKeys[$id])) {
260 7
                $table->foreignKeys[$id] = [$key['table'], $key['from'] => $key['to']];
261
            } else {
262
                // composite FK
263 7
                $table->foreignKeys[$id][$key['from']] = $key['to'];
264
            }
265
        }
266 244
    }
267
268
    /**
269
     * Returns all unique indexes for the given table.
270
     *
271
     * Each array element is of the following structure:
272
     *
273
     * ```php
274
     * [
275
     *     'IndexName1' => ['col1' [, ...]],
276
     *     'IndexName2' => ['col2' [, ...]],
277
     * ]
278
     * ```
279
     *
280
     * @param TableSchema $table the table metadata
281
     * @return array all unique indexes for the given table.
282
     */
283 1
    public function findUniqueIndexes($table)
284
    {
285 1
        $sql = 'PRAGMA index_list(' . $this->quoteSimpleTableName($table->name) . ')';
286 1
        $indexes = $this->db->createCommand($sql)->queryAll();
287 1
        $uniqueIndexes = [];
288
289 1
        foreach ($indexes as $index) {
290 1
            $indexName = $index['name'];
291 1
            $indexInfo = $this->db->createCommand('PRAGMA index_info(' . $this->quoteValue($index['name']) . ')')->queryAll();
292
293 1
            if ($index['unique']) {
294 1
                $uniqueIndexes[$indexName] = [];
295 1
                foreach ($indexInfo as $row) {
296 1
                    $uniqueIndexes[$indexName][] = $row['name'];
297
                }
298
            }
299
        }
300
301 1
        return $uniqueIndexes;
302
    }
303
304
    /**
305
     * Loads the column information into a [[ColumnSchema]] object.
306
     * @param array $info column information
307
     * @return ColumnSchema the column schema object
308
     */
309 244
    protected function loadColumnSchema($info)
310
    {
311 244
        $column = $this->createColumnSchema();
312 244
        $column->name = $info['name'];
313 244
        $column->allowNull = !$info['notnull'];
314 244
        $column->isPrimaryKey = $info['pk'] != 0;
315
316 244
        $column->dbType = strtolower($info['type']);
317 244
        $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
318
319 244
        $column->type = self::TYPE_STRING;
320 244
        if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
321 244
            $type = strtolower($matches[1]);
322 244
            if (isset($this->typeMap[$type])) {
323 244
                $column->type = $this->typeMap[$type];
324
            }
325
326 244
            if (!empty($matches[2])) {
327 235
                $values = explode(',', $matches[2]);
328 235
                $column->size = $column->precision = (int) $values[0];
329 235
                if (isset($values[1])) {
330 77
                    $column->scale = (int) $values[1];
331
                }
332 235
                if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
333 19
                    $column->type = 'boolean';
334 235
                } elseif ($type === 'bit') {
335
                    if ($column->size > 32) {
336
                        $column->type = 'bigint';
337
                    } elseif ($column->size === 32) {
338
                        $column->type = 'integer';
339
                    }
340
                }
341
            }
342
        }
343 244
        $column->phpType = $this->getColumnPhpType($column);
344
345 244
        if (!$column->isPrimaryKey) {
346 243
            if ($info['dflt_value'] === 'null' || $info['dflt_value'] === '' || $info['dflt_value'] === null) {
347 242
                $column->defaultValue = null;
348 108
            } elseif ($column->type === 'timestamp' && $info['dflt_value'] === 'CURRENT_TIMESTAMP') {
349 19
                $column->defaultValue = new Expression('CURRENT_TIMESTAMP');
350
            } else {
351 108
                $value = trim($info['dflt_value'], "'\"");
352 108
                $column->defaultValue = $column->phpTypecast($value);
353
            }
354
        }
355
356 244
        return $column;
357
    }
358
359
    /**
360
     * Sets the isolation level of the current transaction.
361
     * @param string $level The transaction isolation level to use for this transaction.
362
     * This can be either [[Transaction::READ_UNCOMMITTED]] or [[Transaction::SERIALIZABLE]].
363
     * @throws NotSupportedException when unsupported isolation levels are used.
364
     * SQLite only supports SERIALIZABLE and READ UNCOMMITTED.
365
     * @see http://www.sqlite.org/pragma.html#pragma_read_uncommitted
366
     */
367 2
    public function setTransactionIsolationLevel($level)
368
    {
369
        switch ($level) {
370 2
            case Transaction::SERIALIZABLE:
371 1
                $this->db->createCommand('PRAGMA read_uncommitted = False;')->execute();
372 1
                break;
373 2
            case Transaction::READ_UNCOMMITTED:
374 2
                $this->db->createCommand('PRAGMA read_uncommitted = True;')->execute();
375 2
                break;
376
            default:
377
                throw new NotSupportedException(get_class($this) . ' only supports transaction isolation levels READ UNCOMMITTED and SERIALIZABLE.');
378
        }
379 2
    }
380
381
    /**
382
     * Loads multiple types of constraints and returns the specified ones.
383
     * @param string $tableName table name.
384
     * @param string $returnType return type:
385
     * - primaryKey
386
     * - indexes
387
     * - uniques
388
     * @return mixed constraints.
389
     */
390 56
    private function loadTableConstraints($tableName, $returnType)
391
    {
392 56
        $indexes = $this->db->createCommand('PRAGMA INDEX_LIST (' . $this->quoteValue($tableName) . ')')->queryAll();
393 56
        $indexes = $this->normalizePdoRowKeyCase($indexes, true);
394 56
        $tableColumns = null;
395 56
        if (!empty($indexes) && !isset($indexes[0]['origin'])) {
396
            /*
397
             * SQLite may not have an "origin" column in INDEX_LIST
398
             * See https://www.sqlite.org/src/info/2743846cdba572f6
399
             */
400
            $tableColumns = $this->db->createCommand('PRAGMA TABLE_INFO (' . $this->quoteValue($tableName) . ')')->queryAll();
401
            $tableColumns = $this->normalizePdoRowKeyCase($tableColumns, true);
402
            $tableColumns = ArrayHelper::index($tableColumns, 'cid');
403
        }
404
        $result = [
405 56
            'primaryKey' => null,
406
            'indexes' => [],
407
            'uniques' => [],
408
        ];
409 56
        foreach ($indexes as $index) {
410 47
            $columns = $this->db->createCommand('PRAGMA INDEX_INFO (' . $this->quoteValue($index['name']) . ')')->queryAll();
411 47
            $columns = $this->normalizePdoRowKeyCase($columns, true);
412 47
            ArrayHelper::multisort($columns, 'seqno', SORT_ASC, SORT_NUMERIC);
413 47
            if ($tableColumns !== null) {
414
                // SQLite may not have an "origin" column in INDEX_LIST
415
                $index['origin'] = 'c';
416
                if (!empty($columns) && $tableColumns[$columns[0]['cid']]['pk'] > 0) {
417
                    $index['origin'] = 'pk';
418
                } elseif ($index['unique'] && $this->isSystemIdentifier($index['name'])) {
419
                    $index['origin'] = 'u';
420
                }
421
            }
422 47
            $result['indexes'][] = new IndexConstraint([
423 47
                'isPrimary' => $index['origin'] === 'pk',
424 47
                'isUnique' => (bool) $index['unique'],
425 47
                'name' => $index['name'],
426 47
                'columnNames' => ArrayHelper::getColumn($columns, 'name'),
427
            ]);
428 47
            if ($index['origin'] === 'u') {
429 41
                $result['uniques'][] = new Constraint([
430 41
                    'name' => $index['name'],
431 41
                    'columnNames' => ArrayHelper::getColumn($columns, 'name'),
432
                ]);
433 30
            } elseif ($index['origin'] === 'pk') {
434 29
                $result['primaryKey'] = new Constraint([
435 47
                    'columnNames' => ArrayHelper::getColumn($columns, 'name'),
436
                ]);
437
            }
438
        }
439 56
        foreach ($result as $type => $data) {
440 56
            $this->setTableMetadata($tableName, $type, $data);
441
        }
442
443 56
        return $result[$returnType];
444
    }
445
446
    /**
447
     * Return whether the specified identifier is a SQLite system identifier.
448
     * @param string $identifier
449
     * @return bool
450
     * @see https://www.sqlite.org/src/artifact/74108007d286232f
451
     */
452
    private function isSystemIdentifier($identifier)
453
    {
454
        return strpos($identifier, 'sqlite_') === 0;
455
    }
456
}
457