Completed
Push — master ( e33650...15989c )
by José
02:42
created

SQLiteAdapter::getPrimaryKey()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 26.9658

Importance

Changes 0
Metric Value
dl 0
loc 38
c 0
b 0
f 0
ccs 8
cts 24
cp 0.3333
rs 8.0675
cc 8
nc 10
nop 1
crap 26.9658
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
        'CHARACTER',
50
        'VARCHAR',
51
        'VARYING CHARACTER',
52
        'NCHAR',
53
        'NATIVE CHARACTER',
54
        'NVARCHAR'
55
    ];
56 42
57
    protected $suffix = '.sqlite3';
58 42
59 42
    /**
60
     * {@inheritdoc}
61
     */
62
    public function connect()
63
    {
64
        if ($this->connection === null) {
65 42
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
66 42
                // @codeCoverageIgnoreStart
67
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
68
                // @codeCoverageIgnoreEnd
69 42
            }
70
71
            $db = null;
72 42
            $options = $this->getOptions();
73 42
74 42
            // if port is specified use it, otherwise use the MySQL default
75 42
            if (isset($options['memory'])) {
76
                $dsn = 'sqlite::memory:';
77
            } else {
78
                $dsn = 'sqlite:' . $options['name'] . $this->suffix;
79 42
            }
80 42
81
            try {
82
                $db = new \PDO($dsn);
83
            } catch (\PDOException $exception) {
84
                throw new \InvalidArgumentException(sprintf(
85
                    'There was a problem connecting to the database: %s',
86
                    $exception->getMessage()
87 42
                ));
88 42
            }
89 42
90
            $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
91
            $this->setConnection($db);
92
        }
93
    }
94 48
95
    /**
96 48
     * {@inheritdoc}
97 48
     */
98
    public function setOptions(array $options)
99
    {
100
        parent::setOptions($options);
101
102
        if (isset($options['suffix'])) {
103
            $this->suffix = $options['suffix'];
104
        }
105
        //don't "fix" the file extension if it is blank, some people
106
        //might want a SQLITE db file with absolutely no extension.
107
        if (strlen($this->suffix) && substr($this->suffix, 0, 1) !== '.') {
108
            $this->suffix = '.' . $this->suffix;
109
        }
110 1
111
        return $this;
112 1
    }
