Completed
Pull Request — master (#1209)
by
unknown
01:51
created

SQLiteAdapter::getIndexes()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 13
cts 13
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 5
nop 1
crap 4
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 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...
189 42
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
190 42
        }
191 42
192
        // set the primary key(s)
193 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...
194 42
            $sql = rtrim($sql);
195 42
            $sql .= ' PRIMARY KEY (';
196 42
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
197 42
                $sql .= $this->quoteColumnName($options['primary_key']);
198 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...
199 42
                // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function,
200
                // but for now just hard-code the adapter quotes
201
                $sql .= implode(
202 1
                    ',',
203 1
                    array_map(
204 1
                        function ($v) {
205
                            return '`' . $v . '`';
206 1
                        },
207 1
                        $options['primary_key']
208 1
                    )
209 1
                );
210 1
            }
211 1
            $sql .= ')';
212 42
        } else {
213 42
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
214 37
        }
215
216
        // set the foreign keys
217
        $foreignKeys = $table->getForeignKeys();
218 42
        if (!empty($foreignKeys)) {
219 42
            foreach ($foreignKeys as $foreignKey) {
220 1
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
221 1
            }
222 1
        }
223 1
224
        $sql = rtrim($sql) . ');';
225 42
        // execute the sql
226
        $this->execute($sql);
227 42
228
        foreach ($table->getIndexes() as $index) {
229 42
            $this->addIndex($table, $index);
230 6
        }
231 42
    }
232 42
233
    /**
234
     * {@inheritdoc}
235
     */
236
    public function renameTable($tableName, $newTableName)
237 1
    {
238
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
239 1
    }
240 1
241
    /**
242
     * {@inheritdoc}
243
     */
244
    public function dropTable($tableName)
245 1
    {
246
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
247 1
    }
248 1
249
    /**
250
     * {@inheritdoc}
251
     */
252
    public function truncateTable($tableName)
253 1
    {
254
        $sql = sprintf(
255 1
            'DELETE FROM %s',
256 1
            $this->quoteTableName($tableName)
257 1
        );
258 1
259
        $this->execute($sql);
260 1
    }
261 1
262
    /**
263
     * {@inheritdoc}
264
     */
265
    public function getColumns($tableName)
266 1
    {
267
        $columns = [];
268 1
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
269 1
270
        foreach ($rows as $columnInfo) {
271 1
            $column = new Column();
272 1
            $type = strtolower($columnInfo['type']);
273 1
            $column->setName($columnInfo['name'])
274 1
                   ->setNull($columnInfo['notnull'] !== '1')
275 1
                   ->setDefault($columnInfo['dflt_value']);
276 1
277
            $phinxType = $this->getPhinxType($type);
278 1
            $column->setType($phinxType['name'])
279 1
                   ->setLimit($phinxType['limit']);
280 1
281
            if ($columnInfo['pk'] == 1) {
282 1
                $column->setIdentity(true);
283 1
            }
284 1
285
            $columns[] = $column;
286 1
        }
287 1
288
        return $columns;
289 1
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294 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...
295 8
    {
296
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
297 8
        foreach ($rows as $column) {
298 8
            if (strcasecmp($column['name'], $columnName) === 0) {
299 8
                return true;
300 7
            }
301
        }
302 8
303
        return false;
304 8
    }
305
306
    /**
307
     * {@inheritdoc}
308
     */
309 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...
310 4
    {
311
        $sql = sprintf(
312 4
            'ALTER TABLE %s ADD COLUMN %s %s',
313 4
            $this->quoteTableName($table->getName()),
314 4
            $this->quoteColumnName($column->getName()),
315 4
            $this->getColumnSqlDefinition($column)
316 4
        );
317 4
318
        $this->execute($sql);
319 4
    }
320 4
321
    /**
322
     * {@inheritdoc}
323
     */
324
    public function renameColumn($tableName, $columnName, $newColumnName)
325 2
    {
326
        $tmpTableName = 'tmp_' . $tableName;
327 2
328
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
329 2
330
        $sql = '';
331 2
        foreach ($rows as $table) {
332 2
            if ($table['tbl_name'] === $tableName) {
333 2
                $sql = $table['sql'];
334 2
            }
335 2
        }
336 2
337
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
338 2
        $selectColumns = [];
339 2
        $writeColumns = [];
340 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...
341 2
            $selectName = $column['name'];
342 2
            $writeName = ($selectName == $columnName)? $newColumnName : $selectName;
343 2
            $selectColumns[] = $this->quoteColumnName($selectName);
344 2
            $writeColumns[] = $this->quoteColumnName($writeName);
345 2
        }
346 2
347 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...
348 2
            throw new \InvalidArgumentException(sprintf(
349 1
                'The specified column doesn\'t exist: ' . $columnName
350
            ));
351 1
        }
