Completed
Pull Request — master (#1542)
by
unknown
02:40
created

SQLiteAdapter::hasTable()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 36
ccs 26
cts 26
cp 1
rs 8.4106
c 0
b 0
f 0
cc 7
nc 10
nop 1
crap 7
1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use Cake\Database\Connection;
32
use Cake\Database\Driver\Sqlite as SqliteDriver;
33
use Phinx\Db\Table\Column;
34
use Phinx\Db\Table\ForeignKey;
35
use Phinx\Db\Table\Index;
36
use Phinx\Db\Table\Table;
37
use Phinx\Db\Util\AlterInstructions;
38
use Phinx\Util\Literal;
39
40
/**
41
 * Phinx SQLite Adapter.
42
 *
43
 * @author Rob Morgan <[email protected]>
44
 * @author Richard McIntyre <[email protected]>
45
 */
46
class SQLiteAdapter extends PdoAdapter implements AdapterInterface
47
{
48
    protected $definitionsWithLimits = [
49
        'CHAR',
50
        'CHARACTER',
51
        'VARCHAR',
52
        'VARYING CHARACTER',
53
        'NCHAR',
54
        'NATIVE CHARACTER',
55
        'NVARCHAR'
56 42
    ];
57
58 42
    protected $suffix = '.sqlite3';
59 42
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function connect()
64
    {
65 42
        if ($this->connection === null) {
66 42
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
67
                // @codeCoverageIgnoreStart
68
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
69 42
                // @codeCoverageIgnoreEnd
70
            }
71
72 42
            $db = null;
73 42
            $options = $this->getOptions();
74 42
75 42
            // use a memory database if the option was specified
76
            if (!empty($options['memory'])) {
77
                $dsn = 'sqlite::memory:';
78
            } else {
79 42
                $dsn = 'sqlite:' . $options['name'] . $this->suffix;
80 42
            }
81
82
            try {
83
                $db = new \PDO($dsn);
84
            } catch (\PDOException $exception) {
85
                throw new \InvalidArgumentException(sprintf(
86
                    'There was a problem connecting to the database: %s',
87 42
                    $exception->getMessage()
88 42
                ));
89 42
            }
90
91
            $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
92
            $this->setConnection($db);
93
        }
94 48
    }
95
96 48
    /**
97 48
     * {@inheritdoc}
98
     */
99
    public function setOptions(array $options)
100
    {
101
        parent::setOptions($options);
102
103
        if (isset($options['suffix'])) {
104
            $this->suffix = $options['suffix'];
105
        }
106
        //don't "fix" the file extension if it is blank, some people
107
        //might want a SQLITE db file with absolutely no extension.
108
        if (strlen($this->suffix) && substr($this->suffix, 0, 1) !== '.') {
109
            $this->suffix = '.' . $this->suffix;
110 1
        }
111
112 1
        return $this;
113 1
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function disconnect()
119
    {
120
        $this->connection = null;
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function hasTransactions()
127
    {
128
        return true;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 43
    public function beginTransaction()
135
    {
136 43
        $this->getConnection()->beginTransaction();
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 44
    public function commitTransaction()
143
    {
144 44
        $this->getConnection()->commit();
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150 42
    public function rollbackTransaction()
151
    {
152 42
        $this->getConnection()->rollBack();
153 42
    }
154 42
155 12
    /**
156 42
     * {@inheritdoc}
157
     */
158 42
    public function quoteTableName($tableName)
159
    {
160
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
161
    }
162
163
    /**
164 42
     * {@inheritdoc}
165
     */
166
    public function quoteColumnName($columnName)
167 42
    {
168 42
        return '`' . str_replace('`', '``', $columnName) . '`';
169 42
    }
170 35
171 35
    /**
172 35
     * @param string $tableName Table name
173 35
     * @return array
174
     */
175 35
    protected function getSchemaName($tableName)
176 42
    {
177
        if (preg_match("/.\.([^\.]+)$/", $tableName, $match)) {
178 1
            $table = $match[1];
179 1
            $schema = substr($tableName, 0, strlen($tableName) - strlen($match[0]) + 1);
180 1
            $result = ['schema' => $schema, 'table' => $table];
181 1
        } else {
182
            $result = ['schema' => '', 'table' => $tableName];
183 1
        }
184 1
185
        return $result;
186
    }
187 42
188 42
    /**
189 42
     * {@inheritdoc}
190 42
     */
191 42
    public function hasTable($tableName)
192
    {
193
        $info = $this->getSchemaName($tableName);
194 42
        if ($info['schema'] === '') {
195 42
            // if no schema is specified we search all schemata
196 42
            $rows = $this->fetchAll('PRAGMA database_list;');
197 42
            $schemata = [];
198 42
            foreach ($rows as $row) {
199 42
                $schemata[] = $row['name'];
200
            }
201
        } else {
202 1
            // otherwise we search just the specified schema
203 1
            $schemata = (array)$info['schema'];
204 1
        }
205
206 1
        $table = strtolower($info['table']);
207 1
        foreach ($schemata as $schema) {
208 1
            $master = sprintf('%s.%s', $this->quoteColumnName($schema), 'sqlite_master');
209 1
            try {
210 1
                $rows = $this->fetchAll(sprintf('SELECT name FROM %s WHERE type=\'table\' AND lower(name) = %s', $master, $this->quoteString($table)));
211 1
            } catch (\PDOException $e) {
212 42
                // an exception can occur if the schema part of the table refers to a database which is not attached
213 42
                return false;
214 37
            }
215
216
            // this somewhat pedantic check with strtolower is performed because the SQL lower function may be redefined,
217
            // and can act on all Unicode characters if the ICU extension is loaded, while SQL identifiers are only case-insensitive for ASCII
218 42
            foreach ($rows as $row) {
219 42
                if (strtolower($row['name']) === $table) {
220 1
                    return true;
221 1
                }
222 1
            }
223 1
        }
224
225 42
        return false;
226
    }
227 42
228
    /**
229 42
     * {@inheritdoc}
230 6
     */
231 42
    public function createTable(Table $table, array $columns = [], array $indexes = [])
232 42
    {
233
        // Add the default primary key
234
        $options = $table->getOptions();
235 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...
236
            $options['id'] = 'id';
237 1
        }
238
239 1
        if (isset($options['id']) && is_string($options['id'])) {
240 1
            // Handle id => "field_name" to support AUTO_INCREMENT
241
            $column = new Column();
242
            $column->setName($options['id'])
243
                   ->setType('integer')
244
                   ->setIdentity(true);
245 1
246
            array_unshift($columns, $column);
247 1
        }
248 1
249
        $sql = 'CREATE TABLE ';
250
        $sql .= $this->quoteTableName($table->getName()) . ' (';
251
        foreach ($columns as $column) {
252
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
253 1
254
            if (isset($options['primary_key']) && $column->getIdentity()) {
255 1
                //remove column from the primary key array as it is already defined as an autoincrement
256 1
                //primary id
257 1
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
258 1
                if ($identityColumnIndex !== false) {
259
                    unset($options['primary_key'][$identityColumnIndex]);
260 1
261 1
                    if (empty($options['primary_key'])) {
262
                        //The last primary key has been removed
263
                        unset($options['primary_key']);
264
                    }
265
                }
266 1
            }
267
        }
268 1
269 1
        // set the primary key(s)
270 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...
271 1
            $sql = rtrim($sql);
272 1
            $sql .= ' PRIMARY KEY (';
273 1
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
274 1
                $sql .= $this->quoteColumnName($options['primary_key']);
275 1
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
276 1
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
277
            }
278 1
            $sql .= ')';
279 1
        } else {
280 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
281
        }
282 1
283 1
        $sql = rtrim($sql) . ');';
284 1
        // execute the sql
285
        $this->execute($sql);
286 1
287 1
        foreach ($indexes as $index) {
288
            $this->addIndex($table, $index);
289 1
        }
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295 8
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
296
    {
297 8
        $instructions = new AlterInstructions();
298 8
299 8
        // Drop the existing primary key
300 7
        $primaryKey = $this->getPrimaryKey($table->getName());
301
        if (!empty($primaryKey)) {
302 8
            $instructions->merge(
303
                $this->getDropPrimaryKeyInstructions($table, $primaryKey)
304 8
            );
305
        }
306
307
        // Add the primary key(s)
308
        if (!empty($newColumns)) {
309
            if (!is_string($newColumns)) {
310 4
                throw new \InvalidArgumentException(sprintf(
311
                    "Invalid value for primary key: %s",
312 4
                    json_encode($newColumns)
313 4
                ));
314 4
            }
315 4
316 4
            $instructions->merge(
317 4
                $this->getAddPrimaryKeyInstructions($table, $newColumns)
318
            );
319 4
        }
320 4
321
        return $instructions;
322
    }
323
324
    /**
325 2
     * {@inheritdoc}
326
     */
327 2
    protected function getChangeCommentInstructions(Table $table, $newComment)
328
    {
329 2
        throw new \BadMethodCallException('SQLite does not have table comments');
330
    }
331 2
332 2
    /**
333 2
     * {@inheritdoc}
334 2
     */
335 2 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...
336 2
    {
337
        $sql = sprintf(
338 2
            'ALTER TABLE %s RENAME TO %s',
339 2
            $this->quoteTableName($tableName),
340 2
            $this->quoteTableName($newTableName)
341 2
        );
342 2
343 2
        return new AlterInstructions([], [$sql]);
344 2
    }
345 2
346 2
    /**
347
     * {@inheritdoc}
348 2
     */
349 1
    protected function getDropTableInstructions($tableName)
350
    {
351 1
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
352
353
        return new AlterInstructions([], [$sql]);
354 1
    }
355
356 1
    /**
357 1
     * {@inheritdoc}
358 1
     */
359
    public function truncateTable($tableName)
360 1
    {
361 1
        $sql = sprintf(
362
            'DELETE FROM %s',
363
            $this->quoteTableName($tableName)
364 1
        );
365 1
366 1
        $this->execute($sql);
367 1
    }
368 1
369
    /**
370 1
     * {@inheritdoc}
371
     */
372 1
    public function getColumns($tableName)
373
    {
374 1
        $columns = [];
375 1
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
376
377
        foreach ($rows as $columnInfo) {
378
            $column = new Column();
379
            $type = strtolower($columnInfo['type']);
380 6
            $column->setName($columnInfo['name'])
381
                   ->setNull($columnInfo['notnull'] !== '1')
382
                   ->setDefault($columnInfo['dflt_value']);
383
384 6
            $phinxType = $this->getPhinxType($type);
385
386 6
            $column->setType($phinxType['name'])
387
                   ->setLimit($phinxType['limit'])
388 6
                   ->setScale($phinxType['scale']);
389 6
390 6
            if ($columnInfo['pk'] == 1) {
391 6
                $column->setIdentity(true);
392 6
            }
393 6
394
            $columns[] = $column;
395 6
        }
396 6
397 6
        return $columns;
398 6
    }
399 6
400 6
    /**
401 6
     * {@inheritdoc}
402 6
     */
403 6 View Code Duplication
    public function hasColumn($tableName, $columnName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
404
    {
405 6
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
406
        foreach ($rows as $column) {
407
            if (strcasecmp($column['name'], $columnName) === 0) {
408
                return true;
409
            }
410
        }
411 6
412
        return false;
413 6
    }
414 6
415 6
    /**
416 6
     * {@inheritdoc}
417
     */
418 6 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...
419
    {
420 6
        $alter = sprintf(
421
            'ALTER TABLE %s ADD COLUMN %s %s',
422 6
            $this->quoteTableName($table->getName()),
423 6
            $this->quoteColumnName($column->getName()),
424 6
            $this->getColumnSqlDefinition($column)
425 6
        );
426 6
427
        return new AlterInstructions([], [$alter]);
428 6
    }
429
430 6
    /**
431 6
     * Returns the original CREATE statement for the give table
432 6
     *
433
     * @param string $tableName The table name to get the create statement for
434
     * @return string
435
     */
436
    protected function getDeclaringSql($tableName)
437 2
    {
438
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
439
440 2
        $sql = '';
441
        foreach ($rows as $table) {
442 2
            if ($table['tbl_name'] === $tableName) {
443
                $sql = $table['sql'];
444 2
            }
445 2
        }
446 2
447 2
        return $sql;
448 2
    }
449 2
450
    /**
451 2
     * Copies all the data from a tmp table to another table
452 2
     *
453 2
     * @param string $tableName The table name to copy the data to
454 2
     * @param string $tmpTableName The tmp table name where the data is stored
455 2
     * @param string[] $writeColumns The list of columns in the target table
456 2
     * @param string[] $selectColumns The list of columns in the tmp table
457 2
     * @return void
458 2
     */
459 2
    protected function copyDataToNewTable($tableName, $tmpTableName, $writeColumns, $selectColumns)
460
    {
461 2
        $sql = sprintf(
462
            'INSERT INTO %s(%s) SELECT %s FROM %s',
463 2
            $this->quoteTableName($tableName),
464
            implode(', ', $writeColumns),
465
            implode(', ', $selectColumns),
466
            $this->quoteTableName($tmpTableName)
467
        );
468
        $this->execute($sql);
469 2
    }
470
471 2
    /**
472 2
     * Modifies the passed instructions to copy all data from the tmp table into
473 2
     * the provided table and then drops the tmp table.
474
     *
475 2
     * @param AlterInstructions $instructions The instructions to modify
476
     * @param string $tableName The table name to copy the data to
477 2
     * @return AlterInstructions
478 2
     */
479 2
    protected function copyAndDropTmpTable($instructions, $tableName)
480
    {
481 2
        $instructions->addPostStep(function ($state) use ($tableName) {
482
            $this->copyDataToNewTable(
483 2
                $tableName,
484 2
                $state['tmpTableName'],
485 2
                $state['writeColumns'],
486 2
                $state['selectColumns']
487 2
            );
488
489 2
            $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($state['tmpTableName'])));
490
491 2
            return $state;
492 2
        });
493 2
494
        return $instructions;
495
    }
496
497
    /**
498
     * Returns the columns and type to use when copying a table to another in the process
499
     * of altering a table
500
     *
501 9
     * @param string $tableName The table to modify
502
     * @param string $columnName The column name that is about to change
503 9
     * @param string|false $newColumnName Optionally the new name for the column
504 9
     * @return AlterInstructions
505
     */
506 9
    protected function calculateNewTableColumns($tableName, $columnName, $newColumnName)
507 9
    {
508 9
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
509 9
        $selectColumns = [];
510 9
        $writeColumns = [];
511 9
        $columnType = null;
512 9
        $found = false;
513 9
514 9
        foreach ($columns as $column) {
515 9
            $selectName = $column['name'];
516
            $writeName = $selectName;
517
518
            if ($selectName == $columnName) {
519
                $writeName = $newColumnName;
520
                $found = true;
521 9
                $columnType = $column['type'];
522
                $selectName = $newColumnName === false ? $newColumnName : $selectName;
523 9
            }
524 4
525 4
            $selectColumns[] = $selectName;
526
            $writeColumns[] = $writeName;
527 9
        }
528 9
529
        $selectColumns = array_filter($selectColumns, 'strlen');
530 9
        $writeColumns = array_filter($writeColumns, 'strlen');
531 9
        $selectColumns = array_map([$this, 'quoteColumnName'], $selectColumns);
532 9
        $writeColumns = array_map([$this, 'quoteColumnName'], $writeColumns);
533 9
534
        if (!$found) {
535 8
            throw new \InvalidArgumentException(sprintf(
536
                'The specified column doesn\'t exist: ' . $columnName
537 8
            ));
538
        }
539
540
        return compact('writeColumns', 'selectColumns', 'columnType');
541
    }
542
543 1
    /**
544
     * Returns the initial instructions to alter a table using the
545 1
     * rename-alter-copy strategy
546
     *
547 1
     * @param string $tableName The table to modify
548 1
     * @return AlterInstructions
549 1
     */
550
    protected function beginAlterByCopyTable($tableName)
551
    {
552
        $instructions = new AlterInstructions();
553
        $instructions->addPostStep(function ($state) use ($tableName) {
554
            $createSQL = $this->getDeclaringSql($tableName);
555
556
            $tmpTableName = 'tmp_' . $tableName;
557
            $this->execute(
558
                sprintf(
559 8
                    'ALTER TABLE %s RENAME TO %s',
560
                    $this->quoteTableName($tableName),
561 8
                    $this->quoteTableName($tmpTableName)
562 8
                )
563 8
            );
564 8
565 8
            return compact('createSQL', 'tmpTableName') + $state;
566 8
        });
567 8
568 8
        return $instructions;
569 8
    }
570 8
571
    /**
572 8
     * {@inheritdoc}
573 8
     */
574 8
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
575
    {
576
        $instructions = $this->beginAlterByCopyTable($tableName);
577 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...
578
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
579 1
580
            return $newState + $state;
581 1
        });
582 1
583 1
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
584
            $sql = str_replace(
585 1
                $this->quoteColumnName($columnName),
586 1
                $this->quoteColumnName($newColumnName),
587
                $state['createSQL']
588 1
            );
589 1
            $this->execute($sql);
590 1
591 1
            return $state;
592 1
        });
