Completed
Pull Request — master (#1393)
by
unknown
03:29
created

SQLiteAdapter::getForeignKeySqlDefinition()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 26

Duplication

Lines 25
Ratio 96.15 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 25
loc 26
rs 8.8817
c 0
b 0
f 0
ccs 0
cts 0
cp 0
cc 6
nc 5
nop 1
crap 42
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
        'CHARACTER',
50
        'VARCHAR',
51
        'VARYING CHARACTER',
52
        'NCHAR',
53
        'NATIVE CHARACTER',
54
        'NVARCHAR'
55
    ];
56 42
57
    protected $suffix = '.sqlite3';
58 42
59 42
    /**
60
     * {@inheritdoc}
61
     */
62
    public function connect()
63
    {
64
        if ($this->connection === null) {
65 42
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
66 42
                // @codeCoverageIgnoreStart
67
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
68
                // @codeCoverageIgnoreEnd
69 42
            }
70
71
            $db = null;
72 42
            $options = $this->getOptions();
73 42
74 42
            // if port is specified use it, otherwise use the MySQL default
75 42
            if (isset($options['memory'])) {
76
                $dsn = 'sqlite::memory:';
77
            } else {
78
                $dsn = 'sqlite:' . $options['name'] . $this->suffix;
79 42
            }
80 42
81
            try {
82
                $db = new \PDO($dsn);
83
            } catch (\PDOException $exception) {
84
                throw new \InvalidArgumentException(sprintf(
85
                    'There was a problem connecting to the database: %s',
86
                    $exception->getMessage()
87 42
                ));
88 42
            }
89 42
90
            $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
91
            $this->setConnection($db);
92
        }
93
    }
94 48
95
    /**
96 48
     * {@inheritdoc}
97 48
     */
98
    public function setOptions(array $options)
99
    {
100
        parent::setOptions($options);
101
102
        if (isset($options['suffix'])) {
103
            $this->suffix = $options['suffix'];
104
        }
105
        //don't "fix" the file extension if it is blank, some people
106
        //might want a SQLITE db file with absolutely no extension.
107
        if (strlen($this->suffix) && substr($this->suffix, 0, 1) !== '.') {
108
            $this->suffix = '.' . $this->suffix;
109
        }
110 1
111
        return $this;
112 1
    }
