GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( ecf3ef...78a151 )
by Robert
11:40
created

Schema::getServerVersion()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db;
9
10
use Yii;
11
use yii\base\BaseObject;
12
use yii\base\InvalidCallException;
13
use yii\base\InvalidConfigException;
14
use yii\base\NotSupportedException;
15
use yii\caching\Cache;
16
use yii\caching\CacheInterface;
17
use yii\caching\TagDependency;
18
use yii\helpers\StringHelper;
19
20
/**
21
 * Schema is the base class for concrete DBMS-specific schema classes.
22
 *
23
 * Schema represents the database schema information that is DBMS specific.
24
 *
25
 * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the
26
 * sequence object. This property is read-only.
27
 * @property QueryBuilder $queryBuilder The query builder for this connection. This property is read-only.
28
 * @property string[] $schemaNames All schema names in the database, except system schemas. This property is
29
 * read-only.
30
 * @property string[] $tableNames All table names in the database. This property is read-only.
31
 * @property TableSchema[] $tableSchemas The metadata for all tables in the database. Each array element is an
32
 * instance of [[TableSchema]] or its child class. This property is read-only.
33
 * @property string $transactionIsolationLevel The transaction isolation level to use for this transaction.
34
 * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]],
35
 * [[Transaction::REPEATABLE_READ]] and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific
36
 * syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. This property is write-only.
37
 *
38
 * @author Qiang Xue <[email protected]>
39
 * @author Sergey Makinen <[email protected]>
40
 * @since 2.0
41
 */
