Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Doctrine\DBAL\Schema;
4
 
5
use Doctrine\DBAL\DriverManager;
6
use Doctrine\DBAL\Exception;
7
use Doctrine\DBAL\Platforms\SQLite;
8
use Doctrine\DBAL\Platforms\SqlitePlatform;
9
use Doctrine\DBAL\Result;
10
use Doctrine\DBAL\Types\StringType;
11
use Doctrine\DBAL\Types\TextType;
12
use Doctrine\DBAL\Types\Type;
13
use Doctrine\Deprecations\Deprecation;
14
 
15
use function array_change_key_case;
16
use function array_map;
17
use function array_merge;
18
use function count;
19
use function explode;
20
use function file_exists;
21
use function implode;
22
use function preg_match;
23
use function preg_match_all;
24
use function preg_quote;
25
use function preg_replace;
26
use function rtrim;
27
use function str_replace;
28
use function strcasecmp;
29
use function strpos;
30
use function strtolower;
31
use function trim;
32
use function unlink;
33
use function usort;
34
 
35
use const CASE_LOWER;
36
 
37
/**
38
 * Sqlite SchemaManager.
39
 *
40
 * @extends AbstractSchemaManager<SqlitePlatform>
41
 */
42
class SqliteSchemaManager extends AbstractSchemaManager
43
{
44
    /**
45
     * {@inheritDoc}
46
     */
47
    public function listTableNames()
48
    {
49
        return $this->doListTableNames();
50
    }
51
 
52
    /**
53
     * {@inheritDoc}
54
     */
55
    public function listTables()
56
    {
57
        return $this->doListTables();
58
    }
59
 
60
    /**
61
     * {@inheritDoc}
62
     *
63
     * @deprecated Use {@see introspectTable()} instead.
64
     */
65
    public function listTableDetails($name)
66
    {
67
        Deprecation::triggerIfCalledFromOutside(
68
            'doctrine/dbal',
69
            'https://github.com/doctrine/dbal/pull/5595',
70
            '%s is deprecated. Use introspectTable() instead.',
71
            __METHOD__,
72
        );
73
 
74
        return $this->doListTableDetails($name);
75
    }
76
 
77
    /**
78
     * {@inheritDoc}
79
     */
80
    public function listTableColumns($table, $database = null)
81
    {
82
        return $this->doListTableColumns($table, $database);
83
    }
84
 
85
    /**
86
     * {@inheritDoc}
87
     */
88
    public function listTableIndexes($table)
89
    {
90
        return $this->doListTableIndexes($table);
91
    }
92
 
93
    /**
94
     * {@inheritDoc}
95
     */
96
    protected function fetchForeignKeyColumnsByTable(string $databaseName): array
97
    {
98
        $columnsByTable = parent::fetchForeignKeyColumnsByTable($databaseName);
99
 
100
        if (count($columnsByTable) > 0) {
101
            foreach ($columnsByTable as $table => $columns) {
102
                $columnsByTable[$table] = $this->addDetailsToTableForeignKeyColumns($table, $columns);
103
            }
104
        }
105
 
106
        return $columnsByTable;
107
    }
108
 
109
    /**
110
     * {@inheritdoc}
111
     *
112
     * @deprecated Delete the database file using the filesystem.
113
     */
114
    public function dropDatabase($database)
115
    {
116
        Deprecation::trigger(
117
            'doctrine/dbal',
118
            'https://github.com/doctrine/dbal/issues/4963',
119
            'SqliteSchemaManager::dropDatabase() is deprecated. Delete the database file using the filesystem.',
120
        );
121
 
122
        if (! file_exists($database)) {
123
            return;
124
        }
125
 
126
        unlink($database);
127
    }
128
 
129
    /**
130
     * {@inheritdoc}
131
     *
132
     * @deprecated The engine will create the database file automatically.
133
     */
134
    public function createDatabase($database)
135
    {
136
        Deprecation::trigger(
137
            'doctrine/dbal',
138
            'https://github.com/doctrine/dbal/issues/4963',
139
            'SqliteSchemaManager::createDatabase() is deprecated.'
140
                . ' The engine will create the database file automatically.',
141
        );
142
 
143
        $params = $this->_conn->getParams();
144
 
145
        $params['path'] = $database;
146
        unset($params['memory']);
147
 
148
        $conn = DriverManager::getConnection($params);
149
        $conn->connect();
150
        $conn->close();
151
    }
152
 
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
157
    {
158
        if (! $table instanceof Table) {
159
            $table = $this->listTableDetails($table);
160
        }
161
 
162
        $this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [$foreignKey]));
