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.
Passed
Push — master ( 400df7...c8c0ea )
by Robert
16:13
created

QueryBuilder::addForeignKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 7
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db\sqlite;
9
10
use yii\base\InvalidArgumentException;
11
use yii\base\NotSupportedException;
12
use yii\db\Connection;
13
use yii\db\Constraint;
14
use yii\db\Expression;
15
use yii\db\ExpressionInterface;
16
use yii\db\Query;
17
use yii\helpers\StringHelper;
18
19
/**
20
 * QueryBuilder is the query builder for SQLite databases.
21
 *
22
 * @author Qiang Xue <[email protected]>
23
 * @since 2.0
24
 */
25
class QueryBuilder extends \yii\db\QueryBuilder
26
{
27
    /**
28
     * @var array mapping from abstract column types (keys) to physical column types (values).
29
     */
30
    public $typeMap = [
31
        Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
32
        Schema::TYPE_UPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
33
        Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
34
        Schema::TYPE_UBIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
35
        Schema::TYPE_CHAR => 'char(1)',
36
        Schema::TYPE_STRING => 'varchar(255)',
37
        Schema::TYPE_TEXT => 'text',
38
        Schema::TYPE_TINYINT => 'tinyint',
39
        Schema::TYPE_SMALLINT => 'smallint',
40
        Schema::TYPE_INTEGER => 'integer',
41
        Schema::TYPE_BIGINT => 'bigint',
42
        Schema::TYPE_FLOAT => 'float',
43
        Schema::TYPE_DOUBLE => 'double',
44
        Schema::TYPE_DECIMAL => 'decimal(10,0)',
45
        Schema::TYPE_DATETIME => 'datetime',
46
        Schema::TYPE_TIMESTAMP => 'timestamp',
47
        Schema::TYPE_TIME => 'time',
48
        Schema::TYPE_DATE => 'date',
49
        Schema::TYPE_BINARY => 'blob',
50
        Schema::TYPE_BOOLEAN => 'boolean',
51
        Schema::TYPE_MONEY => 'decimal(19,4)',
52
    ];
53
54
55
    /**
56
     * {@inheritdoc}
57
     */
58 507
    protected function defaultExpressionBuilders()
59
    {
60 507
        return array_merge(parent::defaultExpressionBuilders(), [
61 507
            'yii\db\conditions\LikeCondition' => 'yii\db\sqlite\conditions\LikeConditionBuilder',
62
            'yii\db\conditions\InCondition' => 'yii\db\sqlite\conditions\InConditionBuilder',
63
        ]);
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     * @see https://stackoverflow.com/questions/15277373/sqlite-upsert-update-or-insert/15277374#15277374
69
     */
70 25
    public function upsert($table, $insertColumns, $updateColumns, &$params)
71
    {
72
        /** @var Constraint[] $constraints */
73 25
        list($uniqueNames, $insertNames, $updateNames) = $this->prepareUpsertColumns($table, $insertColumns, $updateColumns, $constraints);
74 25
        if (empty($uniqueNames)) {
75 3
            return $this->insert($table, $insertColumns, $params);
76
        }
77 22
        if ($updateNames === []) {
78
            // there are no columns to update
79 1
            $updateColumns = false;
80
        }
81
82 22
        list(, $placeholders, $values, $params) = $this->prepareInsertValues($table, $insertColumns, $params);
83 22
        $insertSql = 'INSERT OR IGNORE INTO ' . $this->db->quoteTableName($table)
84 22
            . (!empty($insertNames) ? ' (' . implode(', ', $insertNames) . ')' : '')
85 22
            . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : $values);
86 22
        if ($updateColumns === false) {
87 5
            return $insertSql;
88
        }
89
90 17
        $updateCondition = ['or'];
91 17
        $quotedTableName = $this->db->quoteTableName($table);
92 17
        foreach ($constraints as $constraint) {
93 17
            $constraintCondition = ['and'];
94 17
            foreach ($constraint->columnNames as $name) {
95 17
                $quotedName = $this->db->quoteColumnName($name);
96 17
                $constraintCondition[] = "$quotedTableName.$quotedName=(SELECT $quotedName FROM `EXCLUDED`)";
97
            }
98 17
            $updateCondition[] = $constraintCondition;
99
        }
100 17
        if ($updateColumns === true) {
101 11
            $updateColumns = [];
102 11
            foreach ($updateNames as $name) {
103 11
                $quotedName = $this->db->quoteColumnName($name);
104 11
                if (strrpos($quotedName, '.') === false) {
105 11
                    $quotedName = "(SELECT $quotedName FROM `EXCLUDED`)";
106
                }
107 11
                $updateColumns[$name] = new Expression($quotedName);
108
            }
109
        }
110 17
        $updateSql = 'WITH "EXCLUDED" (' . implode(', ', $insertNames)
111 17
            . ') AS (' . (!empty($placeholders) ? 'VALUES (' . implode(', ', $placeholders) . ')' : ltrim($values, ' ')) . ') '
112 17
            . $this->update($table, $updateColumns, $updateCondition, $params);
113 17
        return "$updateSql; $insertSql;";
114
    }
115
116
    /**
117
     * Generates a batch INSERT SQL statement.
118
     *
119
     * For example,
120
     *
121
     * ```php
122
     * $connection->createCommand()->batchInsert('user', ['name', 'age'], [
123
     *     ['Tom', 30],
124
     *     ['Jane', 20],
125
     *     ['Linda', 25],
126
     * ])->execute();
127
     * ```
128
     *
129
     * Note that the values in each row must match the corresponding column names.
130
     *
131
     * @param string $table the table that new rows will be inserted into.
132
     * @param array $columns the column names
133
     * @param array|\Generator $rows the rows to be batch inserted into the table
134
     * @return string the batch INSERT SQL statement
135
     */
136 15
    public function batchInsert($table, $columns, $rows, &$params = [])
137
    {
138 15
        if (empty($rows)) {
139 2
            return '';
140
        }
141
142
        // SQLite supports batch insert natively since 3.7.11
143
        // https://www.sqlite.org/releaselog/3_7_11.html
144 14
        $this->db->open(); // ensure pdo is not null
145 14
        if (version_compare($this->db->getServerVersion(), '3.7.11', '>=')) {
146 14
            return parent::batchInsert($table, $columns, $rows, $params);
147
        }
148
149
        $schema = $this->db->getSchema();
150
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
151
            $columnSchemas = $tableSchema->columns;
152
        } else {
153
            $columnSchemas = [];
154
        }
155
156
        $values = [];
157
        foreach ($rows as $row) {
158
            $vs = [];
159
            foreach ($row as $i => $value) {
160
                if (isset($columnSchemas[$columns[$i]])) {
161
                    $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
162
                }
163
                if (is_string($value)) {
164
                    $value = $schema->quoteValue($value);
165
                } elseif (is_float($value)) {
166
                    // ensure type cast always has . as decimal separator in all locales
167
                    $value = StringHelper::floatToString($value);
168
                } elseif ($value === false) {
169
                    $value = 0;
170
                } elseif ($value === null) {
171
                    $value = 'NULL';
172
                } elseif ($value instanceof ExpressionInterface) {
173
                    $value = $this->buildExpression($value, $params);
174
                }
175
                $vs[] = $value;
176
            }
177
            $values[] = implode(', ', $vs);
178
        }
179
        if (empty($values)) {
180
            return '';
181
        }
182
183
        foreach ($columns as $i => $name) {
184
            $columns[$i] = $schema->quoteColumnName($name);
185
        }
186
187
        return 'INSERT INTO ' . $schema->quoteTableName($table)
188
        . ' (' . implode(', ', $columns) . ') SELECT ' . implode(' UNION SELECT ', $values);
189
    }
