Completed
Push — master ( c7757e...39cb21 )
by Luís
16s
created

lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php (3 issues)

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Schema;
21
22
use Doctrine\DBAL\DBALException;
23
use Doctrine\DBAL\Types\StringType;
24
use Doctrine\DBAL\Types\TextType;
25
use Doctrine\DBAL\Types\Type;
26
27
/**
28
 * Sqlite SchemaManager.
29
 *
30
 * @author Konsta Vesterinen <[email protected]>
31
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
32
 * @author Jonathan H. Wage <[email protected]>
33
 * @author Martin Hasoň <[email protected]>
34
 * @since  2.0
35
 */
36
class SqliteSchemaManager extends AbstractSchemaManager
37
{
38
    /**
39
     * {@inheritdoc}
40
     */
41 2
    public function dropDatabase($database)
42
    {
43 2
        if (file_exists($database)) {
44 2
            unlink($database);
45
        }
46 2
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51 2
    public function createDatabase($database)
52
    {
53 2
        $params = $this->_conn->getParams();
54 2
        $driver = $params['driver'];
55
        $options = [
56 2
            'driver' => $driver,
57 2
            'path' => $database
58
        ];
59 2
        $conn = \Doctrine\DBAL\DriverManager::getConnection($options);
60 2
        $conn->connect();
61 2
        $conn->close();
62 2
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 1
    public function renameTable($name, $newName)
68
    {
69 1
        $tableDiff = new TableDiff($name);
70 1
        $tableDiff->fromTable = $this->listTableDetails($name);
71 1
        $tableDiff->newName = $newName;
72 1
        $this->alterTable($tableDiff);
73 1
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
79
    {
80
        $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
81
        $tableDiff->addedForeignKeys[] = $foreignKey;
82
83
        $this->alterTable($tableDiff);
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
90
    {
91
        $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
92
        $tableDiff->changedForeignKeys[] = $foreignKey;
93
94
        $this->alterTable($tableDiff);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100
    public function dropForeignKey($foreignKey, $table)
101
    {
102
        $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
103
        $tableDiff->removedForeignKeys[] = $foreignKey;
104
105
        $this->alterTable($tableDiff);
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111 1
    public function listTableForeignKeys($table, $database = null)
112
    {
113 1
        if (null === $database) {
114 1
            $database = $this->_conn->getDatabase();
115
        }
116 1
        $sql = $this->_platform->getListTableForeignKeysSQL($table, $database);
0 ignored issues
show
The call to Doctrine\DBAL\Platforms\...stTableForeignKeysSQL() has too many arguments starting with $database. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

116
        /** @scrutinizer ignore-call */ 
117
        $sql = $this->_platform->getListTableForeignKeysSQL($table, $database);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
117 1
        $tableForeignKeys = $this->_conn->fetchAll($sql);
118
119 1
        if ( ! empty($tableForeignKeys)) {
120 1
            $createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'");
121 1
            $createSql = isset($createSql[0]['sql']) ? $createSql[0]['sql'] : '';
122
123 1
            if (preg_match_all('#
124
                    (?:CONSTRAINT\s+([^\s]+)\s+)?
125
                    (?:FOREIGN\s+KEY[^\)]+\)\s*)?
126
                    REFERENCES\s+[^\s]+\s+(?:\([^\)]+\))?
127
                    (?:
128
                        [^,]*?
129
                        (NOT\s+DEFERRABLE|DEFERRABLE)
130
                        (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))?
131
                    )?#isx',
132 1
                    $createSql, $match)) {
133
134 1
                $names = array_reverse($match[1]);
135 1
                $deferrable = array_reverse($match[2]);
136 1
                $deferred = array_reverse($match[3]);
137
            } else {
138
                $names = $deferrable = $deferred = [];
139
            }
140
141 1
            foreach ($tableForeignKeys as $key => $value) {
142 1
                $id = $value['id'];
143 1
                $tableForeignKeys[$key]['constraint_name'] = isset($names[$id]) && '' != $names[$id] ? $names[$id] : $id;
144 1
                $tableForeignKeys[$key]['deferrable'] = isset($deferrable[$id]) && 'deferrable' == strtolower($deferrable[$id]) ? true : false;
145 1
                $tableForeignKeys[$key]['deferred'] = isset($deferred[$id]) && 'deferred' == strtolower($deferred[$id]) ? true : false;
146
            }
147
        }
148
149 1
        return $this->_getPortableTableForeignKeysList($tableForeignKeys);
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155 57
    protected function _getPortableTableDefinition($table)
156
    {
157 57
        return $table['name'];
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     *
163
     * @license New BSD License
164
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
165
     */
166 33
    protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
167
    {
168 33
        $indexBuffer = [];
169
170
        // fetch primary
171 33
        $stmt = $this->_conn->executeQuery("PRAGMA TABLE_INFO ('$tableName')");
172 33
        $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC);
173
174 33
        usort($indexArray, function($a, $b) {
175 22
            if ($a['pk'] == $b['pk']) {
176 18
                return $a['cid'] - $b['cid'];
177
            }
178
179 12
            return $a['pk'] - $b['pk'];
180 33
        });
181 33
        foreach ($indexArray as $indexColumnRow) {
182 33
            if ($indexColumnRow['pk'] != "0") {
183 21
                $indexBuffer[] = [
184 21
                    'key_name' => 'primary',
185
                    'primary' => true,
186
                    'non_unique' => false,
187 33
                    'column_name' => $indexColumnRow['name']
188
                ];
189
            }
190
        }
191
192
        // fetch regular indexes
193 33
        foreach ($tableIndexes as $tableIndex) {
194
            // Ignore indexes with reserved names, e.g. autoindexes
195 8
            if (strpos($tableIndex['name'], 'sqlite_') !== 0) {
196 6
                $keyName = $tableIndex['name'];
197 6
                $idx = [];
198 6
                $idx['key_name'] = $keyName;
199 6
                $idx['primary'] = false;
200 6
                $idx['non_unique'] = $tableIndex['unique']?false:true;
201
202 6
                $stmt = $this->_conn->executeQuery("PRAGMA INDEX_INFO ('{$keyName}')");
203 6
                $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC);
204
205 6
                foreach ($indexArray as $indexColumnRow) {
206 6
                    $idx['column_name'] = $indexColumnRow['name'];
207 8
                    $indexBuffer[] = $idx;
208
                }
209
            }
210
        }
211
212 33
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218
    protected function _getPortableTableIndexDefinition($tableIndex)
219
    {
220
        return [
221
            'name' => $tableIndex['name'],
222
            'unique' => (bool) $tableIndex['unique']
223
        ];
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229 40
    protected function _getPortableTableColumnList($table, $database, $tableColumns)
230
    {
231 40
        $list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
232
233
        // find column with autoincrement
234 40
        $autoincrementColumn = null;
235 40
        $autoincrementCount = 0;
236
237 40
        foreach ($tableColumns as $tableColumn) {
238 40
            if ('0' != $tableColumn['pk']) {
239 24
                $autoincrementCount++;
240 24
                if (null === $autoincrementColumn && 'integer' == strtolower($tableColumn['type'])) {
241 40
                    $autoincrementColumn = $tableColumn['name'];
242
                }
243
            }
244
        }
245
246 40
        if (1 == $autoincrementCount && null !== $autoincrementColumn) {
247 23
            foreach ($list as $column) {
248 23
                if ($autoincrementColumn == $column->getName()) {
249 23
                    $column->setAutoincrement(true);
250
                }
251
            }
252
        }
253
254
        // inspect column collation and comments
255 40
        $createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'");
256 40
        $createSql = isset($createSql[0]['sql']) ? $createSql[0]['sql'] : '';
257
258 40
        foreach ($list as $columnName => $column) {
259 40
            $type = $column->getType();
260
261 40
            if ($type instanceof StringType || $type instanceof TextType) {
262 16
                $column->setPlatformOption('collation', $this->parseColumnCollationFromSQL($columnName, $createSql) ?: 'BINARY');
263
            }
264
265 40
            $comment = $this->parseColumnCommentFromSQL($columnName, $createSql);
266
267 40
            if (false !== $comment) {
268 16
                $type = $this->extractDoctrineTypeFromComment($comment, null);
269
270 16
                if (null !== $type) {
271 4
                    $column->setType(Type::getType($type));
272
273 4
                    $comment = $this->removeDoctrineTypeFromComment($comment, $type);
274
                }
275
276 40
                $column->setComment($comment);
277
            }
278
        }
279
280 40
        return $list;
281
    }
282
283
    /**
284
     * {@inheritdoc}
285
     */
286 40
    protected function _getPortableTableColumnDefinition($tableColumn)
287
    {
288 40
        $parts = explode('(', $tableColumn['type']);
289 40
        $tableColumn['type'] = trim($parts[0]);
290 40
        if (isset($parts[1])) {
291 12
            $length = trim($parts[1], ')');
292 12
            $tableColumn['length'] = $length;
293
        }
294
295 40
        $dbType = strtolower($tableColumn['type']);
296 40
        $length = isset($tableColumn['length']) ? $tableColumn['length'] : null;
297 40
        $unsigned = false;
298
299 40
        if (strpos($dbType, ' unsigned') !== false) {
300 1
            $dbType = str_replace(' unsigned', '', $dbType);
301 1
            $unsigned = true;
302
        }
303
304 40
        $fixed = false;
305 40
        $type = $this->_platform->getDoctrineTypeMapping($dbType);
306 40
        $default = $tableColumn['dflt_value'];
307 40
        if ($default == 'NULL') {
308 4
            $default = null;
309
        }
310 40
        if ($default !== null) {
311
            // SQLite returns strings wrapped in single quotes, so we need to strip them
312 6
            $default = preg_replace("/^'(.*)'$/", '\1', $default);
313
        }
314 40
        $notnull = (bool) $tableColumn['notnull'];
315
316 40
        if ( ! isset($tableColumn['name'])) {
317
            $tableColumn['name'] = '';
318
        }
319
320 40
        $precision = null;
321 40
        $scale = null;
322
323
        switch ($dbType) {
324 40
            case 'char':
325 4
                $fixed = true;
326 4
                break;
327 39
            case 'float':
328 39
            case 'double':
329 39
            case 'real':
330 39
            case 'decimal':
331 39
            case 'numeric':
332 4
                if (isset($tableColumn['length'])) {
333 4
                    if (strpos($tableColumn['length'], ',') === false) {
334
                        $tableColumn['length'] .= ",0";
335
                    }
336 4
                    list($precision, $scale) = array_map('trim', explode(',', $tableColumn['length']));
337
                }
338 4
                $length = null;
339 4
                break;
340
        }
341
342
        $options = [
343 40
            'length'   => $length,
344 40
            'unsigned' => (bool) $unsigned,
345 40
            'fixed'    => $fixed,
346 40
            'notnull'  => $notnull,
347 40
            'default'  => $default,
348 40
            'precision' => $precision,
349 40
            'scale'     => $scale,
350
            'autoincrement' => false,
351
        ];
352
353 40
        return new Column($tableColumn['name'], \Doctrine\DBAL\Types\Type::getType($type), $options);
354
    }
355
356
    /**
357
     * {@inheritdoc}
358
     */
359 1
    protected function _getPortableViewDefinition($view)
360
    {
361 1
        return new View($view['name'], $view['sql']);
362
    }
363
364
    /**
365
     * {@inheritdoc}
366
     */
367 1
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
368
    {
369 1
        $list = [];
370 1
        foreach ($tableForeignKeys as $value) {
371 1
            $value = array_change_key_case($value, CASE_LOWER);
372 1
            $name = $value['constraint_name'];
373 1
            if ( ! isset($list[$name])) {
374 1
                if ( ! isset($value['on_delete']) || $value['on_delete'] == "RESTRICT") {
375
                    $value['on_delete'] = null;
376
                }
377 1
                if ( ! isset($value['on_update']) || $value['on_update'] == "RESTRICT") {
378
                    $value['on_update'] = null;
379
                }
380
381 1
                $list[$name] = [
382 1
                    'name' => $name,
383
                    'local' => [],
384
                    'foreign' => [],
385 1
                    'foreignTable' => $value['table'],
386 1
                    'onDelete' => $value['on_delete'],
387 1
                    'onUpdate' => $value['on_update'],
388 1
                    'deferrable' => $value['deferrable'],
389 1
                    'deferred'=> $value['deferred'],
390
                ];
391
            }
392 1
            $list[$name]['local'][] = $value['from'];
393 1
            $list[$name]['foreign'][] = $value['to'];
394
        }
395
396 1
        $result = [];
397 1
        foreach ($list as $constraint) {
398 1
            $result[] = new ForeignKeyConstraint(
399 1
                array_values($constraint['local']), $constraint['foreignTable'],
400 1
                array_values($constraint['foreign']), $constraint['name'],
401
                [
402 1
                    'onDelete' => $constraint['onDelete'],
403 1
                    'onUpdate' => $constraint['onUpdate'],
404 1
                    'deferrable' => $constraint['deferrable'],
405 1
                    'deferred'=> $constraint['deferred'],
406
                ]
407
            );
408
        }
409
410 1
        return $result;
411
    }
412
413
    /**
414
     * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey
415
     * @param \Doctrine\DBAL\Schema\Table|string         $table
416
     *
417
     * @return \Doctrine\DBAL\Schema\TableDiff
418
     *
419
     * @throws \Doctrine\DBAL\DBALException
420
     */
421
    private function getTableDiffForAlterForeignKey(ForeignKeyConstraint $foreignKey, $table)
0 ignored issues
show
The parameter $foreignKey is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

421
    private function getTableDiffForAlterForeignKey(/** @scrutinizer ignore-unused */ ForeignKeyConstraint $foreignKey, $table)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
422
    {
423
        if ( ! $table instanceof Table) {
424
            $tableDetails = $this->tryMethod('listTableDetails', $table);
425
            if (false === $table) {
426
                throw new DBALException(sprintf('Sqlite schema manager requires to modify foreign keys table definition "%s".', $table));
427
            }
428
429
            $table = $tableDetails;
430
        }
431
432
        $tableDiff = new TableDiff($table->getName());
433
        $tableDiff->fromTable = $table;
0 ignored issues
show
Documentation Bug introduced by
It seems like $table can also be of type false. However, the property $fromTable is declared as type Doctrine\DBAL\Schema\Table. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
434
435
        return $tableDiff;
436
    }
437
438
    /**
439
     * @param string $column
440
     * @param string $sql
441
     *
442
     * @return string|false
443
     */
444 21
    private function parseColumnCollationFromSQL($column, $sql)
445
    {
446 21
        if (preg_match(
447 21
            '{(?:'.preg_quote($column).'|'.preg_quote($this->_platform->quoteSingleIdentifier($column)).')
448
                [^,(]+(?:\([^()]+\)[^,]*)?
449
                (?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*
450 21
                COLLATE\s+["\']?([^\s,"\')]+)}isx', $sql, $match)) {
451 7
            return $match[1];
452
        }
453
454 17
        return false;
455
    }
456
457
    /**
458
     * @param string $column
459
     * @param string $sql
460
     *
461
     * @return string|false
462
     */
463 40
    private function parseColumnCommentFromSQL($column, $sql)
464
    {
465 40
        if (preg_match(
466 40
            '{[\s(,](?:'.preg_quote($this->_platform->quoteSingleIdentifier($column)).'|'.preg_quote($column).')
467
            (?:\(.*?\)|[^,(])*?,?((?:\s*--[^\n]*\n?)+)
468 40
            }isx', $sql, $match
469
        )) {
470 16
            $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
471
472 16
            return '' === $comment ? false : $comment;
473
        }
474
475 35
        return false;
476
    }
477
}
478