163
    }
164
 
165
    /**
166
     * {@inheritdoc}
167
     *
168
     * @deprecated Use {@see dropForeignKey()} and {@see createForeignKey()} instead.
169
     */
170
    public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
171
    {
172
        Deprecation::trigger(
173
            'doctrine/dbal',
174
            'https://github.com/doctrine/dbal/pull/4897',
175
            'SqliteSchemaManager::dropAndCreateForeignKey() is deprecated.'
176
                . ' Use SqliteSchemaManager::dropForeignKey() and SqliteSchemaManager::createForeignKey() instead.',
177
        );
178
 
179
        if (! $table instanceof Table) {
180
            $table = $this->listTableDetails($table);
181
        }
182
 
183
        $this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [], [$foreignKey]));
184
    }
185
 
186
    /**
187
     * {@inheritdoc}
188
     */
189
    public function dropForeignKey($foreignKey, $table)
190
    {
191
        if (! $table instanceof Table) {
192
            $table = $this->listTableDetails($table);
193
        }
194
 
195
        $this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [], [], [$foreignKey]));
196
    }
197
 
198
    /**
199
     * {@inheritdoc}
200
     */
201
    public function listTableForeignKeys($table, $database = null)
202
    {
203
        $table = $this->normalizeName($table);
204
 
205
        $columns = $this->selectForeignKeyColumns('', $table)
206
            ->fetchAllAssociative();
207
 
208
        if (count($columns) > 0) {
209
            $columns = $this->addDetailsToTableForeignKeyColumns($table, $columns);
210
        }
211
 
212
        return $this->_getPortableTableForeignKeysList($columns);
213
    }
214
 
215
    /**
216
     * {@inheritdoc}
217
     */
218
    protected function _getPortableTableDefinition($table)
219
    {
220
        return $table['table_name'];
221
    }
222
 
223
    /**
224
     * {@inheritdoc}
225
     *
226
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
227
     */
228
    protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
229
    {
230
        $indexBuffer = [];
231
 
232
        // fetch primary
233
        $indexArray = $this->_conn->fetchAllAssociative('SELECT * FROM PRAGMA_TABLE_INFO (?)', [$tableName]);
234
 
235
        usort(
236
            $indexArray,
237
            /**
238
             * @param array<string,mixed> $a
239
             * @param array<string,mixed> $b
240
             */
241
            static function (array $a, array $b): int {
242
                if ($a['pk'] === $b['pk']) {
243
                    return $a['cid'] - $b['cid'];
244
                }
245
 
246
                return $a['pk'] - $b['pk'];
247
            },
248
        );
249
 
250
        foreach ($indexArray as $indexColumnRow) {
251
            if ($indexColumnRow['pk'] === 0 || $indexColumnRow['pk'] === '0') {
252
                continue;
253
            }
254
 
255
            $indexBuffer[] = [
256
                'key_name' => 'primary',
257
                'primary' => true,
258
                'non_unique' => false,
259
                'column_name' => $indexColumnRow['name'],
260
            ];
261
        }
262
 
263
        // fetch regular indexes
264
        foreach ($tableIndexes as $tableIndex) {
265
            // Ignore indexes with reserved names, e.g. autoindexes
266
            if (strpos($tableIndex['name'], 'sqlite_') === 0) {
267
                continue;
268
            }
269
 
270
            $keyName           = $tableIndex['name'];
271
            $idx               = [];
272
            $idx['key_name']   = $keyName;
273
            $idx['primary']    = false;
274
            $idx['non_unique'] = ! $tableIndex['unique'];
275
 
276
            $indexArray = $this->_conn->fetchAllAssociative('SELECT * FROM PRAGMA_INDEX_INFO (?)', [$keyName]);
277
 
278
            foreach ($indexArray as $indexColumnRow) {
279
                $idx['column_name'] = $indexColumnRow['name'];
280
                $indexBuffer[]      = $idx;
281
            }
282
        }
283
 
284
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
285
    }
