Completed
Pull Request — master (#1537)
by
unknown
01:41
created

SQLiteAdapter::hasTable()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 20
cts 20
cp 1
rs 8.8497
c 0
b 0
f 0
cc 6
nc 12
nop 1
crap 6
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
        $spec = $this->getSchemaName($tableName);
194 42
        switch ($spec['schema']) {
195 42
            case 'main':
196 42
            case '':
197 42
                $master = 'sqlite_master';
198 42
                break;
199 42
            case 'temp':
200
                $master = 'sqlite_temp_master';
201
                break;
202 1
            default:
203 1
                $master = sprintf('%s.%s', $this->quoteColumnName($spec['schema']), 'sqlite_master');
204 1
        }
205
        $table = $spec['table'];
206 1
207 1
        $tables = [];
208 1
        try {
209 1
            $rows = $this->fetchAll(sprintf('SELECT name FROM %s WHERE type=\'table\' AND name=%s', $master, $this->quoteValue($table)));
210 1
        } catch (\PDOException $e) {
211 1
            return false;
212 42
        }
213 42
        foreach ($rows as $row) {
214 37
            $tables[] = strtolower($row[0]);
215
        }
216
217
        return in_array(strtolower($table), $tables);
218 42
    }
219 42
220 1
    /**
221 1
     * {@inheritdoc}
222 1
     */
223 1
    public function createTable(Table $table, array $columns = [], array $indexes = [])
224
    {
225 42
        // Add the default primary key
226
        $options = $table->getOptions();
227 42 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...
228
            $options['id'] = 'id';
229 42
        }
230 6
231 42
        if (isset($options['id']) && is_string($options['id'])) {
232 42
            // Handle id => "field_name" to support AUTO_INCREMENT
233
            $column = new Column();
234
            $column->setName($options['id'])
235
                   ->setType('integer')
236
                   ->setIdentity(true);
237 1
238
            array_unshift($columns, $column);
239 1
        }
240 1
241
        $sql = 'CREATE TABLE ';
242
        $sql .= $this->quoteTableName($table->getName()) . ' (';
243
        foreach ($columns as $column) {
244
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
245 1
246
            if (isset($options['primary_key']) && $column->getIdentity()) {
247 1
                //remove column from the primary key array as it is already defined as an autoincrement
248 1
                //primary id
249
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
250
                if ($identityColumnIndex !== false) {
251
                    unset($options['primary_key'][$identityColumnIndex]);
252
253 1
                    if (empty($options['primary_key'])) {
254
                        //The last primary key has been removed
255 1
                        unset($options['primary_key']);
256 1
                    }
257 1
                }
258 1
            }
259
        }
260 1
261 1
        // set the primary key(s)
262 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...
263
            $sql = rtrim($sql);
264
            $sql .= ' PRIMARY KEY (';
265
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
266 1
                $sql .= $this->quoteColumnName($options['primary_key']);
267
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
268 1
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
269 1
            }
270
            $sql .= ')';
271 1
        } else {
272 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
273 1
        }
274 1
275 1
        $sql = rtrim($sql) . ');';
276 1
        // execute the sql
277
        $this->execute($sql);
278 1
279 1
        foreach ($indexes as $index) {
280 1
            $this->addIndex($table, $index);
281
        }
282 1
    }
283 1
284 1
    /**
285
     * {@inheritdoc}
286 1
     */
287 1
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
288
    {
289 1
        $instructions = new AlterInstructions();
290
291
        // Drop the existing primary key
292
        $primaryKey = $this->getPrimaryKey($table->getName());
293
        if (!empty($primaryKey)) {
294
            $instructions->merge(
295 8
                $this->getDropPrimaryKeyInstructions($table, $primaryKey)
296
            );
297 8
        }
298 8
299 8
        // Add the primary key(s)
300 7
        if (!empty($newColumns)) {
301
            if (!is_string($newColumns)) {
302 8
                throw new \InvalidArgumentException(sprintf(
303
                    "Invalid value for primary key: %s",
304 8
                    json_encode($newColumns)
305
                ));
306
            }
307
308
            $instructions->merge(
309
                $this->getAddPrimaryKeyInstructions($table, $newColumns)
310 4
            );
311
        }
312 4
313 4
        return $instructions;
314 4
    }
315 4
316 4
    /**
317 4
     * {@inheritdoc}
318
     */
319 4
    protected function getChangeCommentInstructions(Table $table, $newComment)
320 4
    {
321
        throw new \BadMethodCallException('SQLite does not have table comments');
322
    }
323
324
    /**
325 2
     * {@inheritdoc}
326
     */
