Completed
Push — master ( 2bdeff...47597f )
by José
15s
created

SQLiteAdapter::getDropTableInstructions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use Cake\Database\Connection;
32
use Cake\Database\Driver\Sqlite as SqliteDriver;
33
use Phinx\Db\Table\Column;
34
use Phinx\Db\Table\ForeignKey;
35
use Phinx\Db\Table\Index;
36
use Phinx\Db\Table\Table;
37
use Phinx\Db\Util\AlterInstructions;
38
use Phinx\Util\Literal;
39
40
/**
41
 * Phinx SQLite Adapter.
42
 *
43
 * @author Rob Morgan <[email protected]>
44
 * @author Richard McIntyre <[email protected]>
45
 */
46
class SQLiteAdapter extends PdoAdapter implements AdapterInterface
47
{
48
    protected $definitionsWithLimits = [
49
        'CHAR',
50
        'CHARACTER',
51
        'VARCHAR',
52
        'VARYING CHARACTER',
53
        'NCHAR',
54
        'NATIVE CHARACTER',
55
        'NVARCHAR'
56 42
    ];
57
58 42
    protected $suffix = '.sqlite3';
59 42
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function connect()
64
    {
65 42
        if ($this->connection === null) {
66 42
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
67
                // @codeCoverageIgnoreStart
68
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
69 42
                // @codeCoverageIgnoreEnd
70
            }
71
72 42
            $db = null;
73 42
            $options = $this->getOptions();
74 42
75 42
            // use a memory database if the option was specified
76
            if (!empty($options['memory'])) {
77
                $dsn = 'sqlite::memory:';
78
            } else {
79 42
                $dsn = 'sqlite:' . $options['name'] . $this->suffix;
80 42
            }
81
82
            try {
83
                $db = new \PDO($dsn);
84
            } catch (\PDOException $exception) {
85
                throw new \InvalidArgumentException(sprintf(
86
                    'There was a problem connecting to the database: %s',
87 42
                    $exception->getMessage()
88 42
                ));
89 42
            }
90
91
            $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
92
            $this->setConnection($db);
93
        }
94 48
    }
95
96 48
    /**
97 48
     * {@inheritdoc}
98
     */
99
    public function setOptions(array $options)
