Completed
Push — master ( 788745...6ab5dd )
by José
01:59 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
     * {@inheritdoc}
173 35
     */
174
    public function hasTable($tableName)
175 35
    {
176 42
        $tables = [];
177
        $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
178 1
        foreach ($rows as $row) {
179 1
            $tables[] = strtolower($row[0]);
180 1
        }
181 1
182
        return in_array(strtolower($tableName), $tables);
183 1
    }
184 1
185
    /**
186
     * {@inheritdoc}
187 42
     */
188 42
    public function createTable(Table $table, array $columns = [], array $indexes = [])
189 42
    {
190 42
        // Add the default primary key
191 42
        $options = $table->getOptions();
192 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...
193
            $options['id'] = 'id';
194 42
        }
195 42
196 42
        if (isset($options['id']) && is_string($options['id'])) {
197 42
            // Handle id => "field_name" to support AUTO_INCREMENT
198 42
            $column = new Column();
199 42
            $column->setName($options['id'])
200
                   ->setType('integer')
201
                   ->setIdentity(true);
202 1
203 1
            array_unshift($columns, $column);
204 1
        }
205
206 1
        $sql = 'CREATE TABLE ';
207 1
        $sql .= $this->quoteTableName($table->getName()) . ' (';
208 1
        foreach ($columns as $column) {
209 1
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
210 1
211 1
            if (isset($options['primary_key']) && $column->getIdentity()) {
212 42
                //remove column from the primary key array as it is already defined as an autoincrement
213 42
                //primary id
214 37
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
215
                if ($identityColumnIndex !== false) {
216
                    unset($options['primary_key'][$identityColumnIndex]);
217
218 42
                    if (empty($options['primary_key'])) {
219 42
                        //The last primary key has been removed
220 1
                        unset($options['primary_key']);
221 1
                    }
222 1
                }
223 1
            }
224
        }
225 42
226
        // set the primary key(s)
227 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...
228
            $sql = rtrim($sql);
229 42
            $sql .= ' PRIMARY KEY (';
230 6
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
231 42
                $sql .= $this->quoteColumnName($options['primary_key']);
232 42
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
233
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
234
            }
235
            $sql .= ')';
236
        } else {
237 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
238
        }
239 1
240 1
        $sql = rtrim($sql) . ');';
241
        // execute the sql
242
        $this->execute($sql);
243
244
        foreach ($indexes as $index) {
245 1
            $this->addIndex($table, $index);
246
        }
247 1
    }
248 1
249
    /**
250
     * {@inheritdoc}
251
     */
252
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
253 1
    {
254
        $instructions = new AlterInstructions();
255 1
256 1
        // Drop the existing primary key
257 1
        $primaryKey = $this->getPrimaryKey($table->getName());
258 1
        if (!empty($primaryKey)) {
259
            $instructions->merge(
260 1
                $this->getDropPrimaryKeyInstructions($table, $primaryKey)
261 1
            );
262
        }
263
264
        // Add the primary key(s)
265
        if (!empty($newColumns)) {
266 1
            if (!is_string($newColumns)) {
267
                throw new \InvalidArgumentException(sprintf(
268 1
                    "Invalid value for primary key: %s",
269 1
                    json_encode($newColumns)
270
                ));
271 1
            }
272 1
273 1
            $instructions->merge(
274 1
                $this->getAddPrimaryKeyInstructions($table, $newColumns)
275 1
            );
276 1
        }
277
278 1
        return $instructions;
279 1
    }
280 1
281
    /**
282 1
     * {@inheritdoc}
283 1
     */
284 1
    protected function getChangeCommentInstructions(Table $table, $newComment)
285
    {
286 1
        throw new \BadMethodCallException('SQLite does not have table comments');
287 1
    }
288
289 1
    /**
290
     * {@inheritdoc}
291
     */
292 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...
293
    {
294
        $sql = sprintf(
295 8
            'ALTER TABLE %s RENAME TO %s',
296
            $this->quoteTableName($tableName),
297 8
            $this->quoteTableName($newTableName)
298 8
        );
299 8
300 7
        return new AlterInstructions([], [$sql]);
301
    }
