Completed
Pull Request — master (#1298)
by
unknown
01:41
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 51
CRAP Score 17

Importance

Changes 0
Metric Value
dl 12
loc 72
ccs 51
cts 51
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
36
/**
37
 * Phinx SQLite Adapter.
38
 *
39
 * @author Rob Morgan <[email protected]>
40
 * @author Richard McIntyre <[email protected]>
41
 */
42
class SQLiteAdapter extends PdoAdapter implements AdapterInterface
43
{
44
    protected $definitionsWithLimits = [
45
        'CHARACTER',
46
        'VARCHAR',
47
        'VARYING CHARACTER',
48
        'NCHAR',
49
        'NATIVE CHARACTER',
50
        'NVARCHAR'
51
    ];
52
53
    /**
54
     * {@inheritdoc}
55
     */
56 42
    public function connect()
57
    {
58 42
        if ($this->connection === null) {
59 42
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
60
                // @codeCoverageIgnoreStart
61
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
62
                // @codeCoverageIgnoreEnd
63
            }
64
65 42
            $db = null;
66 42
            $options = $this->getOptions();
67
68
            // if port is specified use it, otherwise use the MySQL default
69 42
            if (isset($options['memory'])) {
70
                $dsn = 'sqlite::memory:';
71
            } else {
72 42
                $dsn = 'sqlite:' . $options['name'];
73 42
                if (file_exists($options['name'] . '.sqlite3')) {
74 42
                    $dsn = 'sqlite:' . $options['name'] . '.sqlite3';
75 42
                }
76
            }
77
78
            try {
79 42
                $db = new \PDO($dsn);
80 42
            } catch (\PDOException $exception) {
81
                throw new \InvalidArgumentException(sprintf(
82
                    'There was a problem connecting to the database: %s',
83
                    $exception->getMessage()
84
                ));
85
            }
86
87 42
            $this->setConnection($db);
88 42
        }
89 42
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 48
    public function disconnect()
95
    {
96 48
        $this->connection = null;
97 48
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function hasTransactions()
103
    {
104
        return true;
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110 1
    public function beginTransaction()
111
    {
112 1
        $this->execute('BEGIN TRANSACTION');
113 1
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function commitTransaction()
119
    {
120
        $this->execute('COMMIT');
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function rollbackTransaction()
127
    {
128
        $this->execute('ROLLBACK');
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 43
    public function quoteTableName($tableName)
135
    {
136 43
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 44
    public function quoteColumnName($columnName)
143
    {
144 44
        return '`' . str_replace('`', '``', $columnName) . '`';
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150 42
    public function hasTable($tableName)
151
    {
152 42
        $tables = [];
153 42
        $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
154 42
        foreach ($rows as $row) {
155 12
            $tables[] = strtolower($row[0]);
156 42
        }
157
158 42
        return in_array(strtolower($tableName), $tables);
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164 42
    public function createTable(Table $table)
165
    {
166
        // Add the default primary key
167 42
        $columns = $table->getPendingColumns();
168 42
        $options = $table->getOptions();
169 42
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
170 35
            $column = new Column();
171 35
            $column->setName('id')
172 35
                   ->setType('integer')
173 35
                   ->setIdentity(true);
174
175 35
            array_unshift($columns, $column);
176 42
        } elseif (isset($options['id']) && is_string($options['id'])) {
177
            // Handle id => "field_name" to support AUTO_INCREMENT
178 1
            $column = new Column();
179 1
            $column->setName($options['id'])
180 1
                   ->setType('integer')
181 1
                   ->setIdentity(true);
182
183 1
            array_unshift($columns, $column);
184 1
        }
185
186
        $sql = 'CREATE TABLE ';
187 42
        $sql .= $this->quoteTableName($table->getName()) . ' (';
188 42
        foreach ($columns as $column) {
189 42
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
190 42
191 42
            if (isset($options['primary_key']) && $column->getIdentity()) {
192
                //remove column from the primary key array as it is already defined as an autoincrement
193
                //primary id
194 42
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
195 42
                if ($identityColumnIndex !== false) {
196 42
                    unset($options['primary_key'][$identityColumnIndex]);
197 42
198 42
                    if (empty($options['primary_key'])) {
199 42
                        //The last primary key has been removed
200
                        unset($options['primary_key']);
201
                    }
202 1
                }
203 1
            }
204 1
        }
205
206 1
        // set the primary key(s)
207 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...
208 1
            $sql = rtrim($sql);
209 1
            $sql .= ' PRIMARY KEY (';
210 1
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
211 1
                $sql .= $this->quoteColumnName($options['primary_key']);
212 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...
213 42
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
214 37
            }
215
            $sql .= ')';
216
        } else {
217
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
218 42
        }
219 42
220 1
        // set the foreign keys
221 1
        $foreignKeys = $table->getForeignKeys();
222 1
        if (!empty($foreignKeys)) {
223 1
            foreach ($foreignKeys as $foreignKey) {
224
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
225 42
            }
226
        }
227 42
228
        $sql = rtrim($sql) . ');';
229 42
        // execute the sql
230 6
        $this->execute($sql);
231 42
232 42
        foreach ($table->getIndexes() as $index) {
233
            $this->addIndex($table, $index);
234
        }
235
    }
236
237 1
    /**
238
     * {@inheritdoc}
239 1
     */
240 1
    public function renameTable($tableName, $newTableName)
241
    {
242
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
243
    }
244
245 1
    /**
246
     * {@inheritdoc}
247 1
     */
248 1
    public function dropTable($tableName)
249
    {
250
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
251
    }
252
253 1
    /**
254
     * {@inheritdoc}
255 1
     */
256 1
    public function truncateTable($tableName)
257 1
    {
258 1
        $sql = sprintf(
259
            'DELETE FROM %s',
260 1
            $this->quoteTableName($tableName)
261 1
        );
262
263
        $this->execute($sql);
264
    }
265
266 1
    /**
267
     * {@inheritdoc}
268 1
     */
269 1
    public function getColumns($tableName)
270
    {
271 1
        $columns = [];
272 1
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
273 1
274 1
        foreach ($rows as $columnInfo) {
275 1
            $column = new Column();
276 1
            $type = strtolower($columnInfo['type']);
277
            $column->setName($columnInfo['name'])
278 1
                   ->setNull($columnInfo['notnull'] !== '1')
279 1
                   ->setDefault($columnInfo['dflt_value']);
280 1
281
            $phinxType = $this->getPhinxType($type);
282 1
            $column->setType($phinxType['name'])
283 1
                   ->setLimit($phinxType['limit']);
284 1
285
            if ($columnInfo['pk'] == 1) {
286 1
                $column->setIdentity(true);
287 1
            }
288
289 1
            $columns[] = $column;
290
        }
291
292
        return $columns;
293
    }
294
295 8
    /**
296
     * {@inheritdoc}
297 8
     */
298 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...
299 8
    {
300 7
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
301
        foreach ($rows as $column) {
302 8
            if (strcasecmp($column['name'], $columnName) === 0) {
303
                return true;
304 8
            }
305
        }
306
307
        return false;
308
    }
309
310 4
    /**
311
     * {@inheritdoc}
312 4
     */
313 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...
314 4
    {
315 4
        $sql = sprintf(
316 4
            'ALTER TABLE %s ADD COLUMN %s %s',
317 4
            $this->quoteTableName($table->getName()),
318
            $this->quoteColumnName($column->getName()),
319 4
            $this->getColumnSqlDefinition($column)
320 4
        );
321
322
        $this->execute($sql);
323
    }
324
325 2
    /**
326
     * {@inheritdoc}
327 2
     */
328
    public function renameColumn($tableName, $columnName, $newColumnName)
329 2
    {
330
        $tmpTableName = 'tmp_' . $tableName;
331 2
332 2
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
333 2
334 2
        $sql = '';
335 2
        foreach ($rows as $table) {
336 2
            if ($table['tbl_name'] === $tableName) {
337
                $sql = $table['sql'];
338 2
            }
339 2
        }
340 2
341 2
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
342 2
        $selectColumns = [];
343 2
        $writeColumns = [];
344 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...
345 2
            $selectName = $column['name'];
346 2
            $writeName = ($selectName == $columnName)? $newColumnName : $selectName;
347
            $selectColumns[] = $this->quoteColumnName($selectName);
348 2
            $writeColumns[] = $this->quoteColumnName($writeName);
349 1
        }
350
351 1 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...
352
            throw new \InvalidArgumentException(sprintf(
353
                'The specified column doesn\'t exist: ' . $columnName
354 1
            ));
355
        }
356 1
357 1
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
358 1
359
        $sql = str_replace(
360 1
            $this->quoteColumnName($columnName),
361 1
            $this->quoteColumnName($newColumnName),
362
            $sql
363
        );
364 1
        $this->execute($sql);
365 1
366 1
        $sql = sprintf(
367 1
            'INSERT INTO %s(%s) SELECT %s FROM %s',
368 1
            $tableName,
369
            implode(', ', $writeColumns),
370 1
            implode(', ', $selectColumns),
371
            $tmpTableName
372 1
        );
373
374 1
        $this->execute($sql);
375 1
376
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
377
    }
378
379
    /**
380 6
     * {@inheritdoc}
381
     */
382
    public function changeColumn($tableName, $columnName, Column $newColumn)
383
    {
384 6
        // TODO: DRY this up....
385
        $tmpTableName = 'tmp_' . $tableName;
386 6
387
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
388 6
389 6
        $sql = '';
390 6
        foreach ($rows as $table) {
391 6
            if ($table['tbl_name'] === $tableName) {
392 6
                $sql = $table['sql'];
393 6
            }
394
        }
395 6
396 6
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
397 6
        $selectColumns = [];
398 6
        $writeColumns = [];
399 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...
400 6
            $selectName = $column['name'];
401 6
            $writeName = ($selectName === $columnName)? $newColumn->getName() : $selectName;
402 6
            $selectColumns[] = $this->quoteColumnName($selectName);
403 6
            $writeColumns[] = $this->quoteColumnName($writeName);
404
        }
405 6
406 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...
407
            throw new \InvalidArgumentException(sprintf(
408
                'The specified column doesn\'t exist: ' . $columnName
409
            ));
410
        }
411 6
412
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
413 6
414 6
        $sql = preg_replace(
415 6
            sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
416 6
            sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
417
            $sql,
418 6
            1
419
        );
420 6
421
        $this->execute($sql);
422 6
423 6
        $sql = sprintf(
424 6
            'INSERT INTO %s(%s) SELECT %s FROM %s',
425 6
            $tableName,
426 6
            implode(', ', $writeColumns),
427
            implode(', ', $selectColumns),
428 6
            $tmpTableName
429
        );
430 6
431 6
        $this->execute($sql);
432 6
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
433
    }
434
435
    /**
436
     * {@inheritdoc}
437 2
     */
438
    public function dropColumn($tableName, $columnName)
439
    {
440 2
        // TODO: DRY this up....
441
        $tmpTableName = 'tmp_' . $tableName;
442 2
443
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
444 2
445 2
        $sql = '';
446 2
        foreach ($rows as $table) {
447 2
            if ($table['tbl_name'] === $tableName) {
448 2
                $sql = $table['sql'];
449 2
            }
450
        }
451 2
452 2
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
453 2
        $columns = [];
454 2
        $columnType = null;
455 2
        foreach ($rows as $row) {
456 2
            if ($row['name'] !== $columnName) {
457 2
                $columns[] = $row['name'];
458 2
            } else {
459 2
                $found = true;
460
                $columnType = $row['type'];
461 2
            }
462
        }
463 2
464
        if (!isset($found)) {
465
            throw new \InvalidArgumentException(sprintf(
466
                'The specified column doesn\'t exist: ' . $columnName
467
            ));
468
        }
469 2
470
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
471 2
472 2
        $sql = preg_replace(
473 2
            sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)),
474
            "",
475 2
            $sql
476
        );
477 2
478 2
        if (substr($sql, -2) === ', ') {
479 2
            $sql = substr($sql, 0, -2) . ')';
480
        }
481 2
482
        $this->execute($sql);
483 2
484 2
        $sql = sprintf(
485 2
            'INSERT INTO %s(%s) SELECT %s FROM %s',
486 2
            $tableName,
487 2
            implode(', ', $columns),
488
            implode(', ', $columns),
489 2
            $tmpTableName
490
        );
491 2
492 2
        $this->execute($sql);
493 2
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
494
    }
495
496
    /**
497
     * Get an array of indexes from a particular table.
498
     *
499
     * @param string $tableName Table Name
500
     * @return array
501 9
     */
502
    protected function getIndexes($tableName)
503 9
    {
504 9
        $indexes = [];
505
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
506 9
507 9
        foreach ($rows as $row) {
508 9
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
509 9
            if (!isset($indexes[$tableName])) {
510 9
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
511 9
            }
512 9
            foreach ($indexData as $indexItem) {
513 9
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
514 9
            }
515 9
        }
516
517
        return $indexes;
518
    }
519
520
    /**
521 9
     * {@inheritdoc}
522
     */
523 9 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...
524 4
    {
525 4
        if (is_string($columns)) {
526
            $columns = [$columns]; // str to array
527 9
        }
528 9
529
        $columns = array_map('strtolower', $columns);
530 9
        $indexes = $this->getIndexes($tableName);
531 9
532 9
        foreach ($indexes as $index) {
533 9
            $a = array_diff($columns, $index['columns']);
534
            if (empty($a)) {
535 8
                return true;
536
            }
537 8
        }
538
539
        return false;
540
    }
541
542
    /**
543 1
     * {@inheritdoc}
544
     */
545 1 View Code Duplication
    public function hasIndexByName($tableName, $indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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