327 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...
328
    {
329 2
        $sql = sprintf(
330
            'ALTER TABLE %s RENAME TO %s',
331 2
            $this->quoteTableName($tableName),
332 2
            $this->quoteTableName($newTableName)
333 2
        );
334 2
335 2
        return new AlterInstructions([], [$sql]);
336 2
    }
337
338 2
    /**
339 2
     * {@inheritdoc}
340 2
     */
341 2
    protected function getDropTableInstructions($tableName)
342 2
    {
343 2
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
344 2
345 2
        return new AlterInstructions([], [$sql]);
346 2
    }
347
348 2
    /**
349 1
     * {@inheritdoc}
350
     */
351 1
    public function truncateTable($tableName)
352
    {
353
        $sql = sprintf(
354 1
            'DELETE FROM %s',
355
            $this->quoteTableName($tableName)
356 1
        );
357 1
358 1
        $this->execute($sql);
359
    }
360 1
361 1
    /**
362
     * {@inheritdoc}
363
     */
364 1
    public function getColumns($tableName)
365 1
    {
366 1
        $columns = [];
367 1
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
368 1
369
        foreach ($rows as $columnInfo) {
370 1
            $column = new Column();
371
            $type = strtolower($columnInfo['type']);
372 1
            $column->setName($columnInfo['name'])
373
                   ->setNull($columnInfo['notnull'] !== '1')
374 1
                   ->setDefault($columnInfo['dflt_value']);
375 1
376
            $phinxType = $this->getPhinxType($type);
377
378
            $column->setType($phinxType['name'])
379
                   ->setLimit($phinxType['limit'])
380 6
                   ->setScale($phinxType['scale']);
381
382
            if ($columnInfo['pk'] == 1) {
383
                $column->setIdentity(true);
384 6
            }
385
386 6
            $columns[] = $column;
387
        }
388 6
389 6
        return $columns;
390 6
    }
391 6
392 6
    /**
393 6
     * {@inheritdoc}
394
     */
395 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...
396 6
    {
397 6
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
398 6
        foreach ($rows as $column) {
399 6
            if (strcasecmp($column['name'], $columnName) === 0) {
400 6
                return true;
401 6
            }
402 6
        }
403 6
404
        return false;
405 6
    }
406
407
    /**
408
     * {@inheritdoc}
409
     */
410 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...
411 6
    {
412
        $alter = sprintf(
413 6
            'ALTER TABLE %s ADD COLUMN %s %s',
414 6
            $this->quoteTableName($table->getName()),
415 6
            $this->quoteColumnName($column->getName()),
416 6
            $this->getColumnSqlDefinition($column)
417
        );
418 6
419
        return new AlterInstructions([], [$alter]);
420 6
    }
421
422 6
    /**
423 6
     * Returns the original CREATE statement for the give table
424 6
     *
425 6
     * @param string $tableName The table name to get the create statement for
426 6
     * @return string
427
     */
428 6
    protected function getDeclaringSql($tableName)
429
    {
430 6
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
431 6
432 6
        $sql = '';
433
        foreach ($rows as $table) {
434
            if ($table['tbl_name'] === $tableName) {
435
                $sql = $table['sql'];
436
            }
437 2
        }
438
439
        return $sql;
440 2
    }
441
442 2
    /**
443
     * Copies all the data from a tmp table to another table
444 2
     *
445 2
     * @param string $tableName The table name to copy the data to
446 2
     * @param string $tmpTableName The tmp table name where the data is stored
447 2
     * @param string[] $writeColumns The list of columns in the target table
448 2
     * @param string[] $selectColumns The list of columns in the tmp table
449 2
     * @return void
450
     */
451 2
    protected function copyDataToNewTable($tableName, $tmpTableName, $writeColumns, $selectColumns)
452 2
    {
453 2
        $sql = sprintf(
454 2
            'INSERT INTO %s(%s) SELECT %s FROM %s',
455 2
            $this->quoteTableName($tableName),
456 2
            implode(', ', $writeColumns),
457 2
            implode(', ', $selectColumns),
458 2
            $this->quoteTableName($tmpTableName)
459 2
        );
460
        $this->execute($sql);
461 2
    }
462
463 2
    /**
464
     * Modifies the passed instructions to copy all data from the tmp table into
465
     * the provided table and then drops the tmp table.
466
     *
467
     * @param AlterInstructions $instructions The instructions to modify
468
     * @param string $tableName The table name to copy the data to
469 2
     * @return AlterInstructions
470
     */
471 2
    protected function copyAndDropTmpTable($instructions, $tableName)
472 2
    {
473 2
        $instructions->addPostStep(function ($state) use ($tableName) {
474
            $this->copyDataToNewTable(
475 2
                $tableName,
476
                $state['tmpTableName'],
477 2
                $state['writeColumns'],
478 2
                $state['selectColumns']
479 2
            );
480
481 2
            $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($state['tmpTableName'])));
482
483 2
            return $state;
484 2
        });
