Completed
Pull Request — master (#1542)
by
unknown
01:59
created

SQLiteAdapter::getSchemaName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 9.8666
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use Cake\Database\Connection;
32
use Cake\Database\Driver\Sqlite as SqliteDriver;
33
use Phinx\Db\Table\Column;
34
use Phinx\Db\Table\ForeignKey;
35
use Phinx\Db\Table\Index;
36
use Phinx\Db\Table\Table;
37
use Phinx\Db\Util\AlterInstructions;
38
use Phinx\Util\Literal;
39
40
/**
41
 * Phinx SQLite Adapter.
42
 *
43
 * @author Rob Morgan <[email protected]>
44
 * @author Richard McIntyre <[email protected]>
45
 */
46
class SQLiteAdapter extends PdoAdapter implements AdapterInterface
47
{
48
    protected $definitionsWithLimits = [
49
        'CHAR',
50
        'CHARACTER',
51
        'VARCHAR',
52
        'VARYING CHARACTER',
53
        'NCHAR',
54
        'NATIVE CHARACTER',
55
        'NVARCHAR'
56 42
    ];
57
58 42
    protected $suffix = '.sqlite3';
59 42
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function connect()
64
    {
65 42
        if ($this->connection === null) {
66 42
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
67
                // @codeCoverageIgnoreStart
68
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
69 42
                // @codeCoverageIgnoreEnd
70
            }
71
72 42
            $db = null;
73 42
            $options = $this->getOptions();
74 42
75 42
            // use a memory database if the option was specified
76
            if (!empty($options['memory'])) {
77
                $dsn = 'sqlite::memory:';
78
            } else {
79 42
                $dsn = 'sqlite:' . $options['name'] . $this->suffix;
80 42
            }
81
82
            try {
83
                $db = new \PDO($dsn);
84
            } catch (\PDOException $exception) {
85
                throw new \InvalidArgumentException(sprintf(
86
                    'There was a problem connecting to the database: %s',
87 42
                    $exception->getMessage()
88 42
                ));
89 42
            }
90
91
            $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
92
            $this->setConnection($db);
93
        }
94 48
    }
95
96 48
    /**
97 48
     * {@inheritdoc}
98
     */
99
    public function setOptions(array $options)
100
    {
101
        parent::setOptions($options);
102
103
        if (isset($options['suffix'])) {
104
            $this->suffix = $options['suffix'];
105
        }
106
        //don't "fix" the file extension if it is blank, some people
107
        //might want a SQLITE db file with absolutely no extension.
108
        if (strlen($this->suffix) && substr($this->suffix, 0, 1) !== '.') {
109
            $this->suffix = '.' . $this->suffix;
110 1
        }
111
112 1
        return $this;
113 1
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function disconnect()
119
    {
120
        $this->connection = null;
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function hasTransactions()
127
    {
128
        return true;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 43
    public function beginTransaction()
135
    {
136 43
        $this->getConnection()->beginTransaction();
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 44
    public function commitTransaction()
143
    {
144 44
        $this->getConnection()->commit();
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150 42
    public function rollbackTransaction()
151
    {
152 42
        $this->getConnection()->rollBack();
153 42
    }
154 42
155 12
    /**
156 42
     * {@inheritdoc}
157
     */
158 42
    public function quoteTableName($tableName)
159
    {
160
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
161
    }
162
163
    /**
164 42
     * {@inheritdoc}
165
     */
166
    public function quoteColumnName($columnName)
167 42
    {
168 42
        return '`' . str_replace('`', '``', $columnName) . '`';
169 42
    }
170 35
171 35
    /**
172 35
     * @param string $tableName Table name
173 35
     * @return array
174
     */
175 35
    protected function getSchemaName($tableName)
176 42
    {
177
        if (preg_match("/.\.([^\.]+)$/", $tableName, $match)) {
178 1
            $table = $match[1];
179 1
            $schema = substr($tableName, 0, strlen($tableName) - strlen($match[0]) + 1);
180 1
            $result = ['schema' => $schema, 'table' => $table];
181 1
        } else {
182
            $result = ['schema' => '', 'table' => $tableName];
183 1
        }
184 1
185
        return $result;
186
    }
187 42
188 42
    /**
189 42
     * {@inheritdoc}
190 42
     */
191 42
    public function hasTable($tableName)
192
    {
193
        $info = $this->getSchemaName($tableName);
194 42
        if ($info['schema'] === '') {
195 42
            // if no schema is specified we search all schemata
196 42
            $rows = $this->fetchAll('PRAGMA database_list;');
197 42
            $schemata = [];
198 42
            foreach ($rows as $row) {
199 42
                $schemata[] = $row['name'];
200
            }
201
        } else {
202 1
            // otherwise we search just the specified schema
203 1
            $schemata = (array)$info['schema'];
204 1
        }
205
206 1
        $table = strtolower($info['table']);
207 1
        foreach ($schemata as $schema) {
208 1
            if (strtolower($schema) === 'temp') {
209 1
                $master = 'sqlite_temp_master';
210 1
            } else {
211 1
                $master = sprintf('%s.%s', $this->quoteColumnName($schema), 'sqlite_master');
212 42
            }
213 42
            try {
214 37
                $rows = $this->fetchAll(sprintf('SELECT name FROM %s WHERE type=\'table\' AND lower(name) = %s', $master, $this->quoteString($table)));
215
            } catch (\PDOException $e) {
216
                // an exception can occur if the schema part of the table refers to a database which is not attached
217
                return false;
218 42
            }
219 42
220 1
            // this somewhat pedantic check with strtolower is performed because the SQL lower function may be redefined,
221 1
            // and can act on all Unicode characters if the ICU extension is loaded, while SQL identifiers are only case-insensitive for ASCII
222 1
            foreach ($rows as $row) {
223 1
                if (strtolower($row['name']) === $table) {
224
                    return true;
225 42
                }
226
            }
227 42
        }
228
229 42
        return false;
230 6
    }
231 42
232 42
    /**
233
     * {@inheritdoc}
234
     */
235
    public function createTable(Table $table, array $columns = [], array $indexes = [])
236
    {
237 1
        // Add the default primary key
238
        $options = $table->getOptions();
239 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...
240 1
            $options['id'] = 'id';
241
        }
242
243
        if (isset($options['id']) && is_string($options['id'])) {
244
            // Handle id => "field_name" to support AUTO_INCREMENT
245 1
            $column = new Column();
246
            $column->setName($options['id'])
247 1
                   ->setType('integer')
248 1
                   ->setIdentity(true);
249
250
            array_unshift($columns, $column);
251
        }
252
253 1
        $sql = 'CREATE TABLE ';
254
        $sql .= $this->quoteTableName($table->getName()) . ' (';
255 1
        foreach ($columns as $column) {
256 1
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
257 1
258 1
            if (isset($options['primary_key']) && $column->getIdentity()) {
259
                //remove column from the primary key array as it is already defined as an autoincrement
260 1
                //primary id
261 1
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
262
                if ($identityColumnIndex !== false) {
263
                    unset($options['primary_key'][$identityColumnIndex]);
264
265
                    if (empty($options['primary_key'])) {
266 1
                        //The last primary key has been removed
267
                        unset($options['primary_key']);
268 1
                    }
269 1
                }
270
            }
271 1
        }
272 1
273 1
        // set the primary key(s)
274 1 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...
275 1
            $sql = rtrim($sql);
276 1
            $sql .= ' PRIMARY KEY (';
277
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
278 1
                $sql .= $this->quoteColumnName($options['primary_key']);
279 1
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
280 1
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
281
            }
282 1
            $sql .= ')';
283 1
        } else {
284 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
285
        }
286 1
287 1
        $sql = rtrim($sql) . ');';
288
        // execute the sql
289 1
        $this->execute($sql);
290
291
        foreach ($indexes as $index) {
292
            $this->addIndex($table, $index);
293
        }
294
    }
295 8
296
    /**
297 8
     * {@inheritdoc}
298 8
     */
299 8
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
300 7
    {
301
        $instructions = new AlterInstructions();
302 8
303
        // Drop the existing primary key
304 8
        $primaryKey = $this->getPrimaryKey($table->getName());
305
        if (!empty($primaryKey)) {
306
            $instructions->merge(
307
                $this->getDropPrimaryKeyInstructions($table, $primaryKey)
308
            );
309
        }
310 4
311
        // Add the primary key(s)
312 4
        if (!empty($newColumns)) {
313 4
            if (!is_string($newColumns)) {
314 4
                throw new \InvalidArgumentException(sprintf(
315 4
                    "Invalid value for primary key: %s",
316 4
                    json_encode($newColumns)
317 4
                ));
318
            }
319 4
320 4
            $instructions->merge(
321
                $this->getAddPrimaryKeyInstructions($table, $newColumns)
322
            );
323
        }
324
325 2
        return $instructions;
326
    }
327 2
328
    /**
329 2
     * {@inheritdoc}
330
     */
331 2
    protected function getChangeCommentInstructions(Table $table, $newComment)
332 2
    {
333 2
        throw new \BadMethodCallException('SQLite does not have table comments');
334 2
    }
335 2
336 2
    /**
337
     * {@inheritdoc}
338 2
     */
339 2 View Code Duplication
    protected function getRenameTableInstructions($tableName, $newTableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
340 2
    {
341 2
        $sql = sprintf(
342 2
            'ALTER TABLE %s RENAME TO %s',
343 2
            $this->quoteTableName($tableName),
344 2
            $this->quoteTableName($newTableName)
345 2
        );
346 2
347
        return new AlterInstructions([], [$sql]);
348 2
    }
349 1
350
    /**
351 1
     * {@inheritdoc}
352
     */
353
    protected function getDropTableInstructions($tableName)
354 1
    {
355
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
356 1
357 1
        return new AlterInstructions([], [$sql]);
358 1
    }
359
360 1
    /**
361 1
     * {@inheritdoc}
362
     */
363
    public function truncateTable($tableName)
364 1
    {
365 1
        $sql = sprintf(
366 1
            'DELETE FROM %s',
367 1
            $this->quoteTableName($tableName)
368 1
        );
369
370 1
        $this->execute($sql);
371
    }
372 1
373
    /**
374 1
     * {@inheritdoc}
375 1
     */
376
    public function getColumns($tableName)
377
    {
378
        $columns = [];
379
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
380 6
381
        foreach ($rows as $columnInfo) {
382
            $column = new Column();
383
            $type = strtolower($columnInfo['type']);
384 6
            $column->setName($columnInfo['name'])
385
                   ->setNull($columnInfo['notnull'] !== '1')
386 6
                   ->setDefault($columnInfo['dflt_value']);
387
388 6
            $phinxType = $this->getPhinxType($type);
389 6
390 6
            $column->setType($phinxType['name'])
391 6
                   ->setLimit($phinxType['limit'])
392 6
                   ->setScale($phinxType['scale']);
393 6
394
            if ($columnInfo['pk'] == 1) {
395 6
                $column->setIdentity(true);
396 6
            }
397 6
398 6
            $columns[] = $column;
399 6
        }
400 6
401 6
        return $columns;
402 6
    }
403 6
404
    /**
405 6
     * {@inheritdoc}
406
     */
407 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...
408
    {
409
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
410
        foreach ($rows as $column) {
411 6
            if (strcasecmp($column['name'], $columnName) === 0) {
412
                return true;
413 6
            }
414 6
        }
415 6
416 6
        return false;
417
    }
418 6
419
    /**
420 6
     * {@inheritdoc}
421
     */
422 6 View Code Duplication
    protected function getAddColumnInstructions(Table $table, Column $column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
423 6
    {
424 6
        $alter = sprintf(
425 6
            'ALTER TABLE %s ADD COLUMN %s %s',
426 6
            $this->quoteTableName($table->getName()),
427
            $this->quoteColumnName($column->getName()),
428 6
            $this->getColumnSqlDefinition($column)
429
        );
430 6
431 6
        return new AlterInstructions([], [$alter]);
432 6
    }
433
434
    /**
435
     * Returns the original CREATE statement for the give table
436
     *
437 2
     * @param string $tableName The table name to get the create statement for
438
     * @return string
439
     */
440 2
    protected function getDeclaringSql($tableName)
441
    {
442 2
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
443
444 2
        $sql = '';
445 2
        foreach ($rows as $table) {
446 2
            if ($table['tbl_name'] === $tableName) {
447 2
                $sql = $table['sql'];
448 2
            }
449 2
        }
450
451 2
        return $sql;
452 2
    }
453 2
454 2
    /**
455 2
     * Copies all the data from a tmp table to another table
456 2
     *
457 2
     * @param string $tableName The table name to copy the data to
458 2
     * @param string $tmpTableName The tmp table name where the data is stored
459 2
     * @param string[] $writeColumns The list of columns in the target table
460
     * @param string[] $selectColumns The list of columns in the tmp table
461 2
     * @return void
462
     */
463 2
    protected function copyDataToNewTable($tableName, $tmpTableName, $writeColumns, $selectColumns)
464
    {
465
        $sql = sprintf(
466
            'INSERT INTO %s(%s) SELECT %s FROM %s',
467
            $this->quoteTableName($tableName),
468
            implode(', ', $writeColumns),
469 2
            implode(', ', $selectColumns),
470
            $this->quoteTableName($tmpTableName)
471 2
        );
472 2
        $this->execute($sql);
473 2
    }
474
475 2
    /**
476
     * Modifies the passed instructions to copy all data from the tmp table into
477 2
     * the provided table and then drops the tmp table.
478 2
     *
479 2
     * @param AlterInstructions $instructions The instructions to modify
480
     * @param string $tableName The table name to copy the data to
481 2
     * @return AlterInstructions
482
     */
483 2
    protected function copyAndDropTmpTable($instructions, $tableName)
484 2
    {
485 2
        $instructions->addPostStep(function ($state) use ($tableName) {
486 2
            $this->copyDataToNewTable(
487 2
                $tableName,
488
                $state['tmpTableName'],
489 2
                $state['writeColumns'],
490
                $state['selectColumns']
491 2
            );
492 2
493 2
            $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($state['tmpTableName'])));
494
495
            return $state;
496
        });
497
498
        return $instructions;
499
    }
500
501 9
    /**
502
     * Returns the columns and type to use when copying a table to another in the process
503 9
     * of altering a table
504 9
     *
505
     * @param string $tableName The table to modify
506 9
     * @param string $columnName The column name that is about to change
507 9
     * @param string|false $newColumnName Optionally the new name for the column
508 9
     * @return AlterInstructions
509 9
     */
510 9
    protected function calculateNewTableColumns($tableName, $columnName, $newColumnName)
511 9
    {
512 9
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
513 9
        $selectColumns = [];
514 9
        $writeColumns = [];
515 9
        $columnType = null;
516
        $found = false;
517
518
        foreach ($columns as $column) {
519
            $selectName = $column['name'];
520
            $writeName = $selectName;
521 9
522
            if ($selectName == $columnName) {
523 9
                $writeName = $newColumnName;
524 4
                $found = true;
525 4
                $columnType = $column['type'];
526
                $selectName = $newColumnName === false ? $newColumnName : $selectName;
527 9
            }
528 9
529
            $selectColumns[] = $selectName;
530 9
            $writeColumns[] = $writeName;
531 9
        }
532 9
533 9
        $selectColumns = array_filter($selectColumns, 'strlen');
534
        $writeColumns = array_filter($writeColumns, 'strlen');
535 8
        $selectColumns = array_map([$this, 'quoteColumnName'], $selectColumns);
536
        $writeColumns = array_map([$this, 'quoteColumnName'], $writeColumns);
537 8
538
        if (!$found) {
539
            throw new \InvalidArgumentException(sprintf(
540
                'The specified column doesn\'t exist: ' . $columnName
541
            ));
542
        }
543 1
544
        return compact('writeColumns', 'selectColumns', 'columnType');
545 1
    }
546
547 1
    /**
548 1
     * Returns the initial instructions to alter a table using the
549 1
     * rename-alter-copy strategy
550
     *
551
     * @param string $tableName The table to modify
552
     * @return AlterInstructions
553
     */
554
    protected function beginAlterByCopyTable($tableName)
555
    {
556
        $instructions = new AlterInstructions();
557
        $instructions->addPostStep(function ($state) use ($tableName) {
558
            $createSQL = $this->getDeclaringSql($tableName);
559 8
560
            $tmpTableName = 'tmp_' . $tableName;
561 8
            $this->execute(
562 8
                sprintf(
563 8
                    'ALTER TABLE %s RENAME TO %s',
564 8
                    $this->quoteTableName($tableName),
565 8
                    $this->quoteTableName($tmpTableName)
566 8
                )
567 8
            );
568 8
569 8
            return compact('createSQL', 'tmpTableName') + $state;
570 8
        });
571
572 8
        return $instructions;
573 8
    }
574 8
575
    /**
576
     * {@inheritdoc}
577
     */
578
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
579 1
    {
580
        $instructions = $this->beginAlterByCopyTable($tableName);
581 1 View Code Duplication
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
582 1
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
583 1
584
            return $newState + $state;
585 1
        });
586 1
587
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
588 1
            $sql = str_replace(
589 1
                $this->quoteColumnName($columnName),
590 1
                $this->quoteColumnName($newColumnName),
591 1
                $state['createSQL']
592 1
            );
593 1
            $this->execute($sql);
594 1
595 1
            return $state;
596 1
        });