42
abstract class Schema extends BaseObject
43
{
44
    // The following are the supported abstract column data types.
45
    const TYPE_PK = 'pk';
46
    const TYPE_UPK = 'upk';
47
    const TYPE_BIGPK = 'bigpk';
48
    const TYPE_UBIGPK = 'ubigpk';
49
    const TYPE_CHAR = 'char';
50
    const TYPE_STRING = 'string';
51
    const TYPE_TEXT = 'text';
52
    const TYPE_SMALLINT = 'smallint';
53
    const TYPE_INTEGER = 'integer';
54
    const TYPE_BIGINT = 'bigint';
55
    const TYPE_FLOAT = 'float';
56
    const TYPE_DOUBLE = 'double';
57
    const TYPE_DECIMAL = 'decimal';
58
    const TYPE_DATETIME = 'datetime';
59
    const TYPE_TIMESTAMP = 'timestamp';
60
    const TYPE_TIME = 'time';
61
    const TYPE_DATE = 'date';
62
    const TYPE_BINARY = 'binary';
63
    const TYPE_BOOLEAN = 'boolean';
64
    const TYPE_MONEY = 'money';
65
    const TYPE_JSON = 'json';
66
    /**
67
     * Schema cache version, to detect incompatibilities in cached values when the
68
     * data format of the cache changes.
69
     */
70
    const SCHEMA_CACHE_VERSION = 1;
71
72
    /**
73
     * @var Connection the database connection
74
     */
75
    public $db;
76
    /**
77
     * @var string the default schema name used for the current session.
78
     */
79
    public $defaultSchema;
80
    /**
81
     * @var array map of DB errors and corresponding exceptions
82
     * If left part is found in DB error message exception class from the right part is used.
83
     */
84
    public $exceptionMap = [
85
        'SQLSTATE[23' => 'yii\db\IntegrityException',
86
    ];
87
    /**
88
     * @var string column schema class
89
     * @since 2.0.11
90
     */
91
    public $columnSchemaClass = 'yii\db\ColumnSchema';
92
93
    /**
94
     * @var string|string[] character used to quote schema, table, etc. names.
95
     * An array of 2 characters can be used in case starting and ending characters are different.
96
     * @since 2.0.14
97
     */
98
    protected $tableQuoteCharacter = "'";
99
    /**
100
     * @var string|string[] character used to quote column names.
101
     * An array of 2 characters can be used in case starting and ending characters are different.
102
     * @since 2.0.14
103
     */
104
    protected $columnQuoteCharacter = '"';
105
106
    /**
107
     * @var array list of ALL schema names in the database, except system schemas
108
     */
109
    private $_schemaNames;
110
    /**
111
     * @var array list of ALL table names in the database
112
     */
113
    private $_tableNames = [];
114
    /**
115
     * @var array list of loaded table metadata (table name => metadata type => metadata).
116
     */
117
    private $_tableMetadata = [];
118
    /**
119
     * @var QueryBuilder the query builder for this database
120
     */
121
    private $_builder;
122
    /**
123
     * @var string server version as a string.
124
     */
125
    private $_serverVersion;
126
127
128
    /**
129
     * Resolves the table name and schema name (if any).
130
     * @param string $name the table name
131
     * @return TableSchema [[TableSchema]] with resolved table, schema, etc. names.
132
     * @throws NotSupportedException if this method is not supported by the DBMS.
133
     * @since 2.0.13
134
     */
135
    protected function resolveTableName($name)
136
    {
137
        throw new NotSupportedException(get_class($this) . ' does not support resolving table names.');
138
    }
139
140
    /**
141
     * Returns all schema names in the database, including the default one but not system schemas.
142
     * This method should be overridden by child classes in order to support this feature
143
     * because the default implementation simply throws an exception.
144
     * @return array all schema names in the database, except system schemas.
145
     * @throws NotSupportedException if this method is not supported by the DBMS.
146
     * @since 2.0.4
147
     */
148
    protected function findSchemaNames()
149
    {
150
        throw new NotSupportedException(get_class($this) . ' does not support fetching all schema names.');
151
    }
152
153
    /**
154
     * Returns all table names in the database.
155
     * This method should be overridden by child classes in order to support this feature
156
     * because the default implementation simply throws an exception.
157
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
158
     * @return array all table names in the database. The names have NO schema name prefix.
159
     * @throws NotSupportedException if this method is not supported by the DBMS.
160
     */
161
    protected function findTableNames($schema = '')
162
    {
163
        throw new NotSupportedException(get_class($this) . ' does not support fetching all table names.');
164
    }
165
166
    /**
167
     * Loads the metadata for the specified table.
168
     * @param string $name table name
169
     * @return TableSchema|null DBMS-dependent table metadata, `null` if the table does not exist.
170
     */
171
    abstract protected function loadTableSchema($name);
172
173
    /**
174
     * Creates a column schema for the database.
175
     * This method may be overridden by child classes to create a DBMS-specific column schema.
176
     * @return ColumnSchema column schema instance.
177
     * @throws InvalidConfigException if a column schema class cannot be created.
178
     */
179 874
    protected function createColumnSchema()
180
    {
181 874
        return Yii::createObject($this->columnSchemaClass);
182
    }
183
184
    /**
185
     * Obtains the metadata for the named table.
186
     * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
187
     * @param bool $refresh whether to reload the table schema even if it is found in the cache.
188
     * @return TableSchema|null table metadata. `null` if the named table does not exist.
189
     */
190 954
    public function getTableSchema($name, $refresh = false)
191
    {
192 954
        return $this->getTableMetadata($name, 'schema', $refresh);
193
    }
194
195
    /**
196
     * Returns the metadata for all tables in the database.
197
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
198
     * @param bool $refresh whether to fetch the latest available table schemas. If this is `false`,
199
     * cached data may be returned if available.
200
     * @return TableSchema[] the metadata for all tables in the database.
201
     * Each array element is an instance of [[TableSchema]] or its child class.
202
     */
203 11
    public function getTableSchemas($schema = '', $refresh = false)
204
    {
205 11
        return $this->getSchemaMetadata($schema, 'schema', $refresh);
206
    }
207
208
    /**
209
     * Returns all schema names in the database, except system schemas.
210
     * @param bool $refresh whether to fetch the latest available schema names. If this is false,
211
     * schema names fetched previously (if available) will be returned.
212
     * @return string[] all schema names in the database, except system schemas.
213
     * @since 2.0.4
214
     */
215 2
    public function getSchemaNames($refresh = false)
216
    {
217 2
        if ($this->_schemaNames === null || $refresh) {
218 2
            $this->_schemaNames = $this->findSchemaNames();
219
        }
220
221 2
        return $this->_schemaNames;
222
    }
223
224
    /**
225
     * Returns all table names in the database.
226
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
227
     * If not empty, the returned table names will be prefixed with the schema name.
228
     * @param bool $refresh whether to fetch the latest available table names. If this is false,
229
     * table names fetched previously (if available) will be returned.
230
     * @return string[] all table names in the database.
231
     */
232 17
    public function getTableNames($schema = '', $refresh = false)
233
    {
234 17
        if (!isset($this->_tableNames[$schema]) || $refresh) {
235 17
            $this->_tableNames[$schema] = $this->findTableNames($schema);
236
        }
237
238 17
        return $this->_tableNames[$schema];
239
    }
240
241
    /**
242
     * @return QueryBuilder the query builder for this connection.
243
     */
244 938
    public function getQueryBuilder()
245
    {
246 938
        if ($this->_builder === null) {
247 884
            $this->_builder = $this->createQueryBuilder();
248
        }
249
250 938
        return $this->_builder;
251
    }
252
253
    /**
254
     * Determines the PDO type for the given PHP data value.
255
     * @param mixed $data the data whose PDO type is to be determined
256
     * @return int the PDO type
257
     * @see http://www.php.net/manual/en/pdo.constants.php
258
     */
259 988
    public function getPdoType($data)
260
    {
261 988
        static $typeMap = [
262
            // php type => PDO type
263
            'boolean' => \PDO::PARAM_BOOL,
264
            'integer' => \PDO::PARAM_INT,
265
            'string' => \PDO::PARAM_STR,
266
            'resource' => \PDO::PARAM_LOB,
267
            'NULL' => \PDO::PARAM_NULL,
268
        ];
269 988
        $type = gettype($data);
270
271 988
        return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
272
    }
273
274
    /**
275
     * Refreshes the schema.
276
     * This method cleans up all cached table schemas so that they can be re-created later
277
     * to reflect the database schema change.
278
     */
279 43
    public function refresh()
280
    {
281
        /* @var $cache CacheInterface */
282 43
        $cache = is_string($this->db->schemaCache) ? Yii::$app->get($this->db->schemaCache, false) : $this->db->schemaCache;
283 43
        if ($this->db->enableSchemaCache && $cache instanceof CacheInterface) {
284 1
            TagDependency::invalidate($cache, $this->getCacheTag());
285
        }
286 43
        $this->_tableNames = [];
287 43
        $this->_tableMetadata = [];
288 43
    }
289
290
    /**
291
     * Refreshes the particular table schema.
292
     * This method cleans up cached table schema so that it can be re-created later
293
     * to reflect the database schema change.
294
     * @param string $name table name.
295
     * @since 2.0.6
296
     */
297 161
    public function refreshTableSchema($name)
298
    {
299 161
        $rawName = $this->getRawTableName($name);
300 161
        unset($this->_tableMetadata[$rawName]);
301 161
        $this->_tableNames = [];
302
        /* @var $cache CacheInterface */
303 161
        $cache = is_string($this->db->schemaCache) ? Yii::$app->get($this->db->schemaCache, false) : $this->db->schemaCache;
304 161
        if ($this->db->enableSchemaCache && $cache instanceof CacheInterface) {
305 22
            $cache->delete($this->getCacheKey($rawName));
306
        }
307 161
    }
308
309
    /**
310
     * Creates a query builder for the database.
311
     * This method may be overridden by child classes to create a DBMS-specific query builder.
312
     * @return QueryBuilder query builder instance
313
     */
314
    public function createQueryBuilder()
315
    {
316
        return new QueryBuilder($this->db);
317
    }
318
319
    /**
320
     * Create a column schema builder instance giving the type and value precision.
321
     *
322
     * This method may be overridden by child classes to create a DBMS-specific column schema builder.
323
     *
324
     * @param string $type type of the column. See [[ColumnSchemaBuilder::$type]].
325
     * @param int|string|array $length length or precision of the column. See [[ColumnSchemaBuilder::$length]].
326
     * @return ColumnSchemaBuilder column schema builder instance
327
     * @since 2.0.6
328
     */
329 10
    public function createColumnSchemaBuilder($type, $length = null)
330
    {
331 10
        return new ColumnSchemaBuilder($type, $length);
332
    }
333
334
    /**
335
     * Returns all unique indexes for the given table.
336
     *
337
     * Each array element is of the following structure:
338
     *
339
     * ```php
340
     * [
341
     *  'IndexName1' => ['col1' [, ...]],
342
     *  'IndexName2' => ['col2' [, ...]],
343
     * ]
344
     * ```
345
     *
346
     * This method should be overridden by child classes in order to support this feature
347
     * because the default implementation simply throws an exception
348
     * @param TableSchema $table the table metadata
349
     * @return array all unique indexes for the given table.
350
     * @throws NotSupportedException if this method is called
351
     */
352
    public function findUniqueIndexes($table)
353
    {
354
        throw new NotSupportedException(get_class($this) . ' does not support getting unique indexes information.');
355
    }
356
357
    /**
358
     * Returns the ID of the last inserted row or sequence value.
359
     * @param string $sequenceName name of the sequence object (required by some DBMS)
360
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
361
     * @throws InvalidCallException if the DB connection is not active
362
     * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
363
     */
364 73
    public function getLastInsertID($sequenceName = '')
365
    {
366 73
        if ($this->db->isActive) {
367 73
            return $this->db->pdo->lastInsertId($sequenceName === '' ? null : $this->quoteTableName($sequenceName));
368
        }
369
370
        throw new InvalidCallException('DB Connection is not active.');
371
    }
372
373
    /**
374
     * @return bool whether this DBMS supports [savepoint](http://en.wikipedia.org/wiki/Savepoint).
375
     */
376 4
    public function supportsSavepoint()
377
    {
378 4
        return $this->db->enableSavepoint;
379
    }
380
381
    /**
382
     * Creates a new savepoint.
383
     * @param string $name the savepoint name
384
     */
385 4
    public function createSavepoint($name)
386
    {
387 4
        $this->db->createCommand("SAVEPOINT $name")->execute();
388 4
    }
389
390
    /**
391
     * Releases an existing savepoint.
392
     * @param string $name the savepoint name
393
     */
394
    public function releaseSavepoint($name)
395
    {
396
        $this->db->createCommand("RELEASE SAVEPOINT $name")->execute();
397
    }
398
399
    /**
400
     * Rolls back to a previously created savepoint.
401
     * @param string $name the savepoint name
402
     */
403 4
    public function rollBackSavepoint($name)
404
    {
405 4
        $this->db->createCommand("ROLLBACK TO SAVEPOINT $name")->execute();
406 4
    }
407
408
    /**
409
     * Sets the isolation level of the current transaction.
410
     * @param string $level The transaction isolation level to use for this transaction.
411
     * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]]
412
     * and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used
413
     * after `SET TRANSACTION ISOLATION LEVEL`.
414
     * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
415
     */
416 6
    public function setTransactionIsolationLevel($level)
417
    {
418 6
        $this->db->createCommand("SET TRANSACTION ISOLATION LEVEL $level")->execute();
419 6
    }
420
421
    /**
422
     * Executes the INSERT command, returning primary key values.
423
     * @param string $table the table that new rows will be inserted into.
424
     * @param array $columns the column data (name => value) to be inserted into the table.
425
     * @return array|false primary key values or false if the command fails
426
     * @since 2.0.4
427
     */
428 77
    public function insert($table, $columns)
429
    {
430 77
        $command = $this->db->createCommand()->insert($table, $columns);
431 77
        if (!$command->execute()) {
432
            return false;
433
        }
434 77
        $tableSchema = $this->getTableSchema($table);
435 77
        $result = [];
436 77
        foreach ($tableSchema->primaryKey as $name) {
437 74
            if ($tableSchema->columns[$name]->autoIncrement) {
438 70
                $result[$name] = $this->getLastInsertID($tableSchema->sequenceName);
439 70
                break;
440
            }
441
442 6
            $result[$name] = isset($columns[$name]) ? $columns[$name] : $tableSchema->columns[$name]->defaultValue;
443
        }
444
445 77
        return $result;
446
    }
447
448
    /**
449
     * Quotes a string value for use in a query.
450
     * Note that if the parameter is not a string, it will be returned without change.
451
     * @param string $str string to be quoted
452
     * @return string the properly quoted string
453
     * @see http://www.php.net/manual/en/function.PDO-quote.php
454
     */
455 980
    public function quoteValue($str)
456
    {
457 980
        if (!is_string($str)) {
458 4
            return $str;
459
        }
460
461 980
        if (($value = $this->db->getSlavePdo()->quote($str)) !== false) {
462 980
            return $value;
463
        }
464
465
        // the driver doesn't support quote (e.g. oci)
466
        return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
467
    }
468
469
    /**
470
     * Quotes a table name for use in a query.
471
     * If the table name contains schema prefix, the prefix will also be properly quoted.
472
     * If the table name is already quoted or contains '(' or '{{',
473
     * then this method will do nothing.
474
     * @param string $name table name
475
     * @return string the properly quoted table name
476
     * @see quoteSimpleTableName()
477
     */
478 1179
    public function quoteTableName($name)
479
    {
480 1179
        if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
481 454
            return $name;
482
        }
483 1163
        if (strpos($name, '.') === false) {
484 1163
            return $this->quoteSimpleTableName($name);
485
        }
486 5
        $parts = explode('.', $name);
487 5
        foreach ($parts as $i => $part) {
488 5
            $parts[$i] = $this->quoteSimpleTableName($part);
489
        }
490
491 5
        return implode('.', $parts);
492
    }
