Completed
Pull Request — master (#1551)
by
unknown
02:15
created

SQLiteAdapter::getSchemaName()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 7
cts 7
cp 1
rs 9.7
c 0
b 0
f 0
cc 4
nc 6
nop 2
crap 4
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
    // list of supported Phinx column types with their SQL equivalents
49
    // some types have an affinity appended to ensure they do not receive NUMERIC affinity
50
    protected static $supportedColumnTypes = [
51
        self::PHINX_TYPE_BIG_INTEGER => 'biginteger',
52
        self::PHINX_TYPE_BINARY => 'binary_blob',
53
        self::PHINX_TYPE_BLOB => 'blob',
54
        self::PHINX_TYPE_BOOLEAN => 'boolean_integer',
55
        self::PHINX_TYPE_CHAR => 'char',
56 42
        self::PHINX_TYPE_DATE => 'date_text',
57
        self::PHINX_TYPE_DATETIME => 'datetime_text',
58 42
        self::PHINX_TYPE_DOUBLE => 'double',
59 42
        self::PHINX_TYPE_FLOAT => 'float',
60
        self::PHINX_TYPE_INTEGER => 'integer',
61
        self::PHINX_TYPE_JSON => 'json_text',
62
        self::PHINX_TYPE_JSONB => 'jsonb_text',
63
        self::PHINX_TYPE_SMALL_INTEGER => 'smallinteger',
64
        self::PHINX_TYPE_STRING => 'varchar',
65 42
        self::PHINX_TYPE_TEXT => 'text',
66 42
        self::PHINX_TYPE_TIME => 'time_text',
67
        self::PHINX_TYPE_UUID => 'uuid_text',
68
        self::PHINX_TYPE_TIMESTAMP => 'timestamp_text',
69 42
        self::PHINX_TYPE_VARBINARY => 'varbinary_blob'
70
    ];
71
72 42
    // list of aliases of supported column types
73 42
    protected static $supportedColumnTypeAliases = [
74 42
        'varchar' => self::PHINX_TYPE_STRING,
75 42
        'tinyint' => self::PHINX_TYPE_SMALL_INTEGER,
76
        'tinyinteger' => self::PHINX_TYPE_SMALL_INTEGER,
77
        'smallint' => self::PHINX_TYPE_SMALL_INTEGER,
78
        'int' => self::PHINX_TYPE_INTEGER,
79 42
        'mediumint' => self::PHINX_TYPE_INTEGER,
80 42
        'mediuminteger' => self::PHINX_TYPE_INTEGER,
81
        'bigint' => self::PHINX_TYPE_BIG_INTEGER,
82
        'tinytext' => self::PHINX_TYPE_TEXT,
83
        'mediumtext' => self::PHINX_TYPE_TEXT,
84
        'longtext' => self::PHINX_TYPE_TEXT,
85
        'tinyblob' => self::PHINX_TYPE_BLOB,
86
        'mediumblob' => self::PHINX_TYPE_BLOB,
87 42
        'longblob' => self::PHINX_TYPE_BLOB,
88 42
        'real' => self::PHINX_TYPE_FLOAT,
89 42
        'numeric' => self::PHINX_TYPE_FLOAT,
90
    ];
91
92
    // list of known but unsupported Phinx column types
93
    protected static $unsupportedColumnTypes = [
94 48
        self::PHINX_TYPE_BIT,
95
        self::PHINX_TYPE_CIDR,
96 48
        self::PHINX_TYPE_DECIMAL,
97 48
        self::PHINX_TYPE_ENUM,
98
        self::PHINX_TYPE_FILESTREAM,
99
        self::PHINX_TYPE_GEOMETRY,
100
        self::PHINX_TYPE_INET,
101
        self::PHINX_TYPE_INTERVAL,
102
        self::PHINX_TYPE_LINESTRING,
103
        self::PHINX_TYPE_MACADDR,
104
        self::PHINX_TYPE_POINT,
105
        self::PHINX_TYPE_POLYGON,
106
        self::PHINX_TYPE_SET
107
    ];
108
109
    protected $definitionsWithLimits = [
110 1
        'CHAR',
111
        'CHARACTER',
112 1
        'VARCHAR',
113 1
        'VARYING CHARACTER',
114
        'NCHAR',
115
        'NATIVE CHARACTER',
116
        'NVARCHAR'
117
    ];
118
119
    protected $suffix = '.sqlite3';
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function connect()
125
    {
126
        if ($this->connection === null) {
127
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
128
                // @codeCoverageIgnoreStart
129
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
130
                // @codeCoverageIgnoreEnd
131
            }
132
133
            $db = null;
134 43
            $options = $this->getOptions();
135
136 43
            // use a memory database if the option was specified
137
            if (!empty($options['memory'])) {
138
                $dsn = 'sqlite::memory:';
139
            } else {
140
                $dsn = 'sqlite:' . $options['name'] . $this->suffix;
141
            }
142 44
143
            try {
144 44
                $db = new \PDO($dsn);
145
            } catch (\PDOException $exception) {
146
                throw new \InvalidArgumentException(sprintf(
147
                    'There was a problem connecting to the database: %s',
148
                    $exception->getMessage()
149
                ));
150 42
            }
151
152 42
            $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
153 42
            $this->setConnection($db);
154 42
        }
155 12
    }
156 42
157
    /**
158 42
     * {@inheritdoc}
159
     */
160
    public function setOptions(array $options)
