Completed
Push — master ( 3ee855...788745 )
by José
13s queued 11s
created

SQLiteAdapter::getSchemaName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 9.8666
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
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 (strtolower($spec['schema'])) {
195 42
            case 'main':
196 42
            case '':
197 42
                $master = 'sqlite_master';
198 42
                break;
199 42
            default:
200
                $master = sprintf('%s.%s', $this->quoteColumnName($spec['schema']), 'sqlite_master');
201
        }
202 1
        $table = strtolower($spec['table']);
203 1
204 1
        try {
205
            $rows = $this->fetchAll(sprintf('SELECT name FROM %s WHERE type=\'table\' AND lower(name) = %s', $master, $this->quoteString($table)));
206 1
        } catch (\PDOException $e) {
207 1
            // an exception can occur if the schema part of the table refers to a database which is not attached
208 1
            return false;
209 1
        }
210 1
211 1
        // this somewhat pedantic check with strtolower is performed because the SQL lower function may be redefined,
212 42
        // and can act on all Unicode characters if the ICU extension is loaded, while SQL identifiers are only case-insensitive for ASCII
213 42
        foreach ($rows as $row) {
214 37
            if (strtolower($row['name']) == $table) {
215
                return true;
216
            }
217
        }
218 42
219 42
        return false;
220 1
    }
221 1
222 1
    /**
223 1
     * {@inheritdoc}
224
     */
225 42
    public function createTable(Table $table, array $columns = [], array $indexes = [])
226
    {
227 42
        // Add the default primary key
228
        $options = $table->getOptions();
229 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...
230 6
            $options['id'] = 'id';
231 42
        }
232 42
233
        if (isset($options['id']) && is_string($options['id'])) {
234
            // Handle id => "field_name" to support AUTO_INCREMENT
235
            $column = new Column();
236
            $column->setName($options['id'])
237 1
                   ->setType('integer')
238
                   ->setIdentity(true);
239 1
240 1
            array_unshift($columns, $column);
241
        }
242
243
        $sql = 'CREATE TABLE ';
244
        $sql .= $this->quoteTableName($table->getName()) . ' (';
245 1
        foreach ($columns as $column) {
246
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
247 1
248 1
            if (isset($options['primary_key']) && $column->getIdentity()) {
249
                //remove column from the primary key array as it is already defined as an autoincrement
250
                //primary id
251
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
252
                if ($identityColumnIndex !== false) {
253 1
                    unset($options['primary_key'][$identityColumnIndex]);
254
255 1
                    if (empty($options['primary_key'])) {
256 1
                        //The last primary key has been removed
257 1
                        unset($options['primary_key']);
258 1
                    }
259
                }
260 1
            }
261 1
        }
262
263
        // set the primary key(s)
264 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...
265
            $sql = rtrim($sql);
266 1
            $sql .= ' PRIMARY KEY (';
267
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
268 1
                $sql .= $this->quoteColumnName($options['primary_key']);
269 1
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
270
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
271 1
            }
272 1
            $sql .= ')';
273 1
        } else {
274 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
275 1
        }
276 1
277
        $sql = rtrim($sql) . ');';
278 1
        // execute the sql
279 1
        $this->execute($sql);
280 1
281
        foreach ($indexes as $index) {
282 1
            $this->addIndex($table, $index);
283 1
        }
284 1
    }
285
286 1
    /**
287 1
     * {@inheritdoc}
288
     */
289 1
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
290
    {
291
        $instructions = new AlterInstructions();
292
293
        // Drop the existing primary key
294
        $primaryKey = $this->getPrimaryKey($table->getName());
295 8
        if (!empty($primaryKey)) {
296
            $instructions->merge(
297 8
                $this->getDropPrimaryKeyInstructions($table, $primaryKey)
298 8
            );
299 8
        }
300 7
301
        // Add the primary key(s)
302 8
        if (!empty($newColumns)) {
303
            if (!is_string($newColumns)) {
304 8
                throw new \InvalidArgumentException(sprintf(
305
                    "Invalid value for primary key: %s",
306
                    json_encode($newColumns)
307
                ));
308
            }
309
310 4
            $instructions->merge(
311
                $this->getAddPrimaryKeyInstructions($table, $newColumns)
312 4
            );
313 4
        }
314 4
315 4
        return $instructions;
316 4
    }
317 4
318
    /**
319 4
     * {@inheritdoc}
320 4
     */
321
    protected function getChangeCommentInstructions(Table $table, $newComment)
322
    {
323
        throw new \BadMethodCallException('SQLite does not have table comments');
324
    }
325 2
326
    /**
327 2
     * {@inheritdoc}
328
     */