100
    {
101
        parent::setOptions($options);
102
103
        if (isset($options['suffix'])) {
104
            $this->suffix = $options['suffix'];
105
        }
106
        //don't "fix" the file extension if it is blank, some people
107
        //might want a SQLITE db file with absolutely no extension.
108
        if (strlen($this->suffix) && substr($this->suffix, 0, 1) !== '.') {
109
            $this->suffix = '.' . $this->suffix;
110 1
        }
111
112 1
        return $this;
113 1
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function disconnect()
119
    {
120
        $this->connection = null;
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function hasTransactions()
127
    {
128
        return true;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 43
    public function beginTransaction()
135
    {
136 43
        $this->getConnection()->beginTransaction();
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 44
    public function commitTransaction()
143
    {
144 44
        $this->getConnection()->commit();
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150 42
    public function rollbackTransaction()
151
    {
152 42
        $this->getConnection()->rollBack();
153 42
    }
154 42
155 12
    /**
156 42
     * {@inheritdoc}
157
     */
158 42
    public function quoteTableName($tableName)
159
    {
160
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
161
    }
162
163
    /**
164 42
     * {@inheritdoc}
165
     */
166
    public function quoteColumnName($columnName)
167 42
    {
168 42
        return '`' . str_replace('`', '``', $columnName) . '`';
169 42
    }
170 35
171 35
    /**
172 35
     * @param string $tableName Table name
173 35
     * @param boolean $quoted Whether to return the schema name and table name escaped and quoted. If quoted, the schema (if any) will also be appended with a dot
174
     * @return array
175 35
     */
176 42
    protected function getSchemaName($tableName, $quoted = false)
177
    {
178 1
        if (preg_match("/.\.([^\.]+)$/", $tableName, $match)) {
179 1
            $table = $match[1];
180 1
            $schema = substr($tableName, 0, strlen($tableName) - strlen($match[0]) + 1);
181 1
            $result = ['schema' => $schema, 'table' => $table];
182
        } else {
183 1
            $result = ['schema' => '', 'table' => $tableName];
184 1
        }
185
186
        if ($quoted) {
187 42
            $result['schema'] = strlen($result['schema']) ? $this->quoteColumnName($result['schema']) . '.' : '';
188 42
            $result['table'] = $this->quoteColumnName($result['table']);
189 42
        }
190 42
191 42
        return $result;
192
    }
193
194 42
    /**
195 42
     * {@inheritdoc}
196 42
     */
197 42
    public function hasTable($tableName)
198 42
    {
199 42
        $info = $this->getSchemaName($tableName);
200
        if ($info['schema'] === '') {
201
            // if no schema is specified we search all schemata
202 1
            $rows = $this->fetchAll('PRAGMA database_list;');
203 1
            $schemata = [];
204 1
            foreach ($rows as $row) {
205
                $schemata[] = $row['name'];
206 1
            }
207 1
        } else {
208 1
            // otherwise we search just the specified schema
209 1
            $schemata = (array)$info['schema'];
210 1
        }
211 1
212 42
        $table = strtolower($info['table']);
213 42
        foreach ($schemata as $schema) {
214 37
            if (strtolower($schema) === 'temp') {
215
                $master = 'sqlite_temp_master';
216
            } else {
217
                $master = sprintf('%s.%s', $this->quoteColumnName($schema), 'sqlite_master');
218 42
            }
219 42
            try {
220 1
                $rows = $this->fetchAll(sprintf('SELECT name FROM %s WHERE type=\'table\' AND lower(name) = %s', $master, $this->quoteString($table)));
221 1
            } catch (\PDOException $e) {
222 1
                // an exception can occur if the schema part of the table refers to a database which is not attached
223 1
                return false;
224
            }
225 42
226
            // this somewhat pedantic check with strtolower is performed because the SQL lower function may be redefined,
227 42
            // and can act on all Unicode characters if the ICU extension is loaded, while SQL identifiers are only case-insensitive for ASCII
228
            foreach ($rows as $row) {
229 42
                if (strtolower($row['name']) === $table) {
230 6
                    return true;
231 42
                }
232 42
            }
233
        }
234
235
        return false;
236
    }
237 1
238
    /**
239 1
     * Retrieves information about a given table from one of the SQLite pragmas
240 1
     *
241
     * @param string $tableName The table to query
242
     * @param string $pragma The pragma to query
243
     * @return array
244
     */
245 1
    protected function getTableInfo($tableName, $pragma = 'table_info')
246
    {
247 1
        $info = $this->getSchemaName($tableName, true);
248 1
        return $this->fetchAll(sprintf('PRAGMA %s%s(%s)', $info['schema'], $pragma, $info['table']));
249
    }
250
251
    /**
252
     * {@inheritdoc}
253 1
     */
254
    public function createTable(Table $table, array $columns = [], array $indexes = [])
255 1
    {
256 1
        // Add the default primary key
257 1
        $options = $table->getOptions();
258 1 View Code Duplication
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
259
            $options['id'] = 'id';
260 1
        }
261 1
262
        if (isset($options['id']) && is_string($options['id'])) {
263
            // Handle id => "field_name" to support AUTO_INCREMENT
264
            $column = new Column();
265
            $column->setName($options['id'])
266 1
                   ->setType('integer')
267
                   ->setIdentity(true);
268 1
269 1
            array_unshift($columns, $column);
270
        }
271 1
272 1
        $sql = 'CREATE TABLE ';
273 1
        $sql .= $this->quoteTableName($table->getName()) . ' (';
274 1
        foreach ($columns as $column) {
275 1
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
276 1
277
            if (isset($options['primary_key']) && $column->getIdentity()) {
278 1
                //remove column from the primary key array as it is already defined as an autoincrement
279 1
                //primary id
280 1
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
281
                if ($identityColumnIndex !== false) {
282 1
                    unset($options['primary_key'][$identityColumnIndex]);
283 1
284 1
                    if (empty($options['primary_key'])) {
285
                        //The last primary key has been removed
286 1
                        unset($options['primary_key']);
287 1
                    }
288
                }
289 1
            }
290
        }
291
292
        // set the primary key(s)
293 View Code Duplication
        if (isset($options['primary_key'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
294
            $sql = rtrim($sql);
295 8
            $sql .= ' PRIMARY KEY (';
296
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
297 8
                $sql .= $this->quoteColumnName($options['primary_key']);
298 8
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
299 8
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
300 7
            }
301
            $sql .= ')';
302 8
        } else {
303
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
304 8
        }
305
306
        $sql = rtrim($sql) . ');';
307
        // execute the sql
308
        $this->execute($sql);
309
310 4
        foreach ($indexes as $index) {
311
            $this->addIndex($table, $index);
312 4
        }
313 4
    }
314 4
315 4
    /**
316 4
     * {@inheritdoc}
317 4
     */
318
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
319 4
    {
320 4
        $instructions = new AlterInstructions();
321
322
        // Drop the existing primary key
323
        $primaryKey = $this->getPrimaryKey($table->getName());
324
        if (!empty($primaryKey)) {
325 2
            $instructions->merge(
326
                $this->getDropPrimaryKeyInstructions($table, $primaryKey)
327 2
            );
328
        }
329 2
330
        // Add the primary key(s)
331 2
        if (!empty($newColumns)) {
332 2
            if (!is_string($newColumns)) {
333 2
                throw new \InvalidArgumentException(sprintf(
334 2
                    "Invalid value for primary key: %s",
335 2
                    json_encode($newColumns)
336 2
                ));
337
            }
338 2
339 2
            $instructions->merge(
340 2
                $this->getAddPrimaryKeyInstructions($table, $newColumns)
341 2
            );
342 2
        }
343 2
344 2
        return $instructions;
345 2
    }
346 2
347
    /**
348 2
     * {@inheritdoc}
349 1
     */
350
    protected function getChangeCommentInstructions(Table $table, $newComment)
351 1
    {
352
        throw new \BadMethodCallException('SQLite does not have table comments');
353
    }
354 1
355
    /**
356 1
     * {@inheritdoc}
357 1
     */
358 1 View Code Duplication
    protected function getRenameTableInstructions($tableName, $newTableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
359
    {
360 1
        $sql = sprintf(
361 1
            'ALTER TABLE %s RENAME TO %s',
362
            $this->quoteTableName($tableName),
363
            $this->quoteTableName($newTableName)
364 1
        );
365 1
366 1
        return new AlterInstructions([], [$sql]);
367 1
    }
368 1
369
    /**
370 1
     * {@inheritdoc}
371
     */
372 1
    protected function getDropTableInstructions($tableName)
373
    {
374 1
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
375 1
376
        return new AlterInstructions([], [$sql]);
377
    }
378
379
    /**
380 6
     * {@inheritdoc}
381
     */
382
    public function truncateTable($tableName)
383
    {
384 6
        $sql = sprintf(
385
            'DELETE FROM %s',
386 6
            $this->quoteTableName($tableName)
387
        );
388 6
389 6
        $this->execute($sql);
390 6
    }
391 6
392 6
    /**
393 6
     * {@inheritdoc}
394
     */
395 6
    public function getColumns($tableName)
396 6
    {
397 6
        $columns = [];
398 6
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
399 6
400 6
        foreach ($rows as $columnInfo) {
401 6
            $column = new Column();
402 6
            $type = strtolower($columnInfo['type']);
403 6
            $column->setName($columnInfo['name'])
404
                   ->setNull($columnInfo['notnull'] !== '1')
405 6
                   ->setDefault($columnInfo['dflt_value']);
406
407
            $phinxType = $this->getPhinxType($type);
408
409
            $column->setType($phinxType['name'])
410
                   ->setLimit($phinxType['limit'])
411 6
                   ->setScale($phinxType['scale']);
412
413 6
            if ($columnInfo['pk'] == 1) {
414 6
                $column->setIdentity(true);
415 6
            }
416 6
417
            $columns[] = $column;
418 6
        }
419
420 6
        return $columns;
421
    }
422 6
423 6
    /**
424 6
     * {@inheritdoc}
425 6
     */
426 6 View Code Duplication
    public function hasColumn($tableName, $columnName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
427
    {
428 6
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
429
        foreach ($rows as $column) {
430 6
            if (strcasecmp($column['name'], $columnName) === 0) {
431 6
                return true;
432 6
            }
433
        }
434
435
        return false;
436
    }
437 2
438
    /**
439
     * {@inheritdoc}
440 2
     */
441 View Code Duplication
    protected function getAddColumnInstructions(Table $table, Column $column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
442 2
    {
443
        $alter = sprintf(
444 2
            'ALTER TABLE %s ADD COLUMN %s %s',
445 2
            $this->quoteTableName($table->getName()),
446 2
            $this->quoteColumnName($column->getName()),
447 2
            $this->getColumnSqlDefinition($column)
448 2
        );
449 2
450
        return new AlterInstructions([], [$alter]);
451 2
    }
452 2
453 2
    /**
454 2
     * Returns the original CREATE statement for the give table
455 2
     *
456 2
     * @param string $tableName The table name to get the create statement for
457 2
     * @return string
458 2
     */
459 2
    protected function getDeclaringSql($tableName)
460
    {
461 2
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
462
463 2
        $sql = '';
464
        foreach ($rows as $table) {
465
            if ($table['tbl_name'] === $tableName) {
466
                $sql = $table['sql'];
467
            }
468
        }
469 2
470
        return $sql;
471 2
    }
472 2
473 2
    /**
474
     * Copies all the data from a tmp table to another table
475 2
     *
476
     * @param string $tableName The table name to copy the data to
477 2
     * @param string $tmpTableName The tmp table name where the data is stored
478 2
     * @param string[] $writeColumns The list of columns in the target table
479 2
     * @param string[] $selectColumns The list of columns in the tmp table
480
     * @return void
481 2
     */
482
    protected function copyDataToNewTable($tableName, $tmpTableName, $writeColumns, $selectColumns)
483 2
    {
484 2
        $sql = sprintf(
485 2
            'INSERT INTO %s(%s) SELECT %s FROM %s',
486 2
            $this->quoteTableName($tableName),
487 2
            implode(', ', $writeColumns),
488
            implode(', ', $selectColumns),
489 2
            $this->quoteTableName($tmpTableName)
490
        );
491 2
        $this->execute($sql);
492 2
    }
493 2
494
    /**
495
     * Modifies the passed instructions to copy all data from the tmp table into
496
     * the provided table and then drops the tmp table.
497
     *
498
     * @param AlterInstructions $instructions The instructions to modify
499
     * @param string $tableName The table name to copy the data to
500
     * @return AlterInstructions
501 9
     */
502
    protected function copyAndDropTmpTable($instructions, $tableName)
503 9
    {
504 9
        $instructions->addPostStep(function ($state) use ($tableName) {
505
            $this->copyDataToNewTable(
506 9
                $tableName,
507 9
                $state['tmpTableName'],
508 9
                $state['writeColumns'],
509 9
                $state['selectColumns']
510 9
            );
511 9
512 9
            $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($state['tmpTableName'])));
513 9
514 9
            return $state;
515 9
        });
516
517
        return $instructions;
518
    }
519
520
    /**
521 9
     * Returns the columns and type to use when copying a table to another in the process
522
     * of altering a table
523 9
     *
524 4
     * @param string $tableName The table to modify
525 4
     * @param string $columnName The column name that is about to change
526
     * @param string|false $newColumnName Optionally the new name for the column
527 9
     * @return AlterInstructions
528 9
     */
529
    protected function calculateNewTableColumns($tableName, $columnName, $newColumnName)
530 9
    {
531 9
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
532 9
        $selectColumns = [];
533 9
        $writeColumns = [];
534
        $columnType = null;
535 8
        $found = false;
536
537 8
        foreach ($columns as $column) {
538
            $selectName = $column['name'];
539
            $writeName = $selectName;
540
541
            if ($selectName == $columnName) {
542
                $writeName = $newColumnName;
543 1
                $found = true;
544
                $columnType = $column['type'];
545 1
                $selectName = $newColumnName === false ? $newColumnName : $selectName;
546
            }
547 1
548 1
            $selectColumns[] = $selectName;
549 1
            $writeColumns[] = $writeName;
550
        }
551
552
        $selectColumns = array_filter($selectColumns, 'strlen');
553
        $writeColumns = array_filter($writeColumns, 'strlen');
554
        $selectColumns = array_map([$this, 'quoteColumnName'], $selectColumns);
555
        $writeColumns = array_map([$this, 'quoteColumnName'], $writeColumns);
556
557
        if (!$found) {
558
            throw new \InvalidArgumentException(sprintf(
559 8
                'The specified column doesn\'t exist: ' . $columnName
560
            ));
561 8
        }
562 8
563 8
        return compact('writeColumns', 'selectColumns', 'columnType');
564 8
    }
565 8
566 8
    /**
567 8
     * Returns the initial instructions to alter a table using the
568 8
     * rename-alter-copy strategy
569 8
     *
570 8
     * @param string $tableName The table to modify
571
     * @return AlterInstructions
572 8
     */
573 8
    protected function beginAlterByCopyTable($tableName)
574 8
    {
575
        $instructions = new AlterInstructions();
576
        $instructions->addPostStep(function ($state) use ($tableName) {
577
            $createSQL = $this->getDeclaringSql($tableName);
578
579 1
            $tmpTableName = 'tmp_' . $tableName;
580
            $this->execute(
581 1
                sprintf(
582 1
                    'ALTER TABLE %s RENAME TO %s',
583 1
                    $this->quoteTableName($tableName),
584
                    $this->quoteTableName($tmpTableName)
585 1
                )
586 1
            );
587
588 1
            return compact('createSQL', 'tmpTableName') + $state;
589 1
        });
590 1
591 1
        return $instructions;
592 1
    }
593 1
594 1
    /**
595 1
     * {@inheritdoc}
596 1
     */
597 1
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
598
    {
599
        $instructions = $this->beginAlterByCopyTable($tableName);
600 View Code Duplication
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
601
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
602
603
            return $newState + $state;
604
        });
605 1
606
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
607 1
            $sql = str_replace(
608
                $this->quoteColumnName($columnName),
609 1
                $this->quoteColumnName($newColumnName),
610 1
                $state['createSQL']
611 1
            );
612 1
            $this->execute($sql);
613 1
614 1
            return $state;
615 1
        });
616 1
617 1
        return $this->copyAndDropTmpTable($instructions, $tableName);
618
    }
619
620
    /**
621
     * {@inheritdoc}
622
     */
623
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
624
    {
625 5
        $instructions = $this->beginAlterByCopyTable($tableName);
626
627 5
        $newColumnName = $newColumn->getName();
628 View Code Duplication
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
629
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
630 5
631
            return $newState + $state;
632 5
        });
633 5
634 5
        $instructions->addPostStep(function ($state) use ($columnName, $newColumn) {
635
            $sql = preg_replace(
636 1
                sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
637
                sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
638
                $state['createSQL'],
639
                1
640
            );
641
            $this->execute($sql);
642
643
            return $state;
644
        });
645 5
646
        return $this->copyAndDropTmpTable($instructions, $tableName);
647 5
    }