161
    {
162
        parent::setOptions($options);
163
164 42
        if (isset($options['suffix'])) {
165
            $this->suffix = $options['suffix'];
166
        }
167 42
        //don't "fix" the file extension if it is blank, some people
168 42
        //might want a SQLITE db file with absolutely no extension.
169 42
        if (strlen($this->suffix) && substr($this->suffix, 0, 1) !== '.') {
170 35
            $this->suffix = '.' . $this->suffix;
171 35
        }
172 35
173 35
        return $this;
174
    }
175 35
176 42
    /**
177
     * {@inheritdoc}
178 1
     */
179 1
    public function disconnect()
180 1
    {
181 1
        $this->connection = null;
182
    }
183 1
184 1
    /**
185
     * {@inheritdoc}
186
     */
187 42
    public function hasTransactions()
188 42
    {
189 42
        return true;
190 42
    }
191 42
192
    /**
193
     * {@inheritdoc}
194 42
     */
195 42
    public function beginTransaction()
196 42
    {
197 42
        $this->getConnection()->beginTransaction();
198 42
    }
199 42
200
    /**
201
     * {@inheritdoc}
202 1
     */
203 1
    public function commitTransaction()
204 1
    {
205
        $this->getConnection()->commit();
206 1
    }
207 1
208 1
    /**
209 1
     * {@inheritdoc}
210 1
     */
211 1
    public function rollbackTransaction()
212 42
    {
213 42
        $this->getConnection()->rollBack();
214 37
    }
215
216
    /**
217
     * {@inheritdoc}
218 42
     */
219 42
    public function quoteTableName($tableName)
220 1
    {
221 1
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
222 1
    }
223 1
224
    /**
225 42
     * {@inheritdoc}
226
     */
227 42
    public function quoteColumnName($columnName)
228
    {
229 42
        return '`' . str_replace('`', '``', $columnName) . '`';
230 6
    }
231 42
232 42
    /**
233
     * @param string $tableName Table name
234
     * @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
235
     * @return array
236
     */
237 1
    protected function getSchemaName($tableName, $quoted = false)
238
    {
239 1
        if (preg_match("/.\.([^\.]+)$/", $tableName, $match)) {
240 1
            $table = $match[1];
241
            $schema = substr($tableName, 0, strlen($tableName) - strlen($match[0]) + 1);
242
            $result = ['schema' => $schema, 'table' => $table];
243
        } else {
244
            $result = ['schema' => '', 'table' => $tableName];
245 1
        }
246
247 1
        if ($quoted) {
248 1
            $result['schema'] = strlen($result['schema']) ? $this->quoteColumnName($result['schema']) . '.' : '';
249
            $result['table'] = $this->quoteColumnName($result['table']);
250
        }
251
252
        return $result;
253 1
    }
254
255 1
    /**
256 1
     * Retrieves information about a given table from one of the SQLite pragmas
257 1
     *
258 1
     * @param string $tableName The table to query
259
     * @param string $pragma The pragma to query
260 1
     * @return array
261 1
     */
262
    protected function getTableInfo($tableName, $pragma = 'table_info')
263
    {
264
        $info = $this->getSchemaName($tableName, true);
265
        return $this->fetchAll(sprintf('PRAGMA %s%s(%s)', $info['schema'], $pragma, $info['table']));
266 1
    }
267
268 1
    /**
269 1
     * {@inheritdoc}
270
     */
271 1
    public function hasTable($tableName)
272 1
    {
273 1
        $info = $this->getSchemaName($tableName);
274 1
        if ($info['schema'] === '') {
275 1
            // if no schema is specified we search all schemata
276 1
            $rows = $this->fetchAll('PRAGMA database_list;');
277
            $schemata = [];
278 1
            foreach ($rows as $row) {
279 1
                $schemata[] = $row['name'];
280 1
            }
281
        } else {
282 1
            // otherwise we search just the specified schema
283 1
            $schemata = (array)$info['schema'];
284 1
        }
285
286 1
        $table = strtolower($info['table']);
287 1
        foreach ($schemata as $schema) {
288
            if (strtolower($schema) === 'temp') {
289 1
                $master = 'sqlite_temp_master';
290
            } else {
291
                $master = sprintf('%s.%s', $this->quoteColumnName($schema), 'sqlite_master');
292
            }
293
            try {
294
                $rows = $this->fetchAll(sprintf('SELECT name FROM %s WHERE type=\'table\' AND lower(name) = %s', $master, $this->quoteString($table)));
295 8
            } catch (\PDOException $e) {
296
                // an exception can occur if the schema part of the table refers to a database which is not attached
297 8
                return false;
298 8
            }
299 8
300 7
            // this somewhat pedantic check with strtolower is performed because the SQL lower function may be redefined,
301
            // and can act on all Unicode characters if the ICU extension is loaded, while SQL identifiers are only case-insensitive for ASCII
302 8
            foreach ($rows as $row) {
303
                if (strtolower($row['name']) === $table) {
304 8
                    return true;
305
                }
306
            }
307
        }
308
309
        return false;
310 4
    }
311
312 4
    /**
313 4
     * {@inheritdoc}
314 4
     */
315 4
    public function createTable(Table $table, array $columns = [], array $indexes = [])
316 4
    {
317 4
        // Add the default primary key
318
        $options = $table->getOptions();
319 4 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...
320 4
            $options['id'] = 'id';
321
        }
322
323
        if (isset($options['id']) && is_string($options['id'])) {
324
            // Handle id => "field_name" to support AUTO_INCREMENT
325 2
            $column = new Column();
326
            $column->setName($options['id'])
327 2
                   ->setType('integer')
328
                   ->setIdentity(true);
329 2
330
            array_unshift($columns, $column);
331 2
        }
332 2
333 2
        $sql = 'CREATE TABLE ';
