Passed
Push — master ( 0e50ce...8dacd2 )
by Alexander
09:17
created

Schema::loadColumnSchema()   F

Complexity

Conditions 19
Paths 424

Size

Total Lines 58
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 380

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 19
eloc 37
c 1
b 0
f 0
nc 424
nop 1
dl 0
loc 58
ccs 0
cts 38
cp 0
crap 380
rs 1.1499

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db\mssql;
9
10
use Yii;
11
use yii\db\CheckConstraint;
12
use yii\db\Constraint;
13
use yii\db\ConstraintFinderInterface;
14
use yii\db\ConstraintFinderTrait;
15
use yii\db\DefaultValueConstraint;
16
use yii\db\ForeignKeyConstraint;
17
use yii\db\IndexConstraint;
18
use yii\db\ViewFinderTrait;
19
use yii\helpers\ArrayHelper;
20
21
/**
22
 * Schema is the class for retrieving metadata from MS SQL Server databases (version 2008 and above).
23
 *
24
 * @author Timur Ruziev <[email protected]>
25
 * @since 2.0
26
 */
27
class Schema extends \yii\db\Schema implements ConstraintFinderInterface
28
{
29
    use ViewFinderTrait;
30
    use ConstraintFinderTrait;
31
32
    /**
33
     * {@inheritdoc}
34
     */
35
    public $columnSchemaClass = 'yii\db\mssql\ColumnSchema';
36
    /**
37
     * @var string the default schema used for the current session.
38
     */
39
    public $defaultSchema = 'dbo';
40
    /**
41
     * @var array mapping from physical column types (keys) to abstract column types (values)
42
     */
43
    public $typeMap = [
44
        // exact numbers
45
        'bigint' => self::TYPE_BIGINT,
46
        'numeric' => self::TYPE_DECIMAL,
47
        'bit' => self::TYPE_SMALLINT,
48
        'smallint' => self::TYPE_SMALLINT,
49
        'decimal' => self::TYPE_DECIMAL,
50
        'smallmoney' => self::TYPE_MONEY,
51
        'int' => self::TYPE_INTEGER,
52
        'tinyint' => self::TYPE_TINYINT,
53
        'money' => self::TYPE_MONEY,
54
        // approximate numbers
55
        'float' => self::TYPE_FLOAT,
56
        'double' => self::TYPE_DOUBLE,
57
        'real' => self::TYPE_FLOAT,
58
        // date and time
59
        'date' => self::TYPE_DATE,
60
        'datetimeoffset' => self::TYPE_DATETIME,
61
        'datetime2' => self::TYPE_DATETIME,
62
        'smalldatetime' => self::TYPE_DATETIME,
63
        'datetime' => self::TYPE_DATETIME,
64
        'time' => self::TYPE_TIME,
65
        // character strings
66
        'char' => self::TYPE_CHAR,
67
        'varchar' => self::TYPE_STRING,
68
        'text' => self::TYPE_TEXT,
69
        // unicode character strings
70
        'nchar' => self::TYPE_CHAR,
71
        'nvarchar' => self::TYPE_STRING,
72
        'ntext' => self::TYPE_TEXT,
73
        // binary strings
74
        'binary' => self::TYPE_BINARY,
75
        'varbinary' => self::TYPE_BINARY,
76
        'image' => self::TYPE_BINARY,
77
        // other data types
78
        // 'cursor' type cannot be used with tables
79
        'timestamp' => self::TYPE_TIMESTAMP,
80
        'hierarchyid' => self::TYPE_STRING,
81
        'uniqueidentifier' => self::TYPE_STRING,
82
        'sql_variant' => self::TYPE_STRING,
83
        'xml' => self::TYPE_STRING,
84
        'table' => self::TYPE_STRING,
85
    ];
86
87
    /**
88
     * {@inheritdoc}
89
     */
90
    protected $tableQuoteCharacter = ['[', ']'];
91
    /**
92
     * {@inheritdoc}
93
     */
94
    protected $columnQuoteCharacter = ['[', ']'];
95
96
97
    /**
98
     * Resolves the table name and schema name (if any).
99
     * @param string $name the table name
100
     * @return TableSchema resolved table, schema, etc. names.
101
     */
102
    protected function resolveTableName($name)
103
    {
104
        $resolvedName = new TableSchema();
105
        $parts = $this->getTableNameParts($name);
106
        $partCount = count($parts);
107
        if ($partCount === 4) {
108
            // server name, catalog name, schema name and table name passed
109
            $resolvedName->catalogName = $parts[1];
110
            $resolvedName->schemaName = $parts[2];
111
            $resolvedName->name = $parts[3];
112
            $resolvedName->fullName = $resolvedName->catalogName . '.' . $resolvedName->schemaName . '.' . $resolvedName->name;
113
        } elseif ($partCount === 3) {
114
            // catalog name, schema name and table name passed
115
            $resolvedName->catalogName = $parts[0];
116
            $resolvedName->schemaName = $parts[1];
117
            $resolvedName->name = $parts[2];
118
            $resolvedName->fullName = $resolvedName->catalogName . '.' . $resolvedName->schemaName . '.' . $resolvedName->name;
119
        } elseif ($partCount === 2) {
120
            // only schema name and table name passed
121
            $resolvedName->schemaName = $parts[0];
122
            $resolvedName->name = $parts[1];
123
            $resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name;
124
        } else {
125
            // only table name passed
126
            $resolvedName->schemaName = $this->defaultSchema;
127
            $resolvedName->fullName = $resolvedName->name = $parts[0];
128
        }
129
130
        return $resolvedName;
131
    }
132
133
    /**
134
     * {@inheritDoc}
135
     * @param string $name
136
     * @return array
137
     * @since 2.0.22
138
     */
139
    protected function getTableNameParts($name)
140
    {
141
        $parts = [$name];
142
        preg_match_all('/([^.\[\]]+)|\[([^\[\]]+)\]/', $name, $matches);
143
        if (isset($matches[0]) && is_array($matches[0]) && !empty($matches[0])) {
144
            $parts = $matches[0];
145
        }
146
147
        $parts = str_replace(['[', ']'], '', $parts);
148
149
        return $parts;
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     * @see https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-database-principals-transact-sql
155
     */
156
    protected function findSchemaNames()
157
    {
158
        static $sql = <<<'SQL'
159
SELECT [s].[name]
160
FROM [sys].[schemas] AS [s]
161
INNER JOIN [sys].[database_principals] AS [p] ON [p].[principal_id] = [s].[principal_id]
162
WHERE [p].[is_fixed_role] = 0 AND [p].[sid] IS NOT NULL
163
ORDER BY [s].[name] ASC
164
SQL;
165
166
        return $this->db->createCommand($sql)->queryColumn();
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    protected function findTableNames($schema = '')
173
    {
174
        if ($schema === '') {
175
            $schema = $this->defaultSchema;
176
        }
177
178
        $sql = <<<'SQL'
179
SELECT [t].[table_name]
180
FROM [INFORMATION_SCHEMA].[TABLES] AS [t]
181
WHERE [t].[table_schema] = :schema AND [t].[table_type] IN ('BASE TABLE', 'VIEW')
182
ORDER BY [t].[table_name]
183
SQL;
184
        return $this->db->createCommand($sql, [':schema' => $schema])->queryColumn();
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190
    protected function loadTableSchema($name)
191
    {
192
        $table = new TableSchema();
193
        $this->resolveTableNames($table, $name);
194
        $this->findPrimaryKeys($table);
195
        if ($this->findColumns($table)) {
196
            $this->findForeignKeys($table);
197
            return $table;
198
        }
199
200
        return null;
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    protected function getSchemaMetadata($schema, $type, $refresh)
207
    {
208
        $metadata = [];
209
        $methodName = 'getTable' . ucfirst($type);
210
        $tableNames = array_map(function ($table) {
211
            return $this->quoteSimpleTableName($table);
212
        }, $this->getTableNames($schema, $refresh));
213
        foreach ($tableNames as $name) {
214
            if ($schema !== '') {
215
                $name = $schema . '.' . $name;
216
            }
217
            $tableMetadata = $this->$methodName($name, $refresh);
218
            if ($tableMetadata !== null) {
219
                $metadata[] = $tableMetadata;
220
            }
221
        }
222
223
        return $metadata;
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229
    protected function loadTablePrimaryKey($tableName)
230
    {
231
        return $this->loadTableConstraints($tableName, 'primaryKey');
232
    }
233
234
    /**
235
     * {@inheritdoc}
236
     */
237
    protected function loadTableForeignKeys($tableName)
238
    {
239
        return $this->loadTableConstraints($tableName, 'foreignKeys');
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     */
245
    protected function loadTableIndexes($tableName)
246
    {
247
        static $sql = <<<'SQL'
248
SELECT
249
    [i].[name] AS [name],
250
    [iccol].[name] AS [column_name],
251
    [i].[is_unique] AS [index_is_unique],
252
    [i].[is_primary_key] AS [index_is_primary]
253
FROM [sys].[indexes] AS [i]
254
INNER JOIN [sys].[index_columns] AS [ic]
255
    ON [ic].[object_id] = [i].[object_id] AND [ic].[index_id] = [i].[index_id]
256
INNER JOIN [sys].[columns] AS [iccol]
257
    ON [iccol].[object_id] = [ic].[object_id] AND [iccol].[column_id] = [ic].[column_id]
258
WHERE [i].[object_id] = OBJECT_ID(:fullName)
259
ORDER BY [ic].[key_ordinal] ASC
260
SQL;
261
262
        $resolvedName = $this->resolveTableName($tableName);
263
        $indexes = $this->db->createCommand($sql, [
264
            ':fullName' => $resolvedName->fullName,
265
        ])->queryAll();
266
        $indexes = $this->normalizePdoRowKeyCase($indexes, true);
267
        $indexes = ArrayHelper::index($indexes, null, 'name');
268
        $result = [];
269
        foreach ($indexes as $name => $index) {
270
            $result[] = new IndexConstraint([
271
                'isPrimary' => (bool)$index[0]['index_is_primary'],
272
                'isUnique' => (bool)$index[0]['index_is_unique'],
273
                'name' => $name,
274
                'columnNames' => ArrayHelper::getColumn($index, 'column_name'),
275
            ]);
276
        }
277
278
        return $result;
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284
    protected function loadTableUniques($tableName)
285
    {
286
        return $this->loadTableConstraints($tableName, 'uniques');
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292
    protected function loadTableChecks($tableName)
293
    {
294
        return $this->loadTableConstraints($tableName, 'checks');
295
    }
296
297
    /**
298
     * {@inheritdoc}
299
     */
300
    protected function loadTableDefaultValues($tableName)
301
    {
302
        return $this->loadTableConstraints($tableName, 'defaults');
303
    }
304
305
    /**
306
     * {@inheritdoc}
307
     */
308
    public function createSavepoint($name)
309
    {
310
        $this->db->createCommand("SAVE TRANSACTION $name")->execute();
311
    }
312
313
    /**
314
     * {@inheritdoc}
315
     */
316
    public function releaseSavepoint($name)
317
    {
318
        // does nothing as MSSQL does not support this
319
    }
320
321
    /**
322
     * {@inheritdoc}
323
     */
324
    public function rollBackSavepoint($name)
325
    {
326
        $this->db->createCommand("ROLLBACK TRANSACTION $name")->execute();
327
    }
328
329
    /**
330
     * Creates a query builder for the MSSQL database.
331
     * @return QueryBuilder query builder interface.
332
     */
333
    public function createQueryBuilder()
334
    {
335
        return Yii::createObject(QueryBuilder::className(), [$this->db]);
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

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

335
        return Yii::createObject(/** @scrutinizer ignore-deprecated */ QueryBuilder::className(), [$this->db]);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
336
    }
337
338
    /**
339
     * Resolves the table name and schema name (if any).
340
     * @param TableSchema $table the table metadata object
341
     * @param string $name the table name
342
     */
343
    protected function resolveTableNames($table, $name)
344
    {
345
        $parts = $this->getTableNameParts($name);
346
        $partCount = count($parts);
347
        if ($partCount === 4) {
348
            // server name, catalog name, schema name and table name passed
349
            $table->catalogName = $parts[1];
350
            $table->schemaName = $parts[2];
351
            $table->name = $parts[3];
352
            $table->fullName = $table->catalogName . '.' . $table->schemaName . '.' . $table->name;
353
        } elseif ($partCount === 3) {
354
            // catalog name, schema name and table name passed
355
            $table->catalogName = $parts[0];
356
            $table->schemaName = $parts[1];
357
            $table->name = $parts[2];
358
            $table->fullName = $table->catalogName . '.' . $table->schemaName . '.' . $table->name;
359
        } elseif ($partCount === 2) {
360
            // only schema name and table name passed
361
            $table->schemaName = $parts[0];
362
            $table->name = $parts[1];
363
            $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
364
        } else {
365
            // only table name passed
366
            $table->schemaName = $this->defaultSchema;
367
            $table->fullName = $table->name = $parts[0];
368
        }
369
    }
370
371
    /**
372
     * Loads the column information into a [[ColumnSchema]] object.
373
     * @param array $info column information
374
     * @return ColumnSchema the column schema object
375
     */
376
    protected function loadColumnSchema($info)
377
    {
378
        $isVersion2017orLater = version_compare($this->db->getSchema()->getServerVersion(), '14', '>=');
379
        $column = $this->createColumnSchema();
380
381
        $column->name = $info['column_name'];
382
        $column->allowNull = $info['is_nullable'] === 'YES';
383
        $column->dbType = $info['data_type'];
384
        $column->enumValues = []; // mssql has only vague equivalents to enum
385
        $column->isPrimaryKey = null; // primary key will be determined in findColumns() method
386
        $column->autoIncrement = $info['is_identity'] == 1;
387
        $column->isComputed = (bool)$info['is_computed'];
0 ignored issues
show
Bug Best Practice introduced by
The property isComputed does not exist on yii\db\ColumnSchema. Since you implemented __set, consider adding a @property annotation.
Loading history...
388
        $column->unsigned = stripos($column->dbType, 'unsigned') !== false;
389
        $column->comment = $info['comment'] === null ? '' : $info['comment'];
390
391
        $column->type = self::TYPE_STRING;
392
        if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
393
            $type = $matches[1];
394
            if (isset($this->typeMap[$type])) {
395
                $column->type = $this->typeMap[$type];
396
            }
397
398
            if ($isVersion2017orLater && $type === 'bit') {
399
                $column->type = 'boolean';
400
            }
401
402
            if (!empty($matches[2])) {
403
                $values = explode(',', $matches[2]);
404
                $column->size = $column->precision = (int) $values[0];
405
406
                if (isset($values[1])) {
407
                    $column->scale = (int) $values[1];
408
                }
409
410
                if ($isVersion2017orLater === false) {
411
                    if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
412
                        $column->type = 'boolean';
413
                    } elseif ($type === 'bit') {
414
                        if ($column->size > 32) {
415
                            $column->type = 'bigint';
416
                        } elseif ($column->size === 32) {
417
                            $column->type = 'integer';
418
                        }
419
                    }
420
                }
421
            }
422
        }
423
424
        $column->phpType = $this->getColumnPhpType($column);
425
426
        if ($info['column_default'] === '(NULL)') {
427
            $info['column_default'] = null;
428
        }
429
        if (!$column->isPrimaryKey && ($column->type !== 'timestamp' || $info['column_default'] !== 'CURRENT_TIMESTAMP')) {
430
            $column->defaultValue = $column->defaultPhpTypecast($info['column_default']);
0 ignored issues
show
Bug introduced by
The method defaultPhpTypecast() does not exist on yii\db\ColumnSchema. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

430
            /** @scrutinizer ignore-call */ 
431
            $column->defaultValue = $column->defaultPhpTypecast($info['column_default']);
Loading history...
431
        }
432
433
        return $column;
434
    }
435
436
    /**
437
     * Collects the metadata of table columns.
438
     * @param TableSchema $table the table metadata
439
     * @return bool whether the table exists in the database
440
     */
441
    protected function findColumns($table)
442
    {
443
        $columnsTableName = 'INFORMATION_SCHEMA.COLUMNS';
444
        $whereSql = '[t1].[table_name] = ' . $this->db->quoteValue($table->name);
445
        if ($table->catalogName !== null) {
446
            $columnsTableName = "{$table->catalogName}.{$columnsTableName}";
447
            $whereSql .= " AND [t1].[table_catalog] = '{$table->catalogName}'";
448
        }
449
        if ($table->schemaName !== null) {
450
            $whereSql .= " AND [t1].[table_schema] = '{$table->schemaName}'";
451
        }
452
        $columnsTableName = $this->quoteTableName($columnsTableName);
453
454
        $sql = <<<SQL
455
SELECT
456
 [t1].[column_name],
457
 [t1].[is_nullable],
458
 CASE WHEN [t1].[data_type] IN ('char','varchar','nchar','nvarchar','binary','varbinary') THEN
459
    CASE WHEN [t1].[character_maximum_length] = NULL OR [t1].[character_maximum_length] = -1 THEN
460
        [t1].[data_type]
461
    ELSE
462
        [t1].[data_type] + '(' + LTRIM(RTRIM(CONVERT(CHAR,[t1].[character_maximum_length]))) + ')'
463
    END
464
 ELSE
465
    [t1].[data_type]
466
 END AS 'data_type',
467
 [t1].[column_default],
468
 COLUMNPROPERTY(OBJECT_ID([t1].[table_schema] + '.' + [t1].[table_name]), [t1].[column_name], 'IsIdentity') AS is_identity,
469
 COLUMNPROPERTY(OBJECT_ID([t1].[table_schema] + '.' + [t1].[table_name]), [t1].[column_name], 'IsComputed') AS is_computed,
470
 (
471
    SELECT CONVERT(VARCHAR, [t2].[value])
472
		FROM [sys].[extended_properties] AS [t2]
473
		WHERE
474
			[t2].[class] = 1 AND
475
			[t2].[class_desc] = 'OBJECT_OR_COLUMN' AND
476
			[t2].[name] = 'MS_Description' AND
477
			[t2].[major_id] = OBJECT_ID([t1].[TABLE_SCHEMA] + '.' + [t1].[table_name]) AND
478
			[t2].[minor_id] = COLUMNPROPERTY(OBJECT_ID([t1].[TABLE_SCHEMA] + '.' + [t1].[TABLE_NAME]), [t1].[COLUMN_NAME], 'ColumnID')
479
 ) as comment
480
FROM {$columnsTableName} AS [t1]
481
WHERE {$whereSql}
482
SQL;
483
484
        try {
485
            $columns = $this->db->createCommand($sql)->queryAll();
486
            if (empty($columns)) {
487
                return false;
488
            }
489
        } catch (\Exception $e) {
490
            return false;
491
        }
492
        foreach ($columns as $column) {
493
            $column = $this->loadColumnSchema($column);
494
            foreach ($table->primaryKey as $primaryKey) {
495
                if (strcasecmp($column->name, $primaryKey) === 0) {
496
                    $column->isPrimaryKey = true;
497
                    break;
498
                }
499
            }
500
            if ($column->isPrimaryKey && $column->autoIncrement) {
501
                $table->sequenceName = '';
502
            }
503
            $table->columns[$column->name] = $column;
504
        }
505
506
        return true;
507
    }
508
509
    /**
510
     * Collects the constraint details for the given table and constraint type.
511
     * @param TableSchema $table
512
     * @param string $type either PRIMARY KEY or UNIQUE
513
     * @return array each entry contains index_name and field_name
514
     * @since 2.0.4
515
     */
516
    protected function findTableConstraints($table, $type)
517
    {
518
        $keyColumnUsageTableName = 'INFORMATION_SCHEMA.KEY_COLUMN_USAGE';
519
        $tableConstraintsTableName = 'INFORMATION_SCHEMA.TABLE_CONSTRAINTS';
520
        if ($table->catalogName !== null) {
521
            $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName;
522
            $tableConstraintsTableName = $table->catalogName . '.' . $tableConstraintsTableName;
523
        }
524
        $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName);
525
        $tableConstraintsTableName = $this->quoteTableName($tableConstraintsTableName);
526
527
        $sql = <<<SQL
528
SELECT
529
    [kcu].[constraint_name] AS [index_name],
530
    [kcu].[column_name] AS [field_name]
531
FROM {$keyColumnUsageTableName} AS [kcu]
532
LEFT JOIN {$tableConstraintsTableName} AS [tc] ON
533
    [kcu].[table_schema] = [tc].[table_schema] AND
534
    [kcu].[table_name] = [tc].[table_name] AND
535
    [kcu].[constraint_name] = [tc].[constraint_name]
536
WHERE
537
    [tc].[constraint_type] = :type AND
538
    [kcu].[table_name] = :tableName AND
539
    [kcu].[table_schema] = :schemaName
540
SQL;
541
542
        return $this->db
543
            ->createCommand($sql, [
544
                ':tableName' => $table->name,
545
                ':schemaName' => $table->schemaName,
546
                ':type' => $type,
547
            ])
548
            ->queryAll();
549
    }
550
551
    /**
552
     * Collects the primary key column details for the given table.
553
     * @param TableSchema $table the table metadata
554
     */
555
    protected function findPrimaryKeys($table)
556
    {
557
        $result = [];
558
        foreach ($this->findTableConstraints($table, 'PRIMARY KEY') as $row) {
559
            $result[] = $row['field_name'];
560
        }
561
        $table->primaryKey = $result;
562
    }
563
564
    /**
565
     * Collects the foreign key column details for the given table.
566
     * @param TableSchema $table the table metadata
567
     */
568
    protected function findForeignKeys($table)
569
    {
570
        $object = $table->name;
571
        if ($table->schemaName !== null) {
572
            $object = $table->schemaName . '.' . $object;
573
        }
574
        if ($table->catalogName !== null) {
575
            $object = $table->catalogName . '.' . $object;
576
        }
577
578
        // please refer to the following page for more details:
579
        // http://msdn2.microsoft.com/en-us/library/aa175805(SQL.80).aspx
580
        $sql = <<<'SQL'
581
SELECT
582
	[fk].[name] AS [fk_name],
583
	[cp].[name] AS [fk_column_name],
584
	OBJECT_NAME([fk].[referenced_object_id]) AS [uq_table_name],
585
	[cr].[name] AS [uq_column_name]
586
FROM
587
	[sys].[foreign_keys] AS [fk]
588
	INNER JOIN [sys].[foreign_key_columns] AS [fkc] ON
589
		[fk].[object_id] = [fkc].[constraint_object_id]
590
	INNER JOIN [sys].[columns] AS [cp] ON
591
		[fk].[parent_object_id] = [cp].[object_id] AND
592
		[fkc].[parent_column_id] = [cp].[column_id]
593
	INNER JOIN [sys].[columns] AS [cr] ON
594
		[fk].[referenced_object_id] = [cr].[object_id] AND
595
		[fkc].[referenced_column_id] = [cr].[column_id]
596
WHERE
597
	[fk].[parent_object_id] = OBJECT_ID(:object)
598
SQL;
599
600
        $rows = $this->db->createCommand($sql, [
601
            ':object' => $object,
602
        ])->queryAll();
603
604
        $table->foreignKeys = [];
605
        foreach ($rows as $row) {
606
            if (!isset($table->foreignKeys[$row['fk_name']])) {
607
                $table->foreignKeys[$row['fk_name']][] = $row['uq_table_name'];
608
            }
609
            $table->foreignKeys[$row['fk_name']][$row['fk_column_name']] = $row['uq_column_name'];
610
        }
611
    }
612
613
    /**
614
     * {@inheritdoc}
615
     */
616
    protected function findViewNames($schema = '')
617
    {
618
        if ($schema === '') {
619
            $schema = $this->defaultSchema;
620
        }
621
622
        $sql = <<<'SQL'
623
SELECT [t].[table_name]
624
FROM [INFORMATION_SCHEMA].[TABLES] AS [t]
625
WHERE [t].[table_schema] = :schema AND [t].[table_type] = 'VIEW'
626
ORDER BY [t].[table_name]
627
SQL;
628
629
        return $this->db->createCommand($sql, [':schema' => $schema])->queryColumn();
630
    }
631
632
    /**
633
     * Returns all unique indexes for the given table.
634
     *
635
     * Each array element is of the following structure:
636
     *
637
     * ```php
638
     * [
639
     *     'IndexName1' => ['col1' [, ...]],
640
     *     'IndexName2' => ['col2' [, ...]],
641
     * ]
642
     * ```
643
     *
644
     * @param TableSchema $table the table metadata
645
     * @return array all unique indexes for the given table.
646
     * @since 2.0.4
647
     */
648
    public function findUniqueIndexes($table)
649
    {
650
        $result = [];
651
        foreach ($this->findTableConstraints($table, 'UNIQUE') as $row) {
652
            $result[$row['index_name']][] = $row['field_name'];
653
        }
654
655
        return $result;
656
    }
657
658
    /**
659
     * Loads multiple types of constraints and returns the specified ones.
660
     * @param string $tableName table name.
661
     * @param string $returnType return type:
662
     * - primaryKey
663
     * - foreignKeys
664
     * - uniques
665
     * - checks
666
     * - defaults
667
     * @return mixed constraints.
668
     */
669
    private function loadTableConstraints($tableName, $returnType)
670
    {
671
        static $sql = <<<'SQL'
672
SELECT
673
    [o].[name] AS [name],
674
    COALESCE([ccol].[name], [dcol].[name], [fccol].[name], [kiccol].[name]) AS [column_name],
675
    RTRIM([o].[type]) AS [type],
676
    OBJECT_SCHEMA_NAME([f].[referenced_object_id]) AS [foreign_table_schema],
677
    OBJECT_NAME([f].[referenced_object_id]) AS [foreign_table_name],
678
    [ffccol].[name] AS [foreign_column_name],
679
    [f].[update_referential_action_desc] AS [on_update],
680
    [f].[delete_referential_action_desc] AS [on_delete],
681
    [c].[definition] AS [check_expr],
682
    [d].[definition] AS [default_expr]
683
FROM (SELECT OBJECT_ID(:fullName) AS [object_id]) AS [t]
684
INNER JOIN [sys].[objects] AS [o]
685
    ON [o].[parent_object_id] = [t].[object_id] AND [o].[type] IN ('PK', 'UQ', 'C', 'D', 'F')
686
LEFT JOIN [sys].[check_constraints] AS [c]
687
    ON [c].[object_id] = [o].[object_id]
688
LEFT JOIN [sys].[columns] AS [ccol]
689
    ON [ccol].[object_id] = [c].[parent_object_id] AND [ccol].[column_id] = [c].[parent_column_id]
690
LEFT JOIN [sys].[default_constraints] AS [d]
691
    ON [d].[object_id] = [o].[object_id]
692
LEFT JOIN [sys].[columns] AS [dcol]
693
    ON [dcol].[object_id] = [d].[parent_object_id] AND [dcol].[column_id] = [d].[parent_column_id]
694
LEFT JOIN [sys].[key_constraints] AS [k]
695
    ON [k].[object_id] = [o].[object_id]
696
LEFT JOIN [sys].[index_columns] AS [kic]
697
    ON [kic].[object_id] = [k].[parent_object_id] AND [kic].[index_id] = [k].[unique_index_id]
698
LEFT JOIN [sys].[columns] AS [kiccol]
699
    ON [kiccol].[object_id] = [kic].[object_id] AND [kiccol].[column_id] = [kic].[column_id]
700
LEFT JOIN [sys].[foreign_keys] AS [f]
701
    ON [f].[object_id] = [o].[object_id]
702
LEFT JOIN [sys].[foreign_key_columns] AS [fc]
703
    ON [fc].[constraint_object_id] = [o].[object_id]
704
LEFT JOIN [sys].[columns] AS [fccol]
705
    ON [fccol].[object_id] = [fc].[parent_object_id] AND [fccol].[column_id] = [fc].[parent_column_id]
706
LEFT JOIN [sys].[columns] AS [ffccol]
707
    ON [ffccol].[object_id] = [fc].[referenced_object_id] AND [ffccol].[column_id] = [fc].[referenced_column_id]
708
ORDER BY [kic].[key_ordinal] ASC, [fc].[constraint_column_id] ASC
709
SQL;
710
711
        $resolvedName = $this->resolveTableName($tableName);
712
        $constraints = $this->db->createCommand($sql, [
713
            ':fullName' => $resolvedName->fullName,
714
        ])->queryAll();
715
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
716
        $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);
717
        $result = [
718
            'primaryKey' => null,
719
            'foreignKeys' => [],
720
            'uniques' => [],
721
            'checks' => [],
722
            'defaults' => [],
723
        ];
724
        foreach ($constraints as $type => $names) {
725
            foreach ($names as $name => $constraint) {
726
                switch ($type) {
727
                    case 'PK':
728
                        $result['primaryKey'] = new Constraint([
729
                            'name' => $name,
730
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
731
                        ]);
732
                        break;
733
                    case 'F':
734
                        $result['foreignKeys'][] = new ForeignKeyConstraint([
735
                            'name' => $name,
736
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
737
                            'foreignSchemaName' => $constraint[0]['foreign_table_schema'],
738
                            'foreignTableName' => $constraint[0]['foreign_table_name'],
739
                            'foreignColumnNames' => ArrayHelper::getColumn($constraint, 'foreign_column_name'),
740
                            'onDelete' => str_replace('_', '', $constraint[0]['on_delete']),
741
                            'onUpdate' => str_replace('_', '', $constraint[0]['on_update']),
742
                        ]);
743
                        break;
744
                    case 'UQ':
745
                        $result['uniques'][] = new Constraint([
746
                            'name' => $name,
747
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
748
                        ]);
749
                        break;
750
                    case 'C':
751
                        $result['checks'][] = new CheckConstraint([
752
                            'name' => $name,
753
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
754
                            'expression' => $constraint[0]['check_expr'],
755
                        ]);
756
                        break;
757
                    case 'D':
758
                        $result['defaults'][] = new DefaultValueConstraint([
759
                            'name' => $name,
760
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
761
                            'value' => $constraint[0]['default_expr'],
762
                        ]);
763
                        break;
764
                }
765
            }
766
        }
767
        foreach ($result as $type => $data) {
768
            $this->setTableMetadata($tableName, $type, $data);
769
        }
770
771
        return $result[$returnType];
772
    }
773
774
    /**
775
     * {@inheritdoc}
776
     */
777
    public function quoteColumnName($name)
778
    {
779
        if (preg_match('/^\[.*\]$/', $name)) {
780
            return $name;
781
        }
782
783
        return parent::quoteColumnName($name);
784
    }
785
786
    /**
787
     * Retrieving inserted data from a primary key request of type uniqueidentifier (for SQL Server 2005 or later)
788
     * {@inheritdoc}
789
     */
790
    public function insert($table, $columns)
791
    {
792
        $command = $this->db->createCommand()->insert($table, $columns);
793
        if (!$command->execute()) {
794
            return false;
795
        }
796
797
        $isVersion2005orLater = version_compare($this->db->getSchema()->getServerVersion(), '9', '>=');
798
        $inserted = $isVersion2005orLater ? $command->pdoStatement->fetch() : [];
799
800
        $tableSchema = $this->getTableSchema($table);
801
        $result = [];
802
        foreach ($tableSchema->primaryKey as $name) {
803
            // @see https://github.com/yiisoft/yii2/issues/13828 & https://github.com/yiisoft/yii2/issues/17474
804
            if (isset($inserted[$name])) {
805
                $result[$name] = $inserted[$name];
806
            } elseif ($tableSchema->columns[$name]->autoIncrement) {
807
                // for a version earlier than 2005
808
                $result[$name] = $this->getLastInsertID($tableSchema->sequenceName);
809
            } elseif (isset($columns[$name])) {
810
                $result[$name] = $columns[$name];
811
            } else {
812
                $result[$name] = $tableSchema->columns[$name]->defaultValue;
813
            }
814
        }
815
816
        return $result;
817
    }
818
819
    /**
820
     * {@inheritdoc}
821
     */
822
    public function createColumnSchemaBuilder($type, $length = null)
823
    {
824
        return Yii::createObject(ColumnSchemaBuilder::className(), [$type, $length, $this->db]);
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

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

824
        return Yii::createObject(/** @scrutinizer ignore-deprecated */ ColumnSchemaBuilder::className(), [$type, $length, $this->db]);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
825
    }
826
827
}
828