648 5
649
    /**
650
     * {@inheritdoc}
651
     */
652
    protected function getDropColumnInstructions($tableName, $columnName)
653
    {
654
        $instructions = $this->beginAlterByCopyTable($tableName);
655
656 View Code Duplication
        $instructions->addPostStep(function ($state) use ($columnName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
657
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, false);
658
659
            return $newState + $state;
660
        });
661 5
662
        $instructions->addPostStep(function ($state) use ($columnName) {
663 5
            $sql = preg_replace(
664 5
                sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($state['columnType'])),
665 5
                "",
666 5
                $state['createSQL']
667 5
            );
668 5
669 5
            if (substr($sql, -2) === ', ') {
670 5
                $sql = substr($sql, 0, -2) . ')';
671 5
            }
672 5
673 5
            $this->execute($sql);
674
675
            return $state;
676
        });
677
678
        return $this->copyAndDropTmpTable($instructions, $tableName);
679 4
    }
680
681
    /**
682 4
     * Get an array of indexes from a particular table.
683
     *
684 4
     * @param string $tableName Table Name
685 4
     * @return array
686
     */
687 4
    protected function getIndexes($tableName)
688 4
    {
689 4
        $indexes = [];
690 4
        $schema = $this->getSchemaName($tableName, true)['schema'];
691 4
        $indexList = $this->getTableInfo($tableName, 'index_list');
692 4
693
        foreach ($indexList as $index) {
694 4
            $indexData = $this->fetchAll(sprintf('pragma %sindex_info(%s)', $schema, $this->quoteColumnName($index['name'])));
695 4
            $cols = [];
696 4
            foreach ($indexData as $indexItem) {
697 4
                $cols[] = $indexItem['name'];
698 4
            }
699
            $indexes[$index['name']] = $cols;
700 4
        }
701
702 4
        return $indexes;
703 4
    }