329 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...
330
    {
331 2
        $sql = sprintf(
332 2
            'ALTER TABLE %s RENAME TO %s',
333 2
            $this->quoteTableName($tableName),
334 2
            $this->quoteTableName($newTableName)
335 2
        );
336 2
337
        return new AlterInstructions([], [$sql]);
338 2
    }
339 2
340 2
    /**
341 2
     * {@inheritdoc}
342 2
     */
343 2
    protected function getDropTableInstructions($tableName)
344 2
    {
345 2
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
346 2
347
        return new AlterInstructions([], [$sql]);
348 2
    }
349 1
350
    /**
351 1
     * {@inheritdoc}
352
     */
353
    public function truncateTable($tableName)
354 1
    {
355
        $sql = sprintf(
356 1
            'DELETE FROM %s',
357 1
            $this->quoteTableName($tableName)
358 1
        );
359
360 1
        $this->execute($sql);
361 1
    }
362
363
    /**
364 1
     * {@inheritdoc}
365 1
     */
366 1
    public function getColumns($tableName)
367 1
    {
368 1
        $columns = [];
369
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
370 1
371
        foreach ($rows as $columnInfo) {
372 1
            $column = new Column();
373
            $type = strtolower($columnInfo['type']);
374 1
            $column->setName($columnInfo['name'])
375 1
                   ->setNull($columnInfo['notnull'] !== '1')
376
                   ->setDefault($columnInfo['dflt_value']);
377
378
            $phinxType = $this->getPhinxType($type);
379
380 6
            $column->setType($phinxType['name'])
381
                   ->setLimit($phinxType['limit'])
382
                   ->setScale($phinxType['scale']);
383
384 6
            if ($columnInfo['pk'] == 1) {
385
                $column->setIdentity(true);
386 6
            }
387
388 6
            $columns[] = $column;
389 6
        }
390 6
391 6
        return $columns;
392 6
    }
393 6
394
    /**
395 6
     * {@inheritdoc}
396 6
     */
397 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...
398 6
    {
399 6
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
400 6
        foreach ($rows as $column) {
401 6
            if (strcasecmp($column['name'], $columnName) === 0) {
402 6
                return true;
403 6
            }
404
        }
405 6
406
        return false;
407
    }
408
409
    /**
410
     * {@inheritdoc}
411 6
     */
412 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...
413 6
    {
414 6
        $alter = sprintf(
415 6
            'ALTER TABLE %s ADD COLUMN %s %s',
416 6
            $this->quoteTableName($table->getName()),
417
            $this->quoteColumnName($column->getName()),
418 6
            $this->getColumnSqlDefinition($column)
419
        );
420 6
421
        return new AlterInstructions([], [$alter]);
422 6
    }
423 6
424 6
    /**
425 6
     * Returns the original CREATE statement for the give table
426 6
     *
427
     * @param string $tableName The table name to get the create statement for
428 6
     * @return string
429
     */
430 6
    protected function getDeclaringSql($tableName)
431 6
    {
432 6
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
433
434
        $sql = '';
435
        foreach ($rows as $table) {
436
            if ($table['tbl_name'] === $tableName) {
437 2
                $sql = $table['sql'];
438
            }
439
        }
440 2
441
        return $sql;
442 2
    }
443
444 2
    /**
445 2
     * Copies all the data from a tmp table to another table
446 2
     *
447 2
     * @param string $tableName The table name to copy the data to
448 2
     * @param string $tmpTableName The tmp table name where the data is stored
449 2
     * @param string[] $writeColumns The list of columns in the target table
450
     * @param string[] $selectColumns The list of columns in the tmp table
451 2
     * @return void
452 2
     */
453 2
    protected function copyDataToNewTable($tableName, $tmpTableName, $writeColumns, $selectColumns)
454 2
    {
455 2
        $sql = sprintf(
456 2
            'INSERT INTO %s(%s) SELECT %s FROM %s',
457 2
            $this->quoteTableName($tableName),
458 2
            implode(', ', $writeColumns),
459 2
            implode(', ', $selectColumns),
460
            $this->quoteTableName($tmpTableName)
461 2
        );
462
        $this->execute($sql);
463 2
    }
464
465
    /**
466
     * Modifies the passed instructions to copy all data from the tmp table into
467
     * the provided table and then drops the tmp table.
468
     *
469 2
     * @param AlterInstructions $instructions The instructions to modify
470
     * @param string $tableName The table name to copy the data to
471 2
     * @return AlterInstructions
472 2
     */
473 2
    protected function copyAndDropTmpTable($instructions, $tableName)
474
    {
475 2
        $instructions->addPostStep(function ($state) use ($tableName) {
476
            $this->copyDataToNewTable(
477 2
                $tableName,
478 2
                $state['tmpTableName'],
479 2
                $state['writeColumns'],
480
                $state['selectColumns']
481 2
            );
482
483 2
            $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($state['tmpTableName'])));
484 2
485 2
            return $state;
486 2
        });
