Completed
Pull Request — master (#1208)
by
unknown
01:58
created

SQLiteAdapter::getSqlType()   C

Complexity

Conditions 22
Paths 22

Size

Total Lines 50
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 22.1077

Importance

Changes 0
Metric Value
dl 0
loc 50
ccs 31
cts 33
cp 0.9394
rs 5.3012
c 0
b 0
f 0
cc 22
eloc 44
nc 22
nop 2
crap 22.1077

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use Phinx\Db\Table;
32
use Phinx\Db\Table\Column;
33
use Phinx\Db\Table\ForeignKey;
34
use Phinx\Db\Table\Index;
35
36
/**
37
 * Phinx SQLite Adapter.
38
 *
39
 * @author Rob Morgan <[email protected]>
40
 * @author Richard McIntyre <[email protected]>
41
 */
42
class SQLiteAdapter extends PdoAdapter implements AdapterInterface
43
{
44
    protected $definitionsWithLimits = [
45
        'CHARACTER',
46
        'VARCHAR',
47
        'VARYING CHARACTER',
48
        'NCHAR',
49
        'NATIVE CHARACTER',
50
        'NVARCHAR'
51
    ];
52
53
    /**
54
     * {@inheritdoc}
55
     */
56 42
    public function connect()
57
    {
58 42
        if ($this->connection === null) {
59 42
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
60
                // @codeCoverageIgnoreStart
61
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
62
                // @codeCoverageIgnoreEnd
63
            }
64
65 42
            $db = null;
66 42
            $options = $this->getOptions();
67
68
            // if port is specified use it, otherwise use the MySQL default
69 42
            if (isset($options['memory'])) {
70
                $dsn = 'sqlite::memory:';
71
            } else {
72 42
                $dsn = 'sqlite:' . $options['name'];
73 42
                if (file_exists($options['name'] . '.sqlite3')) {
74 42
                    $dsn = 'sqlite:' . $options['name'] . '.sqlite3';
75 42
                }
76
            }
77
78
            try {
79 42
                $db = new \PDO($dsn);
80 42
            } catch (\PDOException $exception) {
81
                throw new \InvalidArgumentException(sprintf(
82
                    'There was a problem connecting to the database: %s',
83
                    $exception->getMessage()
84
                ));
85
            }
86
87 42
            $this->setConnection($db);
88 42
        }
89 42
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 48
    public function disconnect()
95
    {
96 48
        $this->connection = null;
97 48
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function hasTransactions()
103
    {
104
        return true;
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110 1
    public function beginTransaction()
111
    {
112 1
        $this->execute('BEGIN TRANSACTION');
113 1
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function commitTransaction()
119
    {
120
        $this->execute('COMMIT');
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function rollbackTransaction()
127
    {
128
        $this->execute('ROLLBACK');
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 43
    public function quoteTableName($tableName)
135
    {
136 43
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 44
    public function quoteColumnName($columnName)
143
    {
144 44
        return '`' . str_replace('`', '``', $columnName) . '`';
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150 42
    public function hasTable($tableName)
151
    {
152 42
        $tables = [];
153 42
        $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
154 42
        foreach ($rows as $row) {
155 12
            $tables[] = strtolower($row[0]);
156 42
        }
157
158 42
        return in_array(strtolower($tableName), $tables);
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164 42
    public function createTable(Table $table)
165
    {
166
        // Add the default primary key
167 42
        $columns = $table->getPendingColumns();
168 42
        $options = $table->getOptions();
169 42
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
170 35
            $column = new Column();
171 35
            $column->setName('id')
172 35
                   ->setType('integer')
173 35
                   ->setIdentity(true);
174
175 35
            array_unshift($columns, $column);
176 42
        } elseif (isset($options['id']) && is_string($options['id'])) {
177
            // Handle id => "field_name" to support AUTO_INCREMENT
178 1
            $column = new Column();
179 1
            $column->setName($options['id'])
180 1
                   ->setType('integer')
181 1
                   ->setIdentity(true);
182
183 1
            array_unshift($columns, $column);
184 1
        }
185
186
        $sql = 'CREATE TABLE ';
187 42
        $sql .= $this->quoteTableName($table->getName()) . ' (';
188 42 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
        // TODO: DRY this up....
381
        $tmpTableName = 'tmp_' . $tableName;
382
383
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
384 6
385
        $sql = '';
386 6
        foreach ($rows as $table) {
387
            if ($table['tbl_name'] === $tableName) {
388 6
                $sql = $table['sql'];
389 6
            }
390 6
        }
391 6
392 6
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
393 6
        $selectColumns = [];
394
        $writeColumns = [];
395 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...
396 6
            $selectName = $column['name'];
397 6
            $writeName = ($selectName === $columnName)? $newColumn->getName() : $selectName;
398 6
            $selectColumns[] = $this->quoteColumnName($selectName);
399 6
            $writeColumns[] = $this->quoteColumnName($writeName);
400 6
        }
401 6
402 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...
403 6
            throw new \InvalidArgumentException(sprintf(
404
                'The specified column doesn\'t exist: ' . $columnName
405 6
            ));
406
        }
407
408
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
409
410
        $sql = preg_replace(
411 6
            sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
412
            sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
413 6
            $sql,
414 6
            1
415 6
        );
416 6
417
        $this->execute($sql);
418 6
419
        $sql = sprintf(
420 6
            'INSERT INTO %s(%s) SELECT %s FROM %s',
421
            $tableName,
422 6
            implode(', ', $writeColumns),
423 6
            implode(', ', $selectColumns),
424 6
            $tmpTableName
425 6
        );
426 6
427
        $this->execute($sql);
428 6
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
429
    }
430 6
431 6
    /**
432 6
     * {@inheritdoc}
433
     */
434
    public function dropColumn($tableName, $columnName)
435
    {
436
        // TODO: DRY this up....
437 2
        $tmpTableName = 'tmp_' . $tableName;
438
439
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
440 2
441
        $sql = '';
442 2
        foreach ($rows as $table) {
443
            if ($table['tbl_name'] === $tableName) {
444 2
                $sql = $table['sql'];
445 2
            }
446 2
        }
447 2
448 2
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
449 2
        $columns = [];
450
        $columnType = null;
451 2
        foreach ($rows as $row) {
452 2
            if ($row['name'] !== $columnName) {
453 2
                $columns[] = $row['name'];
454 2
            } else {
455 2
                $found = true;
456 2
                $columnType = $row['type'];
457 2
            }
458 2
        }
459 2
460
        if (!isset($found)) {
461 2
            throw new \InvalidArgumentException(sprintf(
462
                'The specified column doesn\'t exist: ' . $columnName
463 2
            ));
464
        }
465
466
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
467
468
        $sql = preg_replace(
469 2
            sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)),
470
            "",
471 2
            $sql
472 2
        );
473 2
474
        if (substr($sql, -2) === ', ') {
475 2
            $sql = substr($sql, 0, -2) . ')';
476
        }
477 2
478 2
        $this->execute($sql);
479 2
480
        $sql = sprintf(
481 2
            'INSERT INTO %s(%s) SELECT %s FROM %s',
482
            $tableName,
483 2
            implode(', ', $columns),
484 2
            implode(', ', $columns),
485 2
            $tmpTableName
486 2
        );
487 2
488
        $this->execute($sql);
489 2
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
490
    }
491 2
492 2
    /**
493 2
     * Get an array of indexes from a particular table.
494
     *
495
     * @param string $tableName Table Name
496
     * @return array
497
     */
498
    protected function getIndexes($tableName)
499
    {
500
        $indexes = [];
501 9
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
502
503 9
        foreach ($rows as $row) {
504 9
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
505
            if (!isset($indexes[$tableName])) {
506 9
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
507 9
            }
508 9
            foreach ($indexData as $indexItem) {
509 9
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
510 9
            }
511 9
        }
512 9
513 9
        return $indexes;
514 9
    }
515 9
516
    /**
517
     * {@inheritdoc}
518
     */
519 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...
520
    {
521 9
        if (is_string($columns)) {
522
            $columns = [$columns]; // str to array
523 9
        }
524 4
525 4
        $columns = array_map('strtolower', $columns);
526
        $indexes = $this->getIndexes($tableName);
527 9
528 9
        foreach ($indexes as $index) {
529
            $a = array_diff($columns, $index['columns']);
530 9
            if (empty($a)) {
531 9
                return true;
532 9
            }
533 9
        }
534
535 8
        return false;
536
    }
537 8
538
    /**
539
     * {@inheritdoc}
540
     */
541 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...
542
    {
543 1
        $indexes = $this->getIndexes($tableName);
544
545 1
        foreach ($indexes as $index) {
546
            if ($indexName === $index['index']) {
547 1
                return true;
548 1
            }
549 1
        }
550
551
        return false;
552
    }
553
554
    /**
555
     * {@inheritdoc}
556
     */
557
    public function addIndex(Table $table, Index $index)
558
    {
559 8
        $indexColumnArray = [];
560
        foreach ($index->getColumns() as $column) {
561 8
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
562 8
        }
563 8
        $indexColumns = implode(',', $indexColumnArray);
564 8
        $this->execute(
565 8
            sprintf(
566 8
                'CREATE %s ON %s (%s)',
567 8
                $this->getIndexSqlDefinition($table, $index),
568 8
                $this->quoteTableName($table->getName()),
569 8
                $indexColumns
570 8
            )
571
        );
572 8
    }
573 8
574 8
    /**
575
     * {@inheritdoc}
576
     */
577
    public function dropIndex($tableName, $columns)
578
    {
579 1
        if (is_string($columns)) {
580
            $columns = [$columns]; // str to array
581 1
        }
582 1
583 1
        $indexes = $this->getIndexes($tableName);
584
        $columns = array_map('strtolower', $columns);
585 1
586 1
        foreach ($indexes as $index) {
587
            $a = array_diff($columns, $index['columns']);
588 1
            if (empty($a)) {
589 1
                $this->execute(
590 1
                    sprintf(
591 1
                        'DROP INDEX %s',
592 1
                        $this->quoteColumnName($index['index'])
593 1
                    )
594 1
                );
595 1
596 1
                return;
597 1
            }
598
        }
599
    }
600
601
    /**
602
     * {@inheritdoc}
603
     */
604 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...
605 1
    {
606
        $indexes = $this->getIndexes($tableName);
607 1
608
        foreach ($indexes as $index) {
609 1
            if ($indexName === $index['index']) {
610 1
                $this->execute(
611 1
                    sprintf(
612 1
                        'DROP INDEX %s',
613 1
                        $this->quoteColumnName($indexName)
614 1
                    )
615 1
                );
616 1
617 1
                return;
618
            }
619
        }
620
    }
621
622
    /**
623
     * {@inheritdoc}
624
     */
625 5
    public function hasForeignKey($tableName, $columns, $constraint = null)
626
    {
627 5
        if (is_string($columns)) {
628
            $columns = [$columns]; // str to array
629
        }
630 5
        $foreignKeys = $this->getForeignKeys($tableName);
631
632 5
        $a = array_diff($columns, $foreignKeys);
633 5
        if (empty($a)) {
634 5
            return true;
635
        }
636 1
637
        return false;
638
    }
639
640
    /**
641
     * Get an array of foreign keys from a particular table.
642
     *
643
     * @param string $tableName Table Name
644
     * @return array
645 5
     */
646
    protected function getForeignKeys($tableName)
647 5
    {
648 5
        $foreignKeys = [];
649
        $rows = $this->fetchAll(
650
            "SELECT sql, tbl_name
651
              FROM (
652
                    SELECT sql sql, type type, tbl_name tbl_name, name name
653
                      FROM sqlite_master
654
                     UNION ALL
655
                    SELECT sql, type, tbl_name, name
656
                      FROM sqlite_temp_master
657
                   )
658
             WHERE type != 'meta'
659
               AND sql NOTNULL
660
               AND name NOT LIKE 'sqlite_%'
661 5
             ORDER BY substr(type, 2, 1), name"
662
        );
663 5
664 5
        foreach ($rows as $row) {
665 5
            if ($row['tbl_name'] === $tableName) {
666 5
                if (strpos($row['sql'], 'REFERENCES') !== false) {
667 5
                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
668 5
                    foreach ($matches[1] as $match) {
669 5
                        $foreignKeys[] = $match;
670 5
                    }
671 5
                }
672 5
            }
673 5
        }
674
675
        return $foreignKeys;
676
    }
677
678
    /**
679 4
     * {@inheritdoc}
680
     */
681
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
682 4
    {
683
        // TODO: DRY this up....
684 4
        $this->execute('pragma foreign_keys = ON');
685 4
686
        $tmpTableName = 'tmp_' . $table->getName();
687 4
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
688 4
689 4
        $sql = '';
690 4
        foreach ($rows as $row) {
691 4
            if ($row['tbl_name'] === $table->getName()) {
692 4
                $sql = $row['sql'];
693
            }
694 4
        }
695 4
696 4
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($table->getName())));
697 4
        $columns = [];
698 4
        foreach ($rows as $column) {
699
            $columns[] = $this->quoteColumnName($column['name']);
700 4
        }
701
702 4
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($table->getName()), $tmpTableName));
703 4
704
        $sql = substr($sql, 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
705 4
        $this->execute($sql);
706 4
707 4
        $sql = sprintf(
708 4
            'INSERT INTO %s(%s) SELECT %s FROM %s',
709 4
            $this->quoteTableName($table->getName()),
710 4
            implode(', ', $columns),
711 4
            implode(', ', $columns),
712
            $this->quoteTableName($tmpTableName)
713 4
        );
714 4
715 4
        $this->execute($sql);
716
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
717
    }
718
719
    /**
720 1
     * {@inheritdoc}
721
     */
722
    public function dropForeignKey($tableName, $columns, $constraint = null)
723 1
    {
724
        // TODO: DRY this up....
725
        if (is_string($columns)) {
726
            $columns = [$columns]; // str to array
727 1
        }
728
729 1
        $tmpTableName = 'tmp_' . $tableName;
730
731 1
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
732 1
733 1
        $sql = '';
734 1
        foreach ($rows as $table) {
735 1
            if ($table['tbl_name'] === $tableName) {
736 1
                $sql = $table['sql'];
737
            }
738 1
        }
739 1
740 1
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
741 1
        $replaceColumns = [];
742 1
        foreach ($rows as $row) {
743 1
            if (!in_array($row['name'], $columns)) {
744 1
                $replaceColumns[] = $row['name'];
745
            } else {
746 1
                $found = true;
747
            }
748 1
        }
749
750
        if (!isset($found)) {
751
            throw new \InvalidArgumentException(sprintf(
752
                'The specified column doesn\'t exist: '
753
            ));
754 1
        }
755
756 1
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $tmpTableName));
757 1
758 1
        foreach ($columns as $columnName) {
759 1
            $search = sprintf(
760 1
                "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
761 1
                $this->quoteColumnName($columnName)
762 1
            );
763
            $sql = preg_replace($search, '', $sql, 1);
764 1
        }
765
766 1
        $this->execute($sql);
767 1
768 1
        $sql = sprintf(
769 1
            'INSERT INTO %s(%s) SELECT %s FROM %s',
770 1
            $tableName,
771
            implode(', ', $columns),
772 1
            implode(', ', $columns),
773
            $tmpTableName
774 1
        );
775 1
776 1
        $this->execute($sql);
777
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
778
    }