704
705 4
    /**
706 4
     * Finds the names of a table's indexes matching the supplied columns
707 4
     *
708 4
     * @param string $tableName The table to which the index belongs
709 4
     * @param string|string[] $columns The columns of the index
710 4
     * @return array
711 4
     */
712
    protected function resolveIndex($tableName, $columns)
713 4
    {
714 4
        $columns = array_map('strtolower', (array)$columns);
715 4
        $indexes = $this->getIndexes($tableName);
716
        $matches = [];
717
718
        foreach ($indexes as $name => $index) {
719
            $indexCols = array_map('strtolower', $index);
720 1
            if ($columns == $indexCols) {
721
                $matches[] = $name;
722
            }
723 1
        }
724
725
        return $matches;
726
    }
727 1
728
    /**
729 1
     * {@inheritdoc}
730
     */
731 1
    public function hasIndex($tableName, $columns)
732 1
    {
733 1
        return (bool)$this->resolveIndex($tableName, $columns);
734 1
    }
735 1
736 1
    /**
737
     * {@inheritdoc}
738 1
     */
739 1
    public function hasIndexByName($tableName, $indexName)
740 1
    {
741 1
        $indexName = strtolower($indexName);
742 1
        $indexes = $this->getIndexes($tableName);
743 1
744 1
        foreach (array_keys($indexes) as $index) {
745
            if ($indexName === strtolower($index)) {
746 1
                return true;
747
            }
748 1
        }
749
750
        return false;
751
    }
752
753
    /**
754 1
     * {@inheritdoc}
755
     */
