Completed
Pull Request — master (#1551)
by
unknown
01:32
created

SQLiteAdapter::getSchemaName()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 12
cts 12
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 known but unsupported Phinx column types
73 42
    protected static $unsupportedColumnTypes = [
74 42
        self::PHINX_TYPE_BIT,
75 42
        self::PHINX_TYPE_CIDR,
76
        self::PHINX_TYPE_DECIMAL,
77
        self::PHINX_TYPE_ENUM,
78
        self::PHINX_TYPE_FILESTREAM,
79 42
        self::PHINX_TYPE_GEOMETRY,
80 42
        self::PHINX_TYPE_INET,
81
        self::PHINX_TYPE_INTERVAL,
82
        self::PHINX_TYPE_LINESTRING,
83
        self::PHINX_TYPE_MACADDR,
84
        self::PHINX_TYPE_POINT,
85
        self::PHINX_TYPE_POLYGON,
86
        self::PHINX_TYPE_SET
87 42
    ];
88 42
89 42
    protected $definitionsWithLimits = [
90
        'CHAR',
91
        'CHARACTER',
92
        'VARCHAR',
93
        'VARYING CHARACTER',
94 48
        'NCHAR',
95
        'NATIVE CHARACTER',
96 48
        'NVARCHAR'
97 48
    ];
98
99
    protected $suffix = '.sqlite3';
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function connect()
105
    {
106
        if ($this->connection === null) {
107
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
108
                // @codeCoverageIgnoreStart
109
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
110 1
                // @codeCoverageIgnoreEnd
111
            }
112 1
113 1
            $db = null;
114
            $options = $this->getOptions();
115
116
            // use a memory database if the option was specified
117
            if (!empty($options['memory'])) {
118
                $dsn = 'sqlite::memory:';
119
            } else {
120
                $dsn = 'sqlite:' . $options['name'] . $this->suffix;
121
            }
122
123
            try {
124
                $db = new \PDO($dsn);
125
            } catch (\PDOException $exception) {
126
                throw new \InvalidArgumentException(sprintf(
127
                    'There was a problem connecting to the database: %s',
128
                    $exception->getMessage()
129
                ));
130
            }
131
132
            $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
133
            $this->setConnection($db);
134 43
        }
135
    }
136 43
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function setOptions(array $options)
141
    {
142 44
        parent::setOptions($options);
143
144 44
        if (isset($options['suffix'])) {
145
            $this->suffix = $options['suffix'];
146
        }
147
        //don't "fix" the file extension if it is blank, some people
148
        //might want a SQLITE db file with absolutely no extension.
149
        if (strlen($this->suffix) && substr($this->suffix, 0, 1) !== '.') {
150 42
            $this->suffix = '.' . $this->suffix;
151
        }
152 42
153 42
        return $this;
154 42
    }
155 12
156 42
    /**
157
     * {@inheritdoc}
158 42
     */
159
    public function disconnect()
160
    {
161
        $this->connection = null;
162
    }
163
164 42
    /**
165
     * {@inheritdoc}
166
     */
167 42
    public function hasTransactions()
168 42
    {
169 42
        return true;
170 35
    }
171 35
172 35
    /**
173 35
     * {@inheritdoc}
174
     */
175 35
    public function beginTransaction()
176 42
    {
177
        $this->getConnection()->beginTransaction();
178 1
    }
179 1
180 1
    /**
181 1
     * {@inheritdoc}
182
     */
183 1
    public function commitTransaction()
184 1
    {
185
        $this->getConnection()->commit();
186
    }
187 42
188 42
    /**
189 42
     * {@inheritdoc}
190 42
     */
191 42
    public function rollbackTransaction()
192
    {
193
        $this->getConnection()->rollBack();
194 42
    }
195 42
196 42
    /**
197 42
     * {@inheritdoc}
198 42
     */
199 42
    public function quoteTableName($tableName)
200
    {
201
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
202 1
    }
203 1
204 1
    /**
205
     * {@inheritdoc}
206 1
     */
207 1
    public function quoteColumnName($columnName)
208 1
    {
209 1
        return '`' . str_replace('`', '``', $columnName) . '`';
210 1
    }
211 1
212 42
    /**
213 42
     * @param string $tableName Table name
214 37
     * @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
215
     * @return array
216
     */
217
    protected function getSchemaName($tableName, $quoted = false)
218 42
    {
219 42
        if (preg_match("/.\.([^\.]+)$/", $tableName, $match)) {
220 1
            $table = $match[1];
221 1
            $schema = substr($tableName, 0, strlen($tableName) - strlen($match[0]) + 1);
222 1
            $result = ['schema' => $schema, 'table' => $table];
223 1
        } else {
224
            $result = ['schema' => '', 'table' => $tableName];
225 42
        }
226
227 42
        if ($quoted) {
228
            $result['schema'] = strlen($result['schema']) ? $this->quoteColumnName($result['schema']) . '.' : '';
229 42
            $result['table'] = $this->quoteColumnName($result['table']);
230 6
        }
231 42
232 42
        return $result;
233
    }
234
235
    /**
236
     * Retrieves information about a given table from one of the SQLite pragmas
237 1
     *
238
     * @param string $tableName The table to query
239 1
     * @param string $pragma The pragma to query
240 1
     * @return array
241
     */
242
    protected function getTableInfo($tableName, $pragma = 'table_info')