286
 
287
    /**
288
     * {@inheritdoc}
289
     */
290
    protected function _getPortableTableColumnList($table, $database, $tableColumns)
291
    {
292
        $list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
293
 
294
        // find column with autoincrement
295
        $autoincrementColumn = null;
296
        $autoincrementCount  = 0;
297
 
298
        foreach ($tableColumns as $tableColumn) {
299
            if ($tableColumn['pk'] === 0 || $tableColumn['pk'] === '0') {
300
                continue;
301
            }
302
 
303
            $autoincrementCount++;
304
            if ($autoincrementColumn !== null || strtolower($tableColumn['type']) !== 'integer') {
305
                continue;
306
            }
307
 
308
            $autoincrementColumn = $tableColumn['name'];
309
        }
310
 
311
        if ($autoincrementCount === 1 && $autoincrementColumn !== null) {
312
            foreach ($list as $column) {
313
                if ($autoincrementColumn !== $column->getName()) {
314
                    continue;
315
                }
316
 
317
                $column->setAutoincrement(true);
318
            }
319
        }
320
 
321
        // inspect column collation and comments
322
        $createSql = $this->getCreateTableSQL($table);
323
 
324
        foreach ($list as $columnName => $column) {
325
            $type = $column->getType();
326
 
327
            if ($type instanceof StringType || $type instanceof TextType) {
328
                $column->setPlatformOption(
329
                    'collation',
330
                    $this->parseColumnCollationFromSQL($columnName, $createSql) ?? 'BINARY',
331
                );
332
            }
333
 
334
            $comment = $this->parseColumnCommentFromSQL($columnName, $createSql);
335
 
336
            if ($comment === null) {
337
                continue;
338
            }
339
 
340
            $type = $this->extractDoctrineTypeFromComment($comment, '');
341
 
342
            if ($type !== '') {
343
                $column->setType(Type::getType($type));
344
 
345
                $comment = $this->removeDoctrineTypeFromComment($comment, $type);
346
            }
347
 
348
            $column->setComment($comment);
349
        }
350
 
351
        return $list;
352
    }
353
 
354
    /**
355
     * {@inheritdoc}
356
     */
357
    protected function _getPortableTableColumnDefinition($tableColumn)
358
    {
359
        $parts               = explode('(', $tableColumn['type']);
360
        $tableColumn['type'] = trim($parts[0]);
361
        if (isset($parts[1])) {
362
            $length                = trim($parts[1], ')');
363
            $tableColumn['length'] = $length;
364
        }
365
 
366
        $dbType   = strtolower($tableColumn['type']);
367
        $length   = $tableColumn['length'] ?? null;
368
        $unsigned = false;
369
 
370
        if (strpos($dbType, ' unsigned') !== false) {
371
            $dbType   = str_replace(' unsigned', '', $dbType);
372
            $unsigned = true;
373
        }
374
 
375
        $fixed   = false;
376
        $type    = $this->_platform->getDoctrineTypeMapping($dbType);
377
        $default = $tableColumn['dflt_value'];
378
        if ($default === 'NULL') {
379
            $default = null;
380
        }
381
 
382
        if ($default !== null) {
383
            // SQLite returns the default value as a literal expression, so we need to parse it
384
            if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) {
385
                $default = str_replace("''", "'", $matches[1]);
386
            }
387
        }
388
 
389
        $notnull = (bool) $tableColumn['notnull'];