493
494
    /**
495
     * Quotes a column name for use in a query.
496
     * If the column name contains prefix, the prefix will also be properly quoted.
497
     * If the column name is already quoted or contains '(', '[[' or '{{',
498
     * then this method will do nothing.
499
     * @param string $name column name
500
     * @return string the properly quoted column name
501
     * @see quoteSimpleColumnName()
502
     */
503 1255
    public function quoteColumnName($name)
504
    {
505 1255
        if (strpos($name, '(') !== false || strpos($name, '[[') !== false) {
506 121
            return $name;
507
        }
508 1246
        if (($pos = strrpos($name, '.')) !== false) {
509 180
            $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.';
510 180
            $name = substr($name, $pos + 1);
511
        } else {
512 1243
            $prefix = '';
513
        }
514 1246
        if (strpos($name, '{{') !== false) {
515 4
            return $name;
516
        }
517
518 1246
        return $prefix . $this->quoteSimpleColumnName($name);
519
    }
520
521
    /**
522
     * Quotes a simple table name for use in a query.
523
     * A simple table name should contain the table name only without any schema prefix.
524
     * If the table name is already quoted, this method will do nothing.
525
     * @param string $name table name
526
     * @return string the properly quoted table name
527
     */
528 1203
    public function quoteSimpleTableName($name)