243
    {
244
        $info = $this->getSchemaName($tableName, true);
245 1
        return $this->fetchAll(sprintf('PRAGMA %s%s(%s)', $info['schema'], $pragma, $info['table']));
246
    }
247 1
248 1
    /**
249
     * {@inheritdoc}
250
     */
251
    public function hasTable($tableName)
252
    {
253 1
        $info = $this->getSchemaName($tableName);
254
        if ($info['schema'] === '') {
255 1
            // if no schema is specified we search all schemata
256 1
            $rows = $this->fetchAll('PRAGMA database_list;');
257 1
            $schemata = [];
258 1
            foreach ($rows as $row) {
259
                $schemata[] = $row['name'];
260 1
            }
261 1
        } else {
262
            // otherwise we search just the specified schema
263
            $schemata = (array)$info['schema'];
264
        }
265
266 1
        $table = strtolower($info['table']);
267
        foreach ($schemata as $schema) {
268 1
            if (strtolower($schema) === 'temp') {
269 1
                $master = 'sqlite_temp_master';
270
            } else {
271 1
                $master = sprintf('%s.%s', $this->quoteColumnName($schema), 'sqlite_master');
272 1
            }
273 1
            try {
274 1
                $rows = $this->fetchAll(sprintf('SELECT name FROM %s WHERE type=\'table\' AND lower(name) = %s', $master, $this->quoteString($table)));
275 1
            } catch (\PDOException $e) {
276 1
                // an exception can occur if the schema part of the table refers to a database which is not attached
277
                return false;
278 1
            }
279 1
280 1
            // this somewhat pedantic check with strtolower is performed because the SQL lower function may be redefined,
281
            // and can act on all Unicode characters if the ICU extension is loaded, while SQL identifiers are only case-insensitive for ASCII
282 1
            foreach ($rows as $row) {
283 1
                if (strtolower($row['name']) === $table) {
284 1
                    return true;
285
                }
286 1
            }
287 1
        }
288
289 1
        return false;
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295 8
    public function createTable(Table $table, array $columns = [], array $indexes = [])
296
    {
297 8
        // Add the default primary key
298 8
        $options = $table->getOptions();
299 8 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...
300 7
            $options['id'] = 'id';
301
        }
302 8
303
        if (isset($options['id']) && is_string($options['id'])) {
304 8
            // Handle id => "field_name" to support AUTO_INCREMENT
305
            $column = new Column();
306
            $column->setName($options['id'])
307
                   ->setType('integer')
308
                   ->setIdentity(true);
309
310 4
            array_unshift($columns, $column);
311
        }
312 4
313 4
        $sql = 'CREATE TABLE ';
314 4
        $sql .= $this->quoteTableName($table->getName()) . ' (';
315 4
        foreach ($columns as $column) {
316 4
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
317 4
318
            if (isset($options['primary_key']) && $column->getIdentity()) {
319 4
                //remove column from the primary key array as it is already defined as an autoincrement
320 4
                //primary id
321
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
322
                if ($identityColumnIndex !== false) {
323
                    unset($options['primary_key'][$identityColumnIndex]);
324
325 2
                    if (empty($options['primary_key'])) {
326
                        //The last primary key has been removed
327 2
                        unset($options['primary_key']);
328
                    }
329 2
                }
330
            }
331 2
        }
332 2
333 2
        // set the primary key(s)
334 2 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...
335 2
            $sql = rtrim($sql);
336 2
            $sql .= ' PRIMARY KEY (';
337
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
338 2
                $sql .= $this->quoteColumnName($options['primary_key']);
339 2
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
340 2
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
341 2
            }
342 2
            $sql .= ')';
343 2
        } else {
344 2
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
345 2
        }
346 2
347
        $sql = rtrim($sql) . ');';
348 2
        // execute the sql
349 1
        $this->execute($sql);
350
351 1
        foreach ($indexes as $index) {
352
            $this->addIndex($table, $index);
353
        }
354 1
    }
355
356 1
    /**
357 1
     * {@inheritdoc}
358 1
     */
359
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
360 1
    {
361 1
        $instructions = new AlterInstructions();
362
363
        // Drop the existing primary key
364 1
        $primaryKey = $this->getPrimaryKey($table->getName());
365 1
        if (!empty($primaryKey)) {
366 1
            $instructions->merge(
367 1
                // FIXME: array access is a hack to make this incomplete implementation work with a correct getPrimaryKey implementation
368 1
                $this->getDropPrimaryKeyInstructions($table, $primaryKey[0])
369
            );
370 1
        }
371
372 1
        // Add the primary key(s)
373
        if (!empty($newColumns)) {
374 1
            if (!is_string($newColumns)) {
375 1
                throw new \InvalidArgumentException(sprintf(
376
                    "Invalid value for primary key: %s",
377
                    json_encode($newColumns)
378
                ));
379
            }
380 6
381
            $instructions->merge(
382
                $this->getAddPrimaryKeyInstructions($table, $newColumns)
383
            );
384 6
        }
385
386 6
        return $instructions;
387
    }
388 6
389 6
    /**
390 6
     * {@inheritdoc}
391 6
     */
392 6
    protected function getChangeCommentInstructions(Table $table, $newComment)
393 6
    {
394
        throw new \BadMethodCallException('SQLite does not have table comments');
395 6
    }
396 6
397 6
    /**
398 6
     * {@inheritdoc}
399 6
     */
