Passed
Push — master ( f4eca7...1ebc33 )
by Def
10:04
created

Schema::getTableChecks()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Schema;
6
7
use PDO;
8
use PDOException;
9
use Yiisoft\Cache\Dependency\TagDependency;
10
use Yiisoft\Db\Cache\SchemaCache;
11
use Yiisoft\Db\Connection\ConnectionInterface;
12
use Yiisoft\Db\Constraint\CheckConstraint;
13
use Yiisoft\Db\Constraint\Constraint;
14
use Yiisoft\Db\Constraint\DefaultValueConstraint;
15
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
16
use Yiisoft\Db\Constraint\IndexConstraint;
17
use Yiisoft\Db\Exception\Exception;
18
use Yiisoft\Db\Exception\IntegrityException;
19
use Yiisoft\Db\Exception\InvalidCallException;
20
use Yiisoft\Db\Exception\NotSupportedException;
21
use Yiisoft\Db\Query\QueryBuilder;
22
23
use function addcslashes;
24
use function array_change_key_case;
25
use function array_key_exists;
26
use function array_map;
27
use function explode;
28
use function gettype;
29
use function implode;
30
use function is_array;
31
use function is_string;
32
use function md5;
33
use function preg_match;
34
use function preg_replace;
35
use function serialize;
36
use function str_replace;
37
use function strlen;
38
use function strpos;
39
use function substr;
40
41
/**
42
 * Schema is the base class for concrete DBMS-specific schema classes.
43
 *
44
 * Schema represents the database schema information that is DBMS specific.
45
 *
46
 * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the sequence
47
 * object. This property is read-only.
48
 * @property QueryBuilder $queryBuilder The query builder for this connection. This property is read-only.
49
 * @property string[] $schemaNames All schema names in the database, except system schemas. This property is read-only.
50
 * @property string $serverVersion Server version as a string. This property is read-only.
51
 * @property string[] $tableNames All table names in the database. This property is read-only.
52
 * @property TableSchema[] $tableSchemas The metadata for all tables in the database. Each array element is an instance
53
 * of {@see TableSchema} or its child class. This property is read-only.
54
 * @property string $transactionIsolationLevel The transaction isolation level to use for this transaction. This can be
55
 * one of {@see Transaction::READ_UNCOMMITTED}, {@see Transaction::READ_COMMITTED},
56
 * {@see Transaction::REPEATABLE_READ} and {@see Transaction::SERIALIZABLE} but also a string containing DBMS specific
57
 * syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. This property is write-only.
58
 * @property CheckConstraint[] $schemaChecks Check constraints for all tables in the database. Each array element is an
59
 * array of {@see CheckConstraint} or its child classes. This property is read-only.
60
 * @property DefaultValueConstraint[] $schemaDefaultValues Default value constraints for all tables in the database.
61
 * Each array element is an array of {@see DefaultValueConstraint} or its child classes. This property is read-only.
62
 * @property ForeignKeyConstraint[] $schemaForeignKeys Foreign keys for all tables in the database. Each array element
63
 * is an array of {@see ForeignKeyConstraint} or its child classes. This property is read-only.
64
 * @property IndexConstraint[] $schemaIndexes Indexes for all tables in the database. Each array element is an array of
65
 * {@see IndexConstraint} or its child classes. This property is read-only.
66
 * @property Constraint[] $schemaPrimaryKeys Primary keys for all tables in the database. Each array element is an
67
 * instance of {@see Constraint} or its child class. This property is read-only.
68
 * @property IndexConstraint[] $schemaUniques Unique constraints for all tables in the database. Each array element is
69
 * an array of {@see IndexConstraint} or its child classes. This property is read-only.
70
 */
