Completed
Push — master ( 03299e...cda308 )
by Carsten
15:04
created

QueryBuilder::dropCheck()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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