593 1
594 1
        return $this->copyAndDropTmpTable($instructions, $tableName);
595 1
    }
596 1
597 1
    /**
598
     * {@inheritdoc}
599
     */
600
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
601
    {
602
        $instructions = $this->beginAlterByCopyTable($tableName);
603
604
        $newColumnName = $newColumn->getName();
605 1 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...
606
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
607 1
608
            return $newState + $state;
609 1
        });
610 1
611 1
        $instructions->addPostStep(function ($state) use ($columnName, $newColumn) {
612 1
            $sql = preg_replace(
613 1
                sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
614 1
                sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
615 1
                $state['createSQL'],
616 1
                1
617 1
            );
618
            $this->execute($sql);
619
620
            return $state;
621
        });
622
623
        return $this->copyAndDropTmpTable($instructions, $tableName);
624
    }
625 5
626
    /**
627 5
     * {@inheritdoc}
628
     */
629
    protected function getDropColumnInstructions($tableName, $columnName)
630 5
    {
631
        $instructions = $this->beginAlterByCopyTable($tableName);
632 5
633 5 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...
634 5
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, false);
635
636 1
            return $newState + $state;
637
        });
638
639
        $instructions->addPostStep(function ($state) use ($columnName) {
640
            $sql = preg_replace(
641
                sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($state['columnType'])),
642
                "",
643
                $state['createSQL']
644
            );
645 5
646
            if (substr($sql, -2) === ', ') {
647 5
                $sql = substr($sql, 0, -2) . ')';
648 5
            }
649
650
            $this->execute($sql);
651
652
            return $state;
653
        });