113 1
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function disconnect()
118
    {
119
        $this->connection = null;
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public function hasTransactions()
126
    {
127
        return true;
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function beginTransaction()
134 43
    {
135
        $this->getConnection()->beginTransaction();
136 43
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function commitTransaction()
142 44
    {
143
        $this->getConnection()->commit();
144 44
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function rollbackTransaction()
150 42
    {
151
        $this->getConnection()->rollBack();
152 42
    }
153 42
154 42
    /**
155 12
     * {@inheritdoc}
156 42
     */
157
    public function quoteTableName($tableName)
158 42
    {
159
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
160
    }
161
162
    /**
163
     * {@inheritdoc}
164 42
     */
165
    public function quoteColumnName($columnName)
166
    {
167 42
        return '`' . str_replace('`', '``', $columnName) . '`';
168 42
    }
169 42
170 35
    /**
171 35
     * {@inheritdoc}
172 35
     */
173 35
    public function hasTable($tableName)
174
    {
175 35
        $tables = [];
176 42
        $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
177
        foreach ($rows as $row) {
178 1
            $tables[] = strtolower($row[0]);
179 1
        }
180 1
181 1
        return in_array(strtolower($tableName), $tables);
182
    }
183 1
184 1
    /**
185
     * {@inheritdoc}
186
     */
187 42
    public function createTable(Table $table, array $columns = [], array $indexes = [])
188 42
    {
189 42
        // Add the default primary key
190 42
        $options = $table->getOptions();
191 42
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
192
            $column = new Column();
193
            $column->setName('id')
194 42
                   ->setType('integer')
195 42
                   ->setIdentity(true);
196 42
197 42
            array_unshift($columns, $column);
198 42
        } elseif (isset($options['id']) && is_string($options['id'])) {
199 42
            // Handle id => "field_name" to support AUTO_INCREMENT
200
            $column = new Column();
201
            $column->setName($options['id'])
202 1
                   ->setType('integer')
203 1
                   ->setIdentity(true);
204 1
205
            array_unshift($columns, $column);
206 1
        }
207 1
208 1
        $sql = 'CREATE TABLE ';
209 1
        $sql .= $this->quoteTableName($table->getName()) . ' (';
210 1
        foreach ($columns as $column) {
211 1
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
212 42
213 42
            if (isset($options['primary_key']) && $column->getIdentity()) {
214 37
                //remove column from the primary key array as it is already defined as an autoincrement
215
                //primary id
216
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
217
                if ($identityColumnIndex !== false) {
218 42
                    unset($options['primary_key'][$identityColumnIndex]);
219 42
220 1
                    if (empty($options['primary_key'])) {
221 1
                        //The last primary key has been removed
222 1
                        unset($options['primary_key']);
223 1
                    }
224
                }
225 42
            }
226
        }
227 42
228
        // set the primary key(s)
229 42 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...
230 6
            $sql = rtrim($sql);
231 42
            $sql .= ' PRIMARY KEY (';
232 42
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
233
                $sql .= $this->quoteColumnName($options['primary_key']);
234
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
235
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
236
            }
237 1
            $sql .= ')';
238
        } else {
239 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
240 1
        }
241
242
        $sql = rtrim($sql) . ');';
243
        // execute the sql
244
        $this->execute($sql);
245 1
246
        foreach ($indexes as $index) {
247 1
            $this->addIndex($table, $index);
248 1
        }
249
    }
250
251
    /**
252
     * {@inheritdoc}
253 1
     */
254
    protected function getChangeTableInstructions(Table $table, array $newOptions)
255 1
    {
256 1
        $instructions = new AlterInstructions();
257 1
258 1
        // Drop the existing primary key
259
        $primaryKey = $this->getPrimaryKey($table->getName());
260 1
        if ((isset($newOptions['id']) || isset($newOptions['primary_key']))
261 1
            && !empty($primaryKey)) {
262
            $this->dropPrimaryKey($table->getName(), $primaryKey);
263
        }
264
265
        // Set the default primary key and add associated column
266 1 View Code Duplication
        if (isset($newOptions['id']) && $newOptions['id'] !== false) {
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...
267
            if ($newOptions['id'] === true) {
268 1
                $newOptions['primary_key'] = 'id';
269 1
            } elseif (is_string($newOptions['id'])) {
270
                // Handle id => "field_name" to support AUTO_INCREMENT
271 1
                $newOptions['primary_key'] = $newOptions['id'];
272 1
            } else {
273 1
                throw new \InvalidArgumentException(sprintf(
274 1
                    "Invalid value for option 'id': %s",
275 1
                    json_encode($newOptions['id'])
276 1
                ));
277
            }
278 1
279 1
            if ($this->hasColumn($table->getName(), $newOptions['primary_key'])) {
280 1
                throw new \RuntimeException(sprintf(
281
                    "Tried to create primary key column %s for table %s, but that column already exists",
282 1
                    $this->quoteColumnName($newOptions['primary_key']),
283 1
                    $this->quoteTableName($table->getName())
284 1
                ));
285
            }
286 1
287 1
            $column = new Column();
288
            $column
289 1
                ->setName($newOptions['primary_key'])
290
                ->setType('integer');
291
            $instructions->merge($this->getAddColumnInstructions($table, $column));
292
        }
293
294
        // Add the primary key(s)
295 8
        if (isset($newOptions['primary_key']) && $newOptions['primary_key'] !== false) {
296
            if (!is_string($newOptions['primary_key'])) {
297 8
                throw new \InvalidArgumentException(sprintf(
298 8
                    "Invalid value for option 'primary_key': %s",
299 8
                    json_encode($newOptions['primary_key'])
300 7
                ));
301
            }
302 8
303
            $instructions->merge(
304 8
                $this->getAddPrimaryKeyInstructions($table, $newOptions['primary_key'])
305
            );
306
        }
307
308
        return $instructions;
309
    }
310 4
311
    /**
312 4
     * {@inheritdoc}
313 4
     */
314 4 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...
315 4
    {
316 4
        $sql = sprintf(
317 4
            'ALTER TABLE %s RENAME TO %s',
318
            $this->quoteTableName($tableName),
319 4
            $this->quoteTableName($newTableName)
320 4
        );
321
322
        return new AlterInstructions([], [$sql]);
323
    }
324
325 2
    /**
326
     * {@inheritdoc}
327 2
     */
328
    protected function getDropTableInstructions($tableName)
329 2
    {
330
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
331 2
332 2
        return new AlterInstructions([], [$sql]);
333 2
    }
334 2
335 2
    /**
336 2
     * {@inheritdoc}
337
     */
338 2
    public function truncateTable($tableName)
339 2
    {
340 2
        $sql = sprintf(
341 2
            'DELETE FROM %s',
342 2
            $this->quoteTableName($tableName)
343 2
        );
344 2
345 2
        $this->execute($sql);
346 2
    }
347
348 2
    /**
349 1
     * {@inheritdoc}
350
     */
351 1
    public function getColumns($tableName)
352
    {
353
        $columns = [];
354 1
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
355
356 1
        foreach ($rows as $columnInfo) {
357 1
            $column = new Column();
358 1
            $type = strtolower($columnInfo['type']);
359
            $column->setName($columnInfo['name'])
360 1
                   ->setNull($columnInfo['notnull'] !== '1')
361 1
                   ->setDefault($columnInfo['dflt_value']);
362
363
            $phinxType = $this->getPhinxType($type);
364 1
            $column->setType($phinxType['name'])
365 1
                   ->setLimit($phinxType['limit']);
366 1
367 1
            if ($columnInfo['pk'] == 1) {
368 1
                $column->setIdentity(true);
369
            }
370 1
371
            $columns[] = $column;
372 1
        }
373
374 1
        return $columns;
375 1
    }
376
377
    /**
378
     * {@inheritdoc}
379
     */
380 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...
381
    {
382
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
383
        foreach ($rows as $column) {
384 6
            if (strcasecmp($column['name'], $columnName) === 0) {
385
                return true;
386 6
            }
387
        }
388 6
389 6
        return false;
390 6
    }
391 6
392 6
    /**
393 6
     * {@inheritdoc}
394
     */
395 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...
396 6
    {
397 6
        $alter = sprintf(
398 6
            'ADD COLUMN %s %s',
399 6
            $this->quoteColumnName($column->getName()),
400 6
            $this->getColumnSqlDefinition($column)
401 6
        );
402 6
403 6
        return new AlterInstructions([$alter]);
404
    }
405 6
406
    /**
407
     * Returns the original CREATE statement for the give table
408
     *
409
     * @param string $tableName The table name to get the create statement for
410
     * @return string
411 6
     */
412
    protected function getDeclaringSql($tableName)
413 6
    {
414 6
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
415 6
416 6
        $sql = '';
417
        foreach ($rows as $table) {
418 6
            if ($table['tbl_name'] === $tableName) {
419
                $sql = $table['sql'];
420 6
            }
421
        }
422 6
423 6
        return $sql;
424 6
    }
425 6
426 6
    /**
427
     * Copies all the data from a tmp table to another table
428 6
     *
429
     * @param string $tableName The table name to copy the data to
430 6
     * @param string $tmpTableName The tmp table name where the data is stored
431 6
     * @param string[] $writeColumns The list of columns in the target table
432 6
     * @param string[] $selectColumns The list of columns in the tmp table
433
     * @return void
434
     */
435
    protected function copyDataToNewTable($tableName, $tmpTableName, $writeColumns, $selectColumns)
436
    {
437 2
        $sql = sprintf(
438
            'INSERT INTO %s(%s) SELECT %s FROM %s',
439
            $this->quoteTableName($tableName),
440 2
            implode(', ', $writeColumns),
441
            implode(', ', $selectColumns),
442 2
            $this->quoteTableName($tmpTableName)
443
        );
444 2
        $this->execute($sql);
445 2
    }
446 2
447 2
    /**
448 2
     * Modifies the passed instructions to copy all data from the tmp table into
449 2
     * the provided table and then drops the tmp table.
450
     *
451 2
     * @param AlterInstructions $instructions The instructions to modify
452 2
     * @param string $tableName The table name to copy the data to
453 2
     * @return AlterInstructions
454 2
     */
455 2
    protected function copyAndDropTmpTable($instructions, $tableName)
456 2
    {
457 2
        $instructions->addPostStep(function ($state) use ($tableName) {
458 2
            $this->copyDataToNewTable(
459 2
                $tableName,
460
                $state['tmpTableName'],
461 2
                $state['writeColumns'],
462
                $state['selectColumns']
463 2
            );
464
465
            $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($state['tmpTableName'])));
466
467
            return $state;
468
        });