485 2
486 2
        return $instructions;
487 2
    }
488
489 2
    /**
490
     * Returns the columns and type to use when copying a table to another in the process
491 2
     * of altering a table
492 2
     *
493 2
     * @param string $tableName The table to modify
494
     * @param string $columnName The column name that is about to change
495
     * @param string|false $newColumnName Optionally the new name for the column
496
     * @return AlterInstructions
497
     */
498
    protected function calculateNewTableColumns($tableName, $columnName, $newColumnName)
499
    {
500
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
501 9
        $selectColumns = [];
502
        $writeColumns = [];
503 9
        $columnType = null;
504 9
        $found = false;
505
506 9
        foreach ($columns as $column) {
507 9
            $selectName = $column['name'];
508 9
            $writeName = $selectName;
509 9
510 9
            if ($selectName == $columnName) {
511 9
                $writeName = $newColumnName;
512 9
                $found = true;
513 9
                $columnType = $column['type'];
514 9
                $selectName = $newColumnName === false ? $newColumnName : $selectName;
515 9
            }
516
517
            $selectColumns[] = $selectName;
518
            $writeColumns[] = $writeName;
519
        }
520
521 9
        $selectColumns = array_filter($selectColumns, 'strlen');
522
        $writeColumns = array_filter($writeColumns, 'strlen');
523 9
        $selectColumns = array_map([$this, 'quoteColumnName'], $selectColumns);
524 4
        $writeColumns = array_map([$this, 'quoteColumnName'], $writeColumns);
525 4
526
        if (!$found) {
527 9
            throw new \InvalidArgumentException(sprintf(
528 9
                'The specified column doesn\'t exist: ' . $columnName
529
            ));
530 9
        }
531 9
532 9
        return compact('writeColumns', 'selectColumns', 'columnType');
533 9
    }
534
535 8
    /**
536
     * Returns the initial instructions to alter a table using the
537 8
     * rename-alter-copy strategy
538
     *
539
     * @param string $tableName The table to modify
540
     * @return AlterInstructions
541
     */
542
    protected function beginAlterByCopyTable($tableName)
543 1
    {
544
        $instructions = new AlterInstructions();
545 1
        $instructions->addPostStep(function ($state) use ($tableName) {
546
            $createSQL = $this->getDeclaringSql($tableName);
547 1
548 1
            $tmpTableName = 'tmp_' . $tableName;
549 1
            $this->execute(
550
                sprintf(
551
                    'ALTER TABLE %s RENAME TO %s',
552
                    $this->quoteTableName($tableName),
553
                    $this->quoteTableName($tmpTableName)
554
                )
555
            );
556
557
            return compact('createSQL', 'tmpTableName') + $state;
558
        });
559 8
560
        return $instructions;
561 8
    }
562 8
563 8
    /**
564 8
     * {@inheritdoc}
565 8
     */
566 8
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
567 8
    {
568 8
        $instructions = $this->beginAlterByCopyTable($tableName);
569 8 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...
570 8
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
571
572 8
            return $newState + $state;
573 8
        });
574 8
575
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
576
            $sql = str_replace(
577
                $this->quoteColumnName($columnName),
578
                $this->quoteColumnName($newColumnName),
579 1
                $state['createSQL']
580
            );
581 1
            $this->execute($sql);
582 1
583 1
            return $state;
584
        });
585 1
586 1
        return $this->copyAndDropTmpTable($instructions, $tableName);
587
    }
588 1
589 1
    /**
590 1
     * {@inheritdoc}
591 1
     */
592 1
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
593 1
    {
594 1
        $instructions = $this->beginAlterByCopyTable($tableName);
595 1
596 1
        $newColumnName = $newColumn->getName();
597 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...
598
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
599
600
            return $newState + $state;
601
        });
602
603
        $instructions->addPostStep(function ($state) use ($columnName, $newColumn) {
604
            $sql = preg_replace(
605 1
                sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
606
                sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
607 1
                $state['createSQL'],
608
                1
609 1
            );
610 1
            $this->execute($sql);
611 1
612 1
            return $state;
613 1
        });
614 1
615 1
        return $this->copyAndDropTmpTable($instructions, $tableName);
616 1
    }
617 1
618
    /**
619
     * {@inheritdoc}
620
     */
621
    protected function getDropColumnInstructions($tableName, $columnName)
622
    {
623
        $instructions = $this->beginAlterByCopyTable($tableName);
624
625 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...
626
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, false);
627 5
628
            return $newState + $state;
629
        });
