Completed
Push — master ( 866733...f03076 )
by José
05:17 queued 03:21
created

SQLiteAdapter::createTable()   D

Complexity

Conditions 17
Paths 240

Size

Total Lines 72
Code Lines 43

Duplication

Lines 12
Ratio 16.67 %

Code Coverage

Tests 50
CRAP Score 17

Importance

Changes 0
Metric Value
dl 12
loc 72
ccs 50
cts 50
cp 1
rs 4.3014
c 0
b 0
f 0
cc 17
eloc 43
nc 240
nop 1
crap 17

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Phinx\Db\Table;
32
use Phinx\Db\Table\Column;
33
use Phinx\Db\Table\ForeignKey;
34
use Phinx\Db\Table\Index;
35
use Phinx\Util\Literal;
36
37
/**
38
 * Phinx SQLite Adapter.
39
 *
40
 * @author Rob Morgan <[email protected]>
41
 * @author Richard McIntyre <[email protected]>
42
 */
43
class SQLiteAdapter extends PdoAdapter implements AdapterInterface
44
{
45
    protected $definitionsWithLimits = [
46
        'CHARACTER',
47
        'VARCHAR',
48
        'VARYING CHARACTER',
49
        'NCHAR',
50
        'NATIVE CHARACTER',
51
        'NVARCHAR'
52
    ];
53
54
    /**
55
     * {@inheritdoc}
56 42
     */
57
    public function connect()
58 42
    {
59 42
        if ($this->connection === null) {
60
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
61
                // @codeCoverageIgnoreStart
62
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
63
                // @codeCoverageIgnoreEnd
64
            }
65 42
66 42
            $db = null;
67
            $options = $this->getOptions();
68
69 42
            // if port is specified use it, otherwise use the MySQL default
70
            if (isset($options['memory'])) {
71
                $dsn = 'sqlite::memory:';
72 42
            } else {
73 42
                $dsn = 'sqlite:' . $options['name'];
74 42
                if (file_exists($options['name'] . '.sqlite3')) {
75 42
                    $dsn = 'sqlite:' . $options['name'] . '.sqlite3';
76
                }
77
            }
78
79 42
            try {
80 42
                $db = new \PDO($dsn);
81
            } catch (\PDOException $exception) {
82
                throw new \InvalidArgumentException(sprintf(
83
                    'There was a problem connecting to the database: %s',
84
                    $exception->getMessage()
85
                ));
86
            }
87 42
88 42
            $this->setConnection($db);
89 42
        }
90
    }
91
92
    /**
93
     * {@inheritdoc}
94 48
     */
95
    public function disconnect()