469 2
470
        return $instructions;
471 2
    }
472 2
473 2
    /**
474
     * Returns the columns and type to use when copying a table to another in the process
475 2
     * of altering a table
476
     *
477 2
     * @param string $tableName The table to modify
478 2
     * @param string $columnName The column name that is about to change
479 2
     * @param string|false $newColumnName Optionally the new name for the column
480
     * @return AlterInstructions
481 2
     */
482
    protected function calculateNewTableColumns($tableName, $columnName, $newColumnName)
483 2
    {
484 2
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
485 2
        $selectColumns = [];
486 2
        $writeColumns = [];
487 2
        $columnType = null;
488
        $found = false;
489 2
490
        foreach ($columns as $column) {
491 2
            $selectName = $column['name'];
492 2
            $writeName = $selectName;
493 2
494
            if ($selectName == $columnName) {
495
                $writeName = $newColumnName;
496
                $found = true;
497
                $columnType = $column['type'];
498
                $selectName = $newColumnName === false ? $newColumnName : $selectName;
499
            }
500
501 9
            $selectColumns[] = $selectName;
502
            $writeColumns[] = $writeName;
503 9
        }
504 9
505
        $selectColumns = array_filter($selectColumns, 'strlen');
506 9
        $writeColumns = array_filter($writeColumns, 'strlen');
507 9
        $selectColumns = array_map([$this, 'quoteColumnName'], $selectColumns);
508 9
        $writeColumns = array_map([$this, 'quoteColumnName'], $writeColumns);
509 9
510 9
        if (!$found) {
511 9
            throw new \InvalidArgumentException(sprintf(
512 9
                'The specified column doesn\'t exist: ' . $columnName
513 9
            ));
514 9
        }
515 9
516
        return compact('writeColumns', 'selectColumns', 'columnType');
517
    }
518
519
    /**
520
     * Returns the initial instructions to alter a table using the
521 9
     * rename-alter-copy strategy
522
     *
523 9
     * @param string $tableName The table to modify
524 4
     * @return AlterInstructions
525 4
     */