334 2
        $sql .= $this->quoteTableName($table->getName()) . ' (';
335 2
        foreach ($columns as $column) {
336 2
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
337
338 2
            if (isset($options['primary_key']) && $column->getIdentity()) {
339 2
                //remove column from the primary key array as it is already defined as an autoincrement
340 2
                //primary id
341 2
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
342 2
                if ($identityColumnIndex !== false) {
343 2
                    unset($options['primary_key'][$identityColumnIndex]);
344 2
345 2
                    if (empty($options['primary_key'])) {
346 2
                        //The last primary key has been removed
347
                        unset($options['primary_key']);
348 2
                    }
349 1
                }
350
            }
351 1
        }
352
353
        // set the primary key(s)
354 1 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...
355
            $sql = rtrim($sql);
356 1
            $sql .= ' PRIMARY KEY (';
357 1
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
358 1
                $sql .= $this->quoteColumnName($options['primary_key']);
359
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
360 1
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
361 1
            }
362
            $sql .= ')';
363
        } else {
364 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
365 1
        }
366 1
367 1
        $sql = rtrim($sql) . ');';
368 1
        // execute the sql
369
        $this->execute($sql);
370 1
371
        foreach ($indexes as $index) {
372 1
            $this->addIndex($table, $index);
373
        }
374 1
    }
375 1
376
    /**
377
     * {@inheritdoc}
378
     */
379
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
380 6
    {
381
        $instructions = new AlterInstructions();
382
383
        // Drop the existing primary key
384 6
        $primaryKey = $this->getPrimaryKey($table->getName());
385
        if (!empty($primaryKey)) {
386 6
            $instructions->merge(
387
                // FIXME: array access is a hack to make this incomplete implementation work with a correct getPrimaryKey implementation
388 6
                $this->getDropPrimaryKeyInstructions($table, $primaryKey[0])
389 6
            );
390 6
        }
391 6
392 6
        // Add the primary key(s)
393 6
        if (!empty($newColumns)) {
394
            if (!is_string($newColumns)) {
395 6
                throw new \InvalidArgumentException(sprintf(
396 6
                    "Invalid value for primary key: %s",
397 6
                    json_encode($newColumns)
398 6
                ));
399 6
            }
400 6
401 6
            $instructions->merge(
402 6
                $this->getAddPrimaryKeyInstructions($table, $newColumns)
403 6
            );
404
        }
405 6
406
        return $instructions;
407
    }
408
409
    /**
410
     * {@inheritdoc}
411 6
     */
412
    protected function getChangeCommentInstructions(Table $table, $newComment)
413 6
    {
414 6
        throw new \BadMethodCallException('SQLite does not have table comments');
415 6
    }
416 6
417
    /**
418 6
     * {@inheritdoc}
419
     */
420 6 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...
421
    {
422 6
        $sql = sprintf(
423 6
            'ALTER TABLE %s RENAME TO %s',
424 6
            $this->quoteTableName($tableName),
425 6
            $this->quoteTableName($newTableName)
426 6
        );
427
428 6
        return new AlterInstructions([], [$sql]);
429
    }
430 6
431 6
    /**
432 6
     * {@inheritdoc}
433
     */
434
    protected function getDropTableInstructions($tableName)
435
    {
436
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
437 2
438
        return new AlterInstructions([], [$sql]);
439
    }
440 2
441
    /**
442 2
     * {@inheritdoc}
443
     */
444 2
    public function truncateTable($tableName)
445 2
    {
446 2
        $sql = sprintf(
447 2
            'DELETE FROM %s',
448 2
            $this->quoteTableName($tableName)
449 2
        );
450
451 2
        $this->execute($sql);
452 2
    }
453 2
454 2
    /**
455 2
     * {@inheritdoc}
456 2
     */
457 2
    public function getColumns($tableName)
458 2
    {
459 2
        $columns = [];
460
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
461 2
462
        foreach ($rows as $columnInfo) {
463 2
            $column = new Column();
464
            $type = strtolower($columnInfo['type']);
465
            $column->setName($columnInfo['name'])
466
                   ->setNull($columnInfo['notnull'] !== '1')
467
                   ->setDefault($columnInfo['dflt_value']);
468
469 2
            $phinxType = $this->getPhinxType($type);
470
471 2
            $column->setType($phinxType['name'])
472 2
                   ->setLimit($phinxType['limit'])
473 2
                   ->setScale($phinxType['scale']);
474
475 2
            if ($columnInfo['pk'] == 1) {
476
                $column->setIdentity(true);
477 2
            }
478 2
479 2
            $columns[] = $column;
480
        }
481 2
482
        return $columns;
483 2
    }
484 2
485 2
    /**
486 2
     * {@inheritdoc}
487 2
     */
488 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...
489 2
    {
490
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
491 2
        foreach ($rows as $column) {
492 2
            if (strcasecmp($column['name'], $columnName) === 0) {
493 2
                return true;
494
            }
495
        }
496
497
        return false;
498
    }
499
500
    /**
501 9
     * {@inheritdoc}
502
     */
503 9 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...
504 9
    {
505
        $alter = sprintf(
506 9
            'ALTER TABLE %s ADD COLUMN %s %s',
507 9
            $this->quoteTableName($table->getName()),
508 9
            $this->quoteColumnName($column->getName()),
509 9
            $this->getColumnSqlDefinition($column)
510 9
        );
511 9
512 9
        return new AlterInstructions([], [$alter]);
513 9
    }
514 9
515 9
    /**
516
     * Returns the original CREATE statement for the give table
517
     *
518
     * @param string $tableName The table name to get the create statement for
519
     * @return string
520
     */