654
655
        return $this->copyAndDropTmpTable($instructions, $tableName);
656
    }
657
658
    /**
659
     * Get an array of indexes from a particular table.
660
     *
661 5
     * @param string $tableName Table Name
662
     * @return array
663 5
     */
664 5
    protected function getIndexes($tableName)
665 5
    {
666 5
        $indexes = [];
667 5
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
668 5
669 5
        foreach ($rows as $row) {
670 5
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
671 5
            if (!isset($indexes[$tableName])) {
672 5
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
673 5
            }
674
            foreach ($indexData as $indexItem) {
675
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
676
            }
677
        }
678
679 4
        return $indexes;
680
    }
681
682 4
    /**
683
     * {@inheritdoc}
684 4
     */
685 4 View Code Duplication
    public function hasIndex($tableName, $columns)
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...
686
    {
687 4
        $columns = array_map('strtolower', (array)$columns);
688 4
        $indexes = $this->getIndexes($tableName);
689 4
690 4
        foreach ($indexes as $index) {
691 4
            $a = array_diff($columns, $index['columns']);
692 4
            if (empty($a)) {
693
                return true;
694 4
            }
695 4
        }
696 4
697 4
        return false;
698 4
    }
699
700 4
    /**
701
     * {@inheritdoc}
702 4
     */