526
    protected function beginAlterByCopyTable($tableName)
527 9
    {
528 9
        $instructions = new AlterInstructions();
529
        $instructions->addPostStep(function ($state) use ($tableName) {
530 9
            $createSQL = $this->getDeclaringSql($tableName);
531 9
532 9
            $tmpTableName = 'tmp_' . $tableName;
533 9
            $this->execute(
534
                sprintf(
535 8
                    'ALTER TABLE %s RENAME TO %s',
536
                    $this->quoteTableName($tableName),
537 8
                    $this->quoteTableName($tmpTableName)
538
                )
539
            );
540
541
            return compact('createSQL', 'tmpTableName') + $state;
542
        });
543 1
544
        return $instructions;
545 1
    }
546
547 1
    /**
548 1
     * {@inheritdoc}
549 1
     */
550
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
551
    {
552
        $instructions = $this->beginAlterByCopyTable($tableName);
553 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...
554
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
555
556
            return $newState + $state;
557
        });
558
559 8
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
560
            $sql = str_replace(
561 8
                $this->quoteColumnName($columnName),
562 8
                $this->quoteColumnName($newColumnName),
563 8
                $state['createSQL']
564 8
            );
565 8
            $this->execute($sql);
566 8
567 8
            return $state;
568 8
        });
569 8
570 8
        return $this->copyAndDropTmpTable($instructions, $tableName);
571
    }
572 8
573 8
    /**
574 8
     * {@inheritdoc}
575
     */
576
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
577
    {
578
        $instructions = $this->beginAlterByCopyTable($tableName);
579 1
580
        $newColumnName = $newColumn->getName();
581 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...
582 1
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
583 1
584
            return $newState + $state;
585 1
        });
586 1
587
        $instructions->addPostStep(function ($state) use ($columnName, $newColumn) {
588 1
            $sql = preg_replace(
589 1
                sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
590 1
                sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
591 1
                $state['createSQL'],
592 1
                1
593 1
            );
594 1
            $this->execute($sql);
595 1
596 1
            return $state;
597 1
        });
598
599
        return $this->copyAndDropTmpTable($instructions, $tableName);
600
    }
601
602
    /**
603
     * {@inheritdoc}
604
     */
605 1
    protected function getDropColumnInstructions($tableName, $columnName)
606
    {
607 1
        $instructions = $this->beginAlterByCopyTable($tableName);
608
609 1 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...
610 1
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, false);
611 1
612 1
            return $newState + $state;
613 1
        });
614 1
615 1
        $instructions->addPostStep(function ($state) use ($columnName) {
616 1
            $sql = preg_replace(
617 1
                sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($state['columnType'])),
618
                "",
619
                $state['createSQL']
620
            );
621
622
            if (substr($sql, -2) === ', ') {
623
                $sql = substr($sql, 0, -2) . ')';
624
            }
625 5
626
            $this->execute($sql);
627 5
628
            return $state;
629
        });
630 5
631
        return $this->copyAndDropTmpTable($instructions, $tableName);
632 5
    }
633 5
634 5
    /**
635
     * Get an array of indexes from a particular table.
636 1
     *
637
     * @param string $tableName Table Name
638
     * @return array
639
     */
640
    protected function getIndexes($tableName)
641
    {
642
        $indexes = [];
643
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
644
645 5
        foreach ($rows as $row) {
646
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
647 5
            if (!isset($indexes[$tableName])) {
648 5
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
649
            }
650
            foreach ($indexData as $indexItem) {
651
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
652
            }
653
        }
654
655
        return $indexes;
656
    }
657
658
    /**
659
     * {@inheritdoc}
660
     */
661 5 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...
662
    {
663 5
        if (is_string($columns)) {
664 5
            $columns = [$columns]; // str to array
665 5
        }
666 5
667 5
        $columns = array_map('strtolower', $columns);
668 5
        $indexes = $this->getIndexes($tableName);
669 5
670 5
        foreach ($indexes as $index) {
671 5
            $a = array_diff($columns, $index['columns']);
672 5
            if (empty($a)) {
673 5
                return true;
674
            }
675
        }
676
677
        return false;
678
    }
679 4
680
    /**
681
     * {@inheritdoc}
682 4
     */
683 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...
684 4
    {
685 4
        $indexes = $this->getIndexes($tableName);
686
687 4
        foreach ($indexes as $index) {
688 4
            if ($indexName === $index['index']) {
689 4
                return true;
690 4
            }
691 4
        }
692 4
693
        return false;
694 4
    }
695 4
696 4
    /**
697 4
     * {@inheritdoc}
698 4
     */
699
    protected function getAddIndexInstructions(Table $table, Index $index)
700 4
    {
701
        $indexColumnArray = [];
702 4
        foreach ($index->getColumns() as $column) {
703 4
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
704
        }
705 4
        $indexColumns = implode(',', $indexColumnArray);
706 4
        $sql = sprintf(
707 4
            'CREATE %s ON %s (%s)',
708 4
            $this->getIndexSqlDefinition($table, $index),
709 4
            $this->quoteTableName($table->getName()),
710 4
            $indexColumns
711 4
        );
712
713 4
        return new AlterInstructions([], [$sql]);
714 4
    }