190
191
    /**
192
     * Creates a SQL statement for resetting the sequence value of a table's primary key.
193
     * The sequence will be reset such that the primary key of the next new row inserted
194
     * will have the specified value or 1.
195
     * @param string $tableName the name of the table whose primary key sequence will be reset
196
     * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
197
     * the next new row's primary key will have a value 1.
198
     * @return string the SQL statement for resetting sequence
199
     * @throws InvalidArgumentException if the table does not exist or there is no sequence associated with the table.
200
     */
201 9
    public function resetSequence($tableName, $value = null)
202
    {
203 9
        $db = $this->db;
204 9
        $table = $db->getTableSchema($tableName);
205 9
        if ($table !== null && $table->sequenceName !== null) {
206 7
            $tableName = $db->quoteTableName($tableName);
207 7
            if ($value === null) {
208 2
                $key = $this->db->quoteColumnName(reset($table->primaryKey));
209 2
                $value = $this->db->useMaster(function (Connection $db) use ($key, $tableName) {
210 2
                    return $db->createCommand("SELECT MAX($key) FROM $tableName")->queryScalar();
211 2
                });
212
            } else {
213 7
                $value = (int) $value - 1;
214
            }
215
216 7
            return "UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'";
217 2
        } elseif ($table === null) {
218 1
            throw new InvalidArgumentException("Table not found: $tableName");
219
        }
220
221 1
        throw new InvalidArgumentException("There is not sequence associated with table '$tableName'.'");
222
    }