400 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...
401 6
    {
402 6
        $sql = sprintf(
403 6
            'ALTER TABLE %s RENAME TO %s',
404
            $this->quoteTableName($tableName),
405 6
            $this->quoteTableName($newTableName)
406
        );
407
408
        return new AlterInstructions([], [$sql]);
409
    }
410
411 6
    /**
412
     * {@inheritdoc}
413 6
     */
414 6
    protected function getDropTableInstructions($tableName)
415 6
    {
416 6
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
417
418 6
        return new AlterInstructions([], [$sql]);
419
    }
420 6
421
    /**
422 6
     * {@inheritdoc}
423 6
     */
424 6
    public function truncateTable($tableName)
425 6
    {
426 6
        $sql = sprintf(
427
            'DELETE FROM %s',
428 6
            $this->quoteTableName($tableName)
429
        );
430 6
431 6
        $this->execute($sql);
432 6
    }
433
434
    /**
435
     * {@inheritdoc}
436
     */
437 2
    public function getColumns($tableName)
438
    {
439
        $columns = [];
440 2
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
441
442 2
        foreach ($rows as $columnInfo) {
443
            $column = new Column();
444 2
            $type = strtolower($columnInfo['type']);
445 2
            $column->setName($columnInfo['name'])
446 2
                   ->setNull($columnInfo['notnull'] !== '1')
447 2
                   ->setDefault($columnInfo['dflt_value']);
448 2
449 2
            $phinxType = $this->getPhinxType($type);
450
451 2
            $column->setType($phinxType['name'])
0 ignored issues
show
Bug introduced by
It seems like $phinxType['name'] can also be of type null; however, Phinx\Db\Table\Column::setType() does only seem to accept string|object<Phinx\Util\Literal>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
452 2
                   ->setLimit($phinxType['limit'])
453 2
                   ->setScale($phinxType['scale']);
454 2
455 2
            if ($columnInfo['pk'] == 1) {
456 2
                $column->setIdentity(true);
457 2
            }
458 2
459 2
            $columns[] = $column;
460
        }
461 2
462
        return $columns;
463 2
    }
464
465
    /**
466
     * {@inheritdoc}
467
     */
468 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...
469 2
    {
470
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
471 2
        foreach ($rows as $column) {
472 2
            if (strcasecmp($column['name'], $columnName) === 0) {
473 2
                return true;
474
            }
475 2
        }
476
477 2
        return false;
478 2
    }
479 2
480
    /**
481 2
     * {@inheritdoc}
482
     */
483 2 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...
484 2
    {
485 2
        $alter = sprintf(
486 2
            'ALTER TABLE %s ADD COLUMN %s %s',
487 2
            $this->quoteTableName($table->getName()),
488
            $this->quoteColumnName($column->getName()),
489 2
            $this->getColumnSqlDefinition($column)
490
        );
491 2
492 2
        return new AlterInstructions([], [$alter]);
493 2
    }
494
495
    /**
496
     * Returns the original CREATE statement for the give table
497
     *
498
     * @param string $tableName The table name to get the create statement for
499
     * @return string
500
     */
501 9
    protected function getDeclaringSql($tableName)
502
    {
503 9
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
504 9
505
        $sql = '';
506 9
        foreach ($rows as $table) {
507 9
            if ($table['tbl_name'] === $tableName) {
508 9
                $sql = $table['sql'];
509 9
            }
510 9
        }
511 9
512 9
        return $sql;
513 9
    }
514 9
515 9
    /**
516
     * Copies all the data from a tmp table to another table
517
     *
518
     * @param string $tableName The table name to copy the data to
519
     * @param string $tmpTableName The tmp table name where the data is stored
520
     * @param string[] $writeColumns The list of columns in the target table
521 9
     * @param string[] $selectColumns The list of columns in the tmp table
522
     * @return void
523 9
     */
524 4
    protected function copyDataToNewTable($tableName, $tmpTableName, $writeColumns, $selectColumns)
525 4
    {
526
        $sql = sprintf(
527 9
            'INSERT INTO %s(%s) SELECT %s FROM %s',
528 9
            $this->quoteTableName($tableName),
529
            implode(', ', $writeColumns),
530 9
            implode(', ', $selectColumns),
531 9
            $this->quoteTableName($tmpTableName)
532 9
        );
533 9
        $this->execute($sql);
534
    }
535 8
536
    /**
537 8
     * Modifies the passed instructions to copy all data from the tmp table into
538
     * the provided table and then drops the tmp table.
539
     *
540
     * @param AlterInstructions $instructions The instructions to modify
541
     * @param string $tableName The table name to copy the data to
542
     * @return AlterInstructions
543 1
     */
544
    protected function copyAndDropTmpTable($instructions, $tableName)
545 1
    {
546
        $instructions->addPostStep(function ($state) use ($tableName) {
547 1
            $this->copyDataToNewTable(
548 1
                $tableName,
549 1
                $state['tmpTableName'],
550
                $state['writeColumns'],
551
                $state['selectColumns']
552
            );
553
554
            $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($state['tmpTableName'])));
555
556
            return $state;
557
        });
558
559 8
        return $instructions;
560
    }
561 8
562 8
    /**
563 8
     * Returns the columns and type to use when copying a table to another in the process
564 8
     * of altering a table
565 8
     *
566 8
     * @param string $tableName The table to modify
567 8
     * @param string $columnName The column name that is about to change
568 8
     * @param string|false $newColumnName Optionally the new name for the column
569 8
     * @return AlterInstructions
570 8
     */