630 5
631
        $instructions->addPostStep(function ($state) use ($columnName) {
632 5
            $sql = preg_replace(
633 5
                sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($state['columnType'])),
634 5
                "",
635
                $state['createSQL']
636 1
            );
637
638
            if (substr($sql, -2) === ', ') {
639
                $sql = substr($sql, 0, -2) . ')';
640
            }
641
642
            $this->execute($sql);
643
644
            return $state;
645 5
        });
646
647 5
        return $this->copyAndDropTmpTable($instructions, $tableName);
648 5
    }
649
650
    /**
651
     * Get an array of indexes from a particular table.
652
     *
653
     * @param string $tableName Table Name
654
     * @return array
655
     */
656
    protected function getIndexes($tableName)
657
    {
658
        $indexes = [];
659
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
660
661 5
        foreach ($rows as $row) {
662
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
663 5
            if (!isset($indexes[$tableName])) {
664 5
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
665 5
            }
666 5
            foreach ($indexData as $indexItem) {
667 5
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
668 5
            }
669 5
        }
670 5
671 5
        return $indexes;
672 5
    }
673 5
674
    /**
675
     * {@inheritdoc}
676
     */
677 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...
678
    {
679 4
        $columns = array_map('strtolower', (array)$columns);
680
        $indexes = $this->getIndexes($tableName);
681
682 4
        foreach ($indexes as $index) {
683
            $a = array_diff($columns, $index['columns']);
684 4
            if (empty($a)) {
685 4
                return true;
686
            }
687 4
        }
688 4
689 4
        return false;
690 4
    }
691 4
692 4
    /**
693
     * {@inheritdoc}
694 4
     */
695 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...
696 4
    {
697 4
        $indexes = $this->getIndexes($tableName);
698 4
699
        foreach ($indexes as $index) {
700 4
            if ($indexName === $index['index']) {
701
                return true;
702 4
            }
703 4
        }
704
705 4
        return false;
706 4
    }
707 4
708 4
    /**
709 4
     * {@inheritdoc}
710 4
     */
711 4
    protected function getAddIndexInstructions(Table $table, Index $index)
712
    {
713 4
        $indexColumnArray = [];
714 4
        foreach ($index->getColumns() as $column) {
715 4
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
716
        }
717
        $indexColumns = implode(',', $indexColumnArray);
718
        $sql = sprintf(
719
            'CREATE %s ON %s (%s)',
720 1
            $this->getIndexSqlDefinition($table, $index),
721
            $this->quoteTableName($table->getName()),
722
            $indexColumns
723 1
        );
724
725
        return new AlterInstructions([], [$sql]);
726
    }
727 1
728
    /**
729 1
     * {@inheritdoc}
730
     */
731 1
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
732 1
    {
733 1
        $indexes = $this->getIndexes($tableName);
734 1
        $columns = array_map('strtolower', (array)$columns);
735 1
        $instructions = new AlterInstructions();
736 1
737
        foreach ($indexes as $index) {
738 1
            $a = array_diff($columns, $index['columns']);
739 1
            if (empty($a)) {
740 1
                $instructions->addPostStep(sprintf(
741 1
                    'DROP INDEX %s',
742 1
                    $this->quoteColumnName($index['index'])
743 1
                ));
744 1
            }
745
        }
746 1
747
        return $instructions;
748 1
    }
749
750
    /**
751
     * {@inheritdoc}
752
     */
753
    protected function getDropIndexByNameInstructions($tableName, $indexName)
754 1
    {
755
        $indexes = $this->getIndexes($tableName);
756 1
        $instructions = new AlterInstructions();
757 1
758 1
        foreach ($indexes as $index) {
759 1
            if ($indexName === $index['index']) {
760 1
                $instructions->addPostStep(sprintf(
761 1
                    'DROP INDEX %s',
762 1
                    $this->quoteColumnName($indexName)
763
                ));
764 1
            }
765
        }
766 1
767 1
        return $instructions;
768 1
    }
769 1
770 1
    /**
771
     * {@inheritdoc}
772 1
     */
773
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
774 1
    {
775 1
        $primaryKey = $this->getPrimaryKey($tableName);
776 1
777
        if (empty($primaryKey)) {
778
            return false;
779
        }
780
781
        $missingColumns = array_diff((array)$columns, (array)$primaryKey);
782
783
        return empty($missingColumns);
784
    }
785
786
    /**
787
     * Get the primary key from a particular table.
788
     *
789
     * @param string $tableName Table Name
790
     * @return string|null
791
     */
792
    protected function getPrimaryKey($tableName)