96 48
    {
97 48
        $this->connection = null;
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    public function hasTransactions()
104
    {
105
        return true;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110 1
     */
111
    public function beginTransaction()
112 1
    {
113 1
        $this->getConnection()->beginTransaction();
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function commitTransaction()
120
    {
121
        $this->getConnection()->commit();
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function rollbackTransaction()
128
    {
129
        $this->getConnection()->rollBack();
130
    }
131
132
    /**
133
     * {@inheritdoc}
134 43
     */
135
    public function quoteTableName($tableName)
136 43
    {
137
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
138
    }
139
140
    /**
141
     * {@inheritdoc}
142 44
     */
143
    public function quoteColumnName($columnName)
144 44
    {
145
        return '`' . str_replace('`', '``', $columnName) . '`';
146
    }
147
148
    /**
149
     * {@inheritdoc}
150 42
     */
151
    public function hasTable($tableName)
152 42
    {
153 42
        $tables = [];
154 42
        $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
155 12
        foreach ($rows as $row) {
156 42
            $tables[] = strtolower($row[0]);
157
        }
158 42
159
        return in_array(strtolower($tableName), $tables);
160
    }
161
162
    /**
163
     * {@inheritdoc}
164 42
     */
165
    public function createTable(Table $table)
166
    {
167 42
        // Add the default primary key
168 42
        $columns = $table->getPendingColumns();
169 42
        $options = $table->getOptions();
170 35
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
171 35
            $column = new Column();
172 35
            $column->setName('id')
173 35
                   ->setType('integer')
174
                   ->setIdentity(true);
175 35
176 42
            array_unshift($columns, $column);
177
        } elseif (isset($options['id']) && is_string($options['id'])) {
178 1
            // Handle id => "field_name" to support AUTO_INCREMENT
179 1
            $column = new Column();
180 1
            $column->setName($options['id'])
181 1
                   ->setType('integer')
182
                   ->setIdentity(true);
183 1
184 1
            array_unshift($columns, $column);
185
        }
186
187 42
        $sql = 'CREATE TABLE ';
188 42
        $sql .= $this->quoteTableName($table->getName()) . ' (';
189 42
        foreach ($columns as $column) {
190 42
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
191 42
192
            if (isset($options['primary_key']) && $column->getIdentity()) {
193
                //remove column from the primary key array as it is already defined as an autoincrement
194 42
                //primary id
195 42
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
196 42
                if ($identityColumnIndex !== false) {
197 42
                    unset($options['primary_key'][$identityColumnIndex]);
198 42
199 42
                    if (empty($options['primary_key'])) {
200
                        //The last primary key has been removed
201
                        unset($options['primary_key']);
202 1
                    }
203 1
                }
204 1
            }
205
        }
206 1
207 1
        // set the primary key(s)
208 1 View Code Duplication
        if (isset($options['primary_key'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
209 1
            $sql = rtrim($sql);
210 1
            $sql .= ' PRIMARY KEY (';
211 1
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
212 42
                $sql .= $this->quoteColumnName($options['primary_key']);
213 42
            } 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...
214 37
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
215
            }
216
            $sql .= ')';
217
        } else {
218 42
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
219 42
        }
220 1
221 1
        // set the foreign keys
222 1
        $foreignKeys = $table->getForeignKeys();
223 1
        if (!empty($foreignKeys)) {
224
            foreach ($foreignKeys as $foreignKey) {
225 42
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
226
            }
227 42
        }
228
229 42
        $sql = rtrim($sql) . ');';
230 6
        // execute the sql
231 42
        $this->execute($sql);
232 42
233
        foreach ($table->getIndexes() as $index) {
234
            $this->addIndex($table, $index);
235
        }
236
    }
237 1
238
    /**
239 1
     * {@inheritdoc}
240 1
     */
241
    public function renameTable($tableName, $newTableName)
242
    {
243
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
244
    }
245 1
246
    /**
247 1
     * {@inheritdoc}
248 1
     */
249
    public function dropTable($tableName)
250
    {
251
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
252
    }
253 1
254
    /**
255 1
     * {@inheritdoc}
256 1
     */
257 1
    public function truncateTable($tableName)
258 1
    {
259
        $sql = sprintf(
260 1
            'DELETE FROM %s',
261 1
            $this->quoteTableName($tableName)
262
        );
263
264
        $this->execute($sql);
265
    }
266 1
267
    /**
268 1
     * {@inheritdoc}
269 1
     */
270
    public function getColumns($tableName)
271 1
    {
272 1
        $columns = [];
273 1
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
274 1
275 1
        foreach ($rows as $columnInfo) {
276 1
            $column = new Column();
277
            $type = strtolower($columnInfo['type']);
278 1
            $column->setName($columnInfo['name'])
279 1
                   ->setNull($columnInfo['notnull'] !== '1')
280 1
                   ->setDefault($columnInfo['dflt_value']);
281
282 1
            $phinxType = $this->getPhinxType($type);
283 1
            $column->setType($phinxType['name'])
284 1
                   ->setLimit($phinxType['limit']);
285
286 1
            if ($columnInfo['pk'] == 1) {
287 1
                $column->setIdentity(true);
288
            }
289 1
290
            $columns[] = $column;
291
        }
292
293
        return $columns;
294
    }
295 8
296
    /**
297 8
     * {@inheritdoc}
298 8
     */
299 8 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...
300 7
    {
301
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
302 8
        foreach ($rows as $column) {
303
            if (strcasecmp($column['name'], $columnName) === 0) {
304 8
                return true;
305
            }
306
        }
307
308
        return false;
309
    }
310 4
311
    /**
312 4
     * {@inheritdoc}
313 4
     */
314 4 View Code Duplication
    public function addColumn(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...
315 4
    {
316 4
        $sql = sprintf(
317 4
            'ALTER TABLE %s ADD COLUMN %s %s',
318
            $this->quoteTableName($table->getName()),
319 4
            $this->quoteColumnName($column->getName()),
320 4
            $this->getColumnSqlDefinition($column)
321
        );
322
323
        $this->execute($sql);
324
    }
325 2
326
    /**
327 2
     * {@inheritdoc}
328
     */
329 2
    public function renameColumn($tableName, $columnName, $newColumnName)
330
    {
331 2
        $tmpTableName = 'tmp_' . $tableName;
332 2
333 2
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
334 2
335 2
        $sql = '';
336 2
        foreach ($rows as $table) {
337
            if ($table['tbl_name'] === $tableName) {
338 2
                $sql = $table['sql'];
339 2
            }
340 2
        }
341 2
342 2
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
343 2
        $selectColumns = [];
344 2
        $writeColumns = [];
345 2 View Code Duplication
        foreach ($columns as $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...
346 2
            $selectName = $column['name'];
347
            $writeName = ($selectName == $columnName)? $newColumnName : $selectName;
348 2
            $selectColumns[] = $this->quoteColumnName($selectName);
349 1
            $writeColumns[] = $this->quoteColumnName($writeName);
350
        }
351 1
352 View Code Duplication
        if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
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...
353
            throw new \InvalidArgumentException(sprintf(
354 1
                'The specified column doesn\'t exist: ' . $columnName
355
            ));
356 1
        }
357 1
358 1
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
359
360 1
        $sql = str_replace(
361 1
            $this->quoteColumnName($columnName),
362
            $this->quoteColumnName($newColumnName),
363
            $sql
364 1
        );
365 1
        $this->execute($sql);
366 1
367 1
        $sql = sprintf(
368 1
            'INSERT INTO %s(%s) SELECT %s FROM %s',
369
            $tableName,
370 1
            implode(', ', $writeColumns),
371
            implode(', ', $selectColumns),
372 1
            $tmpTableName
373
        );
374 1
375 1
        $this->execute($sql);
376
377
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
378
    }
379
380 6
    /**
381
     * {@inheritdoc}
382
     */
383
    public function changeColumn($tableName, $columnName, Column $newColumn)
384 6
    {
385
        // TODO: DRY this up....
386 6
        $tmpTableName = 'tmp_' . $tableName;
387
388 6
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
389 6
390 6
        $sql = '';
391 6
        foreach ($rows as $table) {
392 6
            if ($table['tbl_name'] === $tableName) {
393 6
                $sql = $table['sql'];
394
            }
395 6
        }
396 6
397 6
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
398 6
        $selectColumns = [];
399 6
        $writeColumns = [];
400 6 View Code Duplication
        foreach ($columns as $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...
401 6
            $selectName = $column['name'];
402 6
            $writeName = ($selectName === $columnName)? $newColumn->getName() : $selectName;
403 6
            $selectColumns[] = $this->quoteColumnName($selectName);
404
            $writeColumns[] = $this->quoteColumnName($writeName);
405 6
        }
406
407 View Code Duplication
        if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
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...
408
            throw new \InvalidArgumentException(sprintf(
409
                'The specified column doesn\'t exist: ' . $columnName
410
            ));
411 6
        }
412
413 6
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
414 6
415 6
        $sql = preg_replace(
416 6
            sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
417
            sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
418 6
            $sql,
419
            1
420 6
        );
421
422 6
        $this->execute($sql);
423 6
424 6
        $sql = sprintf(
425 6
            'INSERT INTO %s(%s) SELECT %s FROM %s',
426 6
            $tableName,
427
            implode(', ', $writeColumns),
428 6
            implode(', ', $selectColumns),
429
            $tmpTableName
430 6
        );
431 6
432 6
        $this->execute($sql);
433
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
434
    }
435
436
    /**
437 2
     * {@inheritdoc}
438
     */
439
    public function dropColumn($tableName, $columnName)
440 2
    {
441
        // TODO: DRY this up....
442 2
        $tmpTableName = 'tmp_' . $tableName;
443
444 2
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
445 2
446 2
        $sql = '';
447 2
        foreach ($rows as $table) {
448 2
            if ($table['tbl_name'] === $tableName) {
449 2
                $sql = $table['sql'];
450
            }
451 2
        }
452 2
453 2
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
454 2
        $columns = [];
455 2
        $columnType = null;
456 2
        foreach ($rows as $row) {
457 2
            if ($row['name'] !== $columnName) {
458 2
                $columns[] = $row['name'];
459 2
            } else {
460
                $found = true;
461 2
                $columnType = $row['type'];
462
            }
463 2
        }
464
465
        if (!isset($found)) {
466
            throw new \InvalidArgumentException(sprintf(
467
                'The specified column doesn\'t exist: ' . $columnName
468
            ));
469 2
        }
470
471 2
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
472 2
473 2
        $sql = preg_replace(
474
            sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)),
475 2
            "",
476
            $sql
477 2
        );
478 2
479 2
        if (substr($sql, -2) === ', ') {
480
            $sql = substr($sql, 0, -2) . ')';
481 2
        }
482
483 2
        $this->execute($sql);
484 2
485 2
        $sql = sprintf(
486 2
            'INSERT INTO %s(%s) SELECT %s FROM %s',
487 2
            $tableName,
488
            implode(', ', $columns),
489 2
            implode(', ', $columns),
490
            $tmpTableName
491 2
        );
492 2
493 2
        $this->execute($sql);
494
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
495
    }