529
    {
530 1203
        if (is_string($this->tableQuoteCharacter)) {
531 1203
            $startingCharacter = $endingCharacter = $this->tableQuoteCharacter;
532
        } else {
533
            list($startingCharacter, $endingCharacter) = $this->tableQuoteCharacter;
534
        }
535 1203
        return strpos($name, $startingCharacter) !== false ? $name : $startingCharacter . $name . $endingCharacter;
536
    }
537
538
    /**
539
     * Quotes a simple column name for use in a query.
540
     * A simple column name should contain the column name only without any prefix.
541
     * If the column name is already quoted or is the asterisk character '*', this method will do nothing.
542
     * @param string $name column name
543
     * @return string the properly quoted column name
544
     */
545 1246
    public function quoteSimpleColumnName($name)
546
    {
547 1246
        if (is_string($this->tableQuoteCharacter)) {
548 1246
            $startingCharacter = $endingCharacter = $this->columnQuoteCharacter;
549
        } else {
550
            list($startingCharacter, $endingCharacter) = $this->columnQuoteCharacter;
551
        }
552 1246
        return $name === '*' || strpos($name, $startingCharacter) !== false ? $name : $startingCharacter . $name . $endingCharacter;
553
    }
554
555
    /**
556
     * Unquotes a simple table name.
557
     * A simple table name should contain the table name only without any schema prefix.
558
     * If the table name is not quoted, this method will do nothing.
559
     * @param string $name table name.
560
     * @return string unquoted table name.
561
     * @since 2.0.14
562
     */