793
    {
794
        $rows = $this->fetchAll(
795
            "SELECT sql, tbl_name
796
              FROM (
797
                    SELECT sql sql, type type, tbl_name tbl_name, name name
798
                      FROM sqlite_master
799
                     UNION ALL
800
                    SELECT sql, type, tbl_name, name
801
                      FROM sqlite_temp_master
802
                   )
803
             WHERE type != 'meta'
804
               AND sql NOTNULL
805
               AND name NOT LIKE 'sqlite_%'
806
             ORDER BY substr(type, 2, 1), name"
807
        );
808
809
        foreach ($rows as $row) {
810 43
            if ($row['tbl_name'] === $tableName) {
811
                if (strpos($row['sql'], 'PRIMARY KEY') !== false) {
812
                    preg_match_all("/PRIMARY KEY\s*\(`([^`]+)`\)/", $row['sql'], $matches);
813 43
                    foreach ($matches[1] as $match) {
814 42
                        if (!empty($match)) {
815
                            return $match;
816 43
                        }
817
                    }
818
                    preg_match_all("/`([^`]+)`\s+\w+(\(\d+\))?((\s+NOT)?\s+NULL)?\s+PRIMARY KEY/", $row['sql'], $matches);
819 43
                    foreach ($matches[1] as $match) {
820 1
                        if (!empty($match)) {
821
                            return $match;
822 43
                        }
823 38
                    }
824
                }
825 43
            }
826 42
        }
827
828 43
        return null;
829 2
    }
830
831 43
    /**
832 1
     * {@inheritdoc}
833
     */
834 43
    public function hasForeignKey($tableName, $columns, $constraint = null)
835 1
    {
836
        $foreignKeys = $this->getForeignKeys($tableName);
837 43
838 42
        return !array_diff((array)$columns, $foreignKeys);
839
    }
840 43
841 1
    /**
842
     * Get an array of foreign keys from a particular table.
843 43
     *
844 1
     * @param string $tableName Table Name
845
     * @return array
846 43
     */
847 43
    protected function getForeignKeys($tableName)
848 1
    {
849
        $foreignKeys = [];
850 43
        $rows = $this->fetchAll(
851 42
            "SELECT sql, tbl_name
852
              FROM (
853 5
                    SELECT sql sql, type type, tbl_name tbl_name, name name
854
                      FROM sqlite_master
855 5
                     UNION ALL
856 4
                    SELECT sql, type, tbl_name, name
857
                      FROM sqlite_temp_master
858
                   )
859
             WHERE type != 'meta'
860 1
               AND sql NOTNULL
861 1
               AND name NOT LIKE 'sqlite_%'
862
             ORDER BY substr(type, 2, 1), name"
863
        );
864 1
865
        foreach ($rows as $row) {
866
            if ($row['tbl_name'] === $tableName) {
867 1
                if (strpos($row['sql'], 'REFERENCES') !== false) {
868
                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
869 1
                    foreach ($matches[1] as $match) {
870 1
                        $foreignKeys[] = $match;
871 1
                    }
872
                }
873
            }
874
        }
875
876
        return $foreignKeys;
877
    }
878
879
    /**
880 3
     * @param Table $table The Table
881
     * @param string $column Column Name
882 3
     * @return AlterInstructions
883 1
     */
884
    protected function getAddPrimaryKeyInstructions(Table $table, $column)
885 2
    {
886 2
        $instructions = $this->beginAlterByCopyTable($table->getName());
887 2
888 2
        $tableName = $table->getName();
889 1
        $instructions->addPostStep(function ($state) use ($column) {
890 1
            $matchPattern = "/(`$column`)\s+(\w+(\(\d+\))?)\s+((NOT )?NULL)/";
891 2
892
            $sql = $state['createSQL'];
893
894 2
            if (preg_match($matchPattern, $state['createSQL'], $matches)) {
895 2
                if (isset($matches[2])) {
896 1
                    if ($matches[2] === 'INTEGER') {
897 1
                        $replace = '$1 INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT';
898
                    } else {
899
                        $replace = '$1 $2 NOT NULL PRIMARY KEY';
900 1
                    }
901 2
902
                    $sql = preg_replace($matchPattern, $replace, $state['createSQL'], 1);
903
                }
904
            }
905
906
            $this->execute($sql);
907
908
            return $state;
909
        });
910 2
911 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...
912
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
913
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
914
            $selectColumns = $writeColumns = $names;
915
916 2
            return compact('selectColumns', 'writeColumns') + $state;
917 1
        });
918
919
        return $this->copyAndDropTmpTable($instructions, $tableName);
920 1
    }
921 1
922 2
    /**
923 1
     * @param Table $table Table
924 1
     * @param string $column Column Name
925 2
     * @return AlterInstructions
926 2
     */
927
    protected function getDropPrimaryKeyInstructions($table, $column)