715 4
716
    /**
717
     * {@inheritdoc}
718
     */
719
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
720 1
    {
721
        if (is_string($columns)) {
722
            $columns = [$columns]; // str to array
723 1
        }
724
725
        $indexes = $this->getIndexes($tableName);
726
        $columns = array_map('strtolower', $columns);
727 1
        $instructions = new AlterInstructions();
728
729 1
        foreach ($indexes as $index) {
730
            $a = array_diff($columns, $index['columns']);
731 1
            if (empty($a)) {
732 1
                $instructions->addPostStep(sprintf(
733 1
                    'DROP INDEX %s',
734 1
                    $this->quoteColumnName($index['index'])
735 1
                ));
736 1
            }
737
        }
738 1
739 1
        return $instructions;
740 1
    }
741 1
742 1
    /**
743 1
     * {@inheritdoc}
744 1
     */
745
    protected function getDropIndexByNameInstructions($tableName, $indexName)
746 1
    {
747
        $indexes = $this->getIndexes($tableName);
748 1
        $instructions = new AlterInstructions();
749
750
        foreach ($indexes as $index) {
751
            if ($indexName === $index['index']) {
752
                $instructions->addPostStep(sprintf(
753
                    'DROP INDEX %s',
754 1
                    $this->quoteColumnName($indexName)
755
                ));
756 1
            }
757 1
        }
758 1
759 1
        return $instructions;
760 1
    }
761 1
762 1
    /**
763
     * {@inheritdoc}
764 1
     */
765
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
766 1
    {
767 1
        $primaryKey = $this->getPrimaryKey($tableName);
768 1
769 1
        if (empty($primaryKey)) {
770 1
            return false;
771
        }
772 1
773
        if (is_string($columns)) {
774 1
            $columns = [$columns]; // str to array
775 1
        }
776 1
        $missingColumns = array_diff($columns, [$primaryKey]);
777
778
        return empty($missingColumns);
779
    }
780
781
    /**
782
     * Get the primary key from a particular table.
783
     *
784
     * @param string $tableName Table Name
785
     * @return string|null
786
     */
787
    protected function getPrimaryKey($tableName)
788
    {
789
        $rows = $this->fetchAll(
790
            "SELECT sql, tbl_name
791
              FROM (
792
                    SELECT sql sql, type type, tbl_name tbl_name, name name
793
                      FROM sqlite_master
794
                     UNION ALL
795
                    SELECT sql, type, tbl_name, name
796
                      FROM sqlite_temp_master
797
                   )
798
             WHERE type != 'meta'
799
               AND sql NOTNULL
800
               AND name NOT LIKE 'sqlite_%'
801
             ORDER BY substr(type, 2, 1), name"
802
        );
803
804
        foreach ($rows as $row) {
805
            if ($row['tbl_name'] === $tableName) {
806
                if (strpos($row['sql'], 'PRIMARY KEY') !== false) {
807
                    preg_match_all("/PRIMARY KEY\s*\(`([^`]*)`\)/", $row['sql'], $matches);
808
                    foreach ($matches[1] as $match) {
809
                        if (!empty($match)) {
810 43
                            return $match;
811
                        }
812
                    }
813 43
                    preg_match_all("/`([^`]+)`[\w\s]+PRIMARY KEY/", $row['sql'], $matches);
814 42
                    foreach ($matches[1] as $match) {
815
                        if (!empty($match)) {
816 43
                            return $match;
817
                        }
818
                    }
819 43
                }
820 1
            }
821
        }
822 43
823 38
        return null;
824
    }
825 43
826 42
    /**
827
     * {@inheritdoc}
828 43
     */
829 2
    public function hasForeignKey($tableName, $columns, $constraint = null)
830
    {
831 43
        if (is_string($columns)) {
832 1
            $columns = [$columns]; // str to array
833
        }
834 43
        $foreignKeys = $this->getForeignKeys($tableName);
835 1
836
        return !array_diff($columns, $foreignKeys);
837 43
    }
838 42
839
    /**
840 43
     * Get an array of foreign keys from a particular table.
841 1
     *
842
     * @param string $tableName Table Name
843 43
     * @return array
844 1
     */
845
    protected function getForeignKeys($tableName)
846 43
    {
847 43
        $foreignKeys = [];
848 1
        $rows = $this->fetchAll(
849
            "SELECT sql, tbl_name
850 43
              FROM (
851 42
                    SELECT sql sql, type type, tbl_name tbl_name, name name
852
                      FROM sqlite_master
853 5
                     UNION ALL
854
                    SELECT sql, type, tbl_name, name
855 5
                      FROM sqlite_temp_master
856 4
                   )
857
             WHERE type != 'meta'
858
               AND sql NOTNULL
859
               AND name NOT LIKE 'sqlite_%'
860 1
             ORDER BY substr(type, 2, 1), name"
861 1
        );
862
863
        foreach ($rows as $row) {
864 1
            if ($row['tbl_name'] === $tableName) {
865
                if (strpos($row['sql'], 'REFERENCES') !== false) {
866
                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
867 1
                    foreach ($matches[1] as $match) {
868
                        $foreignKeys[] = $match;
869 1
                    }
870 1
                }
871 1
            }
872
        }
873
874
        return $foreignKeys;
875
    }