521 9
    protected function getDeclaringSql($tableName)
522
    {
523 9
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
524 4
525 4
        $sql = '';
526
        foreach ($rows as $table) {
527 9
            if ($table['tbl_name'] === $tableName) {
528 9
                $sql = $table['sql'];
529
            }
530 9
        }
531 9
532 9
        return $sql;
533 9
    }
534
535 8
    /**
536
     * Copies all the data from a tmp table to another table
537 8
     *
538
     * @param string $tableName The table name to copy the data to
539
     * @param string $tmpTableName The tmp table name where the data is stored
540
     * @param string[] $writeColumns The list of columns in the target table
541
     * @param string[] $selectColumns The list of columns in the tmp table
542
     * @return void
543 1
     */
544
    protected function copyDataToNewTable($tableName, $tmpTableName, $writeColumns, $selectColumns)
545 1
    {
546
        $sql = sprintf(
547 1
            'INSERT INTO %s(%s) SELECT %s FROM %s',
548 1
            $this->quoteTableName($tableName),
549 1
            implode(', ', $writeColumns),
550
            implode(', ', $selectColumns),
551
            $this->quoteTableName($tmpTableName)
552
        );
553
        $this->execute($sql);
554
    }
555
556
    /**
557
     * Modifies the passed instructions to copy all data from the tmp table into
558
     * the provided table and then drops the tmp table.
559 8
     *
560
     * @param AlterInstructions $instructions The instructions to modify
561 8
     * @param string $tableName The table name to copy the data to
562 8
     * @return AlterInstructions
563 8
     */
564 8
    protected function copyAndDropTmpTable($instructions, $tableName)
565 8
    {
566 8
        $instructions->addPostStep(function ($state) use ($tableName) {
567 8
            $this->copyDataToNewTable(
568 8
                $tableName,
569 8
                $state['tmpTableName'],
570 8
                $state['writeColumns'],
571
                $state['selectColumns']
572 8
            );
573 8
574 8
            $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($state['tmpTableName'])));
575
576
            return $state;
577
        });
578
579 1
        return $instructions;
580
    }
581 1
582 1
    /**
583 1
     * Returns the columns and type to use when copying a table to another in the process
584
     * of altering a table
585 1
     *
586 1
     * @param string $tableName The table to modify
587
     * @param string $columnName The column name that is about to change
588 1
     * @param string|false $newColumnName Optionally the new name for the column
589 1
     * @return AlterInstructions
590 1
     */
591 1
    protected function calculateNewTableColumns($tableName, $columnName, $newColumnName)
592 1
    {
593 1
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
594 1
        $selectColumns = [];
595 1
        $writeColumns = [];
596 1
        $columnType = null;
597 1
        $found = false;
598
599
        foreach ($columns as $column) {
600
            $selectName = $column['name'];
601
            $writeName = $selectName;
602
603
            if ($selectName == $columnName) {
604
                $writeName = $newColumnName;
605 1
                $found = true;
606
                $columnType = $column['type'];
607 1
                $selectName = $newColumnName === false ? $newColumnName : $selectName;
608
            }
609 1
610 1
            $selectColumns[] = $selectName;
611 1
            $writeColumns[] = $writeName;
612 1
        }
613 1
614 1
        $selectColumns = array_filter($selectColumns, 'strlen');
615 1
        $writeColumns = array_filter($writeColumns, 'strlen');
616 1
        $selectColumns = array_map([$this, 'quoteColumnName'], $selectColumns);
617 1
        $writeColumns = array_map([$this, 'quoteColumnName'], $writeColumns);
618
619
        if (!$found) {
620
            throw new \InvalidArgumentException(sprintf(
621
                'The specified column doesn\'t exist: ' . $columnName
622
            ));
623
        }
624
625 5
        return compact('writeColumns', 'selectColumns', 'columnType');
626
    }
627 5
628
    /**
629
     * Returns the initial instructions to alter a table using the
630 5
     * rename-alter-copy strategy
631
     *
632 5
     * @param string $tableName The table to modify
633 5
     * @return AlterInstructions
634 5
     */
635
    protected function beginAlterByCopyTable($tableName)
636 1
    {
637
        $instructions = new AlterInstructions();
638
        $instructions->addPostStep(function ($state) use ($tableName) {
639
            $createSQL = $this->getDeclaringSql($tableName);
640
641
            $tmpTableName = 'tmp_' . $tableName;
642
            $this->execute(
643
                sprintf(
644
                    'ALTER TABLE %s RENAME TO %s',
645 5
                    $this->quoteTableName($tableName),
646
                    $this->quoteTableName($tmpTableName)
647 5
                )
648 5
            );
649
650
            return compact('createSQL', 'tmpTableName') + $state;
651
        });
652
653
        return $instructions;
654
    }
655
656
    /**
657
     * {@inheritdoc}
658
     */
659
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
660
    {
661 5
        $instructions = $this->beginAlterByCopyTable($tableName);
662 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...
663 5
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
664 5
665 5
            return $newState + $state;
666 5
        });
667 5
668 5
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
669 5
            $sql = str_replace(
670 5
                $this->quoteColumnName($columnName),
671 5
                $this->quoteColumnName($newColumnName),
672 5
                $state['createSQL']
673 5
            );
674
            $this->execute($sql);
675
676
            return $state;
677
        });
678
679 4
        return $this->copyAndDropTmpTable($instructions, $tableName);
680
    }
681
682 4
    /**
683
     * {@inheritdoc}
684 4
     */