496
497
    /**
498
     * Get an array of indexes from a particular table.
499
     *
500
     * @param string $tableName Table Name
501 9
     * @return array
502
     */
503 9
    protected function getIndexes($tableName)
504 9
    {
505
        $indexes = [];
506 9
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
507 9
508 9
        foreach ($rows as $row) {
509 9
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
510 9
            if (!isset($indexes[$tableName])) {
511 9
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
512 9
            }
513 9
            foreach ($indexData as $indexItem) {
514 9
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
515 9
            }
516
        }
517
518
        return $indexes;
519
    }
520
521 9
    /**
522
     * {@inheritdoc}
523 9
     */
524 4 View Code Duplication
    public function hasIndex($tableName, $columns)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
525 4
    {
526
        if (is_string($columns)) {
527 9
            $columns = [$columns]; // str to array
528 9
        }
529
530 9
        $columns = array_map('strtolower', $columns);
531 9
        $indexes = $this->getIndexes($tableName);
532 9
533 9
        foreach ($indexes as $index) {
534
            $a = array_diff($columns, $index['columns']);
535 8
            if (empty($a)) {
536
                return true;
537 8
            }
538
        }
539
540
        return false;
541
    }
542
543 1
    /**
544
     * {@inheritdoc}
545 1
     */
546 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...
547 1
    {
548 1
        $indexes = $this->getIndexes($tableName);
549 1
550
        foreach ($indexes as $index) {
551
            if ($indexName === $index['index']) {
552
                return true;
553
            }
554
        }
555
556
        return false;
557
    }