352
353
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
354 1
355
        $sql = str_replace(
356 1
            $this->quoteColumnName($columnName),
357 1
            $this->quoteColumnName($newColumnName),
358 1
            $sql
359
        );
360 1
        $this->execute($sql);
361 1
362
        $sql = sprintf(
363
            'INSERT INTO %s(%s) SELECT %s FROM %s',
364 1
            $tableName,
365 1
            implode(', ', $writeColumns),
366 1
            implode(', ', $selectColumns),
367 1
            $tmpTableName
368 1
        );
369
370 1
        $this->execute($sql);
371
372 1
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
373
    }
374 1
375 1
    /**
376
     * {@inheritdoc}
377
     */
378
    public function changeColumn($tableName, $columnName, Column $newColumn)
379
    {
380 6
381
        // TODO: DRY this up....
382
        $tmpTableName = 'tmp_' . $tableName;
383
384 6
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
385
386 6
        $sql = '';
387
        foreach ($rows as $table) {
388 6
            if ($table['tbl_name'] === $tableName) {
389 6
                $sql = $table['sql'];
390 6
            }
391 6
        }
392 6
393 6
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
394
        $selectColumns = [];
395 6
        $writeColumns = [];
396 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...
397 6
            $selectName = $column['name'];
398 6
            $writeName = ($selectName === $columnName)? $newColumn->getName() : $selectName;
399 6
            $selectColumns[] = $this->quoteColumnName($selectName);
400 6
            $writeColumns[] = $this->quoteColumnName($writeName);
401 6
        }
402 6
403 6 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...
404
            throw new \InvalidArgumentException(sprintf(
405 6
                'The specified column doesn\'t exist: ' . $columnName
406
            ));
407
        }
408
409
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
410
411 6
        $sql = preg_replace(
412
            sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
413 6
            sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
414 6
            $sql,
415 6
            1
416 6
        );
417
418 6
        $this->execute($sql);
419
420 6
        $sql = sprintf(
421
            'INSERT INTO %s(%s) SELECT %s FROM %s',
422 6
            $tableName,
423 6
            implode(', ', $writeColumns),
424 6
            implode(', ', $selectColumns),
425 6
            $tmpTableName
426 6
        );
427
428 6
        $this->execute($sql);
429
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
430 6
    }
431 6
432 6
    /**
433
     * {@inheritdoc}
434
     */
435
    public function dropColumn($tableName, $columnName)
436
    {
437 2
        // TODO: DRY this up....
438
        $tmpTableName = 'tmp_' . $tableName;
439
440 2
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
441
442 2
        $sql = '';
443
        foreach ($rows as $table) {
444 2
            if ($table['tbl_name'] === $tableName) {
445 2
                $sql = $table['sql'];
446 2
            }
447 2
        }
448 2
449 2
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
450
        $columns = [];
451 2
        $columnType = null;
452 2
        foreach ($rows as $row) {
453 2
            if ($row['name'] !== $columnName) {
454 2
                $columns[] = $row['name'];
455 2
            } else {
456 2
                $found = true;
457 2
                $columnType = $row['type'];
458 2
            }
459 2
        }
460
461 2
        if (!isset($found)) {
462
            throw new \InvalidArgumentException(sprintf(
463 2
                'The specified column doesn\'t exist: ' . $columnName
464
            ));
465
        }
466
467
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
468
469 2
        $sql = preg_replace(
470
            sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)),
