Passed
Pull Request — master (#3025)
by Michael
19:27 queued 26s
created

SqliteSchemaManager::dropAndCreateForeignKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
ccs 0
cts 4
cp 0
cc 1
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 99
    protected function _getPortableTableDefinition($table)
176
    {
177 99
        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 67
    protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
187
    {
188 67
        $indexBuffer = [];
189
190
        // fetch primary
191 67
        $stmt = $this->_conn->executeQuery("PRAGMA TABLE_INFO ('$tableName')");
192 67
        $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 67
        });
201 67
        foreach ($indexArray as $indexColumnRow) {
202 67
            if ($indexColumnRow['pk'] != "0") {
203 55
                $indexBuffer[] = [
204 55
                    'key_name' => 'primary',
205
                    'primary' => true,
206
                    'non_unique' => false,
207 67
                    'column_name' => $indexColumnRow['name']
208
                ];
209
            }
210
        }
211
212
        // fetch regular indexes
213 67
        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 67
        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 74
    protected function _getPortableTableColumnList($table, $database, $tableColumns)
250
    {
251 74
        $list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
252
253
        // find column with autoincrement
254 74
        $autoincrementColumn = null;
255 74
        $autoincrementCount = 0;
256
257 74
        foreach ($tableColumns as $tableColumn) {
258 74
            if ('0' != $tableColumn['pk']) {
259 58
                $autoincrementCount++;
260 58
                if (null === $autoincrementColumn && 'integer' == strtolower($tableColumn['type'])) {
261 74
                    $autoincrementColumn = $tableColumn['name'];
262
                }
263
            }
264
        }
265
266 74
        if (1 == $autoincrementCount && null !== $autoincrementColumn) {
0 ignored issues
show
introduced by
The condition null !== $autoincrementColumn is always false.
Loading history...
267 57
            foreach ($list as $column) {
268 57
                if ($autoincrementColumn == $column->getName()) {
269 57
                    $column->setAutoincrement(true);
270
                }
271
            }
272
        }
273
274
        // inspect column collation and comments
275 74
        $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 74
        $createSql = $createSql[0]['sql'] ?? '';
277
278 74
        foreach ($list as $columnName => $column) {
279 74
            $type = $column->getType();
280
281 74
            if ($type instanceof StringType || $type instanceof TextType) {
282 16
                $column->setPlatformOption('collation', $this->parseColumnCollationFromSQL($columnName, $createSql) ?: 'BINARY');
283
            }
284
285 74
            $comment = $this->parseColumnCommentFromSQL($columnName, $createSql);
286
287 74
            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 74
                $column->setComment($comment);
297
            }
298
        }
299
300 74
        return $list;
301
    }
302
303
    /**
304
     * {@inheritdoc}
305
     */
306 74
    protected function _getPortableTableColumnDefinition($tableColumn)
307
    {
308 74
        $parts = explode('(', $tableColumn['type']);
309 74
        $tableColumn['type'] = trim($parts[0]);
310 74
        if (isset($parts[1])) {
311 12
            $length = trim($parts[1], ')');
312 12
            $tableColumn['length'] = $length;
313
        }
314
315 74
        $dbType   = strtolower($tableColumn['type']);
316 74
        $length   = $tableColumn['length'] ?? null;
317 74
        $unsigned = false;
318
319 74
        if (strpos($dbType, ' unsigned') !== false) {
320 1
            $dbType = str_replace(' unsigned', '', $dbType);
321 1
            $unsigned = true;
322
        }
323
324 74
        $fixed = false;
325 74
        $type = $this->_platform->getDoctrineTypeMapping($dbType);
326 74
        $default = $tableColumn['dflt_value'];
327 74
        if ($default == 'NULL') {
328 4
            $default = null;
329
        }
330 74
        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 74
        $notnull = (bool) $tableColumn['notnull'];
335
336 74
        if ( ! isset($tableColumn['name'])) {
337
            $tableColumn['name'] = '';
338
        }
339
340 74
        $precision = null;
341 74
        $scale = null;
342
343 2
        switch ($dbType) {
344 74
            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 73
            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 73
            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 73
            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 73
            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 73
            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 74
            'length'   => $length,
364 74
            'unsigned' => (bool) $unsigned,
365 74
            'fixed'    => $fixed,
366 74
            'notnull'  => $notnull,
367 74
            'default'  => $default,
368 74
            'precision' => $precision,
369 74
            'scale'     => $scale,
370
            'autoincrement' => false,
371
        ];
372
373 74
        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 304
    private function parseColumnCollationFromSQL(string $column, string $sql) : ?string
459
    {
460 304
        $pattern = '{(?:\W' . preg_quote($column) . '\W|\W' . preg_quote($this->_platform->quoteSingleIdentifier($column))
461 304
            . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';
462
463 304
        if (preg_match($pattern, $sql, $match) !== 1) {
464 106
            return null;
465
        }
466
467 201
        return $match[1];
468
    }
469
470 470
    private function parseColumnCommentFromSQL(string $column, string $sql) : ?string
471
    {
472 470
        $pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) . '\W|\W' . preg_quote($column)
473 470
            . '\W)(?:\(.*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';
474
475 470
        if (preg_match($pattern, $sql, $match) !== 1) {
476 267
            return null;
477
        }
478
479 214
        $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
480
481 214
        return '' === $comment ? null : $comment;
482
    }
483
}
484