756 1
    protected function getAddIndexInstructions(Table $table, Index $index)
757 1
    {
758 1
        $indexColumnArray = [];
759 1
        foreach ($index->getColumns() as $column) {
760 1
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
761 1
        }
762 1
        $indexColumns = implode(',', $indexColumnArray);
763
        $sql = sprintf(
764 1
            'CREATE %s ON %s (%s)',
765
            $this->getIndexSqlDefinition($table, $index),
766 1
            $this->quoteTableName($table->getName()),
767 1
            $indexColumns
768 1
        );
769 1
770 1
        return new AlterInstructions([], [$sql]);
771
    }
772 1
773
    /**
774 1
     * {@inheritdoc}
775 1
     */
776 1
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
777
    {
778
        $instructions = new AlterInstructions();
779
        $indexNames = $this->resolveIndex($tableName, $columns);
780
        $schema = $this->getSchemaName($tableName, true)['schema'];
781
        foreach ($indexNames as $indexName) {
782
            if (strpos($indexName, 'sqlite_autoindex_') !== 0) {
783
                $instructions->addPostStep(sprintf(
784
                    'DROP INDEX %s%s',
785
                    $schema,
786
                    $this->quoteColumnName($indexName)
787
                ));
788
            }
789
        }
790
791
        return $instructions;
792
    }
793
794
    /**
795
     * {@inheritdoc}
796
     */
797
    protected function getDropIndexByNameInstructions($tableName, $indexName)
798
    {
799
        $instructions = new AlterInstructions();
800
        $indexName = strtolower($indexName);
801
        $indexes = $this->getIndexes($tableName);
802
803
        $found = false;
804
        foreach (array_keys($indexes) as $index) {
805
            if ($indexName === strtolower($index)) {
806
                $found = true;
807
                break;
808
            }
809
        }
810 43
811
        if ($found) {
812
            $schema = $this->getSchemaName($tableName, true)['schema'];
813 43
                $instructions->addPostStep(sprintf(
814 42
                    'DROP INDEX %s%s',
815
                    $schema,
816 43
                    $this->quoteColumnName($indexName)
817
                ));
818
        }
819 43
820 1
        return $instructions;
821
    }
822 43
823 38
    /**
824
     * {@inheritdoc}
825 43
     */
826 42
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
827
    {
828 43
        $primaryKey = $this->getPrimaryKey($tableName);
829 2
830
        if (empty($primaryKey)) {
831 43
            return false;
832 1
        }
833
834 43
        $missingColumns = array_diff((array)$columns, (array)$primaryKey);
835 1
836
        return empty($missingColumns);
837 43
    }
838 42
839
    /**
840 43
     * Get the primary key from a particular table.
841 1
     *
842
     * @param string $tableName Table Name
843 43
     * @return string|null
844 1
     */
845
    protected function getPrimaryKey($tableName)
846 43
    {
847 43
        $rows = $this->fetchAll(
848 1
            "SELECT sql, tbl_name
849
              FROM (
850 43
                    SELECT sql sql, type type, tbl_name tbl_name, name name
851 42
                      FROM sqlite_master
852
                     UNION ALL
853 5
                    SELECT sql, type, tbl_name, name
854
                      FROM sqlite_temp_master
855 5
                   )
856 4
             WHERE type != 'meta'
857
               AND sql NOTNULL
858
               AND name NOT LIKE 'sqlite_%'
859
             ORDER BY substr(type, 2, 1), name"
860 1
        );
861 1
862
        foreach ($rows as $row) {
863
            if ($row['tbl_name'] === $tableName) {
864 1
                if (strpos($row['sql'], 'PRIMARY KEY') !== false) {
865
                    preg_match_all("/PRIMARY KEY\s*\(`([^`]+)`\)/", $row['sql'], $matches);
866
                    foreach ($matches[1] as $match) {
867 1
                        if (!empty($match)) {
868
                            return $match;
869 1
                        }
870 1
                    }
871 1
                    preg_match_all("/`([^`]+)`\s+\w+(\(\d+\))?((\s+NOT)?\s+NULL)?\s+PRIMARY KEY/", $row['sql'], $matches);
872
                    foreach ($matches[1] as $match) {
873
                        if (!empty($match)) {
874
                            return $match;
875
                        }
876
                    }
877
                }
878
            }
879
        }
880 3
881
        return null;
882 3
    }
883 1
884
    /**
885 2
     * {@inheritdoc}
886 2
     */
887 2
    public function hasForeignKey($tableName, $columns, $constraint = null)
888 2
    {
889 1
        $foreignKeys = $this->getForeignKeys($tableName);
890 1
891 2
        return !array_diff((array)$columns, $foreignKeys);
892
    }
893
894 2
    /**
895 2
     * Get an array of foreign keys from a particular table.
896 1
     *
897 1
     * @param string $tableName Table Name
898
     * @return array
899
     */
900 1
    protected function getForeignKeys($tableName)
901 2
    {
902
        $foreignKeys = [];
903
        $rows = $this->fetchAll(
904
            "SELECT sql, tbl_name
905
              FROM (
906
                    SELECT sql sql, type type, tbl_name tbl_name, name name
907
                      FROM sqlite_master
908
                     UNION ALL
909
                    SELECT sql, type, tbl_name, name
910 2
                      FROM sqlite_temp_master
911
                   )
912
             WHERE type != 'meta'
913
               AND sql NOTNULL
914
               AND name NOT LIKE 'sqlite_%'
915
             ORDER BY substr(type, 2, 1), name"
916 2
        );
917 1
918
        foreach ($rows as $row) {
919
            if ($row['tbl_name'] === $tableName) {
920 1
                if (strpos($row['sql'], 'REFERENCES') !== false) {
921 1
                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
922 2
                    foreach ($matches[1] as $match) {
923 1
                        $foreignKeys[] = $match;
924 1
                    }
925 2
                }
926 2
            }
927
        }
928
929
        return $foreignKeys;
930
    }