563
    public function unquoteSimpleTableName($name)
564
    {
565
        if (is_string($this->tableQuoteCharacter)) {
566
            $startingCharacter = $this->tableQuoteCharacter;
567
        } else {
568
            $startingCharacter = $this->tableQuoteCharacter[0];
569
        }
570
        return strpos($name, $startingCharacter) === false ? $name : substr($name, 1, -1);
571
    }
572
573
    /**
574
     * Unquotes a simple column name.
575
     * A simple column name should contain the column name only without any prefix.
576
     * If the column name is not quoted or is the asterisk character '*', this method will do nothing.
577
     * @param string $name column name.
578
     * @return string unquoted column name.
579
     * @since 2.0.14
580
     */
581
    public function unquoteSimpleColumnName($name)
582
    {
583
        if (is_string($this->columnQuoteCharacter)) {
584
            $startingCharacter = $this->columnQuoteCharacter;
585
        } else {
586
            $startingCharacter = $this->columnQuoteCharacter[0];
587
        }
588
        return strpos($name, $startingCharacter) === false ? $name : substr($name, 1, -1);
589
    }
590
591
    /**
592
     * Returns the actual name of a given table name.
593
     * This method will strip off curly brackets from the given table name
594
     * and replace the percentage character '%' with [[Connection::tablePrefix]].
595
     * @param string $name the table name to be converted
596
     * @return string the real name of the given table name
597
     */