558
559 8
    /**
560
     * {@inheritdoc}
561 8
     */
562 8
    public function addIndex(Table $table, Index $index)
563 8
    {
564 8
        $indexColumnArray = [];
565 8
        foreach ($index->getColumns() as $column) {
566 8
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
567 8
        }
568 8
        $indexColumns = implode(',', $indexColumnArray);
569 8
        $this->execute(
570 8
            sprintf(
571
                'CREATE %s ON %s (%s)',
572 8
                $this->getIndexSqlDefinition($table, $index),
573 8
                $this->quoteTableName($table->getName()),
574 8
                $indexColumns
575
            )
576
        );
577
    }
578
579 1
    /**
580
     * {@inheritdoc}
581 1
     */
582 1
    public function dropIndex($tableName, $columns)
583 1
    {
584
        if (is_string($columns)) {
585 1
            $columns = [$columns]; // str to array
586 1
        }
587
588 1
        $indexes = $this->getIndexes($tableName);
589 1
        $columns = array_map('strtolower', $columns);
590 1
591 1
        foreach ($indexes as $index) {
592 1
            $a = array_diff($columns, $index['columns']);
593 1
            if (empty($a)) {
594 1
                $this->execute(
595 1
                    sprintf(
596 1
                        'DROP INDEX %s',
597 1
                        $this->quoteColumnName($index['index'])
598
                    )
599
                );
600
601
                return;
602
            }
603
        }
604
    }