685 4
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
686
    {
687 4
        $instructions = $this->beginAlterByCopyTable($tableName);
688 4
689 4
        $newColumnName = $newColumn->getName();
690 4 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...
691 4
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
692 4
693
            return $newState + $state;
694 4
        });
695 4
696 4
        $instructions->addPostStep(function ($state) use ($columnName, $newColumn) {
697 4
            $sql = preg_replace(
698 4
                sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
699
                sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
700 4
                $state['createSQL'],
701
                1
702 4
            );
703 4
            $this->execute($sql);
704
705 4
            return $state;
706 4
        });
707 4
708 4
        return $this->copyAndDropTmpTable($instructions, $tableName);
709 4
    }
710 4
711 4
    /**
712
     * {@inheritdoc}
713 4
     */
714 4
    protected function getDropColumnInstructions($tableName, $columnName)
715 4
    {
716
        $instructions = $this->beginAlterByCopyTable($tableName);
717
718 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...
719
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, false);
720 1
721
            return $newState + $state;
722
        });
723 1
724
        $instructions->addPostStep(function ($state) use ($columnName) {
725
            $sql = preg_replace(
726
                sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($state['columnType'])),
727 1
                "",
728
                $state['createSQL']
729 1
            );
730
731 1
            if (substr($sql, -2) === ', ') {
732 1
                $sql = substr($sql, 0, -2) . ')';
733 1
            }
734 1
735 1
            $this->execute($sql);
736 1
737
            return $state;
738 1
        });
739 1
740 1
        return $this->copyAndDropTmpTable($instructions, $tableName);
741 1
    }
742 1
743 1
    /**
744 1
     * Get an array of indexes from a particular table.
745
     *
746 1
     * @param string $tableName Table Name
747
     * @return array
748 1
     */
749
    protected function getIndexes($tableName)
750
    {
751
        $indexes = [];
752
        $schema = $this->getSchemaName($tableName, true)['schema'];
753
        $indexList = $this->getTableInfo($tableName, 'index_list');
754 1
755
        foreach ($indexList as $index) {
756 1
            $indexData = $this->fetchAll(sprintf('pragma %sindex_info(%s)', $schema, $this->quoteColumnName($index['name'])));
757 1
            $cols = [];
758 1
            foreach ($indexData as $indexItem) {
759 1
                $cols[] = $indexItem['name'];
760 1
            }
761 1
            $indexes[$index['name']] = $cols;
762 1
        }
763
764 1
        return $indexes;
765
    }
766 1
767 1
    /**
768 1
     * Finds the names of a table's indexes matching the supplied columns
769 1
     *
770 1
     * @param string $tableName The table to which the index belongs
771
     * @param string|string[] $columns The columns of the index
772 1
     * @return array
773
     */
774 1
    protected function resolveIndex($tableName, $columns)
775 1
    {
776 1
        $columns = array_map('strtolower', (array)$columns);
777
        $indexes = $this->getIndexes($tableName);
778
        $matches = [];
779
780
        foreach ($indexes as $name => $index) {
781
            $indexCols = array_map('strtolower', $index);
782
            if ($columns == $indexCols) {
783
                $matches[] = $name;
784
            }
785
        }
786
787
        return $matches;
788
    }
789
790
    /**
791
     * {@inheritdoc}
792
     */
793
    public function hasIndex($tableName, $columns)
794
    {
795
        return (bool)$this->resolveIndex($tableName, $columns);
796
    }
797
798
    /**
799
     * {@inheritdoc}
800
     */
801
    public function hasIndexByName($tableName, $indexName)
802
    {
803
        $indexName = strtolower($indexName);
804
        $indexes = $this->getIndexes($tableName);
805
806
        foreach (array_keys($indexes) as $index) {
807
            if ($indexName === strtolower($index)) {
808
                return true;
809
            }
810 43
        }
811
812
        return false;
813 43
    }
814 42
815
    /**
816 43
     * {@inheritdoc}
817
     */
818
    protected function getAddIndexInstructions(Table $table, Index $index)
819 43
    {
820 1
        $indexColumnArray = [];
821
        foreach ($index->getColumns() as $column) {
822 43
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
823 38
        }
824
        $indexColumns = implode(',', $indexColumnArray);
825 43
        $sql = sprintf(
826 42
            'CREATE %s ON %s (%s)',
827
            $this->getIndexSqlDefinition($table, $index),
828 43
            $this->quoteTableName($table->getName()),
829 2
            $indexColumns
830
        );
831 43
832 1
        return new AlterInstructions([], [$sql]);
833
    }
834 43
835 1
    /**
836
     * {@inheritdoc}
837 43
     */
838 42
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
839
    {
840 43
        $instructions = new AlterInstructions();
841 1
        $indexNames = $this->resolveIndex($tableName, $columns);
842
        $schema = $this->getSchemaName($tableName, true)['schema'];
843 43
        foreach ($indexNames as $indexName) {
844 1
            if (strpos($indexName, 'sqlite_autoindex_') !== 0) {
845
                $instructions->addPostStep(sprintf(
846 43
                    'DROP INDEX %s%s',
847 43
                    $schema,
848 1
                    $this->quoteColumnName($indexName)
849
                ));
850 43
            }
851 42
        }
852
853 5
        return $instructions;
854
    }
855 5
856 4
    /**
857
     * {@inheritdoc}
858
     */
859
    protected function getDropIndexByNameInstructions($tableName, $indexName)