598 1136
    public function getRawTableName($name)
599
    {
600 1136
        if (strpos($name, '{{') !== false) {
601 412
            $name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name);
602
603 412
            return str_replace('%', $this->db->tablePrefix, $name);
604
        }
605
606 782
        return $name;
607
    }
608
609
    /**
610
     * Extracts the PHP type from abstract DB type.
611
     * @param ColumnSchema $column the column schema information
612
     * @return string PHP type name
613
     */
614 874
    protected function getColumnPhpType($column)
615
    {
616 874
        static $typeMap = [
617
            // abstract type => php type
618
            'smallint' => 'integer',
619
            'integer' => 'integer',
620
            'bigint' => 'integer',
621
            'boolean' => 'boolean',
622
            'float' => 'double',
623
            'double' => 'double',
624
            'binary' => 'resource',
625
            'json' => 'array',
626
        ];
627 874
        if (isset($typeMap[$column->type])) {
628 868
            if ($column->type === 'bigint') {
629 38
                return PHP_INT_SIZE === 8 && !$column->unsigned ? 'integer' : 'string';
630 868
            } elseif ($column->type === 'integer') {
631 868
                return PHP_INT_SIZE === 4 && $column->unsigned ? 'string' : 'integer';
632
            }
633
634 443
            return $typeMap[$column->type];
635
        }
636
637 844
        return 'string';
638
    }