487 2
488
        return $instructions;
489 2
    }
490
491 2
    /**
492 2
     * Returns the columns and type to use when copying a table to another in the process
493 2
     * of altering a table
494
     *
495
     * @param string $tableName The table to modify
496
     * @param string $columnName The column name that is about to change
497
     * @param string|false $newColumnName Optionally the new name for the column
498
     * @return AlterInstructions
499
     */
500
    protected function calculateNewTableColumns($tableName, $columnName, $newColumnName)
501 9
    {
502
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
503 9
        $selectColumns = [];
504 9
        $writeColumns = [];
505
        $columnType = null;
506 9
        $found = false;
507 9
508 9
        foreach ($columns as $column) {
509 9
            $selectName = $column['name'];
510 9
            $writeName = $selectName;
511 9
512 9
            if ($selectName == $columnName) {
513 9
                $writeName = $newColumnName;
514 9
                $found = true;
515 9
                $columnType = $column['type'];
516
                $selectName = $newColumnName === false ? $newColumnName : $selectName;
517
            }
518
519
            $selectColumns[] = $selectName;
520
            $writeColumns[] = $writeName;
521 9
        }
522
523 9
        $selectColumns = array_filter($selectColumns, 'strlen');
524 4
        $writeColumns = array_filter($writeColumns, 'strlen');
525 4
        $selectColumns = array_map([$this, 'quoteColumnName'], $selectColumns);
526
        $writeColumns = array_map([$this, 'quoteColumnName'], $writeColumns);
527 9
528 9
        if (!$found) {
529
            throw new \InvalidArgumentException(sprintf(
530 9
                'The specified column doesn\'t exist: ' . $columnName
531 9
            ));
532 9
        }
533 9
534
        return compact('writeColumns', 'selectColumns', 'columnType');
535 8
    }
536
537 8
    /**
538
     * Returns the initial instructions to alter a table using the
539
     * rename-alter-copy strategy
540
     *
541
     * @param string $tableName The table to modify
542
     * @return AlterInstructions
543 1
     */
544
    protected function beginAlterByCopyTable($tableName)
545 1
    {
546
        $instructions = new AlterInstructions();
547 1
        $instructions->addPostStep(function ($state) use ($tableName) {
548 1
            $createSQL = $this->getDeclaringSql($tableName);
549 1
550
            $tmpTableName = 'tmp_' . $tableName;
551
            $this->execute(
552
                sprintf(
553
                    'ALTER TABLE %s RENAME TO %s',
554
                    $this->quoteTableName($tableName),
555
                    $this->quoteTableName($tmpTableName)
556
                )
557
            );
558
559 8
            return compact('createSQL', 'tmpTableName') + $state;
560
        });
561 8
562 8
        return $instructions;
563 8
    }
564 8
565 8
    /**
566 8
     * {@inheritdoc}
567 8
     */
568 8
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
569 8
    {
570 8
        $instructions = $this->beginAlterByCopyTable($tableName);
571 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...
572 8
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
573 8
574 8
            return $newState + $state;
575
        });
576
577
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
578
            $sql = str_replace(
579 1
                $this->quoteColumnName($columnName),
580
                $this->quoteColumnName($newColumnName),
581 1
                $state['createSQL']
582 1
            );
583 1
            $this->execute($sql);
584
585 1
            return $state;
586 1
        });
587
588 1
        return $this->copyAndDropTmpTable($instructions, $tableName);
589 1
    }
590 1
591 1
    /**
592 1
     * {@inheritdoc}
593 1
     */
594 1
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
595 1
    {
596 1
        $instructions = $this->beginAlterByCopyTable($tableName);
597 1
598
        $newColumnName = $newColumn->getName();
599 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...
600
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
601
602
            return $newState + $state;
603
        });
604
605 1
        $instructions->addPostStep(function ($state) use ($columnName, $newColumn) {
606
            $sql = preg_replace(
607 1
                sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
608
                sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
609 1
                $state['createSQL'],
610 1
                1
611 1
            );
612 1
            $this->execute($sql);
613 1
614 1
            return $state;
615 1
        });
616 1
617 1
        return $this->copyAndDropTmpTable($instructions, $tableName);
618
    }
619
620
    /**
621
     * {@inheritdoc}
622
     */
623
    protected function getDropColumnInstructions($tableName, $columnName)
624
    {
625 5
        $instructions = $this->beginAlterByCopyTable($tableName);
626
627 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...
628
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, false);
629
630 5
            return $newState + $state;
631
        });