390
 
391
        if (! isset($tableColumn['name'])) {
392
            $tableColumn['name'] = '';
393
        }
394
 
395
        $precision = null;
396
        $scale     = null;
397
 
398
        switch ($dbType) {
399
            case 'char':
400
                $fixed = true;
401
                break;
402
            case 'float':
403
            case 'double':
404
            case 'real':
405
            case 'decimal':
406
            case 'numeric':
407
                if (isset($tableColumn['length'])) {
408
                    if (strpos($tableColumn['length'], ',') === false) {
409
                        $tableColumn['length'] .= ',0';
410
                    }
411
 
412
                    [$precision, $scale] = array_map('trim', explode(',', $tableColumn['length']));
413
                }
414
 
415
                $length = null;
416
                break;
417
        }
418
 
419
        $options = [
420
            'length'   => $length,
421
            'unsigned' => $unsigned,
422
            'fixed'    => $fixed,
423
            'notnull'  => $notnull,
424
            'default'  => $default,
425
            'precision' => $precision,
426
            'scale'     => $scale,
427
            'autoincrement' => false,
428
        ];
429
 
430
        return new Column($tableColumn['name'], Type::getType($type), $options);
431
    }
432
 
433
    /**
434
     * {@inheritdoc}
435
     */
436
    protected function _getPortableViewDefinition($view)
437
    {
438
        return new View($view['name'], $view['sql']);
439
    }
440
 
441
    /**
442
     * {@inheritdoc}
443
     */
444
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
445
    {
446
        $list = [];
447
        foreach ($tableForeignKeys as $value) {
448
            $value = array_change_key_case($value, CASE_LOWER);
449
            $id    = $value['id'];
450
            if (! isset($list[$id])) {
451
                if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') {
452
                    $value['on_delete'] = null;
453
                }
454
 
455
                if (! isset($value['on_update']) || $value['on_update'] === 'RESTRICT') {
456
                    $value['on_update'] = null;
457
                }
458
 
459
                $list[$id] = [
460
                    'name' => $value['constraint_name'],
461
                    'local' => [],
462
                    'foreign' => [],
463
                    'foreignTable' => $value['table'],
464
                    'onDelete' => $value['on_delete'],
465
                    'onUpdate' => $value['on_update'],
466
                    'deferrable' => $value['deferrable'],
467
                    'deferred' => $value['deferred'],
468
                ];
469
            }
470
 
471
            $list[$id]['local'][] = $value['from'];
472
 
473
            if ($value['to'] === null) {
474
                continue;
475
            }
476
 
477
            $list[$id]['foreign'][] = $value['to'];
478
        }
479
 
480
        return parent::_getPortableTableForeignKeysList($list);
481
    }
482
 
483
    /**
484
     * {@inheritDoc}
485
     */
486
    protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint
487
    {
488
        return new ForeignKeyConstraint(
489
            $tableForeignKey['local'],
490
            $tableForeignKey['foreignTable'],
491
            $tableForeignKey['foreign'],
492
            $tableForeignKey['name'],
493
            [
494
                'onDelete' => $tableForeignKey['onDelete'],
495
                'onUpdate' => $tableForeignKey['onUpdate'],
496
                'deferrable' => $tableForeignKey['deferrable'],
497
                'deferred' => $tableForeignKey['deferred'],
498
            ],
499
        );
500
    }
501
 
502
    private function parseColumnCollationFromSQL(string $column, string $sql): ?string
503
    {
504
        $pattern = '{(?:\W' . preg_quote($column) . '\W|\W'
505
            . preg_quote($this->_platform->quoteSingleIdentifier($column))
506
            . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';
507
 
508
        if (preg_match($pattern, $sql, $match) !== 1) {
509
            return null;
510
        }
511
 
512
        return $match[1];
513
    }
514
 
515
    private function parseTableCommentFromSQL(string $table, string $sql): ?string