113 1
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function disconnect()
118
    {
119
        $this->connection = null;
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public function hasTransactions()
126
    {
127
        return true;
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function beginTransaction()
134 43
    {
135
        $this->getConnection()->beginTransaction();
136 43
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function commitTransaction()
142 44
    {
143
        $this->getConnection()->commit();
144 44
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function rollbackTransaction()
150 42
    {
151
        $this->getConnection()->rollBack();
152 42
    }
153 42
154 42
    /**
155 12
     * {@inheritdoc}
156 42
     */
157
    public function quoteTableName($tableName)
158 42
    {
159
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
160
    }
161
162
    /**
163
     * {@inheritdoc}
164 42
     */
165
    public function quoteColumnName($columnName)
166
    {
167 42
        return '`' . str_replace('`', '``', $columnName) . '`';
168 42
    }
169 42
170 35
    /**
171 35
     * {@inheritdoc}
172 35
     */
173 35
    public function hasTable($tableName)
174
    {
175 35
        $tables = [];
176 42
        $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
177
        foreach ($rows as $row) {
178 1
            $tables[] = strtolower($row[0]);
179 1
        }
180 1
181 1
        return in_array(strtolower($tableName), $tables);
182
    }
183 1
184 1
    /**
185
     * {@inheritdoc}
186
     */
187 42
    public function createTable(Table $table, array $columns = [], array $indexes = [])
188 42
    {
189 42
        // Add the default primary key
190 42
        $options = $table->getOptions();
191 42
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
192
            $column = new Column();
193
            $column->setName('id')
194 42
                   ->setType('integer')
195 42
                   ->setIdentity(true);
196 42
197 42
            array_unshift($columns, $column);
198 42
        } elseif (isset($options['id']) && is_string($options['id'])) {
199 42
            // Handle id => "field_name" to support AUTO_INCREMENT
200
            $column = new Column();
201
            $column->setName($options['id'])
202 1
                   ->setType('integer')
203 1
                   ->setIdentity(true);
204 1
205
            array_unshift($columns, $column);
206 1
        }
207 1
208 1
        $sql = 'CREATE TABLE ';
209 1
        $sql .= $this->quoteTableName($table->getName()) . ' (';
210 1
        foreach ($columns as $column) {
211 1
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
212 42
213 42
            if (isset($options['primary_key']) && $column->getIdentity()) {
214 37
                //remove column from the primary key array as it is already defined as an autoincrement
215
                //primary id
216
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
217
                if ($identityColumnIndex !== false) {
218 42
                    unset($options['primary_key'][$identityColumnIndex]);
219 42
220 1
                    if (empty($options['primary_key'])) {
221 1
                        //The last primary key has been removed
222 1
                        unset($options['primary_key']);
223 1
                    }
224
                }
225 42
            }
226
        }
227 42
228
        // set the primary key(s)
229 42 View Code Duplication
        if (isset($options['primary_key'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
230 6
            $sql = rtrim($sql);
231 42
            $sql .= ' PRIMARY KEY (';
232 42
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
233
                $sql .= $this->quoteColumnName($options['primary_key']);
234
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
235
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
236
            }
237 1
            $sql .= ')';
238
        } else {
239 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
240 1
        }
241
242
        $sql = rtrim($sql) . ');';
243
        // execute the sql
244
        $this->execute($sql);
245 1
246
        foreach ($indexes as $index) {
247 1
            $this->addIndex($table, $index);
248 1
        }
249
    }
250
251
    /**
252
     * {@inheritdoc}
253 1
     */
254
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
255 1
    {
256 1
        $instructions = new AlterInstructions();
257 1
258 1
        // Drop the existing primary key
259
        $primaryKey = $this->getPrimaryKey($table->getName());
260 1
        if (!empty($primaryKey)) {
261 1
            $instructions->merge(
262
                $this->getDropPrimaryKeyInstructions($table, $primaryKey)
263
            );
264
        }
265
266 1
        // Add the primary key(s)
267
        if (!empty($newColumns)) {
268 1
            if (!is_string($newColumns)) {
269 1
                throw new \InvalidArgumentException(sprintf(
270
                    "Invalid value for primary key: %s",
271 1
                    json_encode($newColumns)
272 1
                ));
273 1
            }
274 1
275 1
            $instructions->merge(
276 1
                $this->getAddPrimaryKeyInstructions($table, $newColumns)
277
            );
278 1
        }
279 1
280 1
        return $instructions;
281
    }
282 1
283 1
    /**
284 1
     * {@inheritdoc}
285
     */
286 1
    protected function getChangeCommentInstructions(Table $table, $newComment)
287 1
    {
288
        throw new \BadMethodCallException('SQLite does not have table comments');
289 1
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294 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...
295 8
    {
296
        $sql = sprintf(
297 8
            'ALTER TABLE %s RENAME TO %s',
298 8
            $this->quoteTableName($tableName),
299 8
            $this->quoteTableName($newTableName)
300 7
        );
301
302 8
        return new AlterInstructions([], [$sql]);
303
    }
304 8
305
    /**
306
     * {@inheritdoc}
307
     */
308
    protected function getDropTableInstructions($tableName)
309
    {
310 4
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
311
312 4
        return new AlterInstructions([], [$sql]);
313 4
    }
314 4
315 4
    /**
316 4
     * {@inheritdoc}
317 4
     */
318
    public function truncateTable($tableName)
319 4
    {
320 4
        $sql = sprintf(
321
            'DELETE FROM %s',
322
            $this->quoteTableName($tableName)
323
        );
324
325 2
        $this->execute($sql);
326
    }
327 2
328
    /**
329 2
     * {@inheritdoc}
330
     */
331 2
    public function getColumns($tableName)
332 2
    {
333 2
        $columns = [];
334 2
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
335 2
336 2
        foreach ($rows as $columnInfo) {
337
            $column = new Column();
338 2
            $type = strtolower($columnInfo['type']);
339 2
            $column->setName($columnInfo['name'])
340 2
                   ->setNull($columnInfo['notnull'] !== '1')
341 2
                   ->setDefault($columnInfo['dflt_value']);
342 2
343 2
            $phinxType = $this->getPhinxType($type);
344 2
            $column->setType($phinxType['name'])
345 2
                   ->setLimit($phinxType['limit']);
346 2
347
            if ($columnInfo['pk'] == 1) {
348 2
                $column->setIdentity(true);
349 1
            }
350
351 1
            $columns[] = $column;
352
        }
353
354 1
        return $columns;
355
    }
356 1
357 1
    /**
358 1
     * {@inheritdoc}
359
     */
360 1 View Code Duplication
    public function hasColumn($tableName, $columnName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
361 1
    {
362
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
363
        foreach ($rows as $column) {
364 1
            if (strcasecmp($column['name'], $columnName) === 0) {
365 1
                return true;
366 1
            }
367 1
        }
368 1
369
        return false;
370 1
    }
371
372 1
    /**
373
     * {@inheritdoc}
374 1
     */
375 1 View Code Duplication
    protected function getAddColumnInstructions(Table $table, Column $column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

Loading history...
562 8
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
563 8
564 8
            return $newState + $state;
565 8
        });
566 8
567 8
        $instructions->addPostStep(function ($state) use ($columnName, $newColumn) {
568 8
            $sql = preg_replace(
569 8
                sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
570 8
                sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
571
                $state['createSQL'],
572 8
                1
573 8
            );
574 8
            $this->execute($sql);
575
576
            return $state;
577
        });
578
579 1
        return $this->copyAndDropTmpTable($instructions, $tableName);
580
    }
581 1
582 1
    /**
583 1
     * {@inheritdoc}
584
     */
585 1
    protected function getDropColumnInstructions($tableName, $columnName)
586 1
    {
587
        $instructions = $this->beginAlterByCopyTable($tableName);
588 1
589 1 View Code Duplication
        $instructions->addPostStep(function ($state) use ($columnName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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