639
640
    /**
641
     * Converts a DB exception to a more concrete one if possible.
642
     *
643
     * @param \Exception $e
644
     * @param string $rawSql SQL that produced exception
645
     * @return Exception
646
     */
647 37
    public function convertException(\Exception $e, $rawSql)
648
    {
649 37
        if ($e instanceof Exception) {
650
            return $e;
651
        }
652
653 37
        $exceptionClass = '\yii\db\Exception';
654 37
        foreach ($this->exceptionMap as $error => $class) {
655 37
            if (strpos($e->getMessage(), $error) !== false) {
656 37
                $exceptionClass = $class;
657
            }
658
        }
659 37
        $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
660 37
        $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
661 37
        return new $exceptionClass($message, $errorInfo, (int) $e->getCode(), $e);
662
    }
663
664
    /**
665
     * Returns a value indicating whether a SQL statement is for read purpose.
666
     * @param string $sql the SQL statement
667
     * @return bool whether a SQL statement is for read purpose.
668
     */
669 9
    public function isReadQuery($sql)
670
    {
671 9
        $pattern = '/^\s*(SELECT|SHOW|DESCRIBE)\b/i';
672 9
        return preg_match($pattern, $sql) > 0;
673
    }
674
675
    /**
676
     * Returns a server version as a string comparable by [[\version_compare()]].
677
     * @return string server version as a string.
678
     * @since 2.0.14
679
     */
680 48
    public function getServerVersion()
681
    {
682 48
        if ($this->_serverVersion === null) {
683 48
            $this->_serverVersion = $this->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
684
        }
685 48
        return $this->_serverVersion;
686
    }
687
688
    /**
689
     * Returns the cache key for the specified table name.
690
     * @param string $name the table name.
691
     * @return mixed the cache key.
692
     */
693 25
    protected function getCacheKey($name)
694
    {
695
        return [
696 25
            __CLASS__,
697 25
            $this->db->dsn,
698 25
            $this->db->username,
699 25
            $this->getRawTableName($name),
700
        ];
701
    }
702
703
    /**
704
     * Returns the cache tag name.
705
     * This allows [[refresh()]] to invalidate all cached table schemas.
706
     * @return string the cache tag name
707
     */
708 25
    protected function getCacheTag()
709
    {
710 25
        return md5(serialize([
711 25
            __CLASS__,
712 25
            $this->db->dsn,
713 25
            $this->db->username,
714
        ]));
715
    }
716
717
    /**
718
     * Returns the metadata of the given type for the given table.
719
     * If there's no metadata in the cache, this method will call
720
     * a `'loadTable' . ucfirst($type)` named method with the table name to obtain the metadata.
721
     * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
722
     * @param string $type metadata type.
723
     * @param bool $refresh whether to reload the table metadata even if it is found in the cache.
724
     * @return mixed metadata.
725
     * @since 2.0.13
726
     */
727 1134
    protected function getTableMetadata($name, $type, $refresh)
728
    {
729 1134
        $cache = null;
730 1134
        if ($this->db->enableSchemaCache && !in_array($name, $this->db->schemaCacheExclude, true)) {
731 25
            $schemaCache = is_string($this->db->schemaCache) ? Yii::$app->get($this->db->schemaCache, false) : $this->db->schemaCache;
732 25
            if ($schemaCache instanceof Cache) {
733 25
                $cache = $schemaCache;
734
            }
735
        }
736 1134
        $rawName = $this->getRawTableName($name);
737 1134
        if ($refresh || !isset($this->_tableMetadata[$rawName])) {
738 1090
            $this->loadTableMetadataFromCache($cache, $rawName);
739
        }
740 1134
        if (!array_key_exists($type, $this->_tableMetadata[$rawName])) {
741 1090
            $this->_tableMetadata[$rawName][$type] = $this->{'loadTable' . ucfirst($type)}($rawName);
742 1042
            $this->saveTableMetadataToCache($cache, $rawName);
743
        }
744
745 1086
        return $this->_tableMetadata[$rawName][$type];
746
    }