471 2
            "",
472 2
            $sql
473 2
        );
474
475 2
        if (substr($sql, -2) === ', ') {
476
            $sql = substr($sql, 0, -2) . ')';
477 2
        }
478 2
479 2
        $this->execute($sql);
480
481 2
        $sql = sprintf(
482
            'INSERT INTO %s(%s) SELECT %s FROM %s',
483 2
            $tableName,
484 2
            implode(', ', $columns),
485 2
            implode(', ', $columns),
486 2
            $tmpTableName
487 2
        );
488
489 2
        $this->execute($sql);
490
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
491 2
    }
492 2
493 2
    /**
494
     * Get an array of indexes from a particular table.
495
     *
496
     * @param string $tableName Table Name
497
     * @return array
498
     */
499
    protected function getIndexes($tableName)
500
    {
501 9
        $indexes = [];
502
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
503 9
504 9
        foreach ($rows as $row) {
505
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
506 9
            if (!isset($indexes[$tableName])) {
507 9
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
508 9
            }
509 9
            foreach ($indexData as $indexItem) {
510 9
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
511 9
            }
512 9
        }
513 9
514 9
        return $indexes;
515 9
    }
516
517
    /**
518
     * {@inheritdoc}
519
     */
520 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...
521 9
    {
522
        if (is_string($columns)) {
523 9
            $columns = [$columns]; // str to array
524 4
        }
525 4
526
        $columns = array_map('strtolower', $columns);
527 9
        $indexes = $this->getIndexes($tableName);
528 9
529
        foreach ($indexes as $index) {
530 9
            $a = array_diff($columns, $index['columns']);
531 9
            if (empty($a)) {
532 9
                return true;
533 9
            }
534
        }
535 8
536
        return false;
537 8
    }
538
539
    /**
540
     * {@inheritdoc}
541
     */
542 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...
543 1
    {
544
        $indexes = $this->getIndexes($tableName);
545 1
546
        foreach ($indexes as $index) {
547 1
            if ($indexName === $index['index']) {
548 1
                return true;
549 1
            }
550
        }
551
552
        return false;
553
    }
554
555
    /**
556
     * {@inheritdoc}
557
     */
558
    public function addIndex(Table $table, Index $index)
559 8
    {
560
        $indexColumnArray = [];
561 8
        foreach ($index->getColumns() as $column) {
562 8
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
563 8
        }
564 8
        $indexColumns = implode(',', $indexColumnArray);
565 8
        $this->execute(
566 8
            sprintf(
567 8
                'CREATE %s ON %s (%s)',
568 8
                $this->getIndexSqlDefinition($table, $index),
569 8
                $this->quoteTableName($table->getName()),
570 8
                $indexColumns
571
            )
572 8
        );
573 8
    }
574 8
575
    /**
576
     * {@inheritdoc}
577
     */
578
    public function dropIndex($tableName, $columns)
579 1
    {
580
        if (is_string($columns)) {
581 1
            $columns = [$columns]; // str to array
582 1
        }
583 1
584
        $indexes = $this->getIndexes($tableName);
585 1
        $columns = array_map('strtolower', $columns);
586 1
587
        foreach ($indexes as $index) {
588 1
            $a = array_diff($columns, $index['columns']);
589 1
            if (empty($a)) {
590 1
                $this->execute(
591 1
                    sprintf(
592 1
                        'DROP INDEX %s',
593 1
                        $this->quoteColumnName($index['index'])
594 1
                    )
595 1
                );
596 1
597 1
                return;
598
            }
599
        }
600
    }
601
602
    /**
603
     * {@inheritdoc}
604
     */