302 8
303
    /**
304 8
     * {@inheritdoc}
305
     */
306
    protected function getDropTableInstructions($tableName)
307
    {
308
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
309
310 4
        return new AlterInstructions([], [$sql]);
311
    }
312 4
313 4
    /**
314 4
     * {@inheritdoc}
315 4
     */
316 4
    public function truncateTable($tableName)
317 4
    {
318
        $sql = sprintf(
319 4
            'DELETE FROM %s',
320 4
            $this->quoteTableName($tableName)
321
        );
322
323
        $this->execute($sql);
324
    }
325 2
326
    /**
327 2
     * {@inheritdoc}
328
     */
329 2
    public function getColumns($tableName)
330
    {
331 2
        $columns = [];
332 2
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
333 2
334 2
        foreach ($rows as $columnInfo) {
335 2
            $column = new Column();
336 2
            $type = strtolower($columnInfo['type']);
337
            $column->setName($columnInfo['name'])
338 2
                   ->setNull($columnInfo['notnull'] !== '1')
339 2
                   ->setDefault($columnInfo['dflt_value']);
340 2
341 2
            $phinxType = $this->getPhinxType($type);
342 2
343 2
            $column->setType($phinxType['name'])
344 2
                   ->setLimit($phinxType['limit'])
345 2
                   ->setScale($phinxType['scale']);
346 2
347
            if ($columnInfo['pk'] == 1) {
348 2
                $column->setIdentity(true);
349 1
            }
350
351 1
            $columns[] = $column;
352
        }
353
354 1
        return $columns;
355
    }
356 1
357 1
    /**
358 1
     * {@inheritdoc}
359
     */
360 1 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...
361 1
    {
362
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
363
        foreach ($rows as $column) {
364 1
            if (strcasecmp($column['name'], $columnName) === 0) {
365 1
                return true;
366 1
            }
367 1
        }
368 1
369
        return false;
370 1
    }
371
372 1
    /**
373
     * {@inheritdoc}
374 1
     */
375 1 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...
376
    {
377
        $alter = sprintf(
378
            'ALTER TABLE %s ADD COLUMN %s %s',
379
            $this->quoteTableName($table->getName()),
380 6
            $this->quoteColumnName($column->getName()),
381
            $this->getColumnSqlDefinition($column)
382
        );
383
384 6
        return new AlterInstructions([], [$alter]);
385
    }
386 6
387
    /**
388 6
     * Returns the original CREATE statement for the give table
389 6
     *
390 6
     * @param string $tableName The table name to get the create statement for
391 6
     * @return string
392 6
     */
393 6
    protected function getDeclaringSql($tableName)
394
    {
395 6
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
396 6
397 6
        $sql = '';
398 6
        foreach ($rows as $table) {
399 6
            if ($table['tbl_name'] === $tableName) {
400 6
                $sql = $table['sql'];
401 6
            }
402 6
        }
403 6
404
        return $sql;
405 6
    }
406
407
    /**
408
     * Copies all the data from a tmp table to another table
409
     *
410
     * @param string $tableName The table name to copy the data to
411 6
     * @param string $tmpTableName The tmp table name where the data is stored
412
     * @param string[] $writeColumns The list of columns in the target table
413 6
     * @param string[] $selectColumns The list of columns in the tmp table
414 6
     * @return void
415 6
     */
416 6
    protected function copyDataToNewTable($tableName, $tmpTableName, $writeColumns, $selectColumns)
417
    {
418 6
        $sql = sprintf(
419
            'INSERT INTO %s(%s) SELECT %s FROM %s',
420 6
            $this->quoteTableName($tableName),
421
            implode(', ', $writeColumns),
422 6
            implode(', ', $selectColumns),
423 6
            $this->quoteTableName($tmpTableName)
424 6
        );
425 6
        $this->execute($sql);
426 6
    }
427
428 6
    /**
429
     * Modifies the passed instructions to copy all data from the tmp table into
430 6
     * the provided table and then drops the tmp table.
431 6
     *
432 6
     * @param AlterInstructions $instructions The instructions to modify
433
     * @param string $tableName The table name to copy the data to
434
     * @return AlterInstructions
435
     */