747
748
    /**
749
     * Returns the metadata of the given type for all tables in the given schema.
750
     * This method will call a `'getTable' . ucfirst($type)` named method with the table name
751
     * and the refresh flag to obtain the metadata.
752
     * @param string $schema the schema of the metadata. Defaults to empty string, meaning the current or default schema name.
753
     * @param string $type metadata type.
754
     * @param bool $refresh whether to fetch the latest available table metadata. If this is `false`,
755
     * cached data may be returned if available.
756
     * @return array array of metadata.
757
     * @since 2.0.13
758
     */
759 11
    protected function getSchemaMetadata($schema, $type, $refresh)
760
    {
761 11
        $metadata = [];
762 11
        $methodName = 'getTable' . ucfirst($type);
763 11
        foreach ($this->getTableNames($schema, $refresh) as $name) {
764 11
            if ($schema !== '') {
765
                $name = $schema . '.' . $name;
766
            }
767 11
            $tableMetadata = $this->$methodName($name, $refresh);
768 11
            if ($tableMetadata !== null) {
769 11
                $metadata[] = $tableMetadata;
770
            }
771
        }
772
773 11
        return $metadata;
774
    }
775
776
    /**
777
     * Sets the metadata of the given type for the given table.
778
     * @param string $name table name.
779
     * @param string $type metadata type.
780
     * @param mixed $data metadata.
781
     * @since 2.0.13
782
     */
783 173
    protected function setTableMetadata($name, $type, $data)
784
    {
785 173
        $this->_tableMetadata[$this->getRawTableName($name)][$type] = $data;
786 173
    }
787
788
    /**
789
     * Changes row's array key case to lower if PDO's one is set to uppercase.
790
     * @param array $row row's array or an array of row's arrays.
791
     * @param bool $multiple whether multiple rows or a single row passed.
792
     * @return array normalized row or rows.
793
     * @since 2.0.13
794
     */
795 196
    protected function normalizePdoRowKeyCase(array $row, $multiple)
796
    {
797 196
        if ($this->db->getSlavePdo()->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_UPPER) {
798 156
            return $row;
799
        }
800
801 40
        if ($multiple) {
802 40
            return array_map(function (array $row) {
803 36
                return array_change_key_case($row, CASE_LOWER);
804 40
            }, $row);
805
        }
806
807
        return array_change_key_case($row, CASE_LOWER);
808
    }
809
810
    /**
811
     * Tries to load and populate table metadata from cache.
812
     * @param Cache|null $cache
813
     * @param string $name
814
     */
815 1090
    private function loadTableMetadataFromCache($cache, $name)
816
    {
817 1090
        if ($cache === null) {
818 1065
            $this->_tableMetadata[$name] = [];
819 1065
            return;
820
        }
821
822 25
        $metadata = $cache->get($this->getCacheKey($name));
823 25
        if (!is_array($metadata) || !isset($metadata['cacheVersion']) || $metadata['cacheVersion'] !== static::SCHEMA_CACHE_VERSION) {
824 25
            $this->_tableMetadata[$name] = [];
825 25
            return;
826
        }
827
828 4
        unset($metadata['cacheVersion']);
829 4
        $this->_tableMetadata[$name] = $metadata;
830 4
    }
831
832
    /**
833
     * Saves table metadata to cache.
834
     * @param Cache|null $cache
835
     * @param string $name
836
     */
837 1042
    private function saveTableMetadataToCache($cache, $name)
838
    {
839 1042
        if ($cache === null) {
840 1017
            return;
841
        }
842
843 25
        $metadata = $this->_tableMetadata[$name];
844 25
        $metadata['cacheVersion'] = static::SCHEMA_CACHE_VERSION;
845 25
        $cache->set(
846 25
            $this->getCacheKey($name),
847 25
            $metadata,
848 25
            $this->db->schemaCacheDuration,
849 25
            new TagDependency(['tags' => $this->getCacheTag()])
850
        );
851 25
    }
852
}
853