571
    protected function calculateNewTableColumns($tableName, $columnName, $newColumnName)
572 8
    {
573 8
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
574 8
        $selectColumns = [];
575
        $writeColumns = [];
576
        $columnType = null;
577
        $found = false;
578
579 1
        foreach ($columns as $column) {
580
            $selectName = $column['name'];
581 1
            $writeName = $selectName;
582 1
583 1
            if ($selectName == $columnName) {
584
                $writeName = $newColumnName;
585 1
                $found = true;
586 1
                $columnType = $column['type'];
587
                $selectName = $newColumnName === false ? $newColumnName : $selectName;
588 1
            }
589 1
590 1
            $selectColumns[] = $selectName;
591 1
            $writeColumns[] = $writeName;
592 1
        }
593 1
594 1
        $selectColumns = array_filter($selectColumns, 'strlen');
595 1
        $writeColumns = array_filter($writeColumns, 'strlen');
596 1
        $selectColumns = array_map([$this, 'quoteColumnName'], $selectColumns);
597 1
        $writeColumns = array_map([$this, 'quoteColumnName'], $writeColumns);
598
599
        if (!$found) {
600
            throw new \InvalidArgumentException(sprintf(
601
                'The specified column doesn\'t exist: ' . $columnName
602
            ));
603
        }
604
605 1
        return compact('writeColumns', 'selectColumns', 'columnType');
606
    }
607 1
608
    /**
609 1
     * Returns the initial instructions to alter a table using the
610 1
     * rename-alter-copy strategy
611 1
     *
612 1
     * @param string $tableName The table to modify
613 1
     * @return AlterInstructions
614 1
     */
615 1
    protected function beginAlterByCopyTable($tableName)
616 1
    {
617 1
        $instructions = new AlterInstructions();
618
        $instructions->addPostStep(function ($state) use ($tableName) {
619
            $createSQL = $this->getDeclaringSql($tableName);
620
621
            $tmpTableName = 'tmp_' . $tableName;
622
            $this->execute(
623
                sprintf(
624
                    'ALTER TABLE %s RENAME TO %s',
625 5
                    $this->quoteTableName($tableName),
626
                    $this->quoteTableName($tmpTableName)
627 5
                )
628
            );
629
630 5
            return compact('createSQL', 'tmpTableName') + $state;
631
        });
632 5
633 5
        return $instructions;
634 5
    }
635
636 1
    /**
637
     * {@inheritdoc}
638
     */
639
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
640
    {
641
        $instructions = $this->beginAlterByCopyTable($tableName);
642 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...
643
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
644
645 5
            return $newState + $state;
646
        });
647 5
648 5
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
649
            $sql = str_replace(
650
                $this->quoteColumnName($columnName),
651
                $this->quoteColumnName($newColumnName),
652
                $state['createSQL']
653
            );
654
            $this->execute($sql);
655
656
            return $state;
657
        });
658
659
        return $this->copyAndDropTmpTable($instructions, $tableName);
660
    }
661 5
662
    /**
663 5
     * {@inheritdoc}
664 5
     */
665 5
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
666 5
    {
667 5
        $instructions = $this->beginAlterByCopyTable($tableName);
668 5
669 5
        $newColumnName = $newColumn->getName();
670 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...
671 5
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
672 5
673 5
            return $newState + $state;
674
        });
675
676
        $instructions->addPostStep(function ($state) use ($columnName, $newColumn) {
677
            $sql = preg_replace(
678
                sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
679 4
                sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
680
                $state['createSQL'],
681
                1
682 4
            );
683
            $this->execute($sql);
684 4
685 4
            return $state;
686
        });
687 4
688 4
        return $this->copyAndDropTmpTable($instructions, $tableName);
689 4
    }
690 4
691 4
    /**
692 4
     * {@inheritdoc}
693
     */
694 4
    protected function getDropColumnInstructions($tableName, $columnName)
695 4
    {
696 4
        $instructions = $this->beginAlterByCopyTable($tableName);
697 4
698 4 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...
699
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, false);
700 4
701
            return $newState + $state;
702 4
        });
703 4
704
        $instructions->addPostStep(function ($state) use ($columnName) {
705 4
            $sql = preg_replace(
706 4
                sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($state['columnType'])),
707 4
                "",
708 4
                $state['createSQL']
709 4
            );
710 4
711 4
            if (substr($sql, -2) === ', ') {
712
                $sql = substr($sql, 0, -2) . ')';
713 4
            }
714 4
715 4
            $this->execute($sql);
716
717
            return $state;
718
        });
719
720 1
        return $this->copyAndDropTmpTable($instructions, $tableName);
721
    }
722
723 1
    /**
724
     * Get an array of indexes from a particular table.
725
     *
726
     * @param string $tableName Table Name
727 1
     * @return array
728
     */
729 1
    protected function getIndexes($tableName)
730
    {
731 1
        $indexes = [];
732 1
        $schema = $this->getSchemaName($tableName, true)['schema'];
733 1
        $indexList = $this->getTableInfo($tableName, 'index_list');
734 1
735 1
        foreach ($indexList as $index) {
736 1
            $indexData = $this->fetchAll(sprintf('pragma %sindex_info(%s)', $schema, $this->quoteColumnName($index['name'])));
737
            $cols = [];
738 1
            foreach ($indexData as $indexItem) {
739 1
                $cols[] = $indexItem['name'];
740 1
            }
741 1
            $indexes[$index['name']] = $cols;
742 1
        }
743 1
744 1
        return $indexes;
745
    }