703 4 View Code Duplication
    public function hasIndexByName($tableName, $indexName)
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...
704
    {
705 4
        $indexes = $this->getIndexes($tableName);
706 4
707 4
        foreach ($indexes as $index) {
708 4
            if ($indexName === $index['index']) {
709 4
                return true;
710 4
            }
711 4
        }
712
713 4
        return false;
714 4
    }
715 4
716
    /**
717
     * {@inheritdoc}
718
     */
719
    protected function getAddIndexInstructions(Table $table, Index $index)
720 1
    {
721
        $indexColumnArray = [];
722
        foreach ($index->getColumns() as $column) {
723 1
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
724
        }
725
        $indexColumns = implode(',', $indexColumnArray);
726
        $sql = sprintf(
727 1
            'CREATE %s ON %s (%s)',
728
            $this->getIndexSqlDefinition($table, $index),
729 1
            $this->quoteTableName($table->getName()),
730
            $indexColumns
731 1
        );
732 1
733 1
        return new AlterInstructions([], [$sql]);
734 1
    }
735 1
736 1
    /**
737
     * {@inheritdoc}
738 1
     */
739 1
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
740 1
    {
741 1
        $indexes = $this->getIndexes($tableName);
742 1
        $columns = array_map('strtolower', (array)$columns);
743 1
        $instructions = new AlterInstructions();
744 1
745
        foreach ($indexes as $index) {
746 1
            $a = array_diff($columns, $index['columns']);
747
            if (empty($a)) {
748 1
                $instructions->addPostStep(sprintf(
749
                    'DROP INDEX %s',
750
                    $this->quoteColumnName($index['index'])
751
                ));
752
            }
753
        }
754 1
755
        return $instructions;
756 1
    }