632 5
633 5
        $instructions->addPostStep(function ($state) use ($columnName) {
634 5
            $sql = preg_replace(
635
                sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($state['columnType'])),
636 1
                "",
637
                $state['createSQL']
638
            );
639
640
            if (substr($sql, -2) === ', ') {
641
                $sql = substr($sql, 0, -2) . ')';
642
            }
643
644
            $this->execute($sql);
645 5
646
            return $state;
647 5
        });
648 5
649
        return $this->copyAndDropTmpTable($instructions, $tableName);
650
    }
651
652
    /**
653
     * Get an array of indexes from a particular table.
654
     *
655
     * @param string $tableName Table Name
656
     * @return array
657
     */
658
    protected function getIndexes($tableName)
659
    {
660
        $indexes = [];
661 5
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
662
663 5
        foreach ($rows as $row) {
664 5
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
665 5
            if (!isset($indexes[$tableName])) {
666 5
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
667 5
            }
668 5
            foreach ($indexData as $indexItem) {
669 5
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
670 5
            }
671 5
        }
672 5
673 5
        return $indexes;
674
    }
675
676
    /**
677
     * {@inheritdoc}
678
     */
679 4 View Code Duplication
    public function hasIndex($tableName, $columns)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
680
    {
681
        $columns = array_map('strtolower', (array)$columns);
682 4
        $indexes = $this->getIndexes($tableName);
683
684 4
        foreach ($indexes as $index) {
685 4
            $a = array_diff($columns, $index['columns']);
686
            if (empty($a)) {
687 4
                return true;
688 4
            }
689 4
        }
690 4
691 4
        return false;
692 4
    }
693
694 4
    /**
695 4
     * {@inheritdoc}
696 4
     */
697 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...
698 4
    {
699
        $indexes = $this->getIndexes($tableName);
700 4
701
        foreach ($indexes as $index) {
702 4
            if ($indexName === $index['index']) {
703 4
                return true;
704
            }
705 4
        }
706 4
707 4
        return false;
708 4
    }
709 4
710 4
    /**
711 4
     * {@inheritdoc}
712
     */
713 4
    protected function getAddIndexInstructions(Table $table, Index $index)
714 4
    {
715 4
        $indexColumnArray = [];
716
        foreach ($index->getColumns() as $column) {
717
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
718
        }
719
        $indexColumns = implode(',', $indexColumnArray);
720 1
        $sql = sprintf(
721
            'CREATE %s ON %s (%s)',
722
            $this->getIndexSqlDefinition($table, $index),
723 1
            $this->quoteTableName($table->getName()),
724
            $indexColumns
725
        );
726
727 1
        return new AlterInstructions([], [$sql]);
728
    }
729 1
730
    /**
731 1
     * {@inheritdoc}
732 1
     */
733 1
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
734 1
    {
735 1
        $indexes = $this->getIndexes($tableName);
736 1
        $columns = array_map('strtolower', (array)$columns);
737
        $instructions = new AlterInstructions();
738 1
739 1
        foreach ($indexes as $index) {
740 1
            $a = array_diff($columns, $index['columns']);
741 1
            if (empty($a)) {
742 1
                $instructions->addPostStep(sprintf(
743 1
                    'DROP INDEX %s',
744 1
                    $this->quoteColumnName($index['index'])
745
                ));
746 1
            }
747
        }
748 1
749
        return $instructions;
750
    }
751
752
    /**
753
     * {@inheritdoc}
754 1
     */
755
    protected function getDropIndexByNameInstructions($tableName, $indexName)
756 1
    {
757 1
        $indexes = $this->getIndexes($tableName);
758 1
        $instructions = new AlterInstructions();
759 1
760 1
        foreach ($indexes as $index) {
761 1
            if ($indexName === $index['index']) {
762 1
                $instructions->addPostStep(sprintf(
763
                    'DROP INDEX %s',
764 1
                    $this->quoteColumnName($indexName)
765
                ));
766 1
            }
767 1
        }
768 1
769 1
        return $instructions;
770 1
    }
771
772 1
    /**
773
     * {@inheritdoc}
774 1
     */
775 1
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
776 1
    {
777
        $primaryKey = $this->getPrimaryKey($tableName);
778
779
        if (empty($primaryKey)) {
780
            return false;
781
        }
782
783
        $missingColumns = array_diff((array)$columns, (array)$primaryKey);
784
785
        return empty($missingColumns);
786
    }
787
788
    /**
789
     * Get the primary key from a particular table.
790
     *
791
     * @param string $tableName Table Name
792
     * @return string|null
793
     */
794
    protected function getPrimaryKey($tableName)