71
abstract class Schema implements SchemaInterface
72
{
73
    /**
74
     * Schema cache version, to detect incompatibilities in cached values when the data format of the cache changes.
75
     */
76
    protected const SCHEMA_CACHE_VERSION = 1;
77
78
    /**
79
     * @var string|null the default schema name used for the current session.
80
     */
81
    protected ?string $defaultSchema = null;
82
83
    /**
84
     * @var array map of DB errors and corresponding exceptions. If left part is found in DB error message exception
85
     * class from the right part is used.
86
     */
87
    protected array $exceptionMap = [
88
        'SQLSTATE[23' => IntegrityException::class,
89
    ];
90
91
    /**
92
     * @var string|string[] character used to quote schema, table, etc. names. An array of 2 characters can be used in
93
     * case starting and ending characters are different.
94
     */
95
    protected $tableQuoteCharacter = "'";
96
97
    /**
98
     * @var string|string[] character used to quote column names. An array of 2 characters can be used in case starting
99
     * and ending characters are different.
100
     */
101
    protected $columnQuoteCharacter = '"';
102
    private array $schemaNames = [];
103
    private array $tableNames = [];
104
    private array $tableMetadata = [];
105
    private ?string $serverVersion = null;
106
    private ConnectionInterface $db;
107
    private ?QueryBuilder $builder = null;
108
    private SchemaCache $schemaCache;
109
110 2532
    public function __construct(ConnectionInterface $db, SchemaCache $schemaCache)
111
    {
112 2532
        $this->db = $db;
113 2532
        $this->schemaCache = $schemaCache;
114 2532
    }
115
116
    abstract public function createQueryBuilder(): QueryBuilder;
117
118
    /**
119
     * @inheritDoc
120
     */
121 963
    public function getQueryBuilder(): QueryBuilder
122
    {
123 963
        if ($this->builder === null) {
124 963
            $this->builder = $this->createQueryBuilder();
125
        }
126
127 963
        return $this->builder;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->builder could return the type null which is incompatible with the type-hinted return Yiisoft\Db\Query\QueryBuilder. Consider adding an additional type-check to rule them out.
Loading history...
128
    }
129
130 1665
    public function getDb(): ConnectionInterface
131
    {
132 1665
        return $this->db;
133
    }
134
135
    public function getDefaultSchema(): ?string
136
    {
137
        return $this->defaultSchema;
138
    }
139
140 18
    public function getSchemaCache(): SchemaCache
141
    {
142 18
        return $this->schemaCache;
143
    }
144
145
    /**
146
     * @inheritDoc
147
     */
148 1322
    public function getTableSchema(string $name, bool $refresh = false): ?TableSchema
149
    {
150 1322
        return $this->getTableMetadata($name, self::SCHEMA, $refresh);
151
    }
152
153
    /**
154
     * @inheritDoc
155
     */
156 13
    public function getTableSchemas(string $schema = '', bool $refresh = false): array
157
    {
158 13
        return $this->getSchemaMetadata($schema, self::SCHEMA, $refresh);
0 ignored issues
show
introduced by
The expression return $this->getSchemaM...self::SCHEMA, $refresh) returns an array which contains values of type Yiisoft\Db\Constraint\Constraint|array which are incompatible with the return type Yiisoft\Db\Schema\TableSchema mandated by Yiisoft\Db\Schema\Schema...face::getTableSchemas().
Loading history...
159
    }
160
161
    /**
162
     * @inheritDoc
163
     */
164 4
    public function getSchemaNames(bool $refresh = false): array
165
    {
166 4
        if (empty($this->schemaNames) || $refresh) {
167 4
            $this->schemaNames = $this->findSchemaNames();
168
        }
169
170 4
        return $this->schemaNames;
171
    }
172
173
    /**
174
     * @inheritDoc
175
     */
176 23
    public function getTableNames(string $schema = '', bool $refresh = false): array
177
    {
178 23
        if (!isset($this->tableNames[$schema]) || $refresh) {
179 23
            $this->tableNames[$schema] = $this->findTableNames($schema);
180
        }
181
182 23
        return $this->tableNames[$schema];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->tableNames[$schema] returns the type string which is incompatible with the type-hinted return array.
Loading history...
183
    }
184
185
    /**
186
     * @inheritDoc
187
     */
188 1360
    public function getPdoType($data): int
189
    {
190 1360
        static $typeMap = [
191
            // php type => PDO type
192
            'boolean' => PDO::PARAM_BOOL,
193
            'integer' => PDO::PARAM_INT,
194
            'string' => PDO::PARAM_STR,
195
            'resource' => PDO::PARAM_LOB,
196
            'NULL' => PDO::PARAM_NULL,
197
        ];
198
199 1360
        $type = gettype($data);
200
201 1360
        return $typeMap[$type] ?? PDO::PARAM_STR;
202
    }
203
204
    /**
205
     * @inheritDoc
206
     */
207 96
    public function refresh(): void
208
    {
209 96
        if ($this->schemaCache->isEnabled()) {
210 96
            $this->schemaCache->invalidate($this->getCacheTag());
211
        }
212
213 96
        $this->tableNames = [];
214 96
        $this->tableMetadata = [];
215 96
    }
216
217
    /**
218
     * @inheritDoc
219
     */
220 103
    public function refreshTableSchema(string $name): void
221
    {
222 103
        $rawName = $this->getRawTableName($name);
223
224 103
        unset($this->tableMetadata[$rawName]);
225
226 103
        $this->tableNames = [];
227
228 103
        if ($this->schemaCache->isEnabled()) {
229 103
            $this->schemaCache->remove($this->getCacheKey($rawName));
230
        }
231 103
    }
232
233
    /**
234
     * @inheritDoc
235
     */
236 36
    public function getLastInsertID(string $sequenceName = ''): string