757 1
758 1
    /**
759 1
     * {@inheritdoc}
760 1
     */
761 1
    protected function getDropIndexByNameInstructions($tableName, $indexName)
762 1
    {
763
        $indexes = $this->getIndexes($tableName);
764 1
        $instructions = new AlterInstructions();
765
766 1
        foreach ($indexes as $index) {
767 1
            if ($indexName === $index['index']) {
768 1
                $instructions->addPostStep(sprintf(
769 1
                    'DROP INDEX %s',
770 1
                    $this->quoteColumnName($indexName)
771
                ));
772 1
            }
773
        }
774 1
775 1
        return $instructions;
776 1
    }
777
778
    /**
779
     * {@inheritdoc}
780
     */
781
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
782
    {
783
        $primaryKey = $this->getPrimaryKey($tableName);
784
785
        if (empty($primaryKey)) {
786
            return false;
787
        }
788
789
        $missingColumns = array_diff((array)$columns, (array)$primaryKey);
790
791
        return empty($missingColumns);
792
    }
793
794
    /**
795
     * Get the primary key from a particular table.
796
     *
797
     * @param string $tableName Table Name
798
     * @return string|null
799
     */
800
    protected function getPrimaryKey($tableName)
801
    {
802
        $rows = $this->fetchAll(
803
            "SELECT sql, tbl_name
804
              FROM (
805
                    SELECT sql sql, type type, tbl_name tbl_name, name name
806
                      FROM sqlite_master
807
                     UNION ALL
808
                    SELECT sql, type, tbl_name, name
809
                      FROM sqlite_temp_master
810 43
                   )
811
             WHERE type != 'meta'
812
               AND sql NOTNULL
813 43
               AND name NOT LIKE 'sqlite_%'
814 42
             ORDER BY substr(type, 2, 1), name"
815
        );
816 43
817
        foreach ($rows as $row) {
818
            if ($row['tbl_name'] === $tableName) {
819 43
                if (strpos($row['sql'], 'PRIMARY KEY') !== false) {
820 1
                    preg_match_all("/PRIMARY KEY\s*\(`([^`]+)`\)/", $row['sql'], $matches);
821
                    foreach ($matches[1] as $match) {
822 43
                        if (!empty($match)) {
823 38
                            return $match;
824
                        }
825 43
                    }
826 42
                    preg_match_all("/`([^`]+)`\s+\w+(\(\d+\))?((\s+NOT)?\s+NULL)?\s+PRIMARY KEY/", $row['sql'], $matches);
827
                    foreach ($matches[1] as $match) {
828 43
                        if (!empty($match)) {
829 2
                            return $match;
830
                        }
831 43
                    }
832 1
                }
833
            }
834 43
        }
835 1
836
        return null;
837 43
    }
838 42
839
    /**
840 43
     * {@inheritdoc}
841 1
     */
842
    public function hasForeignKey($tableName, $columns, $constraint = null)
843 43
    {
844 1
        $foreignKeys = $this->getForeignKeys($tableName);
845
846 43
        return !array_diff((array)$columns, $foreignKeys);
847 43
    }
848 1
849
    /**
850 43
     * Get an array of foreign keys from a particular table.
851 42
     *
852
     * @param string $tableName Table Name
853 5
     * @return array
854
     */
855 5
    protected function getForeignKeys($tableName)
856 4
    {
857
        $foreignKeys = [];
858
        $rows = $this->fetchAll(
859
            "SELECT sql, tbl_name
860 1
              FROM (
861 1
                    SELECT sql sql, type type, tbl_name tbl_name, name name
862
                      FROM sqlite_master
863
                     UNION ALL
864 1
                    SELECT sql, type, tbl_name, name
865
                      FROM sqlite_temp_master
866
                   )
867 1
             WHERE type != 'meta'
868
               AND sql NOTNULL
869 1
               AND name NOT LIKE 'sqlite_%'
870 1
             ORDER BY substr(type, 2, 1), name"
871 1
        );
872
873
        foreach ($rows as $row) {
874
            if ($row['tbl_name'] === $tableName) {
875
                if (strpos($row['sql'], 'REFERENCES') !== false) {
876
                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
877
                    foreach ($matches[1] as $match) {
878
                        $foreignKeys[] = $match;
879
                    }
880 3
                }
881
            }
882 3
        }
883 1
884
        return $foreignKeys;
885 2
    }
886 2
887 2
    /**
888 2
     * @param Table $table The Table
889 1
     * @param string $column Column Name
890 1
     * @return AlterInstructions
891 2
     */