860 1
    {
861 1
        $instructions = new AlterInstructions();
862
        $indexName = strtolower($indexName);
863
        $indexes = $this->getIndexes($tableName);
864 1
865
        $found = false;
866
        foreach (array_keys($indexes) as $index) {
867 1
            if ($indexName === strtolower($index)) {
868
                $found = true;
869 1
                break;
870 1
            }
871 1
        }
872
873
        if ($found) {
874
            $schema = $this->getSchemaName($tableName, true)['schema'];
875
                $instructions->addPostStep(sprintf(
876
                    'DROP INDEX %s%s',
877
                    $schema,
878
                    $this->quoteColumnName($indexName)
879
                ));
880 3
        }
881
882 3
        return $instructions;
883 1
    }
884
885 2
    /**
886 2
     * {@inheritdoc}
887 2
     */
888 2
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
889 1
    {
890 1
        if (!is_null($constraint)) {
891 2
            throw new \InvalidArgumentException('SQLite does not support named constraints.');
892
        }
893
894 2
        $columns = array_map('strtolower', (array)$columns);
895 2
        $primaryKey = array_map('strtolower', $this->getPrimaryKey($tableName));
896 1
897 1
        if (array_diff($primaryKey, $columns) || array_diff($columns, $primaryKey)) {
898
            return false;
899
        }
900 1
        
901 2
        return true;
902
    }
903
904
    /**
905
     * Get the primary key from a particular table.
906
     *
907
     * @param string $tableName Table Name
908
     * @return string[]
909
     */
910 2
    protected function getPrimaryKey($tableName)
911
    {
912
        $primaryKey = [];
913
914
        $rows = $this->getTableInfo($tableName);
915
916 2
        foreach ($rows as $row) {
917 1
            if ($row['pk'] > 0) {
918
                $primaryKey[$row['pk'] - 1] = $row['name'];
919
            }
920 1
        }
921 1
922 2
        return $primaryKey;
923 1
    }
924 1
925 2
    /**
926 2
     * {@inheritdoc}
927
     */
928
    public function hasForeignKey($tableName, $columns, $constraint = null)
929
    {
930
        if (!is_null($constraint)) {
931
            throw new \InvalidArgumentException('SQLite does not support named constraints.');
932
        }
933 2
934
        $columns = array_map('strtolower', (array)$columns);
935
        $foreignKeys = $this->getForeignKeys($tableName);
936 1
937 1
        foreach ($foreignKeys as $key) {
938
            $key = array_map('strtolower', $key);
939 1
            if (array_diff($key, $columns) || array_diff($columns, $key)) {
940
                continue;
941
            }
942
            return true;
943
        }
944
945
        return false;
946 48
    }
947
948 48
    /**
949 48
     * Get an array of foreign keys from a particular table.
950
     *
951
     * @param string $tableName Table Name
952
     * @return array
953
     */
954 2
    protected function getForeignKeys($tableName)
955
    {
956 2
        $foreignKeys = [];
957
958
        $rows = $this->getTableInfo($tableName, 'foreign_key_list');
959
960
        foreach ($rows as $row) {
961
            if (!isset($foreignKeys[$row['id']])) {
962 48
                $foreignKeys[$row['id']] = [];
963
            }
964 48
            $foreignKeys[$row['id']][$row['seq']] = $row['from'];
965 47
        }
966 47
967 48
        return $foreignKeys;
968
    }
969
970
    /**
971
     * @param Table $table The Table
972
     * @param string $column Column Name
973
     * @return AlterInstructions
974
     */
975 42
    protected function getAddPrimaryKeyInstructions(Table $table, $column)
976
    {
977 42
        $instructions = $this->beginAlterByCopyTable($table->getName());
978 8
979 42
        $tableName = $table->getName();
980 42
        $instructions->addPostStep(function ($state) use ($column) {
981 42
            $matchPattern = "/(`$column`)\s+(\w+(\(\d+\))?)\s+((NOT )?NULL)/";
982 42
983
            $sql = $state['createSQL'];
984
985
            if (preg_match($matchPattern, $state['createSQL'], $matches)) {
986
                if (isset($matches[2])) {
987
                    if ($matches[2] === 'INTEGER') {
988
                        $replace = '$1 INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT';
989
                    } else {
990
                        $replace = '$1 $2 NOT NULL PRIMARY KEY';
991 42
                    }
992
993 42
                    $sql = preg_replace($matchPattern, $replace, $state['createSQL'], 1);
994 42
                }
995 42
            }
996 42
997
            $this->execute($sql);
998
999 42
            return $state;
1000 42
        });
1001 42
1002 42 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...
1003 42
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
1004 4
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
1005 4
            $selectColumns = $writeColumns = $names;
1006
1007 42
            return compact('selectColumns', 'writeColumns') + $state;
1008
        });
1009 42
1010 42
        return $this->copyAndDropTmpTable($instructions, $tableName);
1011 42
    }
1012
1013 42
    /**
1014
     * @param Table $table Table
1015
     * @param string $column Column Name
1016
     * @return AlterInstructions
1017 42
     */
1018
    protected function getDropPrimaryKeyInstructions($table, $column)
1019 42
    {
1020
        $instructions = $this->beginAlterByCopyTable($table->getName());
1021
1022
        $instructions->addPostStep(function ($state) use ($column) {
1023
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $column, $column);
1024
1025
            return $newState + $state;
1026
        });
1027
1028 42
        $instructions->addPostStep(function ($state) {
1029
            $search = "/(,?\s*PRIMARY KEY\s*\([^\)]*\)|\s+PRIMARY KEY(\s+AUTOINCREMENT)?)/";
1030 42
            $sql = preg_replace($search, '', $state['createSQL'], 1);
1031 2
1032
            if ($sql) {
1033 42
                $this->execute($sql);
1034
            }
1035
1036
            return $state;
1037
        });