746 1
747
    /**
748 1
     * Finds the names of a table's indexes matching the supplied columns
749
     *
750
     * @param string $tableName The table to which the index belongs
751
     * @param string|string[] $columns The columns of the index
752
     * @return array
753
     */
754 1
    protected function resolveIndex($tableName, $columns)
755
    {
756 1
        $columns = array_map('strtolower', (array)$columns);
757 1
        $indexes = $this->getIndexes($tableName);
758 1
        $matches = [];
759 1
760 1
        foreach ($indexes as $name => $index) {
761 1
            $indexCols = array_map('strtolower', $index);
762 1
            if ($columns == $indexCols) {
763
                $matches[] = $name;
764 1
            }
765
        }
766 1
767 1
        return $matches;
768 1
    }
769 1
770 1
    /**
771
     * {@inheritdoc}
772 1
     */
773
    public function hasIndex($tableName, $columns)
774 1
    {
775 1
        return (bool)$this->resolveIndex($tableName, $columns);
776 1
    }
777
778
    /**
779
     * {@inheritdoc}
780
     */
781
    public function hasIndexByName($tableName, $indexName)
782
    {
783
        $indexName = strtolower($indexName);
784
        $indexes = $this->getIndexes($tableName);
785
786
        foreach (array_keys($indexes) as $index) {
787
            if ($indexName === strtolower($index)) {
788
                return true;
789
            }
790
        }
791
792
        return false;
793
    }
794
795
    /**
796
     * {@inheritdoc}
797
     */
798
    protected function getAddIndexInstructions(Table $table, Index $index)
799
    {
800
        $indexColumnArray = [];
801
        foreach ($index->getColumns() as $column) {
802
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
803
        }
804
        $indexColumns = implode(',', $indexColumnArray);
805
        $sql = sprintf(
806
            'CREATE %s ON %s (%s)',
807
            $this->getIndexSqlDefinition($table, $index),
808
            $this->quoteTableName($table->getName()),
809
            $indexColumns
810 43
        );
811
812
        return new AlterInstructions([], [$sql]);
813 43
    }
814 42
815
    /**
816 43
     * {@inheritdoc}
817
     */
818
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
819 43
    {
820 1
        $instructions = new AlterInstructions();
821
        $indexNames = $this->resolveIndex($tableName, $columns);
822 43
        $schema = $this->getSchemaName($tableName, true)['schema'];
823 38
        foreach ($indexNames as $indexName) {
824
            if (strpos($indexName, 'sqlite_autoindex_') !== 0) {
825 43
                $instructions->addPostStep(sprintf(
826 42
                    'DROP INDEX %s%s',
827
                    $schema,
828 43
                    $this->quoteColumnName($indexName)
829 2
                ));
830
            }
831 43
        }
832 1
833
        return $instructions;
834 43
    }
835 1
836
    /**
837 43
     * {@inheritdoc}
838 42
     */
839
    protected function getDropIndexByNameInstructions($tableName, $indexName)
840 43
    {
841 1
        $instructions = new AlterInstructions();
842
        $indexName = strtolower($indexName);
843 43
        $indexes = $this->getIndexes($tableName);
844 1
845
        $found = false;
846 43
        foreach (array_keys($indexes) as $index) {
847 43
            if ($indexName === strtolower($index)) {
848 1
                $found = true;
849
                break;
850 43
            }
851 42
        }
852
853 5
        if ($found) {
854
            $schema = $this->getSchemaName($tableName, true)['schema'];
855 5
                $instructions->addPostStep(sprintf(
856 4
                    'DROP INDEX %s%s',
857
                    $schema,
858
                    $this->quoteColumnName($indexName)
859
                ));
860 1
        }
861 1
862
        return $instructions;
863
    }
864 1
865
    /**
866
     * {@inheritdoc}
867 1
     */
868
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
869 1
    {
870 1
        if (!is_null($constraint)) {
871 1
            throw new \InvalidArgumentException('SQLite does not support named constraints.');
872
        }
873
874
        $columns = array_map('strtolower', (array)$columns);
875
        $primaryKey = array_map('strtolower', $this->getPrimaryKey($tableName));
876
877
        if (array_diff($primaryKey, $columns) || array_diff($columns, $primaryKey)) {
878
            return false;
879
        }
880 3
        
881
        return true;
882 3
    }
883 1
884
    /**
885 2
     * Get the primary key from a particular table.
886 2
     *
887 2
     * @param string $tableName Table Name
888 2
     * @return string[]
889 1
     */
890 1
    protected function getPrimaryKey($tableName)
891 2
    {
892
        $primaryKey = [];
893
894 2
        $rows = $this->getTableInfo($tableName);
895 2
896 1
        foreach ($rows as $row) {
897 1
            if ($row['pk'] > 0) {
898
                $primaryKey[$row['pk'] - 1] = $row['name'];
899
            }
900 1
        }
901 2
902
        return $primaryKey;
903
    }
904
905
    /**
906
     * {@inheritdoc}
907
     */
908
    public function hasForeignKey($tableName, $columns, $constraint = null)