892
    protected function getAddPrimaryKeyInstructions(Table $table, $column)
893
    {
894 2
        $instructions = $this->beginAlterByCopyTable($table->getName());
895 2
896 1
        $tableName = $table->getName();
897 1
        $instructions->addPostStep(function ($state) use ($column) {
898
            $matchPattern = "/(`$column`)\s+(\w+(\(\d+\))?)\s+((NOT )?NULL)/";
899
900 1
            $sql = $state['createSQL'];
901 2
902
            if (preg_match($matchPattern, $state['createSQL'], $matches)) {
903
                if (isset($matches[2])) {
904
                    if ($matches[2] === 'INTEGER') {
905
                        $replace = '$1 INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT';
906
                    } else {
907
                        $replace = '$1 $2 NOT NULL PRIMARY KEY';
908
                    }
909
910 2
                    $sql = preg_replace($matchPattern, $replace, $state['createSQL'], 1);
911
                }
912
            }
913
914
            $this->execute($sql);
915
916 2
            return $state;
917 1
        });
918
919 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...
920 1
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
921 1
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
922 2
            $selectColumns = $writeColumns = $names;
923 1
924 1
            return compact('selectColumns', 'writeColumns') + $state;
925 2
        });
926 2
927
        return $this->copyAndDropTmpTable($instructions, $tableName);
928
    }
929
930
    /**
931
     * @param Table $table Table
932
     * @param string $column Column Name
933 2
     * @return AlterInstructions
934
     */
935
    protected function getDropPrimaryKeyInstructions($table, $column)
936 1
    {
937 1
        $instructions = $this->beginAlterByCopyTable($table->getName());
938
939 1
        $instructions->addPostStep(function ($state) use ($column) {
940
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $column, $column);
941
942
            return $newState + $state;
943
        });
944
945
        $instructions->addPostStep(function ($state) {
946 48
            $search = "/(,?\s*PRIMARY KEY\s*\([^\)]*\)|\s+PRIMARY KEY(\s+AUTOINCREMENT)?)/";
947
            $sql = preg_replace($search, '', $state['createSQL'], 1);
948 48
949 48
            if ($sql) {
950
                $this->execute($sql);
951
            }
952
953
            return $state;
954 2
        });
955
956 2
        return $this->copyAndDropTmpTable($instructions, $table->getName());
957
    }
958
959
    /**
960
     * {@inheritdoc}
961
     */
962 48
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
963
    {
964 48
        $instructions = $this->beginAlterByCopyTable($table->getName());
965 47
966 47
        $tableName = $table->getName();
967 48
        $instructions->addPostStep(function ($state) use ($foreignKey) {
968
            $this->execute('pragma foreign_keys = ON');
969
            $sql = substr($state['createSQL'], 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
970
            $this->execute($sql);
971
972
            return $state;
973
        });
974
975 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...
976
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
977 42
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
978 8
            $selectColumns = $writeColumns = $names;
979 42
980 42
            return compact('selectColumns', 'writeColumns') + $state;
981 42
        });
982 42
983
        return $this->copyAndDropTmpTable($instructions, $tableName);
984
    }
985
986
    /**
987
     * {@inheritdoc}
988
     */
989
    protected function getDropForeignKeyInstructions($tableName, $constraint)
990
    {
991 42
        throw new \BadMethodCallException('SQLite does not have named foreign keys');
992
    }
993 42
994 42
    /**
995 42
     * {@inheritdoc}
996 42
     */
997
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
998
    {
999 42
        $instructions = $this->beginAlterByCopyTable($tableName);
1000 42
1001 42
        $instructions->addPostStep(function ($state) use ($columns) {
1002 42
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columns[0], $columns[0]);
1003 42
1004 4
            $selectColumns = $newState['selectColumns'];
1005 4
            $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...
1006
            $diff = array_diff($columns, $selectColumns);
1007 42
1008
            if (!empty($diff)) {
1009 42
                throw new \InvalidArgumentException(sprintf(
1010 42
                    'The specified columns don\'t exist: ' . implode(', ', $diff)
1011 42
                ));
1012
            }
1013 42
1014
            return $newState + $state;
1015
        });
1016
1017 42
        $instructions->addPostStep(function ($state) use ($columns) {
1018
            $sql = '';
1019 42
1020
            foreach ($columns as $columnName) {
1021
                $search = sprintf(
1022
                    "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
1023
                    $this->quoteColumnName($columnName)
1024
                );
1025
                $sql = preg_replace($search, '', $state['createSQL'], 1);
1026
            }
1027
1028 42
            if ($sql) {
1029
                $this->execute($sql);
1030 42
            }
1031 2
1032
            return $state;
1033 42
        });
1034
1035
        return $this->copyAndDropTmpTable($instructions, $tableName);
1036
    }
1037
1038
    /**
1039
     * {@inheritdoc}
1040
     */
1041
    public function getSqlType($type, $limit = null)
