Completed
Push — master ( 47597f...63c0ca )
by José
21s
created

SQLiteAdapter::getPrimaryKey()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0987

Importance

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