Completed
Push — master ( ba1ce0...665d6b )
by José
08:39 queued 07:13
created

SQLiteAdapter::insert()   B

Complexity

Conditions 3
Paths 1

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 10.0792

Importance

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

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

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

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

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

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

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

Loading history...
214 37
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
215
            }
216
            $sql .= ')';
217
        } else {
218 42
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
219 42
        }
220 1
221 1
        // set the foreign keys
222 1
        $foreignKeys = $table->getForeignKeys();
223 1
        if (!empty($foreignKeys)) {
224
            foreach ($foreignKeys as $foreignKey) {
225 42
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
226
            }
227 42
        }
228
229 42
        $sql = rtrim($sql) . ');';
230 6
        // execute the sql
231 42
        $this->execute($sql);
232 42
233
        foreach ($table->getIndexes() as $index) {
234
            $this->addIndex($table, $index);
235
        }
236
    }
237 1
238
    /**
239 1
     * {@inheritdoc}
240 1
     */
241
    public function renameTable($tableName, $newTableName)
242
    {
243
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
244
    }
245 1
246
    /**
247 1
     * {@inheritdoc}
248 1
     */
249
    public function dropTable($tableName)
250
    {
251
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
252
    }
253 1
254
    /**
255 1
     * {@inheritdoc}
256 1
     */
257 1
    public function truncateTable($tableName)
258 1
    {
259
        $sql = sprintf(
260 1
            'DELETE FROM %s',
261 1
            $this->quoteTableName($tableName)
262
        );
263
264
        $this->execute($sql);
265
    }
266 1
267
    /**
268 1
     * {@inheritdoc}
269 1
     */
270
    public function getColumns($tableName)
271 1
    {
272 1
        $columns = [];
273 1
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
274 1
275 1
        foreach ($rows as $columnInfo) {
276 1
            $column = new Column();
277
            $type = strtolower($columnInfo['type']);
278 1
            $column->setName($columnInfo['name'])
279 1
                   ->setNull($columnInfo['notnull'] !== '1')
280 1
                   ->setDefault($columnInfo['dflt_value']);
281
282 1
            $phinxType = $this->getPhinxType($type);
283 1
            $column->setType($phinxType['name'])
284 1
                   ->setLimit($phinxType['limit']);
285
286 1
            if ($columnInfo['pk'] == 1) {
287 1
                $column->setIdentity(true);
288
            }
289 1
290
            $columns[] = $column;
291
        }
292
293
        return $columns;
294
    }
295 8
296
    /**
297 8
     * {@inheritdoc}
298 8
     */
299 8 View Code Duplication
    public function hasColumn($tableName, $columnName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
300 7
    {
301
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
302 8
        foreach ($rows as $column) {
303
            if (strcasecmp($column['name'], $columnName) === 0) {
304 8
                return true;
305
            }
306
        }
307
308
        return false;
309
    }
310 4
311
    /**
312 4
     * {@inheritdoc}
313 4
     */
314 4 View Code Duplication
    public function addColumn(Table $table, Column $column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
315 4
    {
316 4
        $sql = sprintf(
317 4
            'ALTER TABLE %s ADD COLUMN %s %s',
318
            $this->quoteTableName($table->getName()),
319 4
            $this->quoteColumnName($column->getName()),
320 4
            $this->getColumnSqlDefinition($column)
321
        );
322
323
        $this->execute($sql);
324
    }
325 2
326
    /**
327 2
     * {@inheritdoc}
328
     */
329 2
    public function renameColumn($tableName, $columnName, $newColumnName)
330
    {
331 2
        $tmpTableName = 'tmp_' . $tableName;
332 2
333 2
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
334 2
335 2
        $sql = '';
336 2
        foreach ($rows as $table) {
337
            if ($table['tbl_name'] === $tableName) {
338 2
                $sql = $table['sql'];
339 2
            }
340 2
        }
341 2
342 2
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
343 2
        $selectColumns = [];
344 2
        $writeColumns = [];
345 2 View Code Duplication
        foreach ($columns as $column) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
346 2
            $selectName = $column['name'];
347
            $writeName = ($selectName == $columnName)? $newColumnName : $selectName;
348 2
            $selectColumns[] = $this->quoteColumnName($selectName);
349 1
            $writeColumns[] = $this->quoteColumnName($writeName);
350
        }
351 1
352 View Code Duplication
        if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
353
            throw new \InvalidArgumentException(sprintf(
354 1
                'The specified column doesn\'t exist: ' . $columnName
355
            ));
356 1
        }
357 1
358 1
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
359
360 1
        $sql = str_replace(
361 1
            $this->quoteColumnName($columnName),
362
            $this->quoteColumnName($newColumnName),
363
            $sql
364 1
        );
365 1
        $this->execute($sql);
366 1
367 1
        $sql = sprintf(
368 1
            'INSERT INTO %s(%s) SELECT %s FROM %s',
369
            $tableName,
370 1
            implode(', ', $writeColumns),
371
            implode(', ', $selectColumns),
372 1
            $tmpTableName
373
        );
374 1
375 1
        $this->execute($sql);
376
377
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
378
    }