237
    {
238 36
        if ($this->db->isActive()) {
0 ignored issues
show
Bug introduced by
The method isActive() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

238
        if ($this->db->/** @scrutinizer ignore-call */ isActive()) {
Loading history...
239 36
            return $this->db->getPDO()->lastInsertId(
0 ignored issues
show
Bug introduced by
The method getPDO() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

239
            return $this->db->/** @scrutinizer ignore-call */ getPDO()->lastInsertId(
Loading history...
240 36
                $sequenceName === '' ? null : $this->quoteTableName($sequenceName)
241
            );
242
        }
243
244
        throw new InvalidCallException('DB Connection is not active.');
245
    }
246
247
    /**
248
     * @inheritDoc
249
     */
250 10
    public function supportsSavepoint(): bool
251
    {
252 10
        return $this->db->isSavepointEnabled();
0 ignored issues
show
Bug introduced by
The method isSavepointEnabled() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

252
        return $this->db->/** @scrutinizer ignore-call */ isSavepointEnabled();
Loading history...
253
    }
254
255
    /**
256
     * @inheritDoc
257
     */
258 4
    public function createSavepoint(string $name): void
259
    {
260 4
        $this->db->createCommand("SAVEPOINT $name")->execute();
261 4
    }
262
263
    /**
264
     * @inheritDoc
265
     */
266
    public function releaseSavepoint(string $name): void
267
    {
268
        $this->db->createCommand("RELEASE SAVEPOINT $name")->execute();
269
    }
270
271
    /**
272
     * @inheritDoc
273
     */
274 4
    public function rollBackSavepoint(string $name): void
275
    {
276 4
        $this->db->createCommand("ROLLBACK TO SAVEPOINT $name")->execute();
277 4
    }
278
279
    /**
280
     * @inheritDoc
281
     */
282 8
    public function setTransactionIsolationLevel(string $level): void
283
    {
284 8
        $this->db->createCommand("SET TRANSACTION ISOLATION LEVEL $level")->execute();
285 8
    }
286
287
    /**
288
     * @inheritDoc
289
     */
290 28
    public function insert(string $table, array $columns)
291
    {
292 28
        $command = $this->db->createCommand()->insert($table, $columns);
293
294 28
        if (!$command->execute()) {
295
            return false;
296
        }
297
298 28
        $tableSchema = $this->getTableSchema($table);
299 28
        $result = [];
300
301 28
        foreach ($tableSchema->getPrimaryKey() as $name) {
302 26
            if ($tableSchema->getColumn($name)->isAutoIncrement()) {
303 24
                $result[$name] = $this->getLastInsertID($tableSchema->getSequenceName());
0 ignored issues
show
Bug introduced by
It seems like $tableSchema->getSequenceName() can also be of type null; however, parameter $sequenceName of Yiisoft\Db\Schema\Schema::getLastInsertID() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

303
                $result[$name] = $this->getLastInsertID(/** @scrutinizer ignore-type */ $tableSchema->getSequenceName());
Loading history...
304 24
                break;
305
            }
306
307 4
            $result[$name] = $columns[$name] ?? $tableSchema->getColumn($name)->getDefaultValue();
308
        }
309
310 28
        return $result;
311
    }
312
313
    /**
314
     * @inheritDoc
315
     */
316 1075
    public function quoteValue($str)
317
    {
318 1075
        if (!is_string($str)) {
319 6
            return $str;
320
        }
321
322 1075
        if (($value = $this->db->getSlavePdo()->quote($str)) !== false) {
0 ignored issues
show
Bug introduced by
The method getSlavePdo() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

322
        if (($value = $this->db->/** @scrutinizer ignore-call */ getSlavePdo()->quote($str)) !== false) {
Loading history...
323 1075
            return $value;
324
        }
325
326
        /** the driver doesn't support quote (e.g. oci) */
327
        return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
328
    }
329
330
    /**
331
     * @inheritDoc
332
     */
333 1574
    public function quoteTableName(string $name): string
334
    {
335 1574
        if (strpos($name, '(') === 0 && strpos($name, ')') === strlen($name) - 1) {
336 4
            return $name;
337
        }
338
339 1574
        if (strpos($name, '{{') !== false) {
340 146
            return $name;
341
        }
342
343 1550
        if (strpos($name, '.') === false) {
344 1467
            return $this->quoteSimpleTableName($name);
345
        }
346
347 287
        $parts = $this->getTableNameParts($name);
348
349 287
        foreach ($parts as $i => $part) {
350 287
            $parts[$i] = $this->quoteSimpleTableName($part);
351
        }
352
353 287
        return implode('.', $parts);
354
    }
355
356
    /**
357
     * @inheritDoc
358
     */
359 1698
    public function quoteColumnName(string $name): string
360
    {
361 1698
        if (strpos($name, '(') !== false || strpos($name, '[[') !== false) {
362 149
            return $name;
363
        }
364
365 1683
        if (($pos = strrpos($name, '.')) !== false) {
366 232
            $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.';
367 232
            $name = substr($name, $pos + 1);
368
        } else {
369 1673
            $prefix = '';
370
        }
371
372 1683
        if (strpos($name, '{{') !== false) {
373 4
            return $name;
374
        }
375
376 1683
        return $prefix . $this->quoteSimpleColumnName($name);
377
    }
378
379
    /**
380
     * @inheritDoc
381
     */
382 1344
    public function quoteSimpleTableName(string $name): string
383
    {
384 1344
        if (is_string($this->tableQuoteCharacter)) {
385 987
            $startingCharacter = $endingCharacter = $this->tableQuoteCharacter;
386
        } else {
387 357
            [$startingCharacter, $endingCharacter] = $this->tableQuoteCharacter;
388
        }
389
390 1344
        return strpos($name, $startingCharacter) !== false ? $name : $startingCharacter . $name . $endingCharacter;
391
    }
392
393
    /**
394
     * @inheritDoc
395
     */
396 1683
    public function quoteSimpleColumnName(string $name): string
397
    {
398 1683
        if (is_string($this->columnQuoteCharacter)) {
399 1357
            $startingCharacter = $endingCharacter = $this->columnQuoteCharacter;
400
        } else {
401 326
            [$startingCharacter, $endingCharacter] = $this->columnQuoteCharacter;
402
        }
403
404 1683
        return $name === '*' || strpos($name, $startingCharacter) !== false ? $name : $startingCharacter . $name
405 1683
            . $endingCharacter;
406
    }
407
408
    /**
409
     * @inheritDoc
410
     */
411 5
    public function unquoteSimpleTableName(string $name): string
412
    {
413 5
        if (is_string($this->tableQuoteCharacter)) {
414 5
            $startingCharacter = $this->tableQuoteCharacter;
415
        } else {
416
            $startingCharacter = $this->tableQuoteCharacter[0];
417
        }
418
419 5
        return strpos($name, $startingCharacter) === false ? $name : substr($name, 1, -1);
420
    }
421
422
    /**
423
     * @inheritDoc
424
     */
425
    public function unquoteSimpleColumnName(string $name): string
426
    {
427
        if (is_string($this->columnQuoteCharacter)) {
428
            $startingCharacter = $this->columnQuoteCharacter;
429
        } else {
430
            $startingCharacter = $this->columnQuoteCharacter[0];
431
        }
432
433
        return strpos($name, $startingCharacter) === false ? $name : substr($name, 1, -1);
434
    }
435
436
    /**
437
     * @inheritDoc
438
     */
439 1622
    public function getRawTableName(string $name): string
440
    {
441 1622
        if (strpos($name, '{{') !== false) {
442 130
            $name = preg_replace('/{{(.*?)}}/', '\1', $name);
443
444 130
            return str_replace('%', $this->db->getTablePrefix(), $name);
0 ignored issues
show
Bug introduced by
The method getTablePrefix() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

444
            return str_replace('%', $this->db->/** @scrutinizer ignore-call */ getTablePrefix(), $name);
Loading history...
445
        }
446
447 1622
        return $name;
448
    }
449
450
    /**
451
     * @inheritDoc
452
     */
453 50
    public function convertException(\Exception $e, string $rawSql): Exception
454
    {
455 50
        if ($e instanceof Exception) {
456
            return $e;
457
        }
458
459 50
        $exceptionClass = Exception::class;
460
461 50
        foreach ($this->exceptionMap as $error => $class) {
462 50
            if (strpos($e->getMessage(), $error) !== false) {
463 13
                $exceptionClass = $class;
464
            }
465
        }
466
467 50
        $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
468 50
        $errorInfo = $e instanceof PDOException ? $e->errorInfo : null;
469
470 50
        return new $exceptionClass($message, $errorInfo, $e);
471
    }
472
473
    /**
474
     * @inheritDoc
475
     */
476 12
    public function isReadQuery(string $sql): bool
477
    {
478 12
        $pattern = '/^\s*(SELECT|SHOW|DESCRIBE)\b/i';
479
480 12
        return preg_match($pattern, $sql) > 0;
481
    }
482
483
    /**
484
     * @inheritDoc
485
     */
486 375
    public function getServerVersion(): string
487
    {
488 375
        if ($this->serverVersion === null) {
489 375
            $this->serverVersion = $this->db->getSlavePdo()->getAttribute(PDO::ATTR_SERVER_VERSION);
490
        }
491
492 375
        return $this->serverVersion;
493
    }
494
495
    /**
496
     * @inheritDoc
497
     */
498 153
    public function getTablePrimaryKey(string $name, bool $refresh = false): ?Constraint
499
    {
500 153
        return $this->getTableMetadata($name, SchemaInterface::PRIMARY_KEY, $refresh);
501
    }
502
503
    /**
504
     * @inheritDoc
505
     */
506
    public function getSchemaPrimaryKeys(string $schema = '', bool $refresh = false): array
507
    {
508
        return $this->getSchemaMetadata($schema, SchemaInterface::PRIMARY_KEY, $refresh);
0 ignored issues
show
introduced by
The expression return $this->getSchemaM...:PRIMARY_KEY, $refresh) returns an array which contains values of type Yiisoft\Db\Schema\TableSchema|array which are incompatible with the return type Yiisoft\Db\Constraint\Constraint mandated by Yiisoft\Db\Schema\Schema...:getSchemaPrimaryKeys().
Loading history...
509
    }
510
511
    /**
512
     * @inheritDoc
513
     */
514 20
    public function getTableForeignKeys(string $name, bool $refresh = false): array
515
    {
516 20
        return $this->getTableMetadata($name, SchemaInterface::FOREIGN_KEYS, $refresh);
517
    }
518
519
    /**
520
     * @inheritDoc
521
     */
522
    public function getSchemaForeignKeys(string $schema = '', bool $refresh = false): array
523
    {
524
        return $this->getSchemaMetadata($schema, SchemaInterface::FOREIGN_KEYS, $refresh);
0 ignored issues
show
introduced by
The expression return $this->getSchemaM...FOREIGN_KEYS, $refresh) returns an array which contains values of type Yiisoft\Db\Constraint\Co...t\Db\Schema\TableSchema which are incompatible with the return type Yiisoft\Db\Constraint\ForeignKeyConstraint[] mandated by Yiisoft\Db\Schema\Schema...:getSchemaForeignKeys().
Loading history...
525
    }
526
527
    /**
528
     * @inheritDoc
529
     */
530 141
    public function getTableIndexes(string $name, bool $refresh = false): array
531
    {
532 141
        return $this->getTableMetadata($name, SchemaInterface::INDEXES, $refresh);
533
    }
534
535
    /**
536
     * @inheritDoc
537
     */
538
    public function getSchemaIndexes(string $schema = '', bool $refresh = false): array
539
    {
540
        return $this->getSchemaMetadata($schema, SchemaInterface::INDEXES, $refresh);
0 ignored issues
show
introduced by
The expression return $this->getSchemaM...ace::INDEXES, $refresh) returns an array which contains values of type Yiisoft\Db\Constraint\Co...t\Db\Schema\TableSchema which are incompatible with the return type Yiisoft\Db\Constraint\IndexConstraint[] mandated by Yiisoft\Db\Schema\Schema...ace::getSchemaIndexes().
Loading history...
541
    }