909
    {
910 2
        if (!is_null($constraint)) {
911
            throw new \InvalidArgumentException('SQLite does not support named constraints.');
912
        }
913
914
        $columns = array_map('strtolower', (array)$columns);
915
        $foreignKeys = $this->getForeignKeys($tableName);
916 2
917 1
        foreach ($foreignKeys as $key) {
918
            $key = array_map('strtolower', $key);
919
            if (array_diff($key, $columns) || array_diff($columns, $key)) {
920 1
                continue;
921 1
            }
922 2
            return true;
923 1
        }
924 1
925 2
        return false;
926 2
    }
927
928
    /**
929
     * Get an array of foreign keys from a particular table.
930
     *
931
     * @param string $tableName Table Name
932
     * @return array
933 2
     */
934
    protected function getForeignKeys($tableName)
935
    {
936 1
        $foreignKeys = [];
937 1
938
        $rows = $this->getTableInfo($tableName, 'foreign_key_list');
939 1
940
        foreach ($rows as $row) {
941
            if (!isset($foreignKeys[$row['id']])) {
942
                $foreignKeys[$row['id']] = [];
943
            }
944
            $foreignKeys[$row['id']][$row['seq']] = $row['from'];
945
        }
946 48
947
        return $foreignKeys;
948 48
    }
949 48
950
    /**
951
     * @param Table $table The Table
952
     * @param string $column Column Name
953
     * @return AlterInstructions
954 2
     */
955
    protected function getAddPrimaryKeyInstructions(Table $table, $column)
956 2
    {
957
        $instructions = $this->beginAlterByCopyTable($table->getName());
958
959
        $tableName = $table->getName();
960
        $instructions->addPostStep(function ($state) use ($column) {
961
            $matchPattern = "/(`$column`)\s+(\w+(\(\d+\))?)\s+((NOT )?NULL)/";
962 48
963
            $sql = $state['createSQL'];
964 48
965 47
            if (preg_match($matchPattern, $state['createSQL'], $matches)) {
966 47
                if (isset($matches[2])) {
967 48
                    if ($matches[2] === 'INTEGER') {
968
                        $replace = '$1 INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT';
969
                    } else {
970
                        $replace = '$1 $2 NOT NULL PRIMARY KEY';
971
                    }
972
973
                    $sql = preg_replace($matchPattern, $replace, $state['createSQL'], 1);
974
                }
975 42
            }
976
977 42
            $this->execute($sql);
978 8
979 42
            return $state;
980 42
        });
981 42
982 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...
983
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
984
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
985
            $selectColumns = $writeColumns = $names;
986
987
            return compact('selectColumns', 'writeColumns') + $state;
988
        });
989
990
        return $this->copyAndDropTmpTable($instructions, $tableName);
991 42
    }
992
993 42
    /**
994 42
     * @param Table $table Table
995 42
     * @param string $column Column Name
996 42
     * @return AlterInstructions
997
     */
998
    protected function getDropPrimaryKeyInstructions($table, $column)
999 42
    {
1000 42
        $instructions = $this->beginAlterByCopyTable($table->getName());
1001 42
1002 42
        $instructions->addPostStep(function ($state) use ($column) {
1003 42
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $column, $column);
1004 4
1005 4
            return $newState + $state;
1006
        });
1007 42
1008
        $instructions->addPostStep(function ($state) {
1009 42
            $search = "/(,?\s*PRIMARY KEY\s*\([^\)]*\)|\s+PRIMARY KEY(\s+AUTOINCREMENT)?)/";
1010 42
            $sql = preg_replace($search, '', $state['createSQL'], 1);
1011 42
1012
            if ($sql) {
1013 42
                $this->execute($sql);
1014
            }
1015
1016
            return $state;
1017 42
        });
1018
1019 42
        return $this->copyAndDropTmpTable($instructions, $table->getName());
1020
    }
1021
1022
    /**
1023
     * {@inheritdoc}
1024
     */
1025
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
1026
    {
1027
        $instructions = $this->beginAlterByCopyTable($table->getName());
1028 42
1029
        $tableName = $table->getName();
1030 42
        $instructions->addPostStep(function ($state) use ($foreignKey) {
1031 2
            $this->execute('pragma foreign_keys = ON');
1032
            $sql = substr($state['createSQL'], 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
1033 42
            $this->execute($sql);
1034
1035
            return $state;
1036
        });
1037
1038 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...
1039
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
1040
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
1041
            $selectColumns = $writeColumns = $names;
1042 8
1043
            return compact('selectColumns', 'writeColumns') + $state;
1044 8
        });
1045 2
1046 2
        return $this->copyAndDropTmpTable($instructions, $tableName);
1047 6
    }
1048
1049 8
    /**
1050 3
     * {@inheritdoc}
1051 3
     */
1052 6
    protected function getDropForeignKeyInstructions($tableName, $constraint)
1053 6
    {
1054 6
        throw new \BadMethodCallException('SQLite does not have named foreign keys');
1055 6
    }
1056 6
1057
    /**
1058 8
     * {@inheritdoc}
1059 8
     */