379
380 6
    /**
381
     * {@inheritdoc}
382
     */
383
    public function changeColumn($tableName, $columnName, Column $newColumn)
384 6
    {
385
        // TODO: DRY this up....
386 6
        $tmpTableName = 'tmp_' . $tableName;
387
388 6
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
389 6
390 6
        $sql = '';
391 6
        foreach ($rows as $table) {
392 6
            if ($table['tbl_name'] === $tableName) {
393 6
                $sql = $table['sql'];
394
            }
395 6
        }
396 6
397 6
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
398 6
        $selectColumns = [];
399 6
        $writeColumns = [];
400 6 View Code Duplication
        foreach ($columns as $column) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
401 6
            $selectName = $column['name'];
402 6
            $writeName = ($selectName === $columnName)? $newColumn->getName() : $selectName;
403 6
            $selectColumns[] = $this->quoteColumnName($selectName);
404
            $writeColumns[] = $this->quoteColumnName($writeName);
405 6
        }
406
407 View Code Duplication
        if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
408
            throw new \InvalidArgumentException(sprintf(
409
                'The specified column doesn\'t exist: ' . $columnName
410
            ));
411 6
        }
412
413 6
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
414 6
415 6
        $sql = preg_replace(
416 6
            sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
417
            sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
418 6
            $sql,
419
            1
420 6
        );
421
422 6
        $this->execute($sql);
423 6
424 6
        $sql = sprintf(
425 6
            'INSERT INTO %s(%s) SELECT %s FROM %s',
426 6
            $tableName,
427
            implode(', ', $writeColumns),
428 6
            implode(', ', $selectColumns),
429
            $tmpTableName
430 6
        );
431 6
432 6
        $this->execute($sql);
433
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
434
    }
435
436
    /**
437 2
     * {@inheritdoc}
438
     */
439
    public function dropColumn($tableName, $columnName)
440 2
    {
441
        // TODO: DRY this up....
442 2
        $tmpTableName = 'tmp_' . $tableName;
443
444 2
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
445 2
446 2
        $sql = '';
447 2
        foreach ($rows as $table) {
448 2
            if ($table['tbl_name'] === $tableName) {
449 2
                $sql = $table['sql'];
450
            }
451 2
        }
452 2
453 2
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
454 2
        $columns = [];
455 2
        $columnType = null;
456 2
        foreach ($rows as $row) {
457 2
            if ($row['name'] !== $columnName) {
458 2
                $columns[] = $row['name'];
459 2
            } else {
460
                $found = true;
461 2
                $columnType = $row['type'];
462
            }
463 2
        }
464
465
        if (!isset($found)) {
466
            throw new \InvalidArgumentException(sprintf(
467
                'The specified column doesn\'t exist: ' . $columnName
468
            ));
469 2
        }
470
471 2
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
472 2
473 2
        $sql = preg_replace(
474
            sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)),