779
780
    /**
781
     * {@inheritdoc}
782
     */
783
    public function insert(Table $table, $row)
784
    {
785
        $sql = sprintf(
786
            "INSERT INTO %s ",
787
            $this->quoteTableName($table->getName())
788
        );
789
790
        $columns = array_keys($row);
791
        $sql .= "(" . implode(', ', array_map([$this, 'quoteColumnName'], $columns)) . ")";
792
        $sql .= " VALUES ";
793
794
        $sql .= "(" . implode(', ', array_map(function ($value) {
795
            if (is_numeric($value)) {
796
                return $value;
797
            }
798
799
            if ($value === null) {
800
                return 'null';
801
            }
802
803
                return $this->getConnection()->quote($value);
804
        }, $row)) . ")";
805
806
        $this->execute($sql);
807
    }
808
809
    /**
810 43
     * {@inheritdoc}
811
     */
812
    public function getSqlType($type, $limit = null)
813 43
    {
814 42
        switch ($type) {
815
            case static::PHINX_TYPE_STRING:
816 43
                return ['name' => 'varchar', 'limit' => 255];
817
            case static::PHINX_TYPE_CHAR:
818
                return ['name' => 'char', 'limit' => 255];
819 43
            case static::PHINX_TYPE_TEXT:
820 1
                return ['name' => 'text'];
821
            case static::PHINX_TYPE_INTEGER:
822 43
                return ['name' => 'integer'];
823 38
            case static::PHINX_TYPE_BIG_INTEGER:
824
                return ['name' => 'bigint'];
825 43
            case static::PHINX_TYPE_FLOAT:
826 42
                return ['name' => 'float'];
827
            case static::PHINX_TYPE_DECIMAL:
828 43
                return ['name' => 'decimal'];
829 2
            case static::PHINX_TYPE_DOUBLE:
830
                return ['name' => 'double'];
831 43
            case static::PHINX_TYPE_DATETIME:
832 1
                return ['name' => 'datetime'];
833
            case static::PHINX_TYPE_TIMESTAMP:
834 43
                return ['name' => 'datetime'];
835 1
            case static::PHINX_TYPE_TIME:
836
                return ['name' => 'time'];
837 43
            case static::PHINX_TYPE_DATE:
838 42
                return ['name' => 'date'];
839
            case static::PHINX_TYPE_BLOB:
840 43
            case static::PHINX_TYPE_BINARY:
841 1
                return ['name' => 'blob'];
842
            case static::PHINX_TYPE_BOOLEAN:
843 43
                return ['name' => 'boolean'];
844 1
            case static::PHINX_TYPE_UUID:
845
                return ['name' => 'char', 'limit' => 36];
846 43
            case static::PHINX_TYPE_ENUM:
847 43
                return ['name' => 'enum'];
848 1
            // Geospatial database types
849
            // No specific data types exist in SQLite, instead all geospatial
850 43
            // functionality is handled in the client. See also: SpatiaLite.
851 42
            case static::PHINX_TYPE_GEOMETRY:
852
            case static::PHINX_TYPE_POLYGON:
853 5
                return ['name' => 'text'];
854
            case static::PHINX_TYPE_LINESTRING:
855 5
                return ['name' => 'varchar', 'limit' => 255];
856 4
            case static::PHINX_TYPE_POINT:
857
                return ['name' => 'float'];
858
            default:
859
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
860 1
        }
861 1
    }