1060
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
1061
    {
1062
        $instructions = $this->beginAlterByCopyTable($tableName);
1063
1064
        $instructions->addPostStep(function ($state) use ($columns) {
1065 47
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columns[0], $columns[0]);
1066
1067 47
            $selectColumns = $newState['selectColumns'];
1068
            $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...
1069
            $diff = array_diff($columns, $selectColumns);
1070
1071
            if (!empty($diff)) {
1072
                throw new \InvalidArgumentException(sprintf(
1073
                    'The specified columns don\'t exist: ' . implode(', ', $diff)
1074
                ));
1075
            }
1076 5
1077
            return $newState + $state;
1078 5
        });
1079 5
1080
        $instructions->addPostStep(function ($state) use ($columns) {
1081
            $sql = '';
1082 5
1083 5
            foreach ($columns as $columnName) {
1084 5
                $search = sprintf(
1085 5
                    "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
1086 5
                    $this->quoteColumnName($columnName)
1087 5
                );
1088 5
                $sql = preg_replace($search, '', $state['createSQL'], 1);
1089 5
            }
1090 5
1091 5
            if ($sql) {
1092 5
                $this->execute($sql);
1093 1
            }
1094 1
1095 5
            return $state;
1096 1
        });
1097 1
1098
        return $this->copyAndDropTmpTable($instructions, $tableName);
1099 5
    }
1100
1101
    /**
1102
     * {@inheritdoc}
1103
     */
1104
    public function getSqlType($type, $limit = null)
1105
    {
1106
        $typeLC = strtolower($type);
1107
        if ($type instanceof Literal) {
1108
            $name = $type;
1109
        } elseif (isset(self::$supportedColumnTypes[$typeLC])) {
1110
            $name = self::$supportedColumnTypes[$typeLC];
1111
        } elseif (in_array($typeLC, self::$unsupportedColumnTypes)) {
1112
                throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by SQLite.');
1113
        } else {
1114
            throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not known by SQLite.');
1115
        }
1116
        return ['name' => $name, 'limit' => $limit];
1117
    }
1118
1119
    /**
1120
     * Returns Phinx type by SQL type
1121
     *
1122
     * @param string|null $sqlTypeDef SQL type
1123
     * @return array
1124
     */
1125
    public function getPhinxType($sqlTypeDef)
1126
    {
1127
        $limit = null;
1128
        $scale = null;
1129
        if (is_null($sqlTypeDef)) {
1130
            // in SQLite columns can legitimately have null as a type, which is distinct from the empty string
1131
            $name = null;
1132
        } elseif (!preg_match('/^([a-z]+)(_(?:integer|float|text|blob))?(?:\((\d+)(?:,(\d+))?\))?$/i', $sqlTypeDef, $match)) {
1133
            // doesn't match the pattern of a type we'd know about
1134
            $name = Literal::from($sqlTypeDef);
1135
        } else {
1136
            // possibly a known type
1137
            $type = $match[1];
1138
            $typeLC = strtolower($type);
1139
            $affinity = isset($match[2]) ? $match[2] : '';
1140
            $limit = isset($match[3]) && strlen($match[3]) ? (int)$match[3] : null;
1141
            $scale = isset($match[4]) && strlen($match[4]) ? (int)$match[4] : null;
1142
            if (isset(self::$supportedColumnTypes[$typeLC])) {
1143
                // the type is an explicitly supported type
1144
                $name = $typeLC;
1145
            } else {
1146
                switch ($typeLC) {
1147
                    // common aliases of supported types should be converted
1148
                    case 'varchar':
1149
                        $name = static::PHINX_TYPE_STRING;
1150
                        break;
1151
                    case 'tinyint':
1152 View Code Duplication
                    case 'tinyinteger':
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...
1153
                        if ($limit == 1) {
1154
                            $name = static::PHINX_TYPE_BOOLEAN;
1155
                            $limit = null;
1156
                        } else {
1157
                            $name = static::PHINX_TYPE_SMALL_INTEGER;
1158
                        }
1159
                        break;
1160
                    case 'smallint':
1161
                        $name = static::PHINX_TYPE_SMALL_INTEGER;
1162
                        break;
1163
                    case 'int':
1164
                    case 'mediumint':
1165
                    case 'mediuminteger':
1166
                        $name = static::PHINX_TYPE_INTEGER;
1167
                        break;
1168
                    case 'bigint':
1169
                        $name = static::PHINX_TYPE_BIG_INTEGER;
1170
                        break;
1171
                    case 'tinytext':
1172
                    case 'mediumtext':
1173
                    case 'longtext':
1174
                        $name = static::PHINX_TYPE_TEXT;
1175
                        break;
1176
                    case 'tinyblob':
1177
                    case 'mediumblob':
1178
                    case 'longblob':
1179
                        $name = static::PHINX_TYPE_BLOB;
1180
                        break;
1181
                    case 'real':
1182
                    case 'numeric':
1183
                        $name = self::PHINX_TYPE_FLOAT;
1184
                        break;
1185
                    default:
1186
                        if (in_array($typeLC, self::$unsupportedColumnTypes)) {
1187
                            // unsupported but known types are passed through lowercased, and without appended affinity
1188
                            $name = Literal::from($typeLC);
1189
                        } else {
1190
                            // unknown types are passed through as-is
1191
                            $name = Literal::from($type . $affinity);
1192
                        }
1193
                }
1194
            }
1195
        }
1196
1197
        return [
1198
            'name' => $name,
1199
            'limit' => $limit,
1200
            'scale' => $scale
1201
        ];
1202
    }
1203
1204
    /**
1205
     * {@inheritdoc}
1206
     */
1207
    public function createDatabase($name, $options = [])