605 1
606
    /**
607 1
     * {@inheritdoc}
608
     */
609 1 View Code Duplication
    public function dropIndexByName($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...
610 1
    {
611 1
        $indexes = $this->getIndexes($tableName);
612 1
613 1
        foreach ($indexes as $index) {
614 1
            if ($indexName === $index['index']) {
615 1
                $this->execute(
616 1
                    sprintf(
617 1
                        'DROP INDEX %s',
618
                        $this->quoteColumnName($indexName)
619
                    )
620
                );
621
622
                return;
623
            }
624
        }
625 5
    }
626
627 5
    /**
628
     * {@inheritdoc}
629
     */
630 5
    public function hasForeignKey($tableName, $columns, $constraint = null)
631
    {
632 5
        if (is_string($columns)) {
633 5
            $columns = [$columns]; // str to array
634 5
        }
635
        $foreignKeys = $this->getForeignKeys($tableName);
636 1
637
        $a = array_diff($columns, $foreignKeys);
638
        if (empty($a)) {
639
            return true;
640
        }
641
642
        return false;
643
    }
644
645 5
    /**
646
     * Get an array of foreign keys from a particular table.
647 5
     *
648 5
     * @param string $tableName Table Name
649
     * @return array
650
     */
651
    protected function getForeignKeys($tableName)
652
    {
653
        $foreignKeys = [];
654
        $rows = $this->fetchAll(
655
            "SELECT sql, tbl_name
656
              FROM (
657
                    SELECT sql sql, type type, tbl_name tbl_name, name name
658
                      FROM sqlite_master
659
                     UNION ALL
660
                    SELECT sql, type, tbl_name, name
661 5
                      FROM sqlite_temp_master
662
                   )
663 5
             WHERE type != 'meta'
664 5
               AND sql NOTNULL
665 5
               AND name NOT LIKE 'sqlite_%'
666 5
             ORDER BY substr(type, 2, 1), name"
667 5
        );
668 5
669 5
        foreach ($rows as $row) {
670 5
            if ($row['tbl_name'] === $tableName) {
671 5
                if (strpos($row['sql'], 'REFERENCES') !== false) {
672 5
                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
673 5
                    foreach ($matches[1] as $match) {
674
                        $foreignKeys[] = $match;
675
                    }
676
                }
677
            }
678
        }
679 4
680
        return $foreignKeys;
681
    }
682 4
683
    /**
684 4
     * {@inheritdoc}
685 4
     */
686
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
687 4
    {
688 4
        // TODO: DRY this up....
689 4
        $this->execute('pragma foreign_keys = ON');
690 4
691 4
        $tmpTableName = 'tmp_' . $table->getName();
692 4
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
693
694 4
        $sql = '';
695 4
        foreach ($rows as $row) {
696 4
            if ($row['tbl_name'] === $table->getName()) {
697 4
                $sql = $row['sql'];
698 4
            }
699
        }
700 4
701
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($table->getName())));
702 4
        $columns = [];
703 4
        foreach ($rows as $column) {
704
            $columns[] = $this->quoteColumnName($column['name']);
705 4
        }