862
863
    /**
864 1
     * Returns Phinx type by SQL type
865
     *
866
     * @param string $sqlTypeDef SQL type
867 1
     * @returns string Phinx type
868
     */
869 1
    public function getPhinxType($sqlTypeDef)
870 1
    {
871 1
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
872
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
873
        } else {
874
            $limit = null;
875
            $precision = null;
876
            $type = $matches[1];
877 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...
878
                $limit = $matches[3] ? $matches[3] : null;
879
            }
880 3
            if (count($matches) > 4) {
881
                $precision = $matches[5];
882 3
            }
883 1
            switch ($matches[1]) {
884
                case 'varchar':
885 2
                    $type = static::PHINX_TYPE_STRING;
886 2
                    if ($limit === 255) {
887 2
                        $limit = null;
888 2
                    }
889 1
                    break;
890 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...
891 2
                    $type = static::PHINX_TYPE_CHAR;
892
                    if ($limit === 255) {
893
                        $limit = null;
894 2
                    }
895 2
                    if ($limit === 36) {
896 1
                        $type = static::PHINX_TYPE_UUID;
897 1
                    }
898
                    break;
899
                case 'int':
900 1
                    $type = static::PHINX_TYPE_INTEGER;
901 2
                    if ($limit === 11) {
902
                        $limit = null;
903
                    }
904
                    break;
905
                case 'bigint':
906
                    if ($limit === 11) {
907
                        $limit = null;
908
                    }
909
                    $type = static::PHINX_TYPE_BIG_INTEGER;
910 2
                    break;
911
                case 'blob':
912
                    $type = static::PHINX_TYPE_BINARY;
913
                    break;
914
            }