795
    {
796
        $rows = $this->fetchAll(
797
            "SELECT sql, tbl_name
798
              FROM (
799
                    SELECT sql sql, type type, tbl_name tbl_name, name name
800
                      FROM sqlite_master
801
                     UNION ALL
802
                    SELECT sql, type, tbl_name, name
803
                      FROM sqlite_temp_master
804
                   )
805
             WHERE type != 'meta'
806
               AND sql NOTNULL
807
               AND name NOT LIKE 'sqlite_%'
808
             ORDER BY substr(type, 2, 1), name"
809
        );
810 43
811
        foreach ($rows as $row) {
812
            if ($row['tbl_name'] === $tableName) {
813 43
                if (strpos($row['sql'], 'PRIMARY KEY') !== false) {
814 42
                    preg_match_all("/PRIMARY KEY\s*\(`([^`]+)`\)/", $row['sql'], $matches);
815
                    foreach ($matches[1] as $match) {
816 43
                        if (!empty($match)) {
817
                            return $match;
818
                        }
819 43
                    }
820 1
                    preg_match_all("/`([^`]+)`\s+\w+(\(\d+\))?((\s+NOT)?\s+NULL)?\s+PRIMARY KEY/", $row['sql'], $matches);
821
                    foreach ($matches[1] as $match) {
822 43
                        if (!empty($match)) {
823 38
                            return $match;
824
                        }
825 43
                    }
826 42
                }
827
            }
828 43
        }
829 2
830
        return null;
831 43
    }
832 1
833
    /**
834 43
     * {@inheritdoc}
835 1
     */
836
    public function hasForeignKey($tableName, $columns, $constraint = null)
837 43
    {
838 42
        $foreignKeys = $this->getForeignKeys($tableName);
839
840 43
        return !array_diff((array)$columns, $foreignKeys);
841 1
    }
842
843 43
    /**
844 1
     * Get an array of foreign keys from a particular table.
845
     *
846 43
     * @param string $tableName Table Name
847 43
     * @return array
848 1
     */
849
    protected function getForeignKeys($tableName)
850 43
    {
851 42
        $foreignKeys = [];
852
        $rows = $this->fetchAll(
853 5
            "SELECT sql, tbl_name
854
              FROM (
855 5
                    SELECT sql sql, type type, tbl_name tbl_name, name name
856 4
                      FROM sqlite_master
857
                     UNION ALL
858
                    SELECT sql, type, tbl_name, name
859
                      FROM sqlite_temp_master
860 1
                   )
861 1
             WHERE type != 'meta'
862
               AND sql NOTNULL
863
               AND name NOT LIKE 'sqlite_%'
864 1
             ORDER BY substr(type, 2, 1), name"
865
        );
866
867 1
        foreach ($rows as $row) {
868
            if ($row['tbl_name'] === $tableName) {
869 1
                if (strpos($row['sql'], 'REFERENCES') !== false) {
870 1
                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
871 1
                    foreach ($matches[1] as $match) {
872
                        $foreignKeys[] = $match;
873
                    }
874
                }
875
            }
876
        }
877
878
        return $foreignKeys;
879
    }
880 3
881
    /**
882 3
     * @param Table $table The Table
883 1
     * @param string $column Column Name
884
     * @return AlterInstructions
885 2
     */
886 2
    protected function getAddPrimaryKeyInstructions(Table $table, $column)
887 2
    {
888 2
        $instructions = $this->beginAlterByCopyTable($table->getName());
889 1
890 1
        $tableName = $table->getName();
891 2
        $instructions->addPostStep(function ($state) use ($column) {
892
            $matchPattern = "/(`$column`)\s+(\w+(\(\d+\))?)\s+((NOT )?NULL)/";
893
894 2
            $sql = $state['createSQL'];
895 2
896 1
            if (preg_match($matchPattern, $state['createSQL'], $matches)) {
897 1
                if (isset($matches[2])) {
898
                    if ($matches[2] === 'INTEGER') {
899
                        $replace = '$1 INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT';
900 1
                    } else {
901 2
                        $replace = '$1 $2 NOT NULL PRIMARY KEY';
902
                    }
903
904
                    $sql = preg_replace($matchPattern, $replace, $state['createSQL'], 1);
905
                }
906
            }
907
908
            $this->execute($sql);
909
910 2
            return $state;
911
        });
912
913 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...
914
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
915
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
916 2
            $selectColumns = $writeColumns = $names;
917 1
918
            return compact('selectColumns', 'writeColumns') + $state;
919
        });
920 1
921 1
        return $this->copyAndDropTmpTable($instructions, $tableName);
922 2
    }
923 1
924 1
    /**
925 2
     * @param Table $table Table
926 2
     * @param string $column Column Name
927
     * @return AlterInstructions
928
     */
929
    protected function getDropPrimaryKeyInstructions($table, $column)