223
224
    /**
225
     * Enables or disables integrity check.
226
     * @param bool $check whether to turn on or off the integrity check.
227
     * @param string $schema the schema of the tables. Meaningless for SQLite.
228
     * @param string $table the table name. Meaningless for SQLite.
229
     * @return string the SQL statement for checking integrity
230
     * @throws NotSupportedException this is not supported by SQLite
231
     */
232
    public function checkIntegrity($check = true, $schema = '', $table = '')
233
    {
234
        return 'PRAGMA foreign_keys=' . (int) $check;
235
    }
236
237
    /**
238
     * Builds a SQL statement for truncating a DB table.
239
     * @param string $table the table to be truncated. The name will be properly quoted by the method.
240
     * @return string the SQL statement for truncating a DB table.
241
     */
242 1
    public function truncateTable($table)
243
    {
244 1
        return 'DELETE FROM ' . $this->db->quoteTableName($table);
245
    }
246
247
    /**
248
     * Builds a SQL statement for dropping an index.
249
     * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
250
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
251
     * @return string the SQL statement for dropping an index.
252
     */
253 2
    public function dropIndex($name, $table)
254
    {
255 2
        return 'DROP INDEX ' . $this->db->quoteTableName($name);
256
    }
257
258
    /**
259
     * Builds a SQL statement for dropping a DB column.
260
     * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
261
     * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
262
     * @return string the SQL statement for dropping a DB column.
263
     * @throws NotSupportedException this is not supported by SQLite
264
     */
265
    public function dropColumn($table, $column)
266
    {
267
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
268
    }
269
270
    /**
271
     * Builds a SQL statement for renaming a column.
272
     * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
273
     * @param string $oldName the old name of the column. The name will be properly quoted by the method.
274
     * @param string $newName the new name of the column. The name will be properly quoted by the method.
275
     * @return string the SQL statement for renaming a DB column.
276
     * @throws NotSupportedException this is not supported by SQLite
277
     */
278
    public function renameColumn($table, $oldName, $newName)
279
    {
280
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
281
    }
282
283
    /**
284
     * Builds a SQL statement for adding a foreign key constraint to an existing table.
285
     * The method will properly quote the table and column names.
286
     * @param string $name the name of the foreign key constraint.
287
     * @param string $table the table that the foreign key constraint will be added to.
288
     * @param string|array $columns the name of the column to that the constraint will be added on.
289
     * If there are multiple columns, separate them with commas or use an array to represent them.
290
     * @param string $refTable the table that the foreign key references to.
291
     * @param string|array $refColumns the name of the column that the foreign key references to.
292
     * If there are multiple columns, separate them with commas or use an array to represent them.
293
     * @param string|null $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
294
     * @param string|null $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
295
     * @return string the SQL statement for adding a foreign key constraint to an existing table.
296
     * @throws NotSupportedException this is not supported by SQLite
297
     */