915 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...
916 2
                if ($matches[3] === 1) {
917 1
                    $type = static::PHINX_TYPE_BOOLEAN;
918
                    $limit = null;
919
                }
920 1
            }
921 1
922 2
            $this->getSqlType($type);
923 1
924 1
            return [
925 2
                'name' => $type,
926 2
                'limit' => $limit,
927
                'precision' => $precision
928
            ];
929
        }
930
    }
931
932
    /**
933 2
     * {@inheritdoc}
934
     */
935
    public function createDatabase($name, $options = [])
936 1
    {
937 1
        touch($name . '.sqlite3');
938
    }
939 1
940
    /**
941
     * {@inheritdoc}
942
     */
943
    public function hasDatabase($name)
944
    {
945
        return is_file($name . '.sqlite3');
946 48
    }
947
948 48
    /**
949 48
     * {@inheritdoc}
950
     */
951
    public function dropDatabase($name)
952
    {
953
        if (file_exists($name . '.sqlite3')) {
954 2
            unlink($name . '.sqlite3');
955
        }
956 2
    }
957
958
    /**
959
     * Get the definition for a `DEFAULT` statement.
960
     *
961
     * @param  mixed $default
962 48
     * @return string
963
     */
964 48 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...
965 47
    {
966 47
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
967 48
            $default = $this->getConnection()->quote($default);
968
        } elseif (is_bool($default)) {
969
            $default = $this->castToBool($default);
970
        }