706 4
707 4
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($table->getName()), $tmpTableName));
708 4
709 4
        $sql = substr($sql, 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
710 4
        $this->execute($sql);
711 4
712
        $sql = sprintf(
713 4
            'INSERT INTO %s(%s) SELECT %s FROM %s',
714 4
            $this->quoteTableName($table->getName()),
715 4
            implode(', ', $columns),
716
            implode(', ', $columns),
717
            $this->quoteTableName($tmpTableName)
718
        );
719
720 1
        $this->execute($sql);
721
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
722
    }
723 1
724
    /**
725
     * {@inheritdoc}
726
     */
727 1
    public function dropForeignKey($tableName, $columns, $constraint = null)
728
    {
729 1
        // TODO: DRY this up....
730
        if (is_string($columns)) {
731 1
            $columns = [$columns]; // str to array
732 1
        }
733 1
734 1
        $tmpTableName = 'tmp_' . $tableName;
735 1
736 1
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
737
738 1
        $sql = '';
739 1
        foreach ($rows as $table) {
740 1
            if ($table['tbl_name'] === $tableName) {
741 1
                $sql = $table['sql'];
742 1
            }
743 1
        }
744 1
745
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
746 1
        $replaceColumns = [];
747
        foreach ($rows as $row) {
748 1
            if (!in_array($row['name'], $columns)) {
749
                $replaceColumns[] = $row['name'];
750
            } else {
751
                $found = true;
752
            }
753
        }
754 1
755
        if (!isset($found)) {
756 1
            throw new \InvalidArgumentException(sprintf(
757 1
                'The specified column doesn\'t exist: '
758 1
            ));
759 1
        }
760 1
761 1
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $tmpTableName));
762 1
763
        foreach ($columns as $columnName) {
764 1
            $search = sprintf(
765
                "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
766 1
                $this->quoteColumnName($columnName)
767 1
            );
768 1
            $sql = preg_replace($search, '', $sql, 1);
769 1
        }
770 1
771
        $this->execute($sql);
772 1
773
        $sql = sprintf(
774 1
            'INSERT INTO %s(%s) SELECT %s FROM %s',
775 1
            $tableName,
776 1
            implode(', ', $columns),
777
            implode(', ', $columns),
778
            $tmpTableName
779
        );
780
781
        $this->execute($sql);
782
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
783
    }
784
785
    /**
786
     * {@inheritdoc}
787
     */
788
    public function insert(Table $table, $row)
789
    {
790
        $sql = sprintf(
791
            "INSERT INTO %s ",
792
            $this->quoteTableName($table->getName())
793
        );
794
795
        $columns = array_keys($row);
796
        $sql .= "(" . implode(', ', array_map([$this, 'quoteColumnName'], $columns)) . ")";
797
        $sql .= " VALUES ";
798
799
        $sql .= "(" . implode(', ', array_map(function ($value) {
800
            if (is_numeric($value)) {
801
                return $value;
802
            }
803
804
            if ($value === null) {
805
                return 'null';
806
            }
807
808
                return $this->getConnection()->quote($value);
809
        }, $row)) . ")";
810 43
811
        $this->execute($sql);
812
    }
813 43
814 42
    /**
815
     * {@inheritdoc}
816 43
     */
817
    public function getSqlType($type, $limit = null)