542
543
    /**
544
     * @inheritDoc
545
     */
546 156
    public function getTableUniques(string $name, bool $refresh = false): array
547
    {
548 156
        return $this->getTableMetadata($name, SchemaInterface::UNIQUES, $refresh);
549
    }
550
551
    /**
552
     * @inheritDoc
553
     */
554
    public function getSchemaUniques(string $schema = '', bool $refresh = false): array
555
    {
556
        return $this->getSchemaMetadata($schema, SchemaInterface::UNIQUES, $refresh);
0 ignored issues
show
introduced by
The expression return $this->getSchemaM...ace::UNIQUES, $refresh) returns an array which contains values of type Yiisoft\Db\Constraint\Co...t\Db\Schema\TableSchema which are incompatible with the return type Yiisoft\Db\Constraint\Constraint[] mandated by Yiisoft\Db\Schema\Schema...ace::getSchemaUniques().
Loading history...
557
    }
558
559
    /**
560
     * @inheritDoc
561
     */
562 63
    public function getTableChecks(string $name, bool $refresh = false): array
563
    {
564 63
        return $this->getTableMetadata($name, SchemaInterface::CHECKS, $refresh);
565
    }
566
567
    /**
568
     * @inheritDoc
569
     */
570
    public function getSchemaChecks(string $schema = '', bool $refresh = false): array