931
932
    /**
933 2
     * @param Table $table The Table
934
     * @param string $column Column Name
935
     * @return AlterInstructions
936 1
     */
937 1
    protected function getAddPrimaryKeyInstructions(Table $table, $column)
938
    {
939 1
        $instructions = $this->beginAlterByCopyTable($table->getName());
940
941
        $tableName = $table->getName();
942
        $instructions->addPostStep(function ($state) use ($column) {
943
            $matchPattern = "/(`$column`)\s+(\w+(\(\d+\))?)\s+((NOT )?NULL)/";
944
945
            $sql = $state['createSQL'];
946 48
947
            if (preg_match($matchPattern, $state['createSQL'], $matches)) {
948 48
                if (isset($matches[2])) {
949 48
                    if ($matches[2] === 'INTEGER') {
950
                        $replace = '$1 INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT';
951
                    } else {
952
                        $replace = '$1 $2 NOT NULL PRIMARY KEY';
953
                    }
954 2
955
                    $sql = preg_replace($matchPattern, $replace, $state['createSQL'], 1);
956 2
                }
957
            }
958
959
            $this->execute($sql);
960
961
            return $state;
962 48
        });
963
964 48 View Code Duplication
        $instructions->addPostStep(function ($state) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
965 47
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
966 47
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
967 48
            $selectColumns = $writeColumns = $names;
968
969
            return compact('selectColumns', 'writeColumns') + $state;
970
        });
971
972
        return $this->copyAndDropTmpTable($instructions, $tableName);
973
    }
974
975 42
    /**
976
     * @param Table $table Table
977 42
     * @param string $column Column Name
978 8
     * @return AlterInstructions
979 42
     */
980 42
    protected function getDropPrimaryKeyInstructions($table, $column)
981 42
    {
982 42
        $instructions = $this->beginAlterByCopyTable($table->getName());
983
984
        $instructions->addPostStep(function ($state) use ($column) {
985
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $column, $column);
986
987
            return $newState + $state;
988
        });
989
990
        $instructions->addPostStep(function ($state) {
991 42
            $search = "/(,?\s*PRIMARY KEY\s*\([^\)]*\)|\s+PRIMARY KEY(\s+AUTOINCREMENT)?)/";
992
            $sql = preg_replace($search, '', $state['createSQL'], 1);
993 42
994 42
            if ($sql) {
995 42
                $this->execute($sql);
996 42
            }
997
998
            return $state;
999 42
        });
1000 42
1001 42
        return $this->copyAndDropTmpTable($instructions, $table->getName());
1002 42
    }
1003 42
1004 4
    /**
1005 4
     * {@inheritdoc}
1006
     */
1007 42
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
1008
    {
1009 42
        $instructions = $this->beginAlterByCopyTable($table->getName());
1010 42
1011 42
        $tableName = $table->getName();
1012
        $instructions->addPostStep(function ($state) use ($foreignKey) {
1013 42
            $this->execute('pragma foreign_keys = ON');
1014
            $sql = substr($state['createSQL'], 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
1015
            $this->execute($sql);
1016
1017 42
            return $state;
1018
        });
1019 42
1020 View Code Duplication
        $instructions->addPostStep(function ($state) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1021
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
1022
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
1023
            $selectColumns = $writeColumns = $names;
1024
1025
            return compact('selectColumns', 'writeColumns') + $state;
1026
        });
1027
1028 42
        return $this->copyAndDropTmpTable($instructions, $tableName);
1029
    }
1030 42
1031 2
    /**
1032
     * {@inheritdoc}
1033 42
     */
1034
    protected function getDropForeignKeyInstructions($tableName, $constraint)
1035
    {
1036
        throw new \BadMethodCallException('SQLite does not have named foreign keys');
1037
    }
1038
1039
    /**
1040
     * {@inheritdoc}
1041
     */
1042 8
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
1043
    {
1044 8
        $instructions = $this->beginAlterByCopyTable($tableName);
1045 2
1046 2
        $instructions->addPostStep(function ($state) use ($columns) {
1047 6
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columns[0], $columns[0]);
1048
1049 8
            $selectColumns = $newState['selectColumns'];
1050 3
            $columns = array_map([$this, 'quoteColumnName'], $columns);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $columns, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
1051 3
            $diff = array_diff($columns, $selectColumns);
1052 6
1053 6
            if (!empty($diff)) {
1054 6
                throw new \InvalidArgumentException(sprintf(
1055 6
                    'The specified columns don\'t exist: ' . implode(', ', $diff)
1056 6
                ));
1057
            }
1058 8
1059 8
            return $newState + $state;
1060
        });
1061
1062
        $instructions->addPostStep(function ($state) use ($columns) {
1063
            $sql = '';
1064
1065 47
            foreach ($columns as $columnName) {
1066
                $search = sprintf(
1067 47
                    "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
1068
                    $this->quoteColumnName($columnName)
1069
                );
1070
                $sql = preg_replace($search, '', $state['createSQL'], 1);
1071
            }
1072
1073
            if ($sql) {
1074
                $this->execute($sql);
1075
            }
1076 5
1077
            return $state;
1078 5
        });
1079 5
1080
        return $this->copyAndDropTmpTable($instructions, $tableName);
1081
    }
1082 5
1083 5
    /**
1084 5
     * {@inheritdoc}
1085 5
     */
1086 5
    public function getSqlType($type, $limit = null)