876
877
    /**
878
     * @param Table $table The Table
879
     * @param string $column Column Name
880 3
     * @return AlterInstructions
881
     */
882 3
    protected function getAddPrimaryKeyInstructions(Table $table, $column)
883 1
    {
884
        $instructions = $this->beginAlterByCopyTable($table->getName());
885 2
886 2
        $tableName = $table->getName();
887 2 View Code Duplication
        $instructions->addPostStep(function ($state) use ($column) {
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...
888 2
            $sql = preg_replace("/(`$column`)\s+\w+\s+((NOT )?NULL)/", '$1 INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT', $state['createSQL'], 1);
889 1
            $this->execute($sql);
890 1
891 2
            return $state;
892
        });
893
894 2 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...
895 2
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
896 1
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
897 1
            $selectColumns = $writeColumns = $names;
898
899
            return compact('selectColumns', 'writeColumns') + $state;
900 1
        });
901 2
902
        return $this->copyAndDropTmpTable($instructions, $tableName);
903
    }
904
905
    /**
906
     * @param string $tableName Table Name
907
     * @param string $column Column Name
908
     * @return void
909
     */
910 2
    protected function dropPrimaryKey($tableName, $column)
911
    {
912
        $instructions = $this->beginAlterByCopyTable($tableName);
913
914
        $instructions->addPostStep(function ($state) use ($column) {
915
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $column, $column);
916 2
917 1
            return $newState + $state;
918
        });
919
920 1 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...
921 1
            $search = "/(,?\s*PRIMARY KEY\s*\([^\)]*\)|\s+PRIMARY KEY(\s+AUTOINCREMENT)?)/";
922 2
            $sql = preg_replace($search, '', $state['createSQL'], 1);
923 1
924 1
            if ($sql) {
925 2
                $this->execute($sql);
926 2
            }
927
928
            return $state;
929
        });
930
931
        $instructions = $this->copyAndDropTmpTable($instructions, $tableName);
932
933 2
        $instructions->execute(null, [$this, 'execute']);
934
    }
935
936 1
    /**
937 1
     * {@inheritdoc}
938
     */
939 1
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
940
    {
941
        $instructions = $this->beginAlterByCopyTable($table->getName());
942
943
        $tableName = $table->getName();
944
        $instructions->addPostStep(function ($state) use ($foreignKey) {
945
            $this->execute('pragma foreign_keys = ON');
946 48
            $sql = substr($state['createSQL'], 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
947
            $this->execute($sql);
948 48
949 48
            return $state;
950
        });
951
952 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...
953
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
954 2
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
955
            $selectColumns = $writeColumns = $names;
956 2
957
            return compact('selectColumns', 'writeColumns') + $state;
958
        });
959
960
        return $this->copyAndDropTmpTable($instructions, $tableName);
961
    }
962 48
963
    /**
964 48
     * {@inheritdoc}
965 47
     */
966 47
    protected function getDropForeignKeyInstructions($tableName, $constraint)
967 48
    {
968
        throw new \BadMethodCallException('SQLite does not have named foreign keys');
969
    }
970
971
    /**
972
     * {@inheritdoc}
973
     */
974
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
975 42
    {
976
        $instructions = $this->beginAlterByCopyTable($tableName);
977 42
978 8
        $instructions->addPostStep(function ($state) use ($columns) {
979 42
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columns[0], $columns[0]);
980 42
981 42
            $selectColumns = $newState['selectColumns'];
982 42
            $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...
983
            $diff = array_diff($columns, $selectColumns);
984
985
            if (!empty($diff)) {
986
                throw new \InvalidArgumentException(sprintf(
987
                    'The specified columns doen\'t exist: ' . implode(', ', $diff)
988
                ));
989
            }
990
991 42
            return $newState + $state;
992
        });
993 42
994 42
        $instructions->addPostStep(function ($state) use ($columns) {
995 42
            $sql = '';
996 42
997
            foreach ($columns as $columnName) {
998
                $search = sprintf(
999 42
                    "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
1000 42
                    $this->quoteColumnName($columnName)
1001 42
                );
1002 42
                $sql = preg_replace($search, '', $state['createSQL'], 1);
1003 42
            }
1004 4
1005 4
            if ($sql) {
1006
                $this->execute($sql);
1007 42
            }
1008
1009 42
            return $state;
1010 42
        });
1011 42
1012
        return $this->copyAndDropTmpTable($instructions, $tableName);
1013 42
    }
1014
1015
    /**
1016
     * {@inheritdoc}
1017 42
     */