971
972
        return isset($default) ? ' DEFAULT ' . $default : '';
973
    }
974
975 42
    /**
976
     * Gets the SQLite Column Definition for a Column object.
977 42
     *
978 8
     * @param \Phinx\Db\Table\Column $column Column
979 42
     * @return string
980 42
     */
981 42
    protected function getColumnSqlDefinition(Column $column)
982 42
    {
983
        $sqlType = $this->getSqlType($column->getType());
984
        $def = '';
985
        $def .= strtoupper($sqlType['name']);
986 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...
987
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
988
        }
989
        $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
990
        if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
991 42
            $def .= '(' . ($column->getLimit() ? $column->getLimit() : $sqlType['limit']) . ')';
992
        }
993 42 View Code Duplication
        if (($values = $column->getValues()) && is_array($values)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
994 42
            $def .= " CHECK({$column->getName()} IN ('" . implode("', '", $values) . "'))";
995 42
        }
996 42
997
        $default = $column->getDefault();
998
999 42
        $def .= ($column->isNull() || is_null($default)) ? ' NULL' : ' NOT NULL';
1000 42
        $def .= $this->getDefaultValueDefinition($default);
1001 42
        $def .= ($column->isIdentity()) ? ' PRIMARY KEY AUTOINCREMENT' : '';