475 2
            "",
476
            $sql
477 2
        );
478 2
479 2
        if (substr($sql, -2) === ', ') {
480
            $sql = substr($sql, 0, -2) . ')';
481 2
        }
482
483 2
        $this->execute($sql);
484 2
485 2
        $sql = sprintf(
486 2
            'INSERT INTO %s(%s) SELECT %s FROM %s',
487 2
            $tableName,
488
            implode(', ', $columns),
489 2
            implode(', ', $columns),
490
            $tmpTableName
491 2
        );
492 2
493 2
        $this->execute($sql);
494
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
495
    }
496
497
    /**
498
     * Get an array of indexes from a particular table.
499
     *
500
     * @param string $tableName Table Name
501 9
     * @return array
502
     */
503 9
    protected function getIndexes($tableName)
504 9
    {
505
        $indexes = [];
506 9
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
507 9
508 9
        foreach ($rows as $row) {
509 9
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
510 9
            if (!isset($indexes[$tableName])) {
511 9
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
512 9
            }
513 9
            foreach ($indexData as $indexItem) {
514 9
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
515 9
            }
516
        }
517
518
        return $indexes;
519
    }
520
521 9
    /**
522
     * {@inheritdoc}
523 9
     */
524 4 View Code Duplication
    public function hasIndex($tableName, $columns)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
525 4
    {
526
        if (is_string($columns)) {
527 9
            $columns = [$columns]; // str to array
528 9
        }
529
530 9
        $columns = array_map('strtolower', $columns);
531 9
        $indexes = $this->getIndexes($tableName);
532 9
533 9
        foreach ($indexes as $index) {
534
            $a = array_diff($columns, $index['columns']);
535 8
            if (empty($a)) {
536
                return true;
537 8
            }
538
        }
539
540
        return false;
541
    }
542
543 1
    /**
544
     * {@inheritdoc}
545 1
     */
546 View Code Duplication
    public function hasIndexByName($tableName, $indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
547 1
    {
548 1
        $indexes = $this->getIndexes($tableName);
549 1
550
        foreach ($indexes as $index) {
551
            if ($indexName === $index['index']) {
552
                return true;
553
            }
554
        }
555
556
        return false;
557
    }
558
559 8
    /**
560
     * {@inheritdoc}
561 8
     */
562 8
    public function addIndex(Table $table, Index $index)
563 8
    {
564 8
        $indexColumnArray = [];
565 8
        foreach ($index->getColumns() as $column) {
566 8
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
567 8
        }
568 8
        $indexColumns = implode(',', $indexColumnArray);
569 8
        $this->execute(
570 8
            sprintf(
571
                'CREATE %s ON %s (%s)',
572 8
                $this->getIndexSqlDefinition($table, $index),
573 8
                $this->quoteTableName($table->getName()),
574 8
                $indexColumns
575
            )
576
        );
577
    }
578
579 1
    /**
580
     * {@inheritdoc}
581 1
     */
582 1
    public function dropIndex($tableName, $columns)
583 1
    {
584
        if (is_string($columns)) {
585 1
            $columns = [$columns]; // str to array
586 1
        }
587
588 1
        $indexes = $this->getIndexes($tableName);
589 1
        $columns = array_map('strtolower', $columns);
590 1
591 1
        foreach ($indexes as $index) {
592 1
            $a = array_diff($columns, $index['columns']);
593 1
            if (empty($a)) {
594 1
                $this->execute(
595 1
                    sprintf(
596 1
                        'DROP INDEX %s',
597 1
                        $this->quoteColumnName($index['index'])
598
                    )
599
                );
600
601
                return;
602
            }
603
        }
604
    }
605 1
606
    /**
607 1
     * {@inheritdoc}
608
     */
609 1 View Code Duplication
    public function dropIndexByName($tableName, $indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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