Completed
Push — master ( 63c0ca...7da46b )
by José
19s queued 10s
created

SQLiteAdapter::getPhinxType()   C

Complexity

Conditions 13
Paths 162

Size

Total Lines 42

Duplication

Lines 5
Ratio 11.9 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
dl 5
loc 42
ccs 0
cts 0
cp 0
rs 6.1
c 0
b 0
f 0
cc 13
nc 162
nop 1
crap 182

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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