1208
    {
1209
        touch($name . $this->suffix);
1210
    }
1211
1212
    /**
1213
     * {@inheritdoc}
1214
     */
1215
    public function hasDatabase($name)
1216
    {
1217
        return is_file($name . $this->suffix);
1218
    }
1219
1220
    /**
1221
     * {@inheritdoc}
1222
     */
1223
    public function dropDatabase($name)
1224
    {
1225
        if ($this->getOption('memory')) {
1226
            $this->disconnect();
1227
            $this->connect();
1228
        }
1229
        if (file_exists($name . $this->suffix)) {
1230
            unlink($name . $this->suffix);
1231
        }
1232
    }
1233
1234
    /**
1235
     * Gets the SQLite Column Definition for a Column object.
1236
     *
1237
     * @param \Phinx\Db\Table\Column $column Column
1238
     * @return string
1239
     */
1240
    protected function getColumnSqlDefinition(Column $column)
1241
    {
1242
        $isLiteralType = $column->getType() instanceof Literal;
1243
        if ($isLiteralType) {
1244
            $def = (string)$column->getType();
1245
        } else {
1246
            $sqlType = $this->getSqlType($column->getType());
1247
            $def = strtoupper($sqlType['name']);
1248
1249
            $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
1250
            if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
1251
                $def .= '(' . ($column->getLimit() ?: $sqlType['limit']) . ')';
1252
            }
1253
        }
1254
        if ($column->getPrecision() && $column->getScale()) {
1255
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1256
        }
1257
1258
        $default = $column->getDefault();
1259
1260
        $def .= (!$column->isIdentity() && ($column->isNull() || is_null($default))) ? ' NULL' : ' NOT NULL';
1261
        $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...
1262
        $def .= $column->isIdentity() ? ' PRIMARY KEY AUTOINCREMENT' : '';
1263
1264
        if ($column->getUpdate()) {
1265
            $def .= ' ON UPDATE ' . $column->getUpdate();
1266
        }
1267
1268
        $def .= $this->getCommentDefinition($column);
1269
1270
        return $def;
1271
    }
1272
1273
    /**
1274
     * Gets the comment Definition for a Column object.
1275
     *
1276
     * @param \Phinx\Db\Table\Column $column Column
1277
     * @return string
1278
     */
1279
    protected function getCommentDefinition(Column $column)
1280
    {
1281
        if ($column->getComment()) {
1282
            return ' /* ' . $column->getComment() . ' */ ';
1283
        }
1284
1285
        return '';
1286
    }
1287
1288
    /**
1289
     * Gets the SQLite Index Definition for an Index object.
1290
     *
1291
     * @param \Phinx\Db\Table\Table $table Table
1292
     * @param \Phinx\Db\Table\Index $index Index
1293
     * @return string
1294
     */
1295
    protected function getIndexSqlDefinition(Table $table, Index $index)
1296
    {
1297
        if ($index->getType() === Index::UNIQUE) {
1298
            $def = 'UNIQUE INDEX';
1299
        } else {
1300
            $def = 'INDEX';
1301
        }
1302
        if (is_string($index->getName())) {
1303
            $indexName = $index->getName();
1304
        } else {
1305
            $indexName = $table->getName() . '_';
1306
            foreach ($index->getColumns() as $column) {
1307
                $indexName .= $column . '_';
1308
            }
1309
            $indexName .= 'index';
1310
        }
1311
        $def .= ' `' . $indexName . '`';
1312
1313
        return $def;
1314
    }
1315
1316
    /**
1317
     * {@inheritdoc}
1318
     */
1319
    public function getColumnTypes()
1320
    {
1321
        return array_keys(self::$supportedColumnTypes);
1322
    }
1323
1324
    /**
1325
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1326
     *
1327
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1328
     * @return string
1329
     */
1330 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...
1331
    {
1332
        $def = '';
1333
        if ($foreignKey->getConstraint()) {
1334
            $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...
1335
        } else {
1336
            $columnNames = [];
1337
            foreach ($foreignKey->getColumns() as $column) {
1338
                $columnNames[] = $this->quoteColumnName($column);
1339
            }
1340
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1341
            $refColumnNames = [];
1342
            foreach ($foreignKey->getReferencedColumns() as $column) {
1343
                $refColumnNames[] = $this->quoteColumnName($column);
1344
            }
1345
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1346
            if ($foreignKey->getOnDelete()) {
1347
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1348
            }
1349
            if ($foreignKey->getOnUpdate()) {
1350
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1351
            }
1352
        }
1353
1354
        return $def;
1355
    }
1356
1357
    /**
1358
     * {@inheritDoc}
1359
     *
1360
     */
1361
    public function getDecoratedConnection()
1362
    {
1363
        $options = $this->getOptions();
1364
        $options['quoteIdentifiers'] = true;
1365
        $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...
1366
1367
        if (!empty($options['name'])) {
1368
            $options['database'] = $options['name'];
1369
1370
            if (file_exists($options['name'] . $this->suffix)) {
1371
                $options['database'] = $options['name'] . $this->suffix;
1372
            }
1373
        }
1374
1375
        $driver = new SqliteDriver($options);
1376
        if (method_exists($driver, 'setConnection')) {
1377
            $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...
1378
        } else {
1379
            $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...
1380
        }
1381
1382
        return new Connection(['driver' => $driver] + $options);
1383
    }
1384
}
1385