930
    {
931
        $instructions = $this->beginAlterByCopyTable($table->getName());
932
933 2
        $instructions->addPostStep(function ($state) use ($column) {
934
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $column, $column);
935
936 1
            return $newState + $state;
937 1
        });
938
939 1
        $instructions->addPostStep(function ($state) {
940
            $search = "/(,?\s*PRIMARY KEY\s*\([^\)]*\)|\s+PRIMARY KEY(\s+AUTOINCREMENT)?)/";
941
            $sql = preg_replace($search, '', $state['createSQL'], 1);
942
943
            if ($sql) {
944
                $this->execute($sql);
945
            }
946 48
947
            return $state;
948 48
        });
949 48
950
        return $this->copyAndDropTmpTable($instructions, $table->getName());
951
    }
952
953
    /**
954 2
     * {@inheritdoc}
955
     */
956 2
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
957
    {
958
        $instructions = $this->beginAlterByCopyTable($table->getName());
959
960
        $tableName = $table->getName();
961
        $instructions->addPostStep(function ($state) use ($foreignKey) {
962 48
            $this->execute('pragma foreign_keys = ON');
963
            $sql = substr($state['createSQL'], 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
964 48
            $this->execute($sql);
965 47
966 47
            return $state;
967 48
        });
968
969 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...
970
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
971
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
972
            $selectColumns = $writeColumns = $names;
973
974
            return compact('selectColumns', 'writeColumns') + $state;
975 42
        });
976
977 42
        return $this->copyAndDropTmpTable($instructions, $tableName);
978 8
    }
979 42
980 42
    /**
981 42
     * {@inheritdoc}
982 42
     */
983
    protected function getDropForeignKeyInstructions($tableName, $constraint)
984
    {
985
        throw new \BadMethodCallException('SQLite does not have named foreign keys');
986
    }
987
988
    /**
989
     * {@inheritdoc}
990
     */
991 42
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
992
    {
993 42
        $instructions = $this->beginAlterByCopyTable($tableName);
994 42
995 42
        $instructions->addPostStep(function ($state) use ($columns) {
996 42
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columns[0], $columns[0]);
997
998
            $selectColumns = $newState['selectColumns'];
999 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...
1000 42
            $diff = array_diff($columns, $selectColumns);
1001 42
1002 42
            if (!empty($diff)) {
1003 42
                throw new \InvalidArgumentException(sprintf(
1004 4
                    'The specified columns don\'t exist: ' . implode(', ', $diff)
1005 4
                ));
1006
            }
1007 42
1008
            return $newState + $state;
1009 42
        });
1010 42
1011 42
        $instructions->addPostStep(function ($state) use ($columns) {
1012
            $sql = '';
1013 42
1014
            foreach ($columns as $columnName) {
1015
                $search = sprintf(
1016
                    "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
1017 42
                    $this->quoteColumnName($columnName)
1018
                );
1019 42
                $sql = preg_replace($search, '', $state['createSQL'], 1);
1020
            }
1021
1022
            if ($sql) {
1023
                $this->execute($sql);
1024
            }
1025
1026
            return $state;
1027
        });
1028 42
1029
        return $this->copyAndDropTmpTable($instructions, $tableName);
1030 42
    }
1031 2
1032
    /**
1033 42
     * {@inheritdoc}
1034
     */
1035
    public function getSqlType($type, $limit = null)
1036
    {
1037
        switch ($type) {
1038
            case static::PHINX_TYPE_TEXT:
1039
            case static::PHINX_TYPE_INTEGER:
1040
            case static::PHINX_TYPE_FLOAT:
1041
            case static::PHINX_TYPE_DOUBLE:
1042 8
            case static::PHINX_TYPE_DECIMAL:
1043
            case static::PHINX_TYPE_DATETIME:
1044 8
            case static::PHINX_TYPE_TIME:
1045 2
            case static::PHINX_TYPE_DATE:
1046 2
            case static::PHINX_TYPE_BLOB:
1047 6
            case static::PHINX_TYPE_BOOLEAN:
1048
            case static::PHINX_TYPE_ENUM:
1049 8
                return ['name' => $type];
1050 3
            case static::PHINX_TYPE_STRING:
1051 3
                return ['name' => 'varchar', 'limit' => 255];
1052 6
            case static::PHINX_TYPE_CHAR:
1053 6
                return ['name' => 'char', 'limit' => 255];
1054 6
            case static::PHINX_TYPE_SMALL_INTEGER:
1055 6
                return ['name' => 'smallint'];
1056 6
            case static::PHINX_TYPE_BIG_INTEGER:
1057
                return ['name' => 'bigint'];
1058 8
            case static::PHINX_TYPE_TIMESTAMP:
1059 8
                return ['name' => 'datetime'];
1060
            case static::PHINX_TYPE_BINARY:
1061
                return ['name' => 'blob'];
1062
            case static::PHINX_TYPE_UUID:
1063
                return ['name' => 'char', 'limit' => 36];
1064
            case static::PHINX_TYPE_JSON:
1065 47
            case static::PHINX_TYPE_JSONB:
1066
                return ['name' => 'text'];
1067 47
            // Geospatial database types
1068
            // No specific data types exist in SQLite, instead all geospatial
1069
            // functionality is handled in the client. See also: SpatiaLite.
1070
            case static::PHINX_TYPE_GEOMETRY:
1071
            case static::PHINX_TYPE_POLYGON:
1072
                return ['name' => 'text'];
1073
            case static::PHINX_TYPE_LINESTRING:
1074
                return ['name' => 'varchar', 'limit' => 255];
1075
            case static::PHINX_TYPE_POINT:
1076 5
                return ['name' => 'float'];
1077
            default:
1078 5
                throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by SQLite.');
1079 5
        }
1080
    }