1087 5
    {
1088 5
        switch ($type) {
1089 5
            case static::PHINX_TYPE_TEXT:
1090 5
            case static::PHINX_TYPE_INTEGER:
1091 5
            case static::PHINX_TYPE_FLOAT:
1092 5
            case static::PHINX_TYPE_DOUBLE:
1093 1
            case static::PHINX_TYPE_DECIMAL:
1094 1
            case static::PHINX_TYPE_DATETIME:
1095 5
            case static::PHINX_TYPE_TIME:
1096 1
            case static::PHINX_TYPE_DATE:
1097 1
            case static::PHINX_TYPE_BLOB:
1098
            case static::PHINX_TYPE_BOOLEAN:
1099 5
            case static::PHINX_TYPE_ENUM:
1100
                return ['name' => $type];
1101
            case static::PHINX_TYPE_STRING:
1102
                return ['name' => 'varchar', 'limit' => 255];
1103
            case static::PHINX_TYPE_CHAR:
1104
                return ['name' => 'char', 'limit' => 255];
1105
            case static::PHINX_TYPE_SMALL_INTEGER:
1106
                return ['name' => 'smallint'];
1107
            case static::PHINX_TYPE_BIG_INTEGER:
1108
                return ['name' => 'bigint'];
1109
            case static::PHINX_TYPE_TIMESTAMP:
1110
                return ['name' => 'datetime'];
1111
            case static::PHINX_TYPE_BINARY:
1112
                return ['name' => 'blob'];
1113
            case static::PHINX_TYPE_UUID:
1114
                return ['name' => 'char', 'limit' => 36];
1115
            case static::PHINX_TYPE_JSON:
1116
            case static::PHINX_TYPE_JSONB:
1117
                return ['name' => 'text'];
1118
            // Geospatial database types
1119
            // No specific data types exist in SQLite, instead all geospatial
1120
            // functionality is handled in the client. See also: SpatiaLite.
1121
            case static::PHINX_TYPE_GEOMETRY:
1122
            case static::PHINX_TYPE_POLYGON:
1123
                return ['name' => 'text'];
1124
            case static::PHINX_TYPE_LINESTRING:
1125
                return ['name' => 'varchar', 'limit' => 255];
1126
            case static::PHINX_TYPE_POINT:
1127
                return ['name' => 'float'];
1128
            default:
1129
                throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by SQLite.');
1130
        }
1131
    }
1132
1133
    /**
1134
     * Returns Phinx type by SQL type
1135
     *
1136
     * @param string $sqlTypeDef SQL type
1137
     * @throws UnsupportedColumnTypeException
1138
     * @return array
1139
     */
1140
    public function getPhinxType($sqlTypeDef)
1141
    {
1142
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
1143
            throw new UnsupportedColumnTypeException('Column type "' . $sqlTypeDef . '" is not supported by SQLite.');
1144
        } else {
1145
            $limit = null;
1146
            $scale = null;
1147
            $type = $matches[1];
1148
            if (count($matches) > 2) {
1149
                $limit = $matches[3] ?: null;
1150
            }
1151
            if (count($matches) > 4) {
1152
                $scale = $matches[5];
1153
            }
1154
            switch ($type) {
1155
                case 'varchar':
1156
                    $type = static::PHINX_TYPE_STRING;
1157
                    if ($limit === 255) {
1158
                        $limit = null;
1159
                    }
1160
                    break;
1161 View Code Duplication
                case 'char':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1162
                    $type = static::PHINX_TYPE_CHAR;
1163
                    if ($limit === 255) {
1164
                        $limit = null;
1165
                    }
1166
                    if ($limit === 36) {
1167
                        $type = static::PHINX_TYPE_UUID;
1168
                    }
1169
                    break;
1170
                case 'smallint':
1171
                    $type = static::PHINX_TYPE_SMALL_INTEGER;
1172
                    if ($limit === 11) {
1173
                        $limit = null;
1174
                    }
1175
                    break;
1176
                case 'int':
1177
                    $type = static::PHINX_TYPE_INTEGER;
1178
                    if ($limit === 11) {
1179
                        $limit = null;
1180
                    }
1181
                    break;
1182
                case 'bigint':
1183
                    if ($limit === 11) {
1184
                        $limit = null;
1185
                    }
1186
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1187
                    break;
1188
                case 'blob':
1189
                    $type = static::PHINX_TYPE_BINARY;
1190
                    break;
1191
            }
1192 View Code Duplication
            if ($type === 'tinyint') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1193
                if ($matches[3] === 1) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $matches[3] (string) and 1 (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1194
                    $type = static::PHINX_TYPE_BOOLEAN;
1195
                    $limit = null;
1196
                }
1197
            }
1198
1199
            try {
1200
                // Call this to check if parsed type is supported.
1201
                $this->getSqlType($type);
1202
            } catch (UnsupportedColumnTypeException $e) {
1203
                $type = Literal::from($type);
1204
            }
1205
1206
            return [
1207
                'name' => $type,
1208
                'limit' => $limit,
1209
                'scale' => $scale
1210
            ];
1211
        }
1212
    }
1213
1214
    /**
1215
     * {@inheritdoc}
1216
     */
1217
    public function createDatabase($name, $options = [])
1218
    {
1219
        touch($name . $this->suffix);
1220
    }
1221
1222
    /**
1223
     * {@inheritdoc}
1224
     */
1225
    public function hasDatabase($name)
1226
    {
1227
        return is_file($name . $this->suffix);
1228
    }
1229
1230
    /**
1231
     * {@inheritdoc}
1232
     */
1233
    public function dropDatabase($name)
1234
    {
1235
        if ($this->getOption('memory')) {
1236
            $this->disconnect();
1237
            $this->connect();
1238
        }
1239
        if (file_exists($name . $this->suffix)) {
1240
            unlink($name . $this->suffix);
1241
        }
1242
    }