605 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...
606
    {
607 1
        $indexes = $this->getIndexes($tableName);
608
609 1
        foreach ($indexes as $index) {
610 1
            if ($indexName === $index['index']) {
611 1
                $this->execute(
612 1
                    sprintf(
613 1
                        'DROP INDEX %s',
614 1
                        $this->quoteColumnName($indexName)
615 1
                    )
616 1
                );
617 1
618
                return;
619
            }
620
        }
621
    }
622
623
    /**
624
     * {@inheritdoc}
625 5
     */
626
    public function hasForeignKey($tableName, $columns, $constraint = null)
627 5
    {
628
        if (is_string($columns)) {
629
            $columns = [$columns]; // str to array
630 5
        }
631
        $foreignKeys = $this->getForeignKeys($tableName);
632 5
633 5
        $a = array_diff($columns, $foreignKeys);
634 5
        if (empty($a)) {
635
            return true;
636 1
        }
637
638
        return false;
639
    }
640
641
    /**
642
     * Get an array of foreign keys from a particular table.
643
     *
644
     * @param string $tableName Table Name
645 5
     * @return array
646
     */
647 5
    protected function getForeignKeys($tableName)
648 5
    {
649
        $foreignKeys = [];
650
        $rows = $this->fetchAll(
651
            "SELECT sql, tbl_name
652
              FROM (
653
                    SELECT sql sql, type type, tbl_name tbl_name, name name
654
                      FROM sqlite_master
655
                     UNION ALL
656
                    SELECT sql, type, tbl_name, name
657
                      FROM sqlite_temp_master
658
                   )
659
             WHERE type != 'meta'
660
               AND sql NOTNULL
661 5
               AND name NOT LIKE 'sqlite_%'
662
             ORDER BY substr(type, 2, 1), name"
663 5
        );
664 5
665 5
        foreach ($rows as $row) {
666 5
            if ($row['tbl_name'] === $tableName) {
667 5
                if (strpos($row['sql'], 'REFERENCES') !== false) {
668 5
                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
669 5
                    foreach ($matches[1] as $match) {
670 5
                        $foreignKeys[] = $match;
671 5
                    }
672 5
                }
673 5
            }
674
        }
675
676
        return $foreignKeys;
677
    }
678
679 4
    /**
680
     * {@inheritdoc}
681
     */
682 4
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
683
    {
684 4
        // TODO: DRY this up....
685 4
        $this->execute('pragma foreign_keys = ON');
686
687 4
        $tmpTableName = 'tmp_' . $table->getName();
688 4
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
689 4
690 4
        $sql = '';
691 4
        foreach ($rows as $row) {
692 4
            if ($row['tbl_name'] === $table->getName()) {
693
                $sql = $row['sql'];
694 4
            }
695 4
        }
696 4
697 4
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($table->getName())));
698 4
        $columns = [];
699
        foreach ($rows as $column) {
700 4
            $columns[] = $this->quoteColumnName($column['name']);
701
        }
702 4
703 4
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($table->getName()), $tmpTableName));
704
705 4
        $sql = substr($sql, 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
706 4
        $this->execute($sql);
707 4
708 4
        $sql = sprintf(
709 4
            'INSERT INTO %s(%s) SELECT %s FROM %s',
710 4
            $this->quoteTableName($table->getName()),
711 4
            implode(', ', $columns),
712
            implode(', ', $columns),
713 4
            $this->quoteTableName($tmpTableName)
714 4
        );
715 4
716
        $this->execute($sql);
717
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
718
    }
719
720 1
    /**
721
     * {@inheritdoc}
722
     */
723 1
    public function dropForeignKey($tableName, $columns, $constraint = null)
