Completed
Pull Request — master (#1548)
by
unknown
01:40
created

SQLiteAdapter::getPrimaryKey()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

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

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

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

Loading history...
925 2
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
926 2
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
927
            $selectColumns = $writeColumns = $names;
928
929
            return compact('selectColumns', 'writeColumns') + $state;
930
        });
931
932
        return $this->copyAndDropTmpTable($instructions, $tableName);
933 2
    }
934
935
    /**
936 1
     * @param Table $table Table
937 1
     * @param string $column Column Name
938
     * @return AlterInstructions
939 1
     */
940
    protected function getDropPrimaryKeyInstructions($table, $column)
941
    {
942
        $instructions = $this->beginAlterByCopyTable($table->getName());
943
944
        $instructions->addPostStep(function ($state) use ($column) {
945
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $column, $column);
946 48
947
            return $newState + $state;
948 48
        });
949 48
950
        $instructions->addPostStep(function ($state) {
951
            $search = "/(,?\s*PRIMARY KEY\s*\([^\)]*\)|\s+PRIMARY KEY(\s+AUTOINCREMENT)?)/";
952
            $sql = preg_replace($search, '', $state['createSQL'], 1);
953
954 2
            if ($sql) {
955
                $this->execute($sql);
956 2
            }
957
958
            return $state;
959
        });
960
961
        return $this->copyAndDropTmpTable($instructions, $table->getName());
962 48
    }
963
964 48
    /**
965 47
     * {@inheritdoc}
966 47
     */
967 48
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
968
    {
969
        $instructions = $this->beginAlterByCopyTable($table->getName());
970
971
        $tableName = $table->getName();
972
        $instructions->addPostStep(function ($state) use ($foreignKey) {
973
            $this->execute('pragma foreign_keys = ON');
974
            $sql = substr($state['createSQL'], 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
975 42
            $this->execute($sql);
976
977 42
            return $state;
978 8
        });
979 42
980 42 View Code Duplication
        $instructions->addPostStep(function ($state) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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