298
    public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
299
    {
300
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
301
    }
302
303
    /**
304
     * Builds a SQL statement for dropping a foreign key constraint.
305
     * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
306
     * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
307
     * @return string the SQL statement for dropping a foreign key constraint.
308
     * @throws NotSupportedException this is not supported by SQLite
309
     */
310
    public function dropForeignKey($name, $table)
311
    {
312
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
313
    }
314
315
    /**
316
     * Builds a SQL statement for renaming a DB table.
317
     *
318
     * @param string $table the table to be renamed. The name will be properly quoted by the method.
319
     * @param string $newName the new table name. The name will be properly quoted by the method.
320
     * @return string the SQL statement for renaming a DB table.
321
     */
322 3
    public function renameTable($table, $newName)
323
    {
324 3
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' RENAME TO ' . $this->db->quoteTableName($newName);
325
    }
326
327
    /**
328
     * Builds a SQL statement for changing the definition of a column.
329
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
330
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
331
     * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
332
     * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
333
     * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
334
     * will become 'varchar(255) not null'.
335
     * @return string the SQL statement for changing the definition of a column.
336
     * @throws NotSupportedException this is not supported by SQLite
337
     */
338
    public function alterColumn($table, $column, $type)
339
    {
340
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
341
    }
342
343
    /**
344
     * Builds a SQL statement for adding a primary key constraint to an existing table.
345
     * @param string $name the name of the primary key constraint.
346
     * @param string $table the table that the primary key constraint will be added to.
347
     * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
348
     * @return string the SQL statement for adding a primary key constraint to an existing table.
349
     * @throws NotSupportedException this is not supported by SQLite
350
     */
351
    public function addPrimaryKey($name, $table, $columns)
352
    {
353
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
354
    }
355
356
    /**
357
     * Builds a SQL statement for removing a primary key constraint to an existing table.
358
     * @param string $name the name of the primary key constraint to be removed.
359
     * @param string $table the table that the primary key constraint will be removed from.
360
     * @return string the SQL statement for removing a primary key constraint from an existing table.
361
     * @throws NotSupportedException this is not supported by SQLite
362
     */
363
    public function dropPrimaryKey($name, $table)
364
    {
365
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
366
    }
367
368
    /**
369
     * {@inheritdoc}
370
     * @throws NotSupportedException this is not supported by SQLite.
371
     */
372
    public function addUnique($name, $table, $columns)
373
    {
374
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
375
    }
376
377
    /**
378
     * {@inheritdoc}
379
     * @throws NotSupportedException this is not supported by SQLite.
380
     */
381
    public function dropUnique($name, $table)
382
    {
383
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
384
    }
385
386
    /**
387
     * {@inheritdoc}
388
     * @throws NotSupportedException this is not supported by SQLite.
389
     */
390
    public function addCheck($name, $table, $expression)
391
    {
392
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
393
    }
394
395
    /**
396
     * {@inheritdoc}
397
     * @throws NotSupportedException this is not supported by SQLite.
398
     */
399
    public function dropCheck($name, $table)
400
    {
401
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
402
    }
403
404
    /**
405
     * {@inheritdoc}
406
     * @throws NotSupportedException this is not supported by SQLite.
407
     */
408
    public function addDefaultValue($name, $table, $column, $value)
409
    {
410
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
411
    }
412
413
    /**
414
     * {@inheritdoc}
415
     * @throws NotSupportedException this is not supported by SQLite.
416
     */
417
    public function dropDefaultValue($name, $table)
418
    {
419
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
420
    }
421
422
    /**
423
     * {@inheritdoc}
424
     * @throws NotSupportedException
425
     * @since 2.0.8
426
     */
427
    public function addCommentOnColumn($table, $column, $comment)
428
    {
429
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
430
    }
431
432
    /**
433
     * {@inheritdoc}
434
     * @throws NotSupportedException
435
     * @since 2.0.8
436
     */
437
    public function addCommentOnTable($table, $comment)
438
    {
439
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
440
    }