571
    {
572
        return $this->getSchemaMetadata($schema, SchemaInterface::CHECKS, $refresh);
0 ignored issues
show
introduced by
The expression return $this->getSchemaM...face::CHECKS, $refresh) returns an array which contains values of type Yiisoft\Db\Constraint\Co...t\Db\Schema\TableSchema which are incompatible with the return type Yiisoft\Db\Constraint\CheckConstraint[] mandated by Yiisoft\Db\Schema\Schema...face::getSchemaChecks().
Loading history...
573
    }
574
575
    /**
576
     * @inheritDoc
577
     */
578 61
    public function getTableDefaultValues(string $name, bool $refresh = false): array
579
    {
580 61
        return $this->getTableMetadata($name, SchemaInterface::DEFAULT_VALUES, $refresh);
581
    }
582
583
    /**
584
     * @inheritDoc
585
     */
586
    public function getSchemaDefaultValues(string $schema = '', bool $refresh = false): array
587
    {
588
        return $this->getSchemaMetadata($schema, SchemaInterface::DEFAULT_VALUES, $refresh);
0 ignored issues
show
introduced by
The expression return $this->getSchemaM...FAULT_VALUES, $refresh) returns an array which contains values of type Yiisoft\Db\Schema\TableSchema|array which are incompatible with the return type Yiisoft\Db\Constraint\DefaultValueConstraint mandated by Yiisoft\Db\Schema\Schema...etSchemaDefaultValues().
Loading history...
589
    }