516
    {
517
        $pattern = '/\s* # Allow whitespace characters at start of line
518
CREATE\sTABLE # Match "CREATE TABLE"
519
(?:\W"' . preg_quote($this->_platform->quoteSingleIdentifier($table), '/') . '"\W|\W' . preg_quote($table, '/')
520
            . '\W) # Match table name (quoted and unquoted)
521
( # Start capture
522
   (?:\s*--[^\n]*\n?)+ # Capture anything that starts with whitespaces followed by -- until the end of the line(s)
523
)/ix';
524
 
525
        if (preg_match($pattern, $sql, $match) !== 1) {
526
            return null;
527
        }
528
 
529
        $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
530
 
531
        return $comment === '' ? null : $comment;
532
    }
533
 
534
    private function parseColumnCommentFromSQL(string $column, string $sql): ?string
535
    {
536
        $pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column))
537
            . '\W|\W' . preg_quote($column) . '\W)(?:\([^)]*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';
538
 
539
        if (preg_match($pattern, $sql, $match) !== 1) {
540
            return null;
541
        }
542
 
543
        $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
544
 
545
        return $comment === '' ? null : $comment;
546
    }
547
 
548
    /** @throws Exception */
549
    private function getCreateTableSQL(string $table): string
550
    {
551
        $sql = $this->_conn->fetchOne(
552
            <<<'SQL'
553
SELECT sql
554
  FROM (
555
      SELECT *
556
        FROM sqlite_master
557
   UNION ALL
558
      SELECT *
559
        FROM sqlite_temp_master
560
  )
561
WHERE type = 'table'
562
AND name = ?
563
SQL
564
            ,
565
            [$table],
566
        );
567
 
568
        if ($sql !== false) {
569
            return $sql;
570
        }
571
 
572
        return '';
573
    }
574
 
575
    /**
576
     * @param list<array<string,mixed>> $columns
577
     *
578
     * @return list<array<string,mixed>>
579
     *
580
     * @throws Exception
581
     */
582
    private function addDetailsToTableForeignKeyColumns(string $table, array $columns): array
583
    {
584
        $foreignKeyDetails = $this->getForeignKeyDetails($table);
585
        $foreignKeyCount   = count($foreignKeyDetails);
586
 
587
        foreach ($columns as $i => $column) {
588
            // SQLite identifies foreign keys in reverse order of appearance in SQL
589
            $columns[$i] = array_merge($column, $foreignKeyDetails[$foreignKeyCount - $column['id'] - 1]);
590
        }
591
 
592
        return $columns;
593
    }
594
 
595
    /**
596
     * @param string $table
597
     *
598
     * @return list<array<string, mixed>>
599
     *
600
     * @throws Exception
601
     */
602
    private function getForeignKeyDetails($table)
603
    {
604
        $createSql = $this->getCreateTableSQL($table);
605
 
606
        if (
607
            preg_match_all(
608
                '#
609
                    (?:CONSTRAINT\s+(\S+)\s+)?
610
                    (?:FOREIGN\s+KEY[^)]+\)\s*)?
611
                    REFERENCES\s+\S+\s*(?:\([^)]+\))?
612
                    (?:
613
                        [^,]*?
614
                        (NOT\s+DEFERRABLE|DEFERRABLE)
615
                        (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))?
616
                    )?#isx',
617
                $createSql,
618
                $match,
619
            ) === 0
620
        ) {
621
            return [];
622
        }
623
 
624
        $names      = $match[1];
625
        $deferrable = $match[2];
626
        $deferred   = $match[3];
627
        $details    = [];
628
 
629
        for ($i = 0, $count = count($match[0]); $i < $count; $i++) {
630
            $details[] = [
631
                'constraint_name' => isset($names[$i]) && $names[$i] !== '' ? $names[$i] : null,
632
                'deferrable'      => isset($deferrable[$i]) && strcasecmp($deferrable[$i], 'deferrable') === 0,
633
                'deferred'        => isset($deferred[$i]) && strcasecmp($deferred[$i], 'deferred') === 0,
634
            ];
635
        }
636
 
637
        return $details;