436
    protected function copyAndDropTmpTable($instructions, $tableName)
437 2
    {
438
        $instructions->addPostStep(function ($state) use ($tableName) {
439
            $this->copyDataToNewTable(
440 2
                $tableName,
441
                $state['tmpTableName'],
442 2
                $state['writeColumns'],
443
                $state['selectColumns']
444 2
            );
445 2
446 2
            $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($state['tmpTableName'])));
447 2
448 2
            return $state;
449 2
        });
450
451 2
        return $instructions;
452 2
    }
453 2
454 2
    /**
455 2
     * Returns the columns and type to use when copying a table to another in the process
456 2
     * of altering a table
457 2
     *
458 2
     * @param string $tableName The table to modify
459 2
     * @param string $columnName The column name that is about to change
460
     * @param string|false $newColumnName Optionally the new name for the column
461 2
     * @return AlterInstructions
462
     */
463 2
    protected function calculateNewTableColumns($tableName, $columnName, $newColumnName)
464
    {
465
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
466
        $selectColumns = [];
467
        $writeColumns = [];
468
        $columnType = null;
469 2
        $found = false;
470
471 2
        foreach ($columns as $column) {
472 2
            $selectName = $column['name'];
473 2
            $writeName = $selectName;
474
475 2
            if ($selectName == $columnName) {
476
                $writeName = $newColumnName;
477 2
                $found = true;
478 2
                $columnType = $column['type'];
479 2
                $selectName = $newColumnName === false ? $newColumnName : $selectName;
480
            }
481 2
482
            $selectColumns[] = $selectName;
483 2
            $writeColumns[] = $writeName;
484 2
        }
485 2
486 2
        $selectColumns = array_filter($selectColumns, 'strlen');
487 2
        $writeColumns = array_filter($writeColumns, 'strlen');
488
        $selectColumns = array_map([$this, 'quoteColumnName'], $selectColumns);
489 2
        $writeColumns = array_map([$this, 'quoteColumnName'], $writeColumns);
490
491 2
        if (!$found) {
492 2
            throw new \InvalidArgumentException(sprintf(
493 2
                'The specified column doesn\'t exist: ' . $columnName
494
            ));
495
        }
496
497
        return compact('writeColumns', 'selectColumns', 'columnType');
498
    }
499
500
    /**
501 9
     * Returns the initial instructions to alter a table using the
502
     * rename-alter-copy strategy
503 9
     *
504 9
     * @param string $tableName The table to modify
505
     * @return AlterInstructions
506 9
     */
507 9
    protected function beginAlterByCopyTable($tableName)
508 9
    {
509 9
        $instructions = new AlterInstructions();
510 9
        $instructions->addPostStep(function ($state) use ($tableName) {
511 9
            $createSQL = $this->getDeclaringSql($tableName);
512 9
513 9
            $tmpTableName = 'tmp_' . $tableName;
514 9
            $this->execute(
515 9
                sprintf(
516
                    'ALTER TABLE %s RENAME TO %s',
517
                    $this->quoteTableName($tableName),
518
                    $this->quoteTableName($tmpTableName)
519
                )
520
            );
521 9
522
            return compact('createSQL', 'tmpTableName') + $state;
523 9
        });
524 4
525 4
        return $instructions;
526
    }
527 9
528 9
    /**
529
     * {@inheritdoc}
530 9
     */
531 9
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
532 9
    {
533 9
        $instructions = $this->beginAlterByCopyTable($tableName);
534 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...
535 8
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
536
537 8
            return $newState + $state;
538
        });
539
540
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
541
            $sql = str_replace(
542
                $this->quoteColumnName($columnName),
543 1
                $this->quoteColumnName($newColumnName),
544
                $state['createSQL']
545 1
            );
546
            $this->execute($sql);
547 1
548 1
            return $state;
549 1
        });
550
551
        return $this->copyAndDropTmpTable($instructions, $tableName);
552
    }
553
554
    /**
555
     * {@inheritdoc}
556
     */
557
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
558
    {
559 8
        $instructions = $this->beginAlterByCopyTable($tableName);
560
561 8
        $newColumnName = $newColumn->getName();
562 8 View Code Duplication
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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