1038
1039
        return $this->copyAndDropTmpTable($instructions, $table->getName());
1040
    }
1041
1042 8
    /**
1043
     * {@inheritdoc}
1044 8
     */
1045 2
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
1046 2
    {
1047 6
        $instructions = $this->beginAlterByCopyTable($table->getName());
1048
1049 8
        $tableName = $table->getName();
1050 3
        $instructions->addPostStep(function ($state) use ($foreignKey) {
1051 3
            $this->execute('pragma foreign_keys = ON');
1052 6
            $sql = substr($state['createSQL'], 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
1053 6
            $this->execute($sql);
1054 6
1055 6
            return $state;
1056 6
        });
1057
1058 8 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...
1059 8
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
1060
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
1061
            $selectColumns = $writeColumns = $names;
1062
1063
            return compact('selectColumns', 'writeColumns') + $state;
1064
        });
1065 47
1066
        return $this->copyAndDropTmpTable($instructions, $tableName);
1067 47
    }
1068
1069
    /**
1070
     * {@inheritdoc}
1071
     */
1072
    protected function getDropForeignKeyInstructions($tableName, $constraint)
1073
    {
1074
        throw new \BadMethodCallException('SQLite does not have named foreign keys');
1075
    }
1076 5
1077
    /**
1078 5
     * {@inheritdoc}
1079 5
     */
1080
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
1081
    {
1082 5
        $instructions = $this->beginAlterByCopyTable($tableName);
1083 5
1084 5
        $instructions->addPostStep(function ($state) use ($columns) {
1085 5
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columns[0], $columns[0]);
1086 5
1087 5
            $selectColumns = $newState['selectColumns'];
1088 5
            $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...
1089 5
            $diff = array_diff($columns, $selectColumns);
1090 5
1091 5
            if (!empty($diff)) {
1092 5
                throw new \InvalidArgumentException(sprintf(
1093 1
                    'The specified columns don\'t exist: ' . implode(', ', $diff)
1094 1
                ));
1095 5
            }
1096 1
1097 1
            return $newState + $state;
1098
        });
1099 5
1100
        $instructions->addPostStep(function ($state) use ($columns) {
1101
            $sql = '';
1102
1103
            foreach ($columns as $columnName) {
1104
                $search = sprintf(
1105
                    "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
1106
                    $this->quoteColumnName($columnName)
1107
                );
1108
                $sql = preg_replace($search, '', $state['createSQL'], 1);
1109
            }
1110
1111
            if ($sql) {
1112
                $this->execute($sql);
1113
            }
1114
1115
            return $state;
1116
        });
1117
1118
        return $this->copyAndDropTmpTable($instructions, $tableName);
1119
    }
1120
1121
    /**
1122
     * {@inheritdoc}
1123
     */
1124
    public function getSqlType($type, $limit = null)
1125
    {
1126
        $typeLC = strtolower($type);
1127
        if ($type instanceof Literal) {
1128
            $name = $type;
1129
        } elseif (isset(self::$supportedColumnTypes[$typeLC])) {
1130
            $name = self::$supportedColumnTypes[$typeLC];
1131
        } elseif (in_array($typeLC, self::$unsupportedColumnTypes)) {
1132
                throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by SQLite.');
1133
        } else {
1134
            throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not known by SQLite.');
1135
        }
1136
        return ['name' => $name, 'limit' => $limit];
1137
    }
1138
1139
    /**
1140
     * Returns Phinx type by SQL type
1141
     *
1142
     * @param string|null $sqlTypeDef SQL type
1143
     * @return array
1144
     */
1145
    public function getPhinxType($sqlTypeDef)
1146
    {
1147
        $limit = null;
1148
        $scale = null;
1149
        if (is_null($sqlTypeDef)) {
1150
            // in SQLite columns can legitimately have null as a type, which is distinct from the empty string
1151
            $name = null;
1152
        } elseif (!preg_match('/^([a-z]+)(_(?:integer|float|text|blob))?(?:\((\d+)(?:,(\d+))?\))?$/i', $sqlTypeDef, $match)) {
1153
            // doesn't match the pattern of a type we'd know about
1154
            $name = Literal::from($sqlTypeDef);
1155
        } else {
1156
            // possibly a known type
1157
            $type = $match[1];
1158
            $typeLC = strtolower($type);
1159
            $affinity = isset($match[2]) ? $match[2] : '';
1160
            $limit = isset($match[3]) && strlen($match[3]) ? (int)$match[3] : null;
1161
            $scale = isset($match[4]) && strlen($match[4]) ? (int)$match[4] : null;
1162
            if (isset(self::$supportedColumnTypes[$typeLC])) {
1163
                // the type is an explicitly supported type
1164
                $name = $typeLC;
1165 View Code Duplication
            } elseif ($typeLC === 'tinyint' && $limit == 1) {
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...
1166
                // the type is a MySQL-style boolean
1167
                $name = static::PHINX_TYPE_BOOLEAN;
1168
                $limit = null;
1169
            } elseif (isset(self::$supportedColumnTypeAliases[$typeLC])) {
1170
                // the type is an alias for a supported type
1171
                $name = self::$supportedColumnTypeAliases[$typeLC];
1172
            } elseif (in_array($typeLC, self::$unsupportedColumnTypes)) {
1173
                // unsupported but known types are passed through lowercased, and without appended affinity
1174
                $name = Literal::from($typeLC);
1175
            } else {
1176
                // unknown types are passed through as-is
1177
                $name = Literal::from($type . $affinity);
1178
            }
1179
        }
1180
1181
        return [
1182
            'name' => $name,
1183
            'limit' => $limit,
1184
            'scale' => $scale
1185
        ];
1186
    }