1081
1082 5
    /**
1083 5
     * Returns Phinx type by SQL type
1084 5
     *
1085 5
     * @param string $sqlTypeDef SQL type
1086 5
     * @throws UnsupportedColumnTypeException
1087 5
     * @return array
1088 5
     */
1089 5
    public function getPhinxType($sqlTypeDef)
1090 5
    {
1091 5
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
1092 5
            throw new UnsupportedColumnTypeException('Column type "' . $sqlTypeDef . '" is not supported by SQLite.');
1093 1
        } else {
1094 1
            $limit = null;
1095 5
            $scale = null;
1096 1
            $type = $matches[1];
1097 1
            if (count($matches) > 2) {
1098
                $limit = $matches[3] ?: null;
1099 5
            }
1100
            if (count($matches) > 4) {
1101
                $scale = $matches[5];
1102
            }
1103
            switch ($type) {
1104
                case 'varchar':
1105
                    $type = static::PHINX_TYPE_STRING;
1106
                    if ($limit === 255) {
1107
                        $limit = null;
1108
                    }
1109
                    break;
1110 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...
1111
                    $type = static::PHINX_TYPE_CHAR;
1112
                    if ($limit === 255) {
1113
                        $limit = null;
1114
                    }
1115
                    if ($limit === 36) {
1116
                        $type = static::PHINX_TYPE_UUID;
1117
                    }
1118
                    break;
1119
                case 'smallint':
1120
                    $type = static::PHINX_TYPE_SMALL_INTEGER;
1121
                    if ($limit === 11) {
1122
                        $limit = null;
1123
                    }
1124
                    break;
1125
                case 'int':
1126
                    $type = static::PHINX_TYPE_INTEGER;
1127
                    if ($limit === 11) {
1128
                        $limit = null;
1129
                    }
1130
                    break;
1131
                case 'bigint':
1132
                    if ($limit === 11) {
1133
                        $limit = null;
1134
                    }
1135
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1136
                    break;
1137
                case 'blob':
1138
                    $type = static::PHINX_TYPE_BINARY;
1139
                    break;
1140
            }
1141 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...
1142
                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...
1143
                    $type = static::PHINX_TYPE_BOOLEAN;
1144
                    $limit = null;
1145
                }
1146
            }
1147
1148
            try {
1149
                // Call this to check if parsed type is supported.
1150
                $this->getSqlType($type);
1151
            } catch (UnsupportedColumnTypeException $e) {
1152
                $type = Literal::from($type);
1153
            }
1154
1155
            return [
1156
                'name' => $type,
1157
                'limit' => $limit,
1158
                'scale' => $scale
1159
            ];
1160
        }
1161
    }
1162
1163
    /**
1164
     * {@inheritdoc}
1165
     */
1166
    public function createDatabase($name, $options = [])
1167
    {
1168
        touch($name . $this->suffix);
1169
    }
1170
1171
    /**
1172
     * {@inheritdoc}
1173
     */
1174
    public function hasDatabase($name)
1175
    {
1176
        return is_file($name . $this->suffix);
1177
    }
1178
1179
    /**
1180
     * {@inheritdoc}
1181
     */
1182
    public function dropDatabase($name)
1183
    {
1184
        if ($this->getOption('memory')) {
1185
            $this->disconnect();
1186
            $this->connect();
1187
        }
1188
        if (file_exists($name . $this->suffix)) {
1189
            unlink($name . $this->suffix);
1190
        }
1191
    }
1192
1193
    /**
1194
     * Gets the SQLite Column Definition for a Column object.
1195
     *
1196
     * @param \Phinx\Db\Table\Column $column Column
1197
     * @return string
1198
     */