1018
    public function getSqlType($type, $limit = null)
1019 42
    {
1020
        switch ($type) {
1021
            case static::PHINX_TYPE_TEXT:
1022
            case static::PHINX_TYPE_INTEGER:
1023
            case static::PHINX_TYPE_FLOAT:
1024
            case static::PHINX_TYPE_DECIMAL:
1025
            case static::PHINX_TYPE_DATETIME:
1026
            case static::PHINX_TYPE_TIME:
1027
            case static::PHINX_TYPE_DATE:
1028 42
            case static::PHINX_TYPE_BLOB:
1029
            case static::PHINX_TYPE_BOOLEAN:
1030 42
            case static::PHINX_TYPE_ENUM:
1031 2
                return ['name' => $type];
1032
            case static::PHINX_TYPE_STRING:
1033 42
                return ['name' => 'varchar', 'limit' => 255];
1034
            case static::PHINX_TYPE_CHAR:
1035
                return ['name' => 'char', 'limit' => 255];
1036
            case static::PHINX_TYPE_BIG_INTEGER:
1037
                return ['name' => 'bigint'];
1038
            case static::PHINX_TYPE_TIMESTAMP:
1039
                return ['name' => 'datetime'];
1040
            case static::PHINX_TYPE_BINARY:
1041
                return ['name' => 'blob'];
1042 8
            case static::PHINX_TYPE_UUID:
1043
                return ['name' => 'char', 'limit' => 36];
1044 8
            case static::PHINX_TYPE_JSON:
1045 2
            case static::PHINX_TYPE_JSONB:
1046 2
                return ['name' => 'text'];
1047 6
            // Geospatial database types
1048
            // No specific data types exist in SQLite, instead all geospatial
1049 8
            // functionality is handled in the client. See also: SpatiaLite.
1050 3
            case static::PHINX_TYPE_GEOMETRY:
1051 3
            case static::PHINX_TYPE_POLYGON:
1052 6
                return ['name' => 'text'];
1053 6
            case static::PHINX_TYPE_LINESTRING:
1054 6
                return ['name' => 'varchar', 'limit' => 255];
1055 6
            case static::PHINX_TYPE_POINT:
1056 6
                return ['name' => 'float'];
1057
            default:
1058 8
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
1059 8
        }
1060
    }
1061
1062
    /**
1063
     * Returns Phinx type by SQL type
1064
     *
1065 47
     * @param string $sqlTypeDef SQL type
1066
     * @returns string Phinx type
1067 47
     */
1068
    public function getPhinxType($sqlTypeDef)
1069
    {
1070
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
1071
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
1072
        } else {
1073
            $limit = null;
1074
            $precision = null;
1075
            $type = $matches[1];
1076 5
            if (count($matches) > 2) {
1077
                $limit = $matches[3] ?: null;
1078 5
            }
1079 5
            if (count($matches) > 4) {
1080
                $precision = $matches[5];
1081
            }
1082 5
            switch ($matches[1]) {
1083 5
                case 'varchar':
1084 5
                    $type = static::PHINX_TYPE_STRING;
1085 5
                    if ($limit === 255) {
1086 5
                        $limit = null;
1087 5
                    }
1088 5
                    break;
1089 5 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...
1090 5
                    $type = static::PHINX_TYPE_CHAR;
1091 5
                    if ($limit === 255) {
1092 5
                        $limit = null;
1093 1
                    }
1094 1
                    if ($limit === 36) {
1095 5
                        $type = static::PHINX_TYPE_UUID;
1096 1
                    }
1097 1
                    break;
1098
                case 'int':
1099 5
                    $type = static::PHINX_TYPE_INTEGER;
1100
                    if ($limit === 11) {
1101
                        $limit = null;
1102
                    }
1103
                    break;
1104
                case 'bigint':
1105
                    if ($limit === 11) {
1106
                        $limit = null;
1107
                    }
1108
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1109
                    break;
1110
                case 'blob':
1111
                    $type = static::PHINX_TYPE_BINARY;
1112
                    break;
1113
            }
1114 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...
1115
                if ($matches[3] === 1) {
1116
                    $type = static::PHINX_TYPE_BOOLEAN;
1117
                    $limit = null;
1118
                }
1119
            }
1120
1121
            $this->getSqlType($type);
1122
1123
            return [
1124
                'name' => $type,
1125
                'limit' => $limit,
1126
                'precision' => $precision
1127
            ];
1128
        }
1129
    }
1130
1131
    /**
1132
     * {@inheritdoc}
1133
     */
1134
    public function createDatabase($name, $options = [])
1135
    {
1136
        touch($name . $this->suffix);
1137
    }
1138
1139
    /**
1140
     * {@inheritdoc}
1141
     */
1142
    public function hasDatabase($name)
1143
    {
1144
        return is_file($name . $this->suffix);
1145
    }
1146
1147
    /**
1148
     * {@inheritdoc}
1149
     */
1150
    public function dropDatabase($name)