1042 8
    {
1043
        switch ($type) {
1044 8
            case static::PHINX_TYPE_TEXT:
1045 2
            case static::PHINX_TYPE_INTEGER:
1046 2
            case static::PHINX_TYPE_FLOAT:
1047 6
            case static::PHINX_TYPE_DOUBLE:
1048
            case static::PHINX_TYPE_DECIMAL:
1049 8
            case static::PHINX_TYPE_DATETIME:
1050 3
            case static::PHINX_TYPE_TIME:
1051 3
            case static::PHINX_TYPE_DATE:
1052 6
            case static::PHINX_TYPE_BLOB:
1053 6
            case static::PHINX_TYPE_BOOLEAN:
1054 6
            case static::PHINX_TYPE_ENUM:
1055 6
                return ['name' => $type];
1056 6
            case static::PHINX_TYPE_STRING:
1057
                return ['name' => 'varchar', 'limit' => 255];
1058 8
            case static::PHINX_TYPE_CHAR:
1059 8
                return ['name' => 'char', 'limit' => 255];
1060
            case static::PHINX_TYPE_SMALL_INTEGER:
1061
                return ['name' => 'smallint'];
1062
            case static::PHINX_TYPE_BIG_INTEGER:
1063
                return ['name' => 'bigint'];
1064
            case static::PHINX_TYPE_TIMESTAMP:
1065 47
                return ['name' => 'datetime'];
1066
            case static::PHINX_TYPE_BINARY:
1067 47
                return ['name' => 'blob'];
1068
            case static::PHINX_TYPE_UUID:
1069
                return ['name' => 'char', 'limit' => 36];
1070
            case static::PHINX_TYPE_JSON:
1071
            case static::PHINX_TYPE_JSONB:
1072
                return ['name' => 'text'];
1073
            // Geospatial database types
1074
            // No specific data types exist in SQLite, instead all geospatial
1075
            // functionality is handled in the client. See also: SpatiaLite.
1076 5
            case static::PHINX_TYPE_GEOMETRY:
1077
            case static::PHINX_TYPE_POLYGON:
1078 5
                return ['name' => 'text'];
1079 5
            case static::PHINX_TYPE_LINESTRING:
1080
                return ['name' => 'varchar', 'limit' => 255];
1081
            case static::PHINX_TYPE_POINT:
1082 5
                return ['name' => 'float'];
1083 5
            default:
1084 5
                throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by SQLite.');
1085 5
        }
1086 5
    }
1087 5
1088 5
    /**
1089 5
     * Returns Phinx type by SQL type
1090 5
     *
1091 5
     * @param string $sqlTypeDef SQL type
1092 5
     * @throws UnsupportedColumnTypeException
1093 1
     * @return array
1094 1
     */
1095 5
    public function getPhinxType($sqlTypeDef)
1096 1
    {
1097 1
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
1098
            throw new UnsupportedColumnTypeException('Column type "' . $sqlTypeDef . '" is not supported by SQLite.');
1099 5
        } else {
1100
            $limit = null;
1101
            $scale = null;
1102
            $type = $matches[1];
1103
            if (count($matches) > 2) {
1104
                $limit = $matches[3] ?: null;
1105
            }
1106
            if (count($matches) > 4) {
1107
                $scale = $matches[5];
1108
            }
1109
            switch ($type) {
1110
                case 'varchar':
1111
                    $type = static::PHINX_TYPE_STRING;
1112
                    if ($limit === 255) {
1113
                        $limit = null;
1114
                    }
1115
                    break;
1116 View Code Duplication
                case 'char':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1117
                    $type = static::PHINX_TYPE_CHAR;
1118
                    if ($limit === 255) {
1119
                        $limit = null;
1120
                    }
1121
                    if ($limit === 36) {
1122
                        $type = static::PHINX_TYPE_UUID;
1123
                    }
1124
                    break;
1125
                case 'smallint':
1126
                    $type = static::PHINX_TYPE_SMALL_INTEGER;
1127
                    if ($limit === 11) {
1128
                        $limit = null;
1129
                    }
1130
                    break;
1131
                case 'int':
1132
                    $type = static::PHINX_TYPE_INTEGER;
1133
                    if ($limit === 11) {
1134
                        $limit = null;
1135
                    }
1136
                    break;
1137
                case 'bigint':
1138
                    if ($limit === 11) {
1139
                        $limit = null;
1140
                    }
1141
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1142
                    break;
1143
                case 'blob':
1144
                    $type = static::PHINX_TYPE_BINARY;
1145
                    break;
1146
            }
1147 View Code Duplication
            if ($type === 'tinyint') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1148
                if ($matches[3] === 1) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $matches[3] (string) and 1 (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1149
                    $type = static::PHINX_TYPE_BOOLEAN;
1150
                    $limit = null;
1151
                }
1152
            }
1153
1154
            try {
1155
                // Call this to check if parsed type is supported.
1156
                $this->getSqlType($type);
1157
            } catch (UnsupportedColumnTypeException $e) {
1158
                $type = Literal::from($type);
1159
            }
1160
1161
            return [
1162
                'name' => $type,
1163
                'limit' => $limit,
1164
                'scale' => $scale
1165
            ];
1166
        }
1167
    }
1168
1169
    /**
1170
     * {@inheritdoc}
1171
     */
1172
    public function createDatabase($name, $options = [])
1173
    {
1174
        touch($name . $this->suffix);
1175
    }
1176
1177
    /**
1178
     * {@inheritdoc}
1179
     */
1180
    public function hasDatabase($name)
1181
    {
1182
        return is_file($name . $this->suffix);
1183
    }
1184
1185
    /**
1186
     * {@inheritdoc}
1187
     */