441
442
    /**
443
     * {@inheritdoc}
444
     * @throws NotSupportedException
445
     * @since 2.0.8
446
     */
447
    public function dropCommentFromColumn($table, $column)
448
    {
449
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
450
    }
451
452
    /**
453
     * {@inheritdoc}
454
     * @throws NotSupportedException
455
     * @since 2.0.8
456
     */
457
    public function dropCommentFromTable($table)
458
    {
459
        throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
460
    }
461
462
    /**
463
     * {@inheritdoc}
464
     */
465 421
    public function buildLimit($limit, $offset)
466
    {
467 421
        $sql = '';
468 421
        if ($this->hasLimit($limit)) {
469 43
            $sql = 'LIMIT ' . $limit;
470 43
            if ($this->hasOffset($offset)) {
471 43
                $sql .= ' OFFSET ' . $offset;
472
            }
473 412
        } elseif ($this->hasOffset($offset)) {
474
            // limit is not optional in SQLite
475
            // https://www.sqlite.org/syntaxdiagrams.html#select-stmt
476 2
            $sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1
477
        }
478
479 421
        return $sql;
480
    }
481
482
    /**
483
     * {@inheritdoc}
484
     */
485 421
    public function build($query, $params = [])
486
    {
487 421
        $query = $query->prepare($this);
488
489 421
        $params = empty($params) ? $query->params : array_merge($params, $query->params);
490
491
        $clauses = [
492 421
            $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
493 421
            $this->buildFrom($query->from, $params),
494 421
            $this->buildJoin($query->join, $params),
495 421
            $this->buildWhere($query->where, $params),
496 421
            $this->buildGroupBy($query->groupBy),
497 421
            $this->buildHaving($query->having, $params),
498
        ];
499
500 421
        $sql = implode($this->separator, array_filter($clauses));
501 421
        $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset);
502
503 421
        if (!empty($query->orderBy)) {
504 86
            foreach ($query->orderBy as $expression) {
505 86
                if ($expression instanceof ExpressionInterface) {
506 1
                    $this->buildExpression($expression, $params);
507
                }
508
            }
509
        }
510 421
        if (!empty($query->groupBy)) {
511 3
            foreach ($query->groupBy as $expression) {
512 3
                if ($expression instanceof ExpressionInterface) {
513 1
                    $this->buildExpression($expression, $params);
514
                }
515
            }
516
        }
517
518 421
        $union = $this->buildUnion($query->union, $params);
519 421
        if ($union !== '') {
520 3
            $sql = "$sql{$this->separator}$union";
521
        }
522
523 421
        $with = $this->buildWithQueries($query->withQueries, $params);
524 421
        if ($with !== '') {
525 2
            $sql = "$with{$this->separator}$sql";
526
        }
527
528 421
        return [$sql, $params];
529
    }
530
531
    /**
532
     * {@inheritdoc}
533
     */
534 421
    public function buildUnion($unions, &$params)
535
    {
536 421
        if (empty($unions)) {
537 421
            return '';
538
        }
539
540 3
        $result = '';
541
542 3
        foreach ($unions as $i => $union) {
543 3
            $query = $union['query'];
544 3
            if ($query instanceof Query) {
545 3
                list($unions[$i]['query'], $params) = $this->build($query, $params);
546
            }
547
548 3
            $result .= ' UNION ' . ($union['all'] ? 'ALL ' : '') . ' ' . $unions[$i]['query'];
549
        }
550
551 3
        return trim($result);
552
    }
553
554
    /**
555
     * {@inheritdoc}
556
     */
557 7
    public function createIndex($name, $table, $columns, $unique = false)
558
    {
559 7
        $tableParts = explode('.', $table);
560
561 7
        $schema = null;
562 7
        if (count($tableParts) === 2) {
563 1
            list ($schema, $table) = $tableParts;
564
        }
565
566 7
        return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
567 7
            . $this->db->quoteTableName(($schema ? $schema . '.' : '') . $name) . ' ON '
568 7
            . $this->db->quoteTableName($table)
569 7
            . ' (' . $this->buildColumns($columns) . ')';
570
    }
571
}
572