818
    {
819 43
        switch ($type) {
820 1
            case static::PHINX_TYPE_TEXT:
821
            case static::PHINX_TYPE_INTEGER:
822 43
            case static::PHINX_TYPE_FLOAT:
823 38
            case static::PHINX_TYPE_DECIMAL:
824
            case static::PHINX_TYPE_DATETIME:
825 43
            case static::PHINX_TYPE_TIME:
826 42
            case static::PHINX_TYPE_DATE:
827
            case static::PHINX_TYPE_BLOB:
828 43
            case static::PHINX_TYPE_BOOLEAN:
829 2
            case static::PHINX_TYPE_ENUM:
830
                return ['name' => $type];
831 43
            case static::PHINX_TYPE_STRING:
832 1
                return ['name' => 'varchar', 'limit' => 255];
833
            case static::PHINX_TYPE_CHAR:
834 43
                return ['name' => 'char', 'limit' => 255];
835 1
            case static::PHINX_TYPE_BIG_INTEGER:
836
                return ['name' => 'bigint'];
837 43
            case static::PHINX_TYPE_TIMESTAMP:
838 42
                return ['name' => 'datetime'];
839
            case static::PHINX_TYPE_BINARY:
840 43
                return ['name' => 'blob'];
841 1
            case static::PHINX_TYPE_UUID:
842
                return ['name' => 'char', 'limit' => 36];
843 43
            case static::PHINX_TYPE_JSON:
844 1
            case static::PHINX_TYPE_JSONB:
845
                return ['name' => 'text'];
846 43
            // Geospatial database types
847 43
            // No specific data types exist in SQLite, instead all geospatial
848 1
            // functionality is handled in the client. See also: SpatiaLite.
849
            case static::PHINX_TYPE_GEOMETRY:
850 43
            case static::PHINX_TYPE_POLYGON:
851 42
                return ['name' => 'text'];
852
            case static::PHINX_TYPE_LINESTRING:
853 5
                return ['name' => 'varchar', 'limit' => 255];
854
            case static::PHINX_TYPE_POINT:
855 5
                return ['name' => 'float'];
856 4
            default:
857
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
858
        }
859
    }
860 1
861 1
    /**
862
     * Returns Phinx type by SQL type
863
     *
864 1
     * @param string $sqlTypeDef SQL type
865
     * @returns string Phinx type
866
     */
867 1
    public function getPhinxType($sqlTypeDef)
868
    {
869 1
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
870 1
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
871 1
        } else {
872
            $limit = null;
873
            $precision = null;
874
            $type = $matches[1];
875
            if (count($matches) > 2) {
876
                $limit = $matches[3] ?: null;
877
            }
878
            if (count($matches) > 4) {
879
                $precision = $matches[5];
880 3
            }
881
            switch ($matches[1]) {
882 3
                case 'varchar':
883 1
                    $type = static::PHINX_TYPE_STRING;
884
                    if ($limit === 255) {
885 2
                        $limit = null;
886 2
                    }
887 2
                    break;
888 2 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...
889 1
                    $type = static::PHINX_TYPE_CHAR;
890 1
                    if ($limit === 255) {
891 2
                        $limit = null;
892
                    }
893
                    if ($limit === 36) {
894 2
                        $type = static::PHINX_TYPE_UUID;
895 2
                    }
896 1
                    break;
897 1
                case 'int':
898
                    $type = static::PHINX_TYPE_INTEGER;
899
                    if ($limit === 11) {
900 1
                        $limit = null;
901 2
                    }
902
                    break;
903
                case 'bigint':
904
                    if ($limit === 11) {
905
                        $limit = null;
906
                    }
907
                    $type = static::PHINX_TYPE_BIG_INTEGER;
908
                    break;
909
                case 'blob':
910 2
                    $type = static::PHINX_TYPE_BINARY;
911
                    break;
912
            }
913 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...
914
                if ($matches[3] === 1) {
915
                    $type = static::PHINX_TYPE_BOOLEAN;
916 2
                    $limit = null;
917 1
                }
918
            }
919
920 1
            $this->getSqlType($type);
921 1
922 2
            return [
923 1
                'name' => $type,
924 1
                'limit' => $limit,
925 2
                'precision' => $precision
926 2
            ];
927
        }
928
    }
929
930
    /**
931
     * {@inheritdoc}
932
     */
933 2
    public function createDatabase($name, $options = [])
934
    {
935
        touch($name . '.sqlite3');
936 1
    }
937 1
938
    /**
939 1
     * {@inheritdoc}
940
     */
941
    public function hasDatabase($name)
942
    {
943
        return is_file($name . '.sqlite3');
944
    }
945
946 48
    /**
947
     * {@inheritdoc}
948 48
     */
949 48
    public function dropDatabase($name)
950
    {
951
        if (file_exists($name . '.sqlite3')) {
952
            unlink($name . '.sqlite3');
953
        }
954 2
    }
955
956 2
    /**
957
     * Gets the SQLite Column Definition for a Column object.
958
     *
959
     * @param \Phinx\Db\Table\Column $column Column
960
     * @return string
961
     */