597 1
598
        return $this->copyAndDropTmpTable($instructions, $tableName);
599
    }
600
601
    /**
602
     * {@inheritdoc}
603
     */
604
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
605 1
    {
606
        $instructions = $this->beginAlterByCopyTable($tableName);
607 1
608
        $newColumnName = $newColumn->getName();
609 1 View Code Duplication
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
610 1
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
611 1
612 1
            return $newState + $state;
613 1
        });
614 1
615 1
        $instructions->addPostStep(function ($state) use ($columnName, $newColumn) {
616 1
            $sql = preg_replace(
617 1
                sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
618
                sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
619
                $state['createSQL'],
620
                1
621
            );
622
            $this->execute($sql);
623
624
            return $state;
625 5
        });
626
627 5
        return $this->copyAndDropTmpTable($instructions, $tableName);
628
    }
629
630 5
    /**
631
     * {@inheritdoc}
632 5
     */
633 5
    protected function getDropColumnInstructions($tableName, $columnName)
634 5
    {
635
        $instructions = $this->beginAlterByCopyTable($tableName);
636 1
637 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...
638
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, false);
639
640
            return $newState + $state;
641
        });
642
643
        $instructions->addPostStep(function ($state) use ($columnName) {
644
            $sql = preg_replace(
645 5
                sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($state['columnType'])),
646
                "",
647 5
                $state['createSQL']
648 5
            );
649
650
            if (substr($sql, -2) === ', ') {
651
                $sql = substr($sql, 0, -2) . ')';
652
            }
653
654
            $this->execute($sql);
655
656
            return $state;
657
        });