928
    {
929
        $instructions = $this->beginAlterByCopyTable($table->getName());
930
931
        $instructions->addPostStep(function ($state) use ($column) {
932
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $column, $column);
933 2
934
            return $newState + $state;
935
        });
936 1
937 1
        $instructions->addPostStep(function ($state) {
938
            $search = "/(,?\s*PRIMARY KEY\s*\([^\)]*\)|\s+PRIMARY KEY(\s+AUTOINCREMENT)?)/";
939 1
            $sql = preg_replace($search, '', $state['createSQL'], 1);
940
941
            if ($sql) {
942
                $this->execute($sql);
943
            }
944
945
            return $state;
946 48
        });
947
948 48
        return $this->copyAndDropTmpTable($instructions, $table->getName());
949 48
    }
950
951
    /**
952
     * {@inheritdoc}
953
     */
954 2
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
955
    {
956 2
        $instructions = $this->beginAlterByCopyTable($table->getName());
957
958
        $tableName = $table->getName();
959
        $instructions->addPostStep(function ($state) use ($foreignKey) {
960
            $this->execute('pragma foreign_keys = ON');
961
            $sql = substr($state['createSQL'], 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
962 48
            $this->execute($sql);
963
964 48
            return $state;
965 47
        });
966 47
967 48 View Code Duplication
        $instructions->addPostStep(function ($state) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
968
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
969
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
970
            $selectColumns = $writeColumns = $names;
971
972
            return compact('selectColumns', 'writeColumns') + $state;
973
        });
974
975 42
        return $this->copyAndDropTmpTable($instructions, $tableName);
976
    }
977 42
978 8
    /**
979 42
     * {@inheritdoc}
980 42
     */
981 42
    protected function getDropForeignKeyInstructions($tableName, $constraint)
982 42
    {
983
        throw new \BadMethodCallException('SQLite does not have named foreign keys');
984
    }
985
986
    /**
987
     * {@inheritdoc}
988
     */
989
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
990
    {
991 42
        $instructions = $this->beginAlterByCopyTable($tableName);
992
993 42
        $instructions->addPostStep(function ($state) use ($columns) {
994 42
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columns[0], $columns[0]);
995 42
996 42
            $selectColumns = $newState['selectColumns'];
997
            $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...
998
            $diff = array_diff($columns, $selectColumns);
999 42
1000 42
            if (!empty($diff)) {
1001 42
                throw new \InvalidArgumentException(sprintf(
1002 42
                    'The specified columns don\'t exist: ' . implode(', ', $diff)
1003 42
                ));
1004 4
            }
1005 4
1006
            return $newState + $state;
1007 42
        });
1008
1009 42
        $instructions->addPostStep(function ($state) use ($columns) {
1010 42
            $sql = '';
1011 42
1012
            foreach ($columns as $columnName) {
1013 42
                $search = sprintf(
1014
                    "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
1015
                    $this->quoteColumnName($columnName)
1016
                );
1017 42
                $sql = preg_replace($search, '', $state['createSQL'], 1);
1018
            }
1019 42
1020
            if ($sql) {
1021
                $this->execute($sql);
1022
            }
1023
1024
            return $state;
1025
        });
1026
1027
        return $this->copyAndDropTmpTable($instructions, $tableName);
1028 42
    }
1029
1030 42
    /**
1031 2
     * {@inheritdoc}
1032
     */
1033 42
    public function getSqlType($type, $limit = null)
1034
    {
1035
        switch ($type) {
1036
            case static::PHINX_TYPE_TEXT:
1037
            case static::PHINX_TYPE_INTEGER:
1038
            case static::PHINX_TYPE_FLOAT:
1039
            case static::PHINX_TYPE_DOUBLE:
1040
            case static::PHINX_TYPE_DECIMAL:
1041
            case static::PHINX_TYPE_DATETIME:
1042 8
            case static::PHINX_TYPE_TIME:
1043
            case static::PHINX_TYPE_DATE:
1044 8
            case static::PHINX_TYPE_BLOB:
1045 2
            case static::PHINX_TYPE_BOOLEAN:
1046 2
            case static::PHINX_TYPE_ENUM:
1047 6
                return ['name' => $type];
1048
            case static::PHINX_TYPE_STRING:
1049 8
                return ['name' => 'varchar', 'limit' => 255];
1050 3
            case static::PHINX_TYPE_CHAR:
1051 3
                return ['name' => 'char', 'limit' => 255];
1052 6
            case static::PHINX_TYPE_SMALL_INTEGER:
1053 6
                return ['name' => 'smallint'];
1054 6
            case static::PHINX_TYPE_BIG_INTEGER:
1055 6
                return ['name' => 'bigint'];
1056 6
            case static::PHINX_TYPE_TIMESTAMP:
1057
                return ['name' => 'datetime'];
1058 8
            case static::PHINX_TYPE_BINARY:
1059 8
                return ['name' => 'blob'];
1060
            case static::PHINX_TYPE_UUID:
1061
                return ['name' => 'char', 'limit' => 36];
1062
            case static::PHINX_TYPE_JSON:
1063
            case static::PHINX_TYPE_JSONB:
1064
                return ['name' => 'text'];
1065 47
            // Geospatial database types
1066
            // No specific data types exist in SQLite, instead all geospatial
1067 47
            // functionality is handled in the client. See also: SpatiaLite.
1068
            case static::PHINX_TYPE_GEOMETRY:
1069
            case static::PHINX_TYPE_POLYGON:
1070
                return ['name' => 'text'];
1071
            case static::PHINX_TYPE_LINESTRING:
1072
                return ['name' => 'varchar', 'limit' => 255];
1073
            case static::PHINX_TYPE_POINT:
1074
                return ['name' => 'float'];
1075
            default:
1076 5
                throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by SQLite.');
1077
        }
1078 5
    }