590
591
    /**
592
     * Resolves the table name and schema name (if any).
593
     *
594
     * @param string $name the table name.
595
     *
596
     * @throws NotSupportedException if this method is not supported by the DBMS.
597
     *
598
     * @return TableSchema with resolved table, schema, etc. names.
599
     *
600
     * {@see \Yiisoft\Db\Schema\TableSchema}
601
     */
602
    protected function resolveTableName(string $name): TableSchema
0 ignored issues
show
Unused Code introduced by
The parameter $name 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

602
    protected function resolveTableName(/** @scrutinizer ignore-unused */ string $name): TableSchema

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...
603
    {
604
        throw new NotSupportedException(static::class . ' does not support resolving table names.');
605
    }
606
607
    /**
608
     * Returns all schema names in the database, including the default one but not system schemas.
609
     *
610
     * This method should be overridden by child classes in order to support this feature because the default
611
     * implementation simply throws an exception.
612
     *
613
     * @throws NotSupportedException if this method is not supported by the DBMS.
614
     *
615
     * @return array all schema names in the database, except system schemas.
616
     */
617
    protected function findSchemaNames(): array
618
    {
619
        throw new NotSupportedException(static::class . ' does not support fetching all schema names.');
620
    }
621
622
    /**
623
     * Returns all table names in the database.
624
     *
625
     * This method should be overridden by child classes in order to support this feature because the default
626
     * implementation simply throws an exception.
627
     *
628
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
629
     *
630
     * @throws NotSupportedException if this method is not supported by the DBMS.
631
     *
632
     * @return array all table names in the database. The names have NO schema name prefix.
633
     */
634
    protected function findTableNames(string $schema = ''): array
0 ignored issues
show
Unused Code introduced by
The parameter $schema 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

634
    protected function findTableNames(/** @scrutinizer ignore-unused */ string $schema = ''): array

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...
635
    {
636
        throw new NotSupportedException(static::class . ' does not support fetching all table names.');
637
    }
638
639
    /**
640
     * Loads the metadata for the specified table.
641
     *
642
     * @param string $name table name.
643
     *
644
     * @return TableSchema|null DBMS-dependent table metadata, `null` if the table does not exist.
645
     */
646
    abstract protected function loadTableSchema(string $name): ?TableSchema;
647
648
    /**
649
     * Splits full table name into parts
650
     *
651
     * @param string $name
652
     *
653
     * @return array
654
     */
655 13
    protected function getTableNameParts(string $name): array
656
    {
657 13
        return explode('.', $name);
658
    }
659
660
    /**
661
     * Extracts the PHP type from abstract DB type.
662
     *
663
     * @param ColumnSchema $column the column schema information.
664
     *
665
     * @return string PHP type name.
666
     */
667 1280
    protected function getColumnPhpType(ColumnSchema $column): string
668
    {
669 1280
        static $typeMap = [
670
            // abstract type => php type
671
            self::TYPE_TINYINT => 'integer',
672
            self::TYPE_SMALLINT => 'integer',
673
            self::TYPE_INTEGER => 'integer',
674
            self::TYPE_BIGINT => 'integer',
675
            self::TYPE_BOOLEAN => 'boolean',
676
            self::TYPE_FLOAT => 'double',
677
            self::TYPE_DOUBLE => 'double',
678
            self::TYPE_BINARY => 'resource',
679
            self::TYPE_JSON => 'array',
680
        ];
681
682 1280
        if (isset($typeMap[$column->getType()])) {
683 1269
            if ($column->getType() === 'bigint') {
684 52
                return PHP_INT_SIZE === 8 && !$column->isUnsigned() ? 'integer' : 'string';
685
            }
686
687 1269
            if ($column->getType() === 'integer') {
688 1269
                return PHP_INT_SIZE === 4 && $column->isUnsigned() ? 'string' : 'integer';
689
            }
690
691 367
            return $typeMap[$column->getType()];
692
        }
693
694 1218
        return 'string';
695
    }
696
697
    /**
698
     * Returns the cache key for the specified table name.
699
     *
700
     * @param string $name the table name.
701
     *
702
     * @return array the cache key.
703
     */
704 1622
    protected function getCacheKey(string $name): array
705
    {
706
        return [
707 1622
            __CLASS__,
708 1622
            $this->db->getDsn(),
709 1622
            $this->db->getUsername(),
0 ignored issues
show
Bug introduced by
The method getUsername() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

709
            $this->db->/** @scrutinizer ignore-call */ 
710
                       getUsername(),
Loading history...
710 1622
            $this->getRawTableName($name),
711
        ];
712
    }