724
    {
725
        // TODO: DRY this up....
726
        if (is_string($columns)) {
727 1
            $columns = [$columns]; // str to array
728
        }
729 1
730
        $tmpTableName = 'tmp_' . $tableName;
731 1
732 1
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
733 1
734 1
        $sql = '';
735 1
        foreach ($rows as $table) {
736 1
            if ($table['tbl_name'] === $tableName) {
737
                $sql = $table['sql'];
738 1
            }
739 1
        }
740 1
741 1
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
742 1
        $replaceColumns = [];
743 1
        foreach ($rows as $row) {
744 1
            if (!in_array($row['name'], $columns)) {
745
                $replaceColumns[] = $row['name'];
746 1
            } else {
747
                $found = true;
748 1
            }
749
        }
750
751
        if (!isset($found)) {
752
            throw new \InvalidArgumentException(sprintf(
753
                'The specified column doesn\'t exist: '
754 1
            ));
755
        }
756 1
757 1
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $tmpTableName));
758 1
759 1
        foreach ($columns as $columnName) {
760 1
            $search = sprintf(
761 1
                "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
762 1
                $this->quoteColumnName($columnName)
763
            );
764 1
            $sql = preg_replace($search, '', $sql, 1);
765
        }
766 1
767 1
        $this->execute($sql);
768 1
769 1
        $sql = sprintf(
770 1
            'INSERT INTO %s(%s) SELECT %s FROM %s',
771
            $tableName,
772 1
            implode(', ', $columns),
773
            implode(', ', $columns),
774 1
            $tmpTableName
775 1
        );
776 1
777
        $this->execute($sql);
778
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
779
    }
780
781
    /**
782
     * {@inheritdoc}
783
     */
784
    public function insert(Table $table, $row)
785
    {
786
        $sql = sprintf(
787
            "INSERT INTO %s ",
788
            $this->quoteTableName($table->getName())
789
        );
790
791
        $columns = array_keys($row);
792
        $sql .= "(". implode(', ', array_map([$this, 'quoteColumnName'], $columns)) . ")";
793
        $sql .= " VALUES ";
794
795
        $sql .= "(" . implode(', ', array_map(function ($value) {
796
            if (is_numeric($value)) {
797
                return $value;
798
            }
799
800
            if ($value === null) {
801
                return 'null';
802
            }
803
804
                return $this->getConnection()->quote($value);
805
        }, $row)) . ")";
806
807
        $this->execute($sql);
808
    }
809
810 43
    /**
811
     * {@inheritdoc}
812
     */
813 43
    public function getSqlType($type, $limit = null)
814 42
    {
815
        switch ($type) {
816 43
            case static::PHINX_TYPE_STRING:
817
                return ['name' => 'varchar', 'limit' => 255];
818
            case static::PHINX_TYPE_CHAR:
819 43
                return ['name' => 'char', 'limit' => 255];
820 1
            case static::PHINX_TYPE_TEXT:
821
                return ['name' => 'text'];
822 43
            case static::PHINX_TYPE_INTEGER:
823 38
                return ['name' => 'integer'];
824
            case static::PHINX_TYPE_BIG_INTEGER:
825 43
                return ['name' => 'bigint'];
826 42
            case static::PHINX_TYPE_FLOAT:
827
                return ['name' => 'float'];
828 43
            case static::PHINX_TYPE_DECIMAL:
829 2
                return ['name' => 'decimal'];
830
            case static::PHINX_TYPE_DATETIME:
831 43
                return ['name' => 'datetime'];
832 1
            case static::PHINX_TYPE_TIMESTAMP:
833
                return ['name' => 'datetime'];
834 43
            case static::PHINX_TYPE_TIME:
835 1
                return ['name' => 'time'];
836
            case static::PHINX_TYPE_DATE:
837 43
                return ['name' => 'date'];
838 42
            case static::PHINX_TYPE_BLOB:
839
            case static::PHINX_TYPE_BINARY:
840 43
                return ['name' => 'blob'];
841 1
            case static::PHINX_TYPE_BOOLEAN:
842
                return ['name' => 'boolean'];
843 43
            case static::PHINX_TYPE_UUID:
844 1
                return ['name' => 'char', 'limit' => 36];
845
            case static::PHINX_TYPE_ENUM:
846 43
                return ['name' => 'enum'];
847 43
            // Geospatial database types
848 1
            // No specific data types exist in SQLite, instead all geospatial
849
            // functionality is handled in the client. See also: SpatiaLite.
850 43
            case static::PHINX_TYPE_GEOMETRY:
851 42
            case static::PHINX_TYPE_POLYGON:
852
                return ['name' => 'text'];
853 5
            case static::PHINX_TYPE_LINESTRING:
854
                return ['name' => 'varchar', 'limit' => 255];
855 5
            case static::PHINX_TYPE_POINT:
856 4
                return ['name' => 'float'];
857
            default:
858
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
859
        }
860 1
    }