1151
    {
1152
        if (file_exists($name . '.sqlite3')) {
1153
            unlink($name . '.sqlite3');
1154
        }
1155
    }
1156
1157
    /**
1158
     * Gets the SQLite Column Definition for a Column object.
1159
     *
1160
     * @param \Phinx\Db\Table\Column $column Column
1161
     * @return string
1162
     */
1163
    protected function getColumnSqlDefinition(Column $column)
1164
    {
1165
        $isLiteralType = $column->getType() instanceof Literal;
1166
        if ($isLiteralType) {
1167
            $def = (string)$column->getType();
1168
        } else {
1169
            $sqlType = $this->getSqlType($column->getType());
1170
            $def = strtoupper($sqlType['name']);
1171
1172
            $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
1173
            if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
1174
                $def .= '(' . ($column->getLimit() ?: $sqlType['limit']) . ')';
1175
            }
1176
        }
1177 View Code Duplication
        if ($column->getPrecision() && $column->getScale()) {
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...
1178
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1179
        }
1180 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...
1181
            $def .= " CHECK({$column->getName()} IN ('" . implode("', '", $values) . "'))";
1182
        }
1183
1184
        $default = $column->getDefault();
1185
1186
        $def .= (!$column->isIdentity() && ($column->isNull() || is_null($default))) ? ' NULL' : ' NOT NULL';
1187
        $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...
1188
        $def .= $column->isIdentity() ? ' PRIMARY KEY AUTOINCREMENT' : '';
1189
1190
        if ($column->getUpdate()) {
1191
            $def .= ' ON UPDATE ' . $column->getUpdate();
1192
        }
1193
1194
        $def .= $this->getCommentDefinition($column);
1195
1196
        return $def;
1197
    }
1198
1199
    /**
1200
     * Gets the comment Definition for a Column object.
1201
     *
1202
     * @param \Phinx\Db\Table\Column $column Column
1203
     * @return string
1204
     */
1205
    protected function getCommentDefinition(Column $column)
1206
    {
1207
        if ($column->getComment()) {
1208
            return ' /* ' . $column->getComment() . ' */ ';
1209
        }
1210
1211
        return '';
1212
    }
1213
1214
    /**
1215
     * Gets the SQLite Index Definition for an Index object.
1216
     *
1217
     * @param \Phinx\Db\Table\Table $table Table
1218
     * @param \Phinx\Db\Table\Index $index Index
1219
     * @return string
1220
     */
1221
    protected function getIndexSqlDefinition(Table $table, Index $index)
1222
    {
1223
        if ($index->getType() === Index::UNIQUE) {
1224
            $def = 'UNIQUE INDEX';
1225
        } else {
1226
            $def = 'INDEX';
1227
        }
1228
        if (is_string($index->getName())) {
1229
            $indexName = $index->getName();
1230
        } else {
1231
            $indexName = $table->getName() . '_';
1232
            foreach ($index->getColumns() as $column) {
1233
                $indexName .= $column . '_';
1234
            }
1235
            $indexName .= 'index';
1236
        }
1237
        $def .= ' `' . $indexName . '`';
1238
1239
        return $def;
1240
    }
1241
1242
    /**
1243
     * {@inheritdoc}
1244
     */
1245
    public function getColumnTypes()
1246
    {
1247
        return array_merge(parent::getColumnTypes(), ['enum', 'json', 'jsonb']);
1248
    }
1249
1250
    /**
1251
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1252
     *
1253
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1254
     * @return string
1255
     */
1256 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...
1257
    {
1258
        $def = '';
1259
        if ($foreignKey->getConstraint()) {
1260
            $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...
1261
        } else {
1262
            $columnNames = [];
1263
            foreach ($foreignKey->getColumns() as $column) {
1264
                $columnNames[] = $this->quoteColumnName($column);
1265
            }
1266
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1267
            $refColumnNames = [];
1268
            foreach ($foreignKey->getReferencedColumns() as $column) {
1269
                $refColumnNames[] = $this->quoteColumnName($column);
1270
            }
1271
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1272
            if ($foreignKey->getOnDelete()) {
1273
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1274
            }
1275
            if ($foreignKey->getOnUpdate()) {
1276
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1277
            }
1278
        }
1279
1280
        return $def;
1281
    }
1282
1283
    /**
1284
     * {@inheritDoc}
1285
     *
1286
     */
1287
    public function getDecoratedConnection()
1288
    {
1289
        $options = $this->getOptions();
1290
        $options['quoteIdentifiers'] = true;
1291
        $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...
1292
1293
        if (!empty($options['name'])) {
1294
            $options['database'] = $options['name'];
1295
1296
            if (file_exists($options['name'] . $this->suffix)) {
1297
                $options['database'] = $options['name'] . $this->suffix;
1298
            }
1299
        }
1300
1301
        $driver = new SqliteDriver($options);
1302
        if (method_exists($driver, 'setConnection')) {
1303
            $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...
1304
        } else {
1305
            $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...
1306
        }
1307
1308
        return new Connection(['driver' => $driver] + $options);
1309
    }
1310
}
1311