1243
1244
    /**
1245
     * Gets the SQLite Column Definition for a Column object.
1246
     *
1247
     * @param \Phinx\Db\Table\Column $column Column
1248
     * @return string
1249
     */
1250
    protected function getColumnSqlDefinition(Column $column)
1251
    {
1252
        $isLiteralType = $column->getType() instanceof Literal;
1253
        if ($isLiteralType) {
1254
            $def = (string)$column->getType();
1255
        } else {
1256
            $sqlType = $this->getSqlType($column->getType());
1257
            $def = strtoupper($sqlType['name']);
1258
1259
            $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
1260
            if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
1261
                $def .= '(' . ($column->getLimit() ?: $sqlType['limit']) . ')';
1262
            }
1263
        }
1264
        if ($column->getPrecision() && $column->getScale()) {
1265
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1266
        }
1267 View Code Duplication
        if (($values = $column->getValues()) && is_array($values)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1268
            $def .= " CHECK({$this->quoteColumnName($column->getName())} IN ('" . implode("', '", $values) . "'))";
1269
        }
1270
1271
        $default = $column->getDefault();
1272
1273
        $def .= (!$column->isIdentity() && ($column->isNull() || is_null($default))) ? ' NULL' : ' NOT NULL';
1274
        $def .= $this->getDefaultValueDefinition($default, $column->getType());
0 ignored issues
show
Bug introduced by
It seems like $column->getType() targeting Phinx\Db\Table\Column::getType() can also be of type object<Phinx\Util\Literal>; however, Phinx\Db\Adapter\PdoAdap...efaultValueDefinition() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1275
        $def .= $column->isIdentity() ? ' PRIMARY KEY AUTOINCREMENT' : '';
1276
1277
        if ($column->getUpdate()) {
1278
            $def .= ' ON UPDATE ' . $column->getUpdate();
1279
        }
1280
1281
        $def .= $this->getCommentDefinition($column);
1282
1283
        return $def;
1284
    }
1285
1286
    /**
1287
     * Gets the comment Definition for a Column object.
1288
     *
1289
     * @param \Phinx\Db\Table\Column $column Column
1290
     * @return string
1291
     */
1292
    protected function getCommentDefinition(Column $column)
1293
    {
1294
        if ($column->getComment()) {
1295
            return ' /* ' . $column->getComment() . ' */ ';
1296
        }
1297
1298
        return '';
1299
    }
1300
1301
    /**
1302
     * Gets the SQLite Index Definition for an Index object.
1303
     *
1304
     * @param \Phinx\Db\Table\Table $table Table
1305
     * @param \Phinx\Db\Table\Index $index Index
1306
     * @return string
1307
     */
1308
    protected function getIndexSqlDefinition(Table $table, Index $index)
1309
    {
1310
        if ($index->getType() === Index::UNIQUE) {
1311
            $def = 'UNIQUE INDEX';
1312
        } else {
1313
            $def = 'INDEX';
1314
        }
1315
        if (is_string($index->getName())) {
1316
            $indexName = $index->getName();
1317
        } else {
1318
            $indexName = $table->getName() . '_';
1319
            foreach ($index->getColumns() as $column) {
1320
                $indexName .= $column . '_';
1321
            }
1322
            $indexName .= 'index';
1323
        }
1324
        $def .= ' `' . $indexName . '`';
1325
1326
        return $def;
1327
    }
1328
1329
    /**
1330
     * {@inheritdoc}
1331
     */
1332
    public function getColumnTypes()
1333
    {
1334
        return array_merge(parent::getColumnTypes(), ['enum', 'json', 'jsonb']);
1335
    }
1336
1337
    /**
1338
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1339
     *
1340
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1341
     * @return string
1342
     */
1343 View Code Duplication
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1344
    {
1345
        $def = '';
1346
        if ($foreignKey->getConstraint()) {
1347
            $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
0 ignored issues
show
Bug introduced by
It seems like $foreignKey->getConstraint() targeting Phinx\Db\Table\ForeignKey::getConstraint() can also be of type boolean; however, Phinx\Db\Adapter\SQLiteAdapter::quoteColumnName() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1348
        } else {
1349
            $columnNames = [];
1350
            foreach ($foreignKey->getColumns() as $column) {
1351
                $columnNames[] = $this->quoteColumnName($column);
1352
            }
1353
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1354
            $refColumnNames = [];
1355
            foreach ($foreignKey->getReferencedColumns() as $column) {
1356
                $refColumnNames[] = $this->quoteColumnName($column);
1357
            }
1358
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1359
            if ($foreignKey->getOnDelete()) {
1360
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1361
            }
1362
            if ($foreignKey->getOnUpdate()) {
1363
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1364
            }
1365
        }
1366
1367
        return $def;
1368
    }
1369
1370
    /**
1371
     * {@inheritDoc}
1372
     *
1373
     */
1374
    public function getDecoratedConnection()
1375
    {
1376
        $options = $this->getOptions();
1377
        $options['quoteIdentifiers'] = true;
1378
        $database = ':memory:';
0 ignored issues
show
Unused Code introduced by
$database is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1379
1380
        if (!empty($options['name'])) {
1381
            $options['database'] = $options['name'];
1382
1383
            if (file_exists($options['name'] . $this->suffix)) {
1384
                $options['database'] = $options['name'] . $this->suffix;
1385
            }
1386
        }
1387
1388
        $driver = new SqliteDriver($options);
1389
        if (method_exists($driver, 'setConnection')) {
1390
            $driver->setConnection($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can be null; however, setConnection() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1391
        } else {
1392
            $driver->connection($this->connection);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Database\Driver::connection() has been deprecated with message: 3.6.0 Use getConnection()/setConnection() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1393
        }
1394
1395
        return new Connection(['driver' => $driver] + $options);
1396
    }
1397
}
1398