861 1
862
    /**
863
     * Returns Phinx type by SQL type
864 1
     *
865
     * @param string $sqlTypeDef SQL type
866
     * @returns string Phinx type
867 1
     */
868
    public function getPhinxType($sqlTypeDef)
869 1
    {
870 1
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
871 1
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
872
        } else {
873
            $limit = null;
874
            $precision = null;
875
            $type = $matches[1];
876 View Code Duplication
            if (count($matches) > 2) {
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...
877
                $limit = $matches[3] ? $matches[3] : null;
878
            }
879
            if (count($matches) > 4) {
880 3
                $precision = $matches[5];
881
            }
882 3
            switch ($matches[1]) {
883 1
                case 'varchar':
884
                    $type = static::PHINX_TYPE_STRING;
885 2
                    if ($limit === 255) {
886 2
                        $limit = null;
887 2
                    }
888 2
                    break;
889 1 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...
890 1
                    $type = static::PHINX_TYPE_CHAR;
891 2
                    if ($limit === 255) {
892
                        $limit = null;
893
                    }
894 2
                    if ($limit === 36) {
895 2
                        $type = static::PHINX_TYPE_UUID;
896 1
                    }
897 1
                    break;
898
                case 'int':
899
                    $type = static::PHINX_TYPE_INTEGER;
900 1
                    if ($limit === 11) {
901 2
                        $limit = null;
902
                    }
903
                    break;
904
                case 'bigint':
905
                    if ($limit === 11) {
906
                        $limit = null;
907
                    }
908
                    $type = static::PHINX_TYPE_BIG_INTEGER;
909
                    break;
910 2
                case 'blob':
911
                    $type = static::PHINX_TYPE_BINARY;
912
                    break;
913
            }
914 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...
915
                if ($matches[3] === 1) {
916 2
                    $type = static::PHINX_TYPE_BOOLEAN;
917 1
                    $limit = null;
918
                }
919
            }
920 1
921 1
            $this->getSqlType($type);
922 2
923 1
            return [
924 1
                'name' => $type,
925 2
                'limit' => $limit,
926 2
                'precision' => $precision
927
            ];
928
        }
929
    }
930
931
    /**
932
     * {@inheritdoc}
933 2
     */
934
    public function createDatabase($name, $options = [])
935
    {
936 1
        touch($name . '.sqlite3');
937 1
    }
938
939 1
    /**
940
     * {@inheritdoc}
941
     */
942
    public function hasDatabase($name)
943
    {
944
        return is_file($name . '.sqlite3');
945
    }
946 48
947
    /**
948 48
     * {@inheritdoc}
949 48
     */
950
    public function dropDatabase($name)
951
    {
952
        if (file_exists($name . '.sqlite3')) {
953
            unlink($name . '.sqlite3');
954 2
        }
955
    }
956 2
957
    /**
958
     * Get the definition for a `DEFAULT` statement.
959
     *
960
     * @param  mixed $default
961
     * @return string
962 48
     */
963 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...
964 48
    {
965 47
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
966 47
            $default = $this->getConnection()->quote($default);
967 48
        } elseif (is_bool($default)) {
968
            $default = $this->castToBool($default);
969
        }
970
971
        return isset($default) ? ' DEFAULT ' . $default : '';
972
    }
973
974
    /**
975 42
     * Gets the SQLite Column Definition for a Column object.
976
     *
977 42
     * @param \Phinx\Db\Table\Column $column Column
978 8
     * @return string
979 42
     */