713
714
    /**
715
     * Returns the cache tag name.
716
     *
717
     * This allows {@see refresh()} to invalidate all cached table schemas.
718
     *
719
     * @return string the cache tag name.
720
     */
721 1622
    protected function getCacheTag(): string
722
    {
723 1622
        return md5(serialize([
724 1622
            __CLASS__,
725 1622
            $this->db->getDsn(),
726 1622
            $this->db->getUsername(),
727
        ]));
728
    }
729
730
    /**
731
     * Returns the metadata of the given type for the given table.
732
     *
733
     * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
734
     * @param string $type metadata type.
735
     * @param bool $refresh whether to reload the table metadata even if it is found in the cache.
736
     *
737
     * @return mixed metadata.
738
     */
739 1622
    protected function getTableMetadata(string $name, string $type, bool $refresh = false)
740
    {
741 1622
        $rawName = $this->getRawTableName($name);
742
743 1622
        if (!isset($this->tableMetadata[$rawName])) {
744 1622
            $this->loadTableMetadataFromCache($rawName);
745
        }
746
747 1622
        if ($refresh || !array_key_exists($type, $this->tableMetadata[$rawName])) {
748 1622
            $this->tableMetadata[$rawName][$type] = $this->loadTableTypeMetadata($type, $rawName);
749 1562
            $this->saveTableMetadataToCache($rawName);
750
        }
751
752 1562
        return $this->tableMetadata[$rawName][$type];
753
    }
754
755
    /**
756
     * Returns the metadata of the given type for all tables in the given schema.
757
     *
758
     * @param string $schema the schema of the metadata. Defaults to empty string, meaning the current or default schema
759
     * name.
760
     * @param string $type metadata type.
761
     * @param bool $refresh whether to fetch the latest available table metadata. If this is `false`, cached data may be
762
     * returned if available.
763
     *
764
     * @throws NotSupportedException
765
     *
766
     * @return array array of metadata.
767
     */
768 13
    protected function getSchemaMetadata(string $schema, string $type, bool $refresh): array
769
    {
770 13
        $metadata = [];
771
772 13
        foreach ($this->getTableNames($schema, $refresh) as $name) {
773 13
            if ($schema !== '') {
774
                $name = $schema . '.' . $name;
775
            }
776
777 13
            $tableMetadata = $this->getTableTypeMetadata($type, $name, $refresh);
778
779 13
            if ($tableMetadata !== null) {
780 13
                $metadata[] = $tableMetadata;
781
            }
782
        }
783
784 13
        return $metadata;
785
    }
786
787
    /**
788
     * Sets the metadata of the given type for the given table.
789
     *
790
     * @param string $name table name.
791
     * @param string $type metadata type.
792
     * @param mixed $data metadata.
793
     */
794 297
    protected function setTableMetadata(string $name, string $type, $data): void
795
    {
796 297
        $this->tableMetadata[$this->getRawTableName($name)][$type] = $data;
797 297
    }
798
799
    /**
800
     * Changes row's array key case to lower if PDO's one is set to uppercase.
801
     *
802
     * @param array $row row's array or an array of row's arrays.
803
     * @param bool $multiple whether multiple rows or a single row passed.
804
     *
805
     * @throws Exception
806
     *
807
     * @return array normalized row or rows.
808
     */
809 341
    protected function normalizePdoRowKeyCase(array $row, bool $multiple): array
810
    {
811 341
        if ($this->db->getSlavePdo()->getAttribute(PDO::ATTR_CASE) !== PDO::CASE_UPPER) {
812 265
            return $row;
813
        }
814
815 76
        if ($multiple) {
816 76
            return array_map(static function (array $row) {
817 73
                return array_change_key_case($row, CASE_LOWER);
818 76
            }, $row);
819
        }
820
821
        return array_change_key_case($row, CASE_LOWER);
822
    }
823
824
    /**
825
     * Tries to load and populate table metadata from cache.
826
     *
827
     * @param string $rawName
828
     */
829 1622
    private function loadTableMetadataFromCache(string $rawName): void
830
    {
831 1622
        if (!$this->schemaCache->isEnabled() || $this->schemaCache->isExcluded($rawName)) {
832
            $this->tableMetadata[$rawName] = [];
833
            return;
834
        }
835
836 1622
        $metadata = $this->schemaCache->getOrSet(
837 1622
            $this->getCacheKey($rawName),
838 1622
            null,
839 1622
            $this->schemaCache->getDuration(),
840 1622
            new TagDependency($this->getCacheTag()),
841
        );
842
843
        if (
844 1622
            !is_array($metadata) ||
845 1622
            !isset($metadata['cacheVersion']) ||
846 1622
            $metadata['cacheVersion'] !== static::SCHEMA_CACHE_VERSION
847
        ) {
848 1622
            $this->tableMetadata[$rawName] = [];
849
850 1622
            return;
851
        }
852
853 841
        unset($metadata['cacheVersion']);
854 841
        $this->tableMetadata[$rawName] = $metadata;
855 841
    }