962 48
    protected function getColumnSqlDefinition(Column $column)
963
    {
964 48
        $isLiteralType = $column->getType() instanceof Literal;
965 47
        if ($isLiteralType) {
966 47
            $def = (string)$column->getType();
967 48
        } else {
968
            $sqlType = $this->getSqlType($column->getType());
969
            $def = strtoupper($sqlType['name']);
970
971
            $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
972
            if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
973
                $def .= '(' . ($column->getLimit() ?: $sqlType['limit']) . ')';
974
            }
975 42
        }
976 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...
977 42
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
978 8
        }
979 42 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...
980 42
            $def .= " CHECK({$column->getName()} IN ('" . implode("', '", $values) . "'))";
981 42
        }
982 42
983
        $default = $column->getDefault();
984
985
        $def .= ($column->isNull() || is_null($default)) ? ' NULL' : ' NOT NULL';
986
        $def .= $this->getDefaultValueDefinition($default);
987
        $def .= $column->isIdentity() ? ' PRIMARY KEY AUTOINCREMENT' : '';
988
989
        if ($column->getUpdate()) {
990
            $def .= ' ON UPDATE ' . $column->getUpdate();
991 42
        }
992
993 42
        $def .= $this->getCommentDefinition($column);
994 42
995 42
        return $def;
996 42
    }
997
998
    /**
999 42
     * Gets the comment Definition for a Column object.
1000 42
     *
1001 42
     * @param \Phinx\Db\Table\Column $column Column
1002 42
     * @return string
1003 42
     */
1004 4
    protected function getCommentDefinition(Column $column)
1005 4
    {
1006
        if ($column->getComment()) {
1007 42
            return ' /* ' . $column->getComment() . ' */ ';
1008
        }
1009 42
1010 42
        return '';
1011 42
    }
1012
1013 42
    /**
1014
     * Gets the SQLite Index Definition for an Index object.
1015
     *
1016
     * @param \Phinx\Db\Table $table Table
1017 42
     * @param \Phinx\Db\Table\Index $index Index
1018
     * @return string
1019 42
     */
1020
    protected function getIndexSqlDefinition(Table $table, Index $index)
1021
    {
1022
        if ($index->getType() === Index::UNIQUE) {
1023
            $def = 'UNIQUE INDEX';
1024
        } else {
1025
            $def = 'INDEX';
1026
        }
1027
        if (is_string($index->getName())) {
1028 42
            $indexName = $index->getName();
1029
        } else {
1030 42
            $indexName = $table->getName() . '_';
1031 2
            foreach ($index->getColumns() as $column) {
1032
                $indexName .= $column . '_';
1033 42
            }
1034
            $indexName .= 'index';
1035
        }
1036
        $def .= ' `' . $indexName . '`';
1037
1038
        return $def;
1039
    }
1040
1041
    /**
1042 8
     * {@inheritdoc}
1043
     */
1044 8
    public function getColumnTypes()
1045 2
    {
1046 2
        return array_merge(parent::getColumnTypes(), ['enum', 'json', 'jsonb']);
1047 6
    }
1048
1049 8
    /**
1050 3
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1051 3
     *
1052 6
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1053 6
     * @return string
1054 6
     */
1055 6 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...
1056 6
    {
1057
        $def = '';
1058 8
        if ($foreignKey->getConstraint()) {
1059 8
            $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...
1060
        } else {
1061
            $columnNames = [];
1062
            foreach ($foreignKey->getColumns() as $column) {
1063
                $columnNames[] = $this->quoteColumnName($column);
1064
            }
1065 47
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1066
            $refColumnNames = [];
1067 47
            foreach ($foreignKey->getReferencedColumns() as $column) {
1068
                $refColumnNames[] = $this->quoteColumnName($column);
1069
            }
1070
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1071
            if ($foreignKey->getOnDelete()) {
1072
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1073
            }
1074
            if ($foreignKey->getOnUpdate()) {
1075
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1076 5
            }
1077
        }
1078 5
1079 5
        return $def;
1080
    }
1081
}
1082