1002 42
1003 42
        if ($column->getUpdate()) {
1004 4
            $def .= ' ON UPDATE ' . $column->getUpdate();
1005 4
        }
1006
1007 42
        $def .= $this->getCommentDefinition($column);
1008
1009 42
        return $def;
1010 42
    }
1011 42
1012
    /**
1013 42
     * Gets the comment Definition for a Column object.
1014
     *
1015
     * @param \Phinx\Db\Table\Column $column Column
1016
     * @return string
1017 42
     */
1018
    protected function getCommentDefinition(Column $column)
1019 42
    {
1020
        if ($column->getComment()) {
1021
            return ' /* ' . $column->getComment() . ' */ ';
1022
        }
1023
1024
        return '';
1025
    }
1026
1027
    /**
1028 42
     * Gets the SQLite Index Definition for an Index object.
1029
     *
1030 42
     * @param \Phinx\Db\Table $table Table
1031 2
     * @param \Phinx\Db\Table\Index $index Index
1032
     * @return string
1033 42
     */
1034
    protected function getIndexSqlDefinition(Table $table, Index $index)
1035
    {
1036
        if ($index->getType() === Index::UNIQUE) {
1037
            $def = 'UNIQUE INDEX';
1038
        } else {
1039
            $def = 'INDEX';
1040
        }
1041
        if (is_string($index->getName())) {
1042 8
            $indexName = $index->getName();
1043
        } else {
1044 8
            $indexName = $table->getName() . '_';
1045 2
            foreach ($index->getColumns() as $column) {
1046 2
                $indexName .= $column . '_';
1047 6
            }
1048
            $indexName .= 'index';
1049 8
        }
1050 3
        $def .= ' `' . $indexName . '`';
1051 3
1052 6
        return $def;
1053 6
    }
1054 6
1055 6
    /**
1056 6
     * {@inheritdoc}
1057
     */
1058 8
    public function getColumnTypes()
1059 8
    {
1060
        return array_merge(parent::getColumnTypes(), ['enum']);
1061
    }
1062
1063
    /**
1064
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1065 47
     *
1066
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1067 47
     * @return string
1068
     */
1069 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...
1070
    {
1071
        $def = '';
1072
        if ($foreignKey->getConstraint()) {
1073
            $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...
1074
        } else {
1075
            $columnNames = [];
1076 5
            foreach ($foreignKey->getColumns() as $column) {
1077
                $columnNames[] = $this->quoteColumnName($column);
1078 5
            }
1079 5
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1080
            $refColumnNames = [];
1081
            foreach ($foreignKey->getReferencedColumns() as $column) {
1082 5
                $refColumnNames[] = $this->quoteColumnName($column);
1083 5
            }
1084 5
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1085 5
            if ($foreignKey->getOnDelete()) {
1086 5
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1087 5
            }
1088 5
            if ($foreignKey->getOnUpdate()) {
1089 5
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1090 5
            }
1091 5
        }
1092 5
1093 1
        return $def;
1094 1
    }
1095
}
1096