Completed
Push — master ( da6087...8e2986 )
by José
03:06 queued 01:21
created

SQLiteAdapter::getDefaultValueDefinition()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 10
Code Lines 6

Duplication

Lines 9
Ratio 90 %

Code Coverage

Tests 4
CRAP Score 5

Importance

Changes 0
Metric Value
dl 9
loc 10
ccs 4
cts 4
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 6
nc 6
nop 1
crap 5
1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use Phinx\Db\Table;
32
use Phinx\Db\Table\Column;
33
use Phinx\Db\Table\ForeignKey;
34
use Phinx\Db\Table\Index;
35
use Phinx\Util\Literal;
36
37
/**
38
 * Phinx SQLite Adapter.
39
 *
40
 * @author Rob Morgan <[email protected]>
41
 * @author Richard McIntyre <[email protected]>
42
 */
43
class SQLiteAdapter extends PdoAdapter implements AdapterInterface
44
{
45
    protected $definitionsWithLimits = [
46
        'CHARACTER',
47
        'VARCHAR',
48
        'VARYING CHARACTER',
49
        'NCHAR',
50
        'NATIVE CHARACTER',
51
        'NVARCHAR'
52
    ];
53
54
    /**
55
     * {@inheritdoc}
56 42
     */
57
    public function connect()
58 42
    {
59 42
        if ($this->connection === null) {
60
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
61
                // @codeCoverageIgnoreStart
62
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
63
                // @codeCoverageIgnoreEnd
64
            }
65 42
66 42
            $db = null;
67
            $options = $this->getOptions();
68
69 42
            // if port is specified use it, otherwise use the MySQL default
70
            if (isset($options['memory'])) {
71
                $dsn = 'sqlite::memory:';
72 42
            } else {
73 42
                $dsn = 'sqlite:' . $options['name'];
74 42
                if (file_exists($options['name'] . '.sqlite3')) {
75 42
                    $dsn = 'sqlite:' . $options['name'] . '.sqlite3';
76
                }
77
            }
78
79 42
            try {
80 42
                $db = new \PDO($dsn);
81
            } catch (\PDOException $exception) {
82
                throw new \InvalidArgumentException(sprintf(
83
                    'There was a problem connecting to the database: %s',
84
                    $exception->getMessage()
85
                ));
86
            }
87 42
88 42
            $this->setConnection($db);
89 42
        }
90
    }
91
92
    /**
93
     * {@inheritdoc}
94 48
     */
95
    public function disconnect()
96 48
    {
97 48
        $this->connection = null;
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    public function hasTransactions()
104
    {
105
        return true;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110 1
     */
111
    public function beginTransaction()
112 1
    {
113 1
        $this->getConnection()->beginTransaction();
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function commitTransaction()
120
    {
121
        $this->getConnection()->commit();
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function rollbackTransaction()
128
    {
129
        $this->getConnection()->rollBack();
130
    }
131
132
    /**
133
     * {@inheritdoc}
134 43
     */
135
    public function quoteTableName($tableName)
136 43
    {
137
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
138
    }
139
140
    /**
141
     * {@inheritdoc}
142 44
     */
143
    public function quoteColumnName($columnName)
144 44
    {
145
        return '`' . str_replace('`', '``', $columnName) . '`';
146
    }
147
148
    /**
149
     * {@inheritdoc}
150 42
     */
151
    public function hasTable($tableName)
152 42
    {
153 42
        $tables = [];
154 42
        $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
155 12
        foreach ($rows as $row) {
156 42
            $tables[] = strtolower($row[0]);
157
        }
158 42
159
        return in_array(strtolower($tableName), $tables);
160
    }
161
162
    /**
163
     * {@inheritdoc}
164 42
     */
165
    public function createTable(Table $table)
166
    {
167 42
        // Add the default primary key
168 42
        $columns = $table->getPendingColumns();
169 42
        $options = $table->getOptions();
170 35
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
171 35
            $column = new Column();
172 35
            $column->setName('id')
173 35
                   ->setType('integer')
174
                   ->setIdentity(true);
175 35
176 42
            array_unshift($columns, $column);
177
        } elseif (isset($options['id']) && is_string($options['id'])) {
178 1
            // Handle id => "field_name" to support AUTO_INCREMENT
179 1
            $column = new Column();
180 1
            $column->setName($options['id'])
181 1
                   ->setType('integer')
182
                   ->setIdentity(true);
183 1
184 1
            array_unshift($columns, $column);
185
        }
186
187 42
        $sql = 'CREATE TABLE ';
188 42
        $sql .= $this->quoteTableName($table->getName()) . ' (';
189 42 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...
190 42
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
191 42
        }
192
193
        // set the primary key(s)
194 42 View Code Duplication
        if (isset($options['primary_key'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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