1199
    protected function getColumnSqlDefinition(Column $column)
1200
    {
1201
        $isLiteralType = $column->getType() instanceof Literal;
1202
        if ($isLiteralType) {
1203
            $def = (string)$column->getType();
1204
        } else {
1205
            $sqlType = $this->getSqlType($column->getType());
1206
            $def = strtoupper($sqlType['name']);
1207
1208
            $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
1209
            if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
1210
                $def .= '(' . ($column->getLimit() ?: $sqlType['limit']) . ')';
1211
            }
1212
        }
1213
        if ($column->getPrecision() && $column->getScale()) {
1214
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1215
        }
1216 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...
1217
            $def .= " CHECK({$this->quoteColumnName($column->getName())} IN ('" . implode("', '", $values) . "'))";
1218
        }
1219
1220
        $default = $column->getDefault();
1221
1222
        $def .= (!$column->isIdentity() && ($column->isNull() || is_null($default))) ? ' NULL' : ' NOT NULL';
1223
        $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...
1224
        $def .= $column->isIdentity() ? ' PRIMARY KEY AUTOINCREMENT' : '';
1225
1226
        if ($column->getUpdate()) {
1227
            $def .= ' ON UPDATE ' . $column->getUpdate();
1228
        }
1229
1230
        $def .= $this->getCommentDefinition($column);
1231
1232
        return $def;
1233
    }
1234
1235
    /**
1236
     * Gets the comment Definition for a Column object.
1237
     *
1238
     * @param \Phinx\Db\Table\Column $column Column
1239
     * @return string
1240
     */
1241
    protected function getCommentDefinition(Column $column)
1242
    {
1243
        if ($column->getComment()) {
1244
            return ' /* ' . $column->getComment() . ' */ ';
1245
        }
1246
1247
        return '';
1248
    }
1249
1250
    /**
1251
     * Gets the SQLite Index Definition for an Index object.
1252
     *
1253
     * @param \Phinx\Db\Table\Table $table Table
1254
     * @param \Phinx\Db\Table\Index $index Index
1255
     * @return string
1256
     */
1257
    protected function getIndexSqlDefinition(Table $table, Index $index)
1258
    {
1259
        if ($index->getType() === Index::UNIQUE) {
1260
            $def = 'UNIQUE INDEX';
1261
        } else {
1262
            $def = 'INDEX';
1263
        }
1264
        if (is_string($index->getName())) {
1265
            $indexName = $index->getName();
1266
        } else {
1267
            $indexName = $table->getName() . '_';
1268
            foreach ($index->getColumns() as $column) {
1269
                $indexName .= $column . '_';
1270
            }
1271
            $indexName .= 'index';
1272
        }
1273
        $def .= ' `' . $indexName . '`';
1274
1275
        return $def;
1276
    }
1277
1278
    /**
1279
     * {@inheritdoc}
1280
     */
1281
    public function getColumnTypes()
1282
    {
1283
        return array_merge(parent::getColumnTypes(), ['enum', 'json', 'jsonb']);
1284
    }
1285
1286
    /**
1287
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1288
     *
1289
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1290
     * @return string
1291
     */
1292 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...
1293
    {
1294
        $def = '';
1295
        if ($foreignKey->getConstraint()) {
1296
            $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...
1297
        } else {
1298
            $columnNames = [];
1299
            foreach ($foreignKey->getColumns() as $column) {
1300
                $columnNames[] = $this->quoteColumnName($column);
1301
            }
1302
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1303
            $refColumnNames = [];
1304
            foreach ($foreignKey->getReferencedColumns() as $column) {
1305
                $refColumnNames[] = $this->quoteColumnName($column);
1306
            }
1307
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1308
            if ($foreignKey->getOnDelete()) {
1309
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1310
            }
1311
            if ($foreignKey->getOnUpdate()) {
1312
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1313
            }
1314
        }
1315
1316
        return $def;
1317
    }
1318
1319
    /**
1320
     * {@inheritDoc}
1321
     *
1322
     */
1323
    public function getDecoratedConnection()
1324
    {
1325
        $options = $this->getOptions();
1326
        $options['quoteIdentifiers'] = true;
1327
        $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...
1328
1329
        if (!empty($options['name'])) {
1330
            $options['database'] = $options['name'];
1331
1332
            if (file_exists($options['name'] . $this->suffix)) {
1333
                $options['database'] = $options['name'] . $this->suffix;
1334
            }
1335
        }
1336
1337
        $driver = new SqliteDriver($options);
1338
        if (method_exists($driver, 'setConnection')) {
1339
            $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...
1340
        } else {
1341
            $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...
1342
        }
1343
1344
        return new Connection(['driver' => $driver] + $options);
1345
    }
1346
}
1347