638
    }
639
 
640
    public function createComparator(): Comparator
641
    {
642
        return new SQLite\Comparator($this->_platform);
643
    }
644
 
645
    /**
646
     * {@inheritDoc}
647
     *
648
     * @deprecated
649
     */
650
    public function getSchemaSearchPaths()
651
    {
652
        Deprecation::triggerIfCalledFromOutside(
653
            'doctrine/dbal',
654
            'https://github.com/doctrine/dbal/pull/4821',
655
            'SqliteSchemaManager::getSchemaSearchPaths() is deprecated.',
656
        );
657
 
658
        // SQLite does not support schemas or databases
659
        return [];
660
    }
661
 
662
    protected function selectTableNames(string $databaseName): Result
663
    {
664
        $sql = <<<'SQL'
665
SELECT name AS table_name
666
FROM sqlite_master
667
WHERE type = 'table'
668
  AND name != 'sqlite_sequence'
669
  AND name != 'geometry_columns'
670
  AND name != 'spatial_ref_sys'
671
UNION ALL
672
SELECT name
673
FROM sqlite_temp_master
674
WHERE type = 'table'
675
ORDER BY name
676
SQL;
677
 
678
        return $this->_conn->executeQuery($sql);
679
    }
680
 
681
    protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
682
    {
683
        $sql = <<<'SQL'
684
            SELECT t.name AS table_name,
685
                   c.*
686
              FROM sqlite_master t
687
              JOIN pragma_table_info(t.name) c
688
SQL;
689
 
690
        $conditions = [
691
            "t.type = 'table'",
692
            "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
693
        ];
694
        $params     = [];
695
 
696
        if ($tableName !== null) {
697
            $conditions[] = 't.name = ?';
698
            $params[]     = str_replace('.', '__', $tableName);
699
        }
700
 
701
        $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, c.cid';
702
 
703
        return $this->_conn->executeQuery($sql, $params);
704
    }
705
 
706
    protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
707
    {
708
        $sql = <<<'SQL'
709
            SELECT t.name AS table_name,
710
                   i.*
711
              FROM sqlite_master t
712
              JOIN pragma_index_list(t.name) i
713
SQL;
714
 
715
        $conditions = [
716
            "t.type = 'table'",
717
            "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
718
        ];
719
        $params     = [];
720
 
721
        if ($tableName !== null) {
722
            $conditions[] = 't.name = ?';
723
            $params[]     = str_replace('.', '__', $tableName);
724
        }
725
 
726
        $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, i.seq';
727
 
728
        return $this->_conn->executeQuery($sql, $params);
729
    }
730
 
731
    protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
732
    {
733
        $sql = <<<'SQL'
734
            SELECT t.name AS table_name,
735
                   p.*
736
              FROM sqlite_master t
737
              JOIN pragma_foreign_key_list(t.name) p
738
                ON p."seq" != "-1"
739
SQL;
740
 
741
        $conditions = [
742
            "t.type = 'table'",
743
            "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
744
        ];
745
        $params     = [];
746
 
747
        if ($tableName !== null) {
748
            $conditions[] = 't.name = ?';
749
            $params[]     = str_replace('.', '__', $tableName);
750
        }
751
 
752
        $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, p.id DESC, p.seq';
753
 
754
        return $this->_conn->executeQuery($sql, $params);
755
    }
756
 
757
    /**
758
     * {@inheritDoc}
759
     */
760
    protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
761
    {
762
        if ($tableName === null) {
763
            $tables = $this->listTableNames();
764
        } else {
765
            $tables = [$tableName];
766
        }
767
 
768
        $tableOptions = [];
769
        foreach ($tables as $table) {
770
            $comment = $this->parseTableCommentFromSQL($table, $this->getCreateTableSQL($table));
771
 
772
            if ($comment === null) {
773
                continue;
774
            }
775
 
776
            $tableOptions[$table]['comment'] = $comment;
777
        }
778
 
779
        return $tableOptions;
780
    }
781
}