1187
1188
    /**
1189
     * {@inheritdoc}
1190
     */
1191
    public function createDatabase($name, $options = [])
1192
    {
1193
        touch($name . $this->suffix);
1194
    }
1195
1196
    /**
1197
     * {@inheritdoc}
1198
     */
1199
    public function hasDatabase($name)
1200
    {
1201
        return is_file($name . $this->suffix);
1202
    }
1203
1204
    /**
1205
     * {@inheritdoc}
1206
     */
1207
    public function dropDatabase($name)
1208
    {
1209
        if ($this->getOption('memory')) {
1210
            $this->disconnect();
1211
            $this->connect();
1212
        }
1213
        if (file_exists($name . $this->suffix)) {
1214
            unlink($name . $this->suffix);
1215
        }
1216
    }
1217
1218
    /**
1219
     * Gets the SQLite Column Definition for a Column object.
1220
     *
1221
     * @param \Phinx\Db\Table\Column $column Column
1222
     * @return string
1223
     */
1224
    protected function getColumnSqlDefinition(Column $column)
1225
    {
1226
        $isLiteralType = $column->getType() instanceof Literal;
1227
        if ($isLiteralType) {
1228
            $def = (string)$column->getType();
1229
        } else {
1230
            $sqlType = $this->getSqlType($column->getType());
1231
            $def = strtoupper($sqlType['name']);
1232
1233
            $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
1234
            if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
1235
                $def .= '(' . ($column->getLimit() ?: $sqlType['limit']) . ')';
1236
            }
1237
        }
1238
        if ($column->getPrecision() && $column->getScale()) {
1239
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1240
        }
1241
1242
        $default = $column->getDefault();
1243
1244
        $def .= (!$column->isIdentity() && ($column->isNull() || is_null($default))) ? ' NULL' : ' NOT NULL';
1245
        $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...
1246
        $def .= $column->isIdentity() ? ' PRIMARY KEY AUTOINCREMENT' : '';
1247
1248
        if ($column->getUpdate()) {
1249
            $def .= ' ON UPDATE ' . $column->getUpdate();
1250
        }
1251
1252
        $def .= $this->getCommentDefinition($column);
1253
1254
        return $def;
1255
    }
1256
1257
    /**
1258
     * Gets the comment Definition for a Column object.
1259
     *
1260
     * @param \Phinx\Db\Table\Column $column Column
1261
     * @return string
1262
     */
1263
    protected function getCommentDefinition(Column $column)
1264
    {
1265
        if ($column->getComment()) {
1266
            return ' /* ' . $column->getComment() . ' */ ';
1267
        }
1268
1269
        return '';
1270
    }
1271
1272
    /**
1273
     * Gets the SQLite Index Definition for an Index object.
1274
     *
1275
     * @param \Phinx\Db\Table\Table $table Table
1276
     * @param \Phinx\Db\Table\Index $index Index
1277
     * @return string
1278
     */
1279
    protected function getIndexSqlDefinition(Table $table, Index $index)
1280
    {
1281
        if ($index->getType() === Index::UNIQUE) {
1282
            $def = 'UNIQUE INDEX';
1283
        } else {
1284
            $def = 'INDEX';
1285
        }
1286
        if (is_string($index->getName())) {
1287
            $indexName = $index->getName();
1288
        } else {
1289
            $indexName = $table->getName() . '_';
1290
            foreach ($index->getColumns() as $column) {
1291
                $indexName .= $column . '_';
1292
            }
1293
            $indexName .= 'index';
1294
        }
1295
        $def .= ' `' . $indexName . '`';
1296
1297
        return $def;
1298
    }
1299
1300
    /**
1301
     * {@inheritdoc}
1302
     */
1303
    public function getColumnTypes()
1304
    {
1305
        return array_keys(self::$supportedColumnTypes);
1306
    }
1307
1308
    /**
1309
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1310
     *
1311
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1312
     * @return string
1313
     */
1314 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...
1315
    {
1316
        $def = '';
1317
        if ($foreignKey->getConstraint()) {
1318
            $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...
1319
        } else {
1320
            $columnNames = [];
1321
            foreach ($foreignKey->getColumns() as $column) {
1322
                $columnNames[] = $this->quoteColumnName($column);
1323
            }
1324
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1325
            $refColumnNames = [];
1326
            foreach ($foreignKey->getReferencedColumns() as $column) {
1327
                $refColumnNames[] = $this->quoteColumnName($column);
1328
            }
1329
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1330
            if ($foreignKey->getOnDelete()) {
1331
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1332
            }
1333
            if ($foreignKey->getOnUpdate()) {
1334
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1335
            }
1336
        }
1337
1338
        return $def;
1339
    }
1340
1341
    /**
1342
     * {@inheritDoc}
1343
     *
1344
     */
1345
    public function getDecoratedConnection()
1346
    {
1347
        $options = $this->getOptions();
1348
        $options['quoteIdentifiers'] = true;
1349
        $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...
1350
1351
        if (!empty($options['name'])) {
1352
            $options['database'] = $options['name'];
1353
1354
            if (file_exists($options['name'] . $this->suffix)) {
1355
                $options['database'] = $options['name'] . $this->suffix;
1356
            }
1357
        }
1358
1359
        $driver = new SqliteDriver($options);
1360
        if (method_exists($driver, 'setConnection')) {
1361
            $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...
1362
        } else {
1363
            $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...
1364
        }
1365
1366
        return new Connection(['driver' => $driver] + $options);
1367
    }
1368
}
1369