Passed
Pull Request — master (#3183)
by Sergei
15:27
created

SqliteSchemaManager::dropForeignKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 4
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
crap 2
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\FetchMode;
24
use Doctrine\DBAL\Types\StringType;
25
use Doctrine\DBAL\Types\TextType;
26
use Doctrine\DBAL\Types\Type;
27
use const CASE_LOWER;
28
use function array_change_key_case;
29
use function array_map;
30
use function array_reverse;
31
use function array_values;
32
use function explode;
33
use function file_exists;
34
use function preg_match;
35
use function preg_match_all;
36
use function preg_quote;
37
use function preg_replace;
38
use function rtrim;
39
use function sprintf;
40
use function str_replace;
41
use function strpos;
42
use function strtolower;
43
use function trim;
44
use function unlink;
45
use function usort;
46
47
/**
48
 * Sqlite SchemaManager.
49
 *
50
 * @author Konsta Vesterinen <[email protected]>
51
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
52
 * @author Jonathan H. Wage <[email protected]>
53
 * @author Martin Hasoň <[email protected]>
54
 * @since  2.0
55
 */
56
class SqliteSchemaManager extends AbstractSchemaManager
57
{
58
    /**
59
     * {@inheritdoc}
60
     */
61 2
    public function dropDatabase($database)
62
    {
63 2
        if (file_exists($database)) {
64 2
            unlink($database);
65
        }
66 2
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71 2
    public function createDatabase($database)
72
    {
73 2
        $params = $this->_conn->getParams();
74 2
        $driver = $params['driver'];
75
        $options = [
76 2
            'driver' => $driver,
77 2
            'path' => $database
78
        ];
79 2
        $conn = \Doctrine\DBAL\DriverManager::getConnection($options);
80 2
        $conn->connect();
81 2
        $conn->close();
82 2
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87 1
    public function renameTable($name, $newName)
88
    {
89 1
        $tableDiff = new TableDiff($name);
90 1
        $tableDiff->fromTable = $this->listTableDetails($name);
91 1
        $tableDiff->newName = $newName;
92 1
        $this->alterTable($tableDiff);
93 1
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
99
    {
100
        $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
101
        $tableDiff->addedForeignKeys[] = $foreignKey;
102
103
        $this->alterTable($tableDiff);
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
110
    {
111
        $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
112
        $tableDiff->changedForeignKeys[] = $foreignKey;
113
114
        $this->alterTable($tableDiff);
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function dropForeignKey($foreignKey, $table)
121
    {
122
        $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
123
        $tableDiff->removedForeignKeys[] = $foreignKey;
124
125
        $this->alterTable($tableDiff);
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131 1
    public function listTableForeignKeys($table, $database = null)
132
    {
133 1
        if (null === $database) {
134 1
            $database = $this->_conn->getDatabase();
135
        }
136 1
        $sql = $this->_platform->getListTableForeignKeysSQL($table, $database);
0 ignored issues
show
Unused Code introduced by
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

136
        /** @scrutinizer ignore-call */ 
137
        $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...
137 1
        $tableForeignKeys = $this->_conn->fetchAll($sql);
138
139 1
        if ( ! empty($tableForeignKeys)) {
140 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'");
141 1
            $createSql = $createSql[0]['sql'] ?? '';
142
143 1
            if (preg_match_all('#
144
                    (?:CONSTRAINT\s+([^\s]+)\s+)?
145
                    (?:FOREIGN\s+KEY[^\)]+\)\s*)?
146
                    REFERENCES\s+[^\s]+\s+(?:\([^\)]+\))?
147
                    (?:
148
                        [^,]*?
149
                        (NOT\s+DEFERRABLE|DEFERRABLE)
150
                        (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))?
151
                    )?#isx',
152 1
                    $createSql, $match)) {
153
154 1
                $names = array_reverse($match[1]);
155 1
                $deferrable = array_reverse($match[2]);
156 1
                $deferred = array_reverse($match[3]);
157
            } else {
158
                $names = $deferrable = $deferred = [];
159
            }
160
161 1
            foreach ($tableForeignKeys as $key => $value) {
162 1
                $id = $value['id'];
163 1
                $tableForeignKeys[$key]['constraint_name'] = isset($names[$id]) && '' != $names[$id] ? $names[$id] : $id;
164 1
                $tableForeignKeys[$key]['deferrable'] = isset($deferrable[$id]) && 'deferrable' == strtolower($deferrable[$id]) ? true : false;
165 1
                $tableForeignKeys[$key]['deferred'] = isset($deferred[$id]) && 'deferred' == strtolower($deferred[$id]) ? true : false;
166
            }
167
        }
168
169 1
        return $this->_getPortableTableForeignKeysList($tableForeignKeys);
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175 103
    protected function _getPortableTableDefinition($table)
176
    {
177 103
        return $table['name'];
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     *
183
     * @license New BSD License
184
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
185
     */
186 71
    protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
187
    {
188 71
        $indexBuffer = [];
189
190
        // fetch primary
191 71
        $stmt = $this->_conn->executeQuery("PRAGMA TABLE_INFO ('$tableName')");
192 71
        $indexArray = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
193
194
        usort($indexArray, function($a, $b) {
195 22
            if ($a['pk'] == $b['pk']) {
196 18
                return $a['cid'] - $b['cid'];
197
            }
198
199 12
            return $a['pk'] - $b['pk'];
200 71
        });
201 71
        foreach ($indexArray as $indexColumnRow) {
202 71
            if ($indexColumnRow['pk'] != "0") {
203 59
                $indexBuffer[] = [
204 59
                    'key_name' => 'primary',
205
                    'primary' => true,
206
                    'non_unique' => false,
207 71
                    'column_name' => $indexColumnRow['name']
208
                ];
209
            }
210
        }
211
212
        // fetch regular indexes
213 71
        foreach ($tableIndexes as $tableIndex) {
214
            // Ignore indexes with reserved names, e.g. autoindexes
215 8
            if (strpos($tableIndex['name'], 'sqlite_') !== 0) {
216 6
                $keyName = $tableIndex['name'];
217 6
                $idx = [];
218 6
                $idx['key_name'] = $keyName;
219 6
                $idx['primary'] = false;
220 6
                $idx['non_unique'] = $tableIndex['unique']?false:true;
221
222 6
                $stmt = $this->_conn->executeQuery("PRAGMA INDEX_INFO ('{$keyName}')");
223 6
                $indexArray = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
224
225 6
                foreach ($indexArray as $indexColumnRow) {
226 6
                    $idx['column_name'] = $indexColumnRow['name'];
227 8
                    $indexBuffer[] = $idx;
228
                }
229
            }
230
        }
231
232 71
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    protected function _getPortableTableIndexDefinition($tableIndex)
239
    {
240
        return [
241
            'name' => $tableIndex['name'],
242
            'unique' => (bool) $tableIndex['unique']
243
        ];
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249 78
    protected function _getPortableTableColumnList($table, $database, $tableColumns)
250
    {
251 78
        $list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
252
253
        // find column with autoincrement
254 78
        $autoincrementColumn = null;
255 78
        $autoincrementCount = 0;
256
257 78
        foreach ($tableColumns as $tableColumn) {
258 78
            if ('0' != $tableColumn['pk']) {
259 62
                $autoincrementCount++;
260 62
                if (null === $autoincrementColumn && 'integer' == strtolower($tableColumn['type'])) {
261 78
                    $autoincrementColumn = $tableColumn['name'];
262
                }
263
            }
264
        }
265
266 78
        if (1 == $autoincrementCount && null !== $autoincrementColumn) {
0 ignored issues
show
introduced by
The condition null !== $autoincrementColumn is always false.
Loading history...
267 61
            foreach ($list as $column) {
268 61
                if ($autoincrementColumn == $column->getName()) {
269 61
                    $column->setAutoincrement(true);
270
                }
271
            }
272
        }
273
274
        // inspect column collation and comments
275 78
        $createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'");
276 78
        $createSql = $createSql[0]['sql'] ?? '';
277
278 78
        foreach ($list as $columnName => $column) {
279 78
            $type = $column->getType();
280
281 78
            if ($type instanceof StringType || $type instanceof TextType) {
282 16
                $column->setPlatformOption('collation', $this->parseColumnCollationFromSQL($columnName, $createSql) ?: 'BINARY');
283
            }
284
285 78
            $comment = $this->parseColumnCommentFromSQL($columnName, $createSql);
286
287 78
            if ($comment !== null) {
288 16
                $type = $this->extractDoctrineTypeFromComment($comment, null);
289
290 16
                if (null !== $type) {
291 4
                    $column->setType(Type::getType($type));
292
293 4
                    $comment = $this->removeDoctrineTypeFromComment($comment, $type);
294
                }
295
296 78
                $column->setComment($comment);
297
            }
298
        }
299
300 78
        return $list;
301
    }
302
303
    /**
304
     * {@inheritdoc}
305
     */
306 78
    protected function _getPortableTableColumnDefinition($tableColumn)
307
    {
308 78
        $parts = explode('(', $tableColumn['type']);
309 78
        $tableColumn['type'] = trim($parts[0]);
310 78
        if (isset($parts[1])) {
311 12
            $length = trim($parts[1], ')');
312 12
            $tableColumn['length'] = $length;
313
        }
314
315 78
        $dbType   = strtolower($tableColumn['type']);
316 78
        $length   = $tableColumn['length'] ?? null;
317 78
        $unsigned = false;
318
319 78
        if (strpos($dbType, ' unsigned') !== false) {
320 1
            $dbType = str_replace(' unsigned', '', $dbType);
321 1
            $unsigned = true;
322
        }
323
324 78
        $fixed = false;
325 78
        $type = $this->_platform->getDoctrineTypeMapping($dbType);
326 78
        $default = $tableColumn['dflt_value'];
327 78
        if ($default == 'NULL') {
328 4
            $default = null;
329
        }
330 78
        if ($default !== null) {
331
            // SQLite returns strings wrapped in single quotes, so we need to strip them
332 6
            $default = preg_replace("/^'(.*)'$/", '\1', $default);
333
        }
334 78
        $notnull = (bool) $tableColumn['notnull'];
335
336 78
        if ( ! isset($tableColumn['name'])) {
337
            $tableColumn['name'] = '';
338
        }
339
340 78
        $precision = null;
341 78
        $scale = null;
342
343 2
        switch ($dbType) {
344 78
            case 'char':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
345 3
                $fixed = true;
346 3
                break;
347 77
            case 'float':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
348 77
            case 'double':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
349 77
            case 'real':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
350 77
            case 'decimal':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
351 77
            case 'numeric':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
352 4
                if (isset($tableColumn['length'])) {
353 4
                    if (strpos($tableColumn['length'], ',') === false) {
354
                        $tableColumn['length'] .= ",0";
355
                    }
356 4
                    list($precision, $scale) = array_map('trim', explode(',', $tableColumn['length']));
357
                }
358 4
                $length = null;
359 4
                break;
360
        }
361
362
        $options = [
363 78
            'length'   => $length,
364 78
            'unsigned' => (bool) $unsigned,
365 78
            'fixed'    => $fixed,
366 78
            'notnull'  => $notnull,
367 78
            'default'  => $default,
368 78
            'precision' => $precision,
369 78
            'scale'     => $scale,
370
            'autoincrement' => false,
371
        ];
372
373 78
        return new Column($tableColumn['name'], \Doctrine\DBAL\Types\Type::getType($type), $options);
374
    }
375
376
    /**
377
     * {@inheritdoc}
378
     */
379 1
    protected function _getPortableViewDefinition($view)
380
    {
381 1
        return new View($view['name'], $view['sql']);
382
    }
383
384
    /**
385
     * {@inheritdoc}
386
     */
387 1
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
388
    {
389 1
        $list = [];
390 1
        foreach ($tableForeignKeys as $value) {
391 1
            $value = array_change_key_case($value, CASE_LOWER);
392 1
            $name = $value['constraint_name'];
393 1
            if ( ! isset($list[$name])) {
394 1
                if ( ! isset($value['on_delete']) || $value['on_delete'] == "RESTRICT") {
395
                    $value['on_delete'] = null;
396
                }
397 1
                if ( ! isset($value['on_update']) || $value['on_update'] == "RESTRICT") {
398
                    $value['on_update'] = null;
399
                }
400
401 1
                $list[$name] = [
402 1
                    'name' => $name,
403
                    'local' => [],
404
                    'foreign' => [],
405 1
                    'foreignTable' => $value['table'],
406 1
                    'onDelete' => $value['on_delete'],
407 1
                    'onUpdate' => $value['on_update'],
408 1
                    'deferrable' => $value['deferrable'],
409 1
                    'deferred'=> $value['deferred'],
410
                ];
411
            }
412 1
            $list[$name]['local'][] = $value['from'];
413 1
            $list[$name]['foreign'][] = $value['to'];
414
        }
415
416 1
        $result = [];
417 1
        foreach ($list as $constraint) {
418 1
            $result[] = new ForeignKeyConstraint(
419 1
                array_values($constraint['local']), $constraint['foreignTable'],
420 1
                array_values($constraint['foreign']), $constraint['name'],
421
                [
422 1
                    'onDelete' => $constraint['onDelete'],
423 1
                    'onUpdate' => $constraint['onUpdate'],
424 1
                    'deferrable' => $constraint['deferrable'],
425 1
                    'deferred'=> $constraint['deferred'],
426
                ]
427
            );
428
        }
429
430 1
        return $result;
431
    }
432
433
    /**
434
     * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey
435
     * @param \Doctrine\DBAL\Schema\Table|string         $table
436
     *
437
     * @return \Doctrine\DBAL\Schema\TableDiff
438
     *
439
     * @throws \Doctrine\DBAL\DBALException
440
     */
441
    private function getTableDiffForAlterForeignKey(ForeignKeyConstraint $foreignKey, $table)
0 ignored issues
show
Unused Code introduced by
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

441
    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...
442
    {
443
        if ( ! $table instanceof Table) {
444
            $tableDetails = $this->tryMethod('listTableDetails', $table);
445
            if (false === $table) {
0 ignored issues
show
introduced by
The condition false === $table is always false.
Loading history...
446
                throw new DBALException(sprintf('Sqlite schema manager requires to modify foreign keys table definition "%s".', $table));
447
            }
448
449
            $table = $tableDetails;
450
        }
451
452
        $tableDiff = new TableDiff($table->getName());
453
        $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...
454
455
        return $tableDiff;
456
    }
457
458 336
    private function parseColumnCollationFromSQL(string $column, string $sql) : ?string
459
    {
460 336
        $pattern = '{(?:\W' . preg_quote($column) . '\W|\W' . preg_quote($this->_platform->quoteSingleIdentifier($column))
461 336
            . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';
462
463 336
        if (preg_match($pattern, $sql, $match) !== 1) {
464 116
            return null;
465
        }
466
467 223
        return $match[1];
468
    }
469
470 518
    private function parseColumnCommentFromSQL(string $column, string $sql) : ?string
471
    {
472 518
        $pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) . '\W|\W' . preg_quote($column)
473 518
            . '\W)(?:\(.*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';
474
475 518
        if (preg_match($pattern, $sql, $match) !== 1) {
476 293
            return null;
477
        }
478
479 236
        $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
480
481 236
        return '' === $comment ? null : $comment;
482
    }
483
}
484