856
857
    /**
858
     * Saves table metadata to cache.
859
     *
860
     * @param string $rawName
861
     */
862 1562
    private function saveTableMetadataToCache(string $rawName): void
863
    {
864 1562
        if ($this->schemaCache->isEnabled() === false || $this->schemaCache->isExcluded($rawName) === true) {
865
            return;
866
        }
867
868 1562
        $metadata = $this->tableMetadata[$rawName];
869
870 1562
        $metadata['cacheVersion'] = static::SCHEMA_CACHE_VERSION;
871
872 1562
        $this->schemaCache->set(
873 1562
            $this->getCacheKey($rawName),
874
            $metadata,
875 1562
            $this->schemaCache->getDuration(),
876 1562
            new TagDependency($this->getCacheTag()),
877
        );
878 1562
    }
879
880
    /**
881
     * This method returns the desired metadata type for the table name.
882
     *
883
     * @param string $type
884
     * @param string $name
885
     *
886
     * @return mixed
887
     */
888 1622
    protected function loadTableTypeMetadata(string $type, string $name)
889
    {
890
        switch ($type) {
891 1622
            case SchemaInterface::SCHEMA:
892 1322
                return $this->loadTableSchema($name);
893 413
            case SchemaInterface::PRIMARY_KEY:
894 153
                return $this->loadTablePrimaryKey($name);
895 332
            case SchemaInterface::UNIQUES:
896 66
                return $this->loadTableUniques($name);
897 267
            case SchemaInterface::FOREIGN_KEYS:
898 20
                return $this->loadTableForeignKeys($name);
899 247
            case SchemaInterface::INDEXES:
900 123
                return $this->loadTableIndexes($name);
901 124
            case SchemaInterface::DEFAULT_VALUES:
902 61
                return $this->loadTableDefaultValues($name);
903 63
            case SchemaInterface::CHECKS:
904 63
                return $this->loadTableChecks($name);
905
        }
906
907
        return null;
908
    }
909
910
    /**
911
     * This method returns the desired metadata type for table name (with refresh if needed)
912
     *
913
     * @param string $type
914
     * @param string $name
915
     * @param bool $refresh
916
     *
917
     * @return mixed
918
     */
919 13
    protected function getTableTypeMetadata(string $type, string $name, bool $refresh = false)
920
    {
921
        switch ($type) {
922 13
            case SchemaInterface::SCHEMA:
923 13
                return $this->getTableSchema($name, $refresh);
924
            case SchemaInterface::PRIMARY_KEY:
925
                return $this->getTablePrimaryKey($name, $refresh);
926
            case SchemaInterface::UNIQUES:
927
                return $this->getTableUniques($name, $refresh);
928
            case SchemaInterface::FOREIGN_KEYS:
929
                return $this->getTableForeignKeys($name, $refresh);
930
            case SchemaInterface::INDEXES:
931
                return $this->getTableIndexes($name, $refresh);
932
            case SchemaInterface::DEFAULT_VALUES:
933
                return $this->getTableDefaultValues($name, $refresh);
934
            case SchemaInterface::CHECKS:
935
                return $this->getTableChecks($name, $refresh);
936
        }
937
938
        return null;
939
    }
940
941
    /**
942
     * Loads a primary key for the given table.
943
     *
944
     * @param string $tableName table name.
945
     *
946
     * @return Constraint|null primary key for the given table, `null` if the table has no primary key.
947
     */
948
    abstract protected function loadTablePrimaryKey(string $tableName): ?Constraint;
949
950
    /**
951
     * Loads all foreign keys for the given table.
952
     *
953
     * @param string $tableName table name.
954
     *
955
     * @return array foreign keys for the given table.
956
     */
957
    abstract protected function loadTableForeignKeys(string $tableName): array;
958
959
    /**
960
     * Loads all indexes for the given table.
961
     *
962
     * @param string $tableName table name.
963
     *
964
     * @return array indexes for the given table.
965
     */
966
    abstract protected function loadTableIndexes(string $tableName): array;
967
968
    /**
969
     * Loads all unique constraints for the given table.
970
     *
971
     * @param string $tableName table name.
972
     *
973
     * @return array unique constraints for the given table.
974
     */
975
    abstract protected function loadTableUniques(string $tableName): array;
976
977
    /**
978
     * Loads all check constraints for the given table.
979
     *
980
     * @param string $tableName table name.
981
     *
982
     * @return array check constraints for the given table.
983
     */
984
    abstract protected function loadTableChecks(string $tableName): array;
985
986
    /**
987
     * Loads all default value constraints for the given table.
988
     *
989
     * @param string $tableName table name.
990
     *
991
     * @return array default value constraints for the given table.
992
     */
993
    abstract protected function loadTableDefaultValues(string $tableName): array;
994
}
995