1188
    public function dropDatabase($name)
1189
    {
1190
        if ($this->getOption('memory')) {
1191
            $this->disconnect();
1192
            $this->connect();
1193
        }
1194
        if (file_exists($name . $this->suffix)) {
1195
            unlink($name . $this->suffix);
1196
        }
1197
    }
1198
1199
    /**
1200
     * Gets the SQLite Column Definition for a Column object.
1201
     *
1202
     * @param \Phinx\Db\Table\Column $column Column
1203
     * @return string
1204
     */
1205
    protected function getColumnSqlDefinition(Column $column)
1206
    {
1207
        $isLiteralType = $column->getType() instanceof Literal;
1208
        if ($isLiteralType) {
1209
            $def = (string)$column->getType();
1210
        } else {
1211
            $sqlType = $this->getSqlType($column->getType());
1212
            $def = strtoupper($sqlType['name']);
1213
1214
            $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
1215
            if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
1216
                $def .= '(' . ($column->getLimit() ?: $sqlType['limit']) . ')';
1217
            }
1218
        }
1219
        if ($column->getPrecision() && $column->getScale()) {
1220
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1221
        }
1222 View Code Duplication
        if (($values = $column->getValues()) && is_array($values)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1223
            $def .= " CHECK({$this->quoteColumnName($column->getName())} IN ('" . implode("', '", $values) . "'))";
1224
        }
1225
1226
        $default = $column->getDefault();
1227
1228
        $def .= (!$column->isIdentity() && ($column->isNull() || is_null($default))) ? ' NULL' : ' NOT NULL';
1229
        $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...
1230
        $def .= $column->isIdentity() ? ' PRIMARY KEY AUTOINCREMENT' : '';
1231
1232
        if ($column->getUpdate()) {
1233
            $def .= ' ON UPDATE ' . $column->getUpdate();
1234
        }
1235
1236
        $def .= $this->getCommentDefinition($column);
1237
1238
        return $def;
1239
    }
1240
1241
    /**
1242
     * Gets the comment Definition for a Column object.
1243
     *
1244
     * @param \Phinx\Db\Table\Column $column Column
1245
     * @return string
1246
     */
1247
    protected function getCommentDefinition(Column $column)
1248
    {
1249
        if ($column->getComment()) {
1250
            return ' /* ' . $column->getComment() . ' */ ';
1251
        }
1252
1253
        return '';
1254
    }
1255
1256
    /**
1257
     * Gets the SQLite Index Definition for an Index object.
1258
     *
1259
     * @param \Phinx\Db\Table\Table $table Table
1260
     * @param \Phinx\Db\Table\Index $index Index
1261
     * @return string
1262
     */
1263
    protected function getIndexSqlDefinition(Table $table, Index $index)
1264
    {
1265
        if ($index->getType() === Index::UNIQUE) {
1266
            $def = 'UNIQUE INDEX';
1267
        } else {
1268
            $def = 'INDEX';
1269
        }
1270
        if (is_string($index->getName())) {
1271
            $indexName = $index->getName();
1272
        } else {
1273
            $indexName = $table->getName() . '_';
1274
            foreach ($index->getColumns() as $column) {
1275
                $indexName .= $column . '_';
1276
            }
1277
            $indexName .= 'index';
1278
        }
1279
        $def .= ' `' . $indexName . '`';
1280
1281
        return $def;
1282
    }
1283
1284
    /**
1285
     * {@inheritdoc}
1286
     */
1287
    public function getColumnTypes()
1288
    {
1289
        return array_merge(parent::getColumnTypes(), ['enum', 'json', 'jsonb']);
1290
    }
1291
1292
    /**
1293
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1294
     *
1295
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1296
     * @return string
1297
     */
1298 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...
1299
    {
1300
        $def = '';
1301
        if ($foreignKey->getConstraint()) {
1302
            $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...
1303
        } else {
1304
            $columnNames = [];
1305
            foreach ($foreignKey->getColumns() as $column) {
1306
                $columnNames[] = $this->quoteColumnName($column);
1307
            }
1308
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1309
            $refColumnNames = [];
1310
            foreach ($foreignKey->getReferencedColumns() as $column) {
1311
                $refColumnNames[] = $this->quoteColumnName($column);
1312
            }
1313
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1314
            if ($foreignKey->getOnDelete()) {
1315
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1316
            }
1317
            if ($foreignKey->getOnUpdate()) {
1318
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1319
            }
1320
        }
1321
1322
        return $def;
1323
    }
1324
1325
    /**
1326
     * {@inheritDoc}
1327
     *
1328
     */
1329
    public function getDecoratedConnection()
1330
    {
1331
        $options = $this->getOptions();
1332
        $options['quoteIdentifiers'] = true;
1333
        $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...
1334
1335
        if (!empty($options['name'])) {
1336
            $options['database'] = $options['name'];
1337
1338
            if (file_exists($options['name'] . $this->suffix)) {
1339
                $options['database'] = $options['name'] . $this->suffix;
1340
            }
1341
        }
1342
1343
        $driver = new SqliteDriver($options);
1344
        if (method_exists($driver, 'setConnection')) {
1345
            $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...
1346
        } else {
1347
            $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...
1348
        }
1349
1350
        return new Connection(['driver' => $driver] + $options);
1351
    }
1352
}
1353