980 42
    protected function getColumnSqlDefinition(Column $column)
981 42
    {
982 42
        $sqlType = $this->getSqlType($column->getType());
983
        $def = '';
984
        $def .= strtoupper($sqlType['name']);
985 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...
986
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
987
        }
988
        $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
989
        if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
990
            $def .= '(' . ($column->getLimit() ? $column->getLimit() : $sqlType['limit']) . ')';
991 42
        }
992 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...
993 42
            $def .= " CHECK({$column->getName()} IN ('" . implode("', '", $values) . "'))";
994 42
        }
995 42
996 42
        $default = $column->getDefault();
997
998
        $def .= ($column->isNull() || is_null($default)) ? ' NULL' : ' NOT NULL';
999 42
        $def .= $this->getDefaultValueDefinition($default);
1000 42
        $def .= ($column->isIdentity()) ? ' PRIMARY KEY AUTOINCREMENT' : '';
1001 42
1002 42
        if ($column->getUpdate()) {
1003 42
            $def .= ' ON UPDATE ' . $column->getUpdate();
1004 4
        }
1005 4
1006
        $def .= $this->getCommentDefinition($column);
1007 42
1008
        return $def;
1009 42
    }
1010 42
1011 42
    /**
1012
     * Gets the comment Definition for a Column object.
1013 42
     *
1014
     * @param \Phinx\Db\Table\Column $column Column
1015
     * @return string
1016
     */
1017 42
    protected function getCommentDefinition(Column $column)
1018
    {
1019 42
        if ($column->getComment()) {
1020
            return ' /* ' . $column->getComment() . ' */ ';
1021
        }
1022
1023
        return '';
1024
    }
1025
1026
    /**
1027
     * Gets the SQLite Index Definition for an Index object.
1028 42
     *
1029
     * @param \Phinx\Db\Table $table Table
1030 42
     * @param \Phinx\Db\Table\Index $index Index
1031 2
     * @return string
1032
     */
1033 42
    protected function getIndexSqlDefinition(Table $table, Index $index)
1034
    {
1035
        if ($index->getType() === Index::UNIQUE) {
1036
            $def = 'UNIQUE INDEX';
1037
        } else {
1038
            $def = 'INDEX';
1039
        }
1040
        if (is_string($index->getName())) {
1041
            $indexName = $index->getName();
1042 8
        } else {
1043
            $indexName = $table->getName() . '_';
1044 8
            foreach ($index->getColumns() as $column) {
1045 2
                $indexName .= $column . '_';
1046 2
            }
1047 6
            $indexName .= 'index';
1048
        }
1049 8
        $def .= ' `' . $indexName . '`';
1050 3
1051 3
        return $def;
1052 6
    }
1053 6
1054 6
    /**
1055 6
     * {@inheritdoc}
1056 6
     */
1057
    public function getColumnTypes()
1058 8
    {
1059 8
        return array_merge(parent::getColumnTypes(), ['enum']);
1060
    }
1061
1062
    /**
1063
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1064
     *
1065 47
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1066
     * @return string
1067 47
     */
1068 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...
1069
    {
1070
        $def = '';
1071
        if ($foreignKey->getConstraint()) {
1072
            $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...
1073
        } else {
1074
            $columnNames = [];
1075
            foreach ($foreignKey->getColumns() as $column) {
1076 5
                $columnNames[] = $this->quoteColumnName($column);
1077
            }
1078 5
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1079 5
            $refColumnNames = [];
1080
            foreach ($foreignKey->getReferencedColumns() as $column) {
1081
                $refColumnNames[] = $this->quoteColumnName($column);
1082 5
            }
1083 5
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1084 5
            if ($foreignKey->getOnDelete()) {
1085 5
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1086 5
            }
1087 5
            if ($foreignKey->getOnUpdate()) {
1088 5
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1089 5
            }
1090 5
        }
1091 5
1092 5
        return $def;
1093 1
    }
1094
}
1095