658
659
        return $this->copyAndDropTmpTable($instructions, $tableName);
660
    }
661 5
662
    /**
663 5
     * Get an array of indexes from a particular table.
664 5
     *
665 5
     * @param string $tableName Table Name
666 5
     * @return array
667 5
     */
668 5
    protected function getIndexes($tableName)
669 5
    {
670 5
        $indexes = [];
671 5
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
672 5
673 5
        foreach ($rows as $row) {
674
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
675
            if (!isset($indexes[$tableName])) {
676
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
677
            }
678
            foreach ($indexData as $indexItem) {
679 4
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
680
            }
681
        }
682 4
683
        return $indexes;
684 4
    }
685 4
686
    /**
687 4
     * {@inheritdoc}
688 4
     */
689 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...
690 4
    {
691 4
        $columns = array_map('strtolower', (array)$columns);
692 4
        $indexes = $this->getIndexes($tableName);
693
694 4
        foreach ($indexes as $index) {
695 4
            $a = array_diff($columns, $index['columns']);
696 4
            if (empty($a)) {
697 4
                return true;
698 4
            }
699
        }
700 4
701
        return false;
702 4
    }
703 4
704
    /**
705 4
     * {@inheritdoc}
706 4
     */
707 4 View Code Duplication
    public function hasIndexByName($tableName, $indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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