1079 5
1080
    /**
1081
     * Returns Phinx type by SQL type
1082 5
     *
1083 5
     * @param string $sqlTypeDef SQL type
1084 5
     * @throws UnsupportedColumnTypeException
1085 5
     * @return array
1086 5
     */
1087 5
    public function getPhinxType($sqlTypeDef)
1088 5
    {
1089 5
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
1090 5
            throw new UnsupportedColumnTypeException('Column type "' . $sqlTypeDef . '" is not supported by SQLite.');
1091 5
        } else {
1092 5
            $limit = null;
1093 1
            $scale = null;
1094 1
            $type = $matches[1];
1095 5
            if (count($matches) > 2) {
1096 1
                $limit = $matches[3] ?: null;
1097 1
            }
1098
            if (count($matches) > 4) {
1099 5
                $scale = $matches[5];
1100
            }
1101
            switch ($type) {
1102
                case 'varchar':
1103
                    $type = static::PHINX_TYPE_STRING;
1104
                    if ($limit === 255) {
1105
                        $limit = null;
1106
                    }
1107
                    break;
1108 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...
1109
                    $type = static::PHINX_TYPE_CHAR;
1110
                    if ($limit === 255) {
1111
                        $limit = null;
1112
                    }
1113
                    if ($limit === 36) {
1114
                        $type = static::PHINX_TYPE_UUID;
1115
                    }
1116
                    break;
1117
                case 'smallint':
1118
                    $type = static::PHINX_TYPE_SMALL_INTEGER;
1119
                    if ($limit === 11) {
1120
                        $limit = null;
1121
                    }
1122
                    break;
1123
                case 'int':
1124
                    $type = static::PHINX_TYPE_INTEGER;
1125
                    if ($limit === 11) {
1126
                        $limit = null;
1127
                    }
1128
                    break;
1129
                case 'bigint':
1130
                    if ($limit === 11) {
1131
                        $limit = null;
1132
                    }
1133
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1134
                    break;
1135
                case 'blob':
1136
                    $type = static::PHINX_TYPE_BINARY;
1137
                    break;
1138
            }
1139 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...
1140
                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...
1141
                    $type = static::PHINX_TYPE_BOOLEAN;
1142
                    $limit = null;
1143
                }
1144
            }
1145
1146
            try {
1147
                // Call this to check if parsed type is supported.
1148
                $this->getSqlType($type);
1149
            } catch (UnsupportedColumnTypeException $e) {
1150
                $type = Literal::from($type);
1151
            }
1152
1153
            return [
1154
                'name' => $type,
1155
                'limit' => $limit,
1156
                'scale' => $scale
1157
            ];
1158
        }
1159
    }
1160
1161
    /**
1162
     * {@inheritdoc}
1163
     */
1164
    public function createDatabase($name, $options = [])
1165
    {
1166
        touch($name . $this->suffix);
1167
    }
1168
1169
    /**
1170
     * {@inheritdoc}
1171
     */
1172
    public function hasDatabase($name)
1173
    {
1174
        return is_file($name . $this->suffix);
1175
    }
1176
1177
    /**
1178
     * {@inheritdoc}
1179
     */
1180
    public function dropDatabase($name)
1181
    {
1182
        if ($this->getOption('memory')) {
1183
            $this->disconnect();
1184
            $this->connect();
1185
        }
1186
        if (file_exists($name . $this->suffix)) {
1187
            unlink($name . $this->suffix);
1188
        }
1189
    }
1190
1191
    /**
1192
     * Gets the SQLite Column Definition for a Column object.
1193
     *
1194
     * @param \Phinx\Db\Table\Column $column Column
1195
     * @return string
1196
     */
1197
    protected function getColumnSqlDefinition(Column $column)
1198
    {
1199
        $isLiteralType = $column->getType() instanceof Literal;
1200
        if ($isLiteralType) {
1201
            $def = (string)$column->getType();
1202
        } else {
1203
            $sqlType = $this->getSqlType($column->getType());
1204
            $def = strtoupper($sqlType['name']);
1205
1206
            $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
1207
            if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
1208
                $def .= '(' . ($column->getLimit() ?: $sqlType['limit']) . ')';
1209
            }
1210
        }
1211
        if ($column->getPrecision() && $column->getScale()) {
1212
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1213
        }
1214 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...
1215
            $def .= " CHECK({$this->quoteColumnName($column->getName())} IN ('" . implode("', '", $values) . "'))";
1216
        }
1217
1218
        $default = $column->getDefault();
1219
1220
        $def .= (!$column->isIdentity() && ($column->isNull() || is_null($default))) ? ' NULL' : ' NOT NULL';
1221
        $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...
1222
        $def .= $column->isIdentity() ? ' PRIMARY KEY AUTOINCREMENT' : '';
1223
1224
        if ($column->getUpdate()) {
1225
            $def .= ' ON UPDATE ' . $column->getUpdate();
1226
        }
1227
1228
        $def .= $this->getCommentDefinition($column);
1229
1230
        return $def;
1231
    }
1232
1233
    /**
1234
     * Gets the comment Definition for a Column object.
1235
     *
1236
     * @param \Phinx\Db\Table\Column $column Column
1237
     * @return string
1238
     */
1239
    protected function getCommentDefinition(Column $column)
1240
    {
1241
        if ($column->getComment()) {
1242
            return ' /* ' . $column->getComment() . ' */ ';
1243
        }
1244
1245
        return '';
1246
    }
1247
1248
    /**
1249
     * Gets the SQLite Index Definition for an Index object.
1250
     *
1251
     * @param \Phinx\Db\Table\Table $table Table
1252
     * @param \Phinx\Db\Table\Index $index Index
1253
     * @return string
1254
     */
1255
    protected function getIndexSqlDefinition(Table $table, Index $index)
1256
    {
1257
        if ($index->getType() === Index::UNIQUE) {
1258
            $def = 'UNIQUE INDEX';
1259
        } else {
1260
            $def = 'INDEX';
1261
        }
1262
        if (is_string($index->getName())) {
1263
            $indexName = $index->getName();
1264
        } else {
1265
            $indexName = $table->getName() . '_';
1266
            foreach ($index->getColumns() as $column) {
1267
                $indexName .= $column . '_';
1268
            }
1269
            $indexName .= 'index';
1270
        }
1271
        $def .= ' `' . $indexName . '`';
1272
1273
        return $def;
1274
    }
1275
1276
    /**
1277
     * {@inheritdoc}
1278
     */
1279
    public function getColumnTypes()
1280
    {
1281
        return array_merge(parent::getColumnTypes(), ['enum', 'json', 'jsonb']);
1282
    }
1283
1284
    /**
1285
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1286
     *
1287
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1288
     * @return string
1289
     */
1290 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...
1291
    {
1292
        $def = '';
1293
        if ($foreignKey->getConstraint()) {
1294
            $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...
1295
        } else {
1296
            $columnNames = [];
1297
            foreach ($foreignKey->getColumns() as $column) {
1298
                $columnNames[] = $this->quoteColumnName($column);
1299
            }
1300
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1301
            $refColumnNames = [];
1302
            foreach ($foreignKey->getReferencedColumns() as $column) {
1303
                $refColumnNames[] = $this->quoteColumnName($column);
1304
            }
1305
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1306
            if ($foreignKey->getOnDelete()) {
1307
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1308
            }
1309
            if ($foreignKey->getOnUpdate()) {
1310
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1311
            }
1312
        }
1313
1314
        return $def;
1315
    }
1316
1317
    /**
1318
     * {@inheritDoc}
1319
     *
1320
     */
1321
    public function getDecoratedConnection()
1322
    {
1323
        $options = $this->getOptions();
1324
        $options['quoteIdentifiers'] = true;
1325
        $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...
1326
1327
        if (!empty($options['name'])) {
1328
            $options['database'] = $options['name'];
1329
1330
            if (file_exists($options['name'] . $this->suffix)) {
1331
                $options['database'] = $options['name'] . $this->suffix;
1332
            }
1333
        }
1334
1335
        $driver = new SqliteDriver($options);
1336
        if (method_exists($driver, 'setConnection')) {
1337
            $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...
1338
        } else {
1339
            $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...
1340
        }
1341
1342
        return new Connection(['driver' => $driver] + $options);
1343
    }
1344
}
1345