Total Complexity | 87 |
Total Lines | 967 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like MssqlSchema often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use MssqlSchema, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
39 | final class MssqlSchema extends Schema implements ConstraintFinderInterface |
||
40 | { |
||
41 | use ViewFinderTrait; |
||
42 | use ConstraintFinderTrait; |
||
43 | |||
44 | /** |
||
45 | * @var string the default schema used for the current session. |
||
46 | */ |
||
47 | protected ?string $defaultSchema = 'dbo'; |
||
48 | |||
49 | /** |
||
50 | * @var array mapping from physical column types (keys) to abstract column types (values) |
||
51 | */ |
||
52 | private array $typeMap = [ |
||
53 | /* exact numbers */ |
||
54 | 'bigint' => self::TYPE_BIGINT, |
||
55 | 'numeric' => self::TYPE_DECIMAL, |
||
56 | 'bit' => self::TYPE_SMALLINT, |
||
57 | 'smallint' => self::TYPE_SMALLINT, |
||
58 | 'decimal' => self::TYPE_DECIMAL, |
||
59 | 'smallmoney' => self::TYPE_MONEY, |
||
60 | 'int' => self::TYPE_INTEGER, |
||
61 | 'tinyint' => self::TYPE_TINYINT, |
||
62 | 'money' => self::TYPE_MONEY, |
||
63 | |||
64 | /* approximate numbers */ |
||
65 | 'float' => self::TYPE_FLOAT, |
||
66 | 'double' => self::TYPE_DOUBLE, |
||
67 | 'real' => self::TYPE_FLOAT, |
||
68 | |||
69 | /* date and time */ |
||
70 | 'date' => self::TYPE_DATE, |
||
71 | 'datetimeoffset' => self::TYPE_DATETIME, |
||
72 | 'datetime2' => self::TYPE_DATETIME, |
||
73 | 'smalldatetime' => self::TYPE_DATETIME, |
||
74 | 'datetime' => self::TYPE_DATETIME, |
||
75 | 'time' => self::TYPE_TIME, |
||
76 | |||
77 | /* character strings */ |
||
78 | 'char' => self::TYPE_CHAR, |
||
79 | 'varchar' => self::TYPE_STRING, |
||
80 | 'text' => self::TYPE_TEXT, |
||
81 | |||
82 | /* unicode character strings */ |
||
83 | 'nchar' => self::TYPE_CHAR, |
||
84 | 'nvarchar' => self::TYPE_STRING, |
||
85 | 'ntext' => self::TYPE_TEXT, |
||
86 | |||
87 | /* binary strings */ |
||
88 | 'binary' => self::TYPE_BINARY, |
||
89 | 'varbinary' => self::TYPE_BINARY, |
||
90 | 'image' => self::TYPE_BINARY, |
||
91 | |||
92 | /** |
||
93 | * other data types |
||
94 | * 'cursor' type cannot be used with tables |
||
95 | */ |
||
96 | 'timestamp' => self::TYPE_TIMESTAMP, |
||
97 | 'hierarchyid' => self::TYPE_STRING, |
||
98 | 'uniqueidentifier' => self::TYPE_STRING, |
||
99 | 'sql_variant' => self::TYPE_STRING, |
||
100 | 'xml' => self::TYPE_STRING, |
||
101 | 'table' => self::TYPE_STRING, |
||
102 | ]; |
||
103 | |||
104 | protected $tableQuoteCharacter = ['[', ']']; |
||
105 | protected $columnQuoteCharacter = ['[', ']']; |
||
106 | |||
107 | /** |
||
108 | * Resolves the table name and schema name (if any). |
||
109 | * |
||
110 | * @param string $name the table name. |
||
111 | * |
||
112 | * @return MssqlTableSchema resolved table, schema, etc. names. |
||
113 | */ |
||
114 | protected function resolveTableName(string $name): MssqlTableSchema |
||
115 | { |
||
116 | $resolvedName = new MssqlTableSchema(); |
||
117 | |||
118 | $parts = $this->getTableNameParts($name); |
||
119 | $partCount = count($parts); |
||
120 | |||
121 | if ($partCount === 4) { |
||
122 | /* server name, catalog name, schema name and table name passed */ |
||
123 | $resolvedName->catalogName($parts[1]); |
||
124 | $resolvedName->schemaName($parts[2]); |
||
125 | $resolvedName->name($parts[3]); |
||
126 | $resolvedName->fullName( |
||
127 | $resolvedName->getCatalogName() . '.' . $resolvedName->getSchemaName() . '.' . $resolvedName->getName() |
||
128 | ); |
||
129 | } elseif ($partCount === 3) { |
||
130 | /* catalog name, schema name and table name passed */ |
||
131 | $resolvedName->catalogName($parts[0]); |
||
132 | $resolvedName->schemaName($parts[1]); |
||
133 | $resolvedName->name($parts[2]); |
||
134 | $resolvedName->fullName( |
||
135 | $resolvedName->getCatalogName() . '.' . $resolvedName->getSchemaName() . '.' . $resolvedName->getName() |
||
136 | ); |
||
137 | } elseif ($partCount === 2) { |
||
138 | /* only schema name and table name passed */ |
||
139 | $resolvedName->schemaName($parts[0]); |
||
140 | $resolvedName->name($parts[1]); |
||
141 | $resolvedName->fullName( |
||
142 | $resolvedName->getSchemaName() !== $this->defaultSchema ? $resolvedName->getSchemaName() . '.' : '' |
||
143 | ) . $resolvedName->getName(); |
||
144 | } else { |
||
145 | /* only table name passed */ |
||
146 | $resolvedName->schemaName($this->defaultSchema); |
||
147 | $resolvedName->name($parts[0]); |
||
148 | $resolvedName->fullName($resolvedName->getName()); |
||
149 | } |
||
150 | |||
151 | return $resolvedName; |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * Splits full table name into parts. |
||
156 | * |
||
157 | * @param string $name |
||
158 | * |
||
159 | * @return array |
||
160 | */ |
||
161 | protected function getTableNameParts(string $name): array |
||
162 | { |
||
163 | $parts = [$name]; |
||
164 | |||
165 | preg_match_all('/([^.\[\]]+)|\[([^\[\]]+)]/', $name, $matches); |
||
166 | |||
167 | if (isset($matches[0]) && is_array($matches[0]) && !empty($matches[0])) { |
||
168 | $parts = $matches[0]; |
||
169 | } |
||
170 | |||
171 | $parts = str_replace(['[', ']'], '', $parts); |
||
172 | |||
173 | return $parts; |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * Returns all schema names in the database, including the default one but not system schemas. |
||
178 | * |
||
179 | * This method should be overridden by child classes in order to support this feature because the default |
||
180 | * implementation simply throws an exception. |
||
181 | * |
||
182 | * @throws Exception |
||
183 | * @throws InvalidArgumentException |
||
184 | * @throws InvalidConfigException |
||
185 | * |
||
186 | * @return array all schema names in the database, except system schemas. |
||
187 | * |
||
188 | * {@see https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-database-principals-transact-sql} |
||
189 | */ |
||
190 | protected function findSchemaNames(): array |
||
191 | { |
||
192 | static $sql = <<<'SQL' |
||
193 | SELECT [s].[name] |
||
194 | FROM [sys].[schemas] AS [s] |
||
195 | INNER JOIN [sys].[database_principals] AS [p] ON [p].[principal_id] = [s].[principal_id] |
||
196 | WHERE [p].[is_fixed_role] = 0 AND [p].[sid] IS NOT NULL |
||
197 | ORDER BY [s].[name] ASC |
||
198 | SQL; |
||
199 | |||
200 | return $this->getDb()->createCommand($sql)->queryColumn(); |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * Returns all table names in the database. |
||
205 | * |
||
206 | * This method should be overridden by child classes in order to support this feature because the default |
||
207 | * implementation simply throws an exception. |
||
208 | * |
||
209 | * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. |
||
210 | * |
||
211 | * @throws Exception |
||
212 | * @throws InvalidArgumentException |
||
213 | * @throws InvalidConfigException |
||
214 | * |
||
215 | * @return array all table names in the database. The names have NO schema name prefix. |
||
216 | */ |
||
217 | protected function findTableNames(string $schema = ''): array |
||
218 | { |
||
219 | if ($schema === '') { |
||
220 | $schema = $this->defaultSchema; |
||
221 | } |
||
222 | |||
223 | $sql = <<<'SQL' |
||
224 | SELECT [t].[table_name] |
||
225 | FROM [INFORMATION_SCHEMA].[TABLES] AS [t] |
||
226 | WHERE [t].[table_schema] = :schema AND [t].[table_type] IN ('BASE TABLE', 'VIEW') |
||
227 | ORDER BY [t].[table_name] |
||
228 | SQL; |
||
229 | |||
230 | $tables = $this->getDb()->createCommand($sql, [':schema' => $schema])->queryColumn(); |
||
231 | |||
232 | $tables = array_map(static function ($item) { |
||
233 | return '[' . $item . ']'; |
||
234 | }, $tables); |
||
235 | |||
236 | return $tables; |
||
237 | } |
||
238 | |||
239 | /** |
||
240 | * Loads the metadata for the specified table. |
||
241 | * |
||
242 | * @param string $name table name. |
||
243 | * |
||
244 | * @throws Exception |
||
245 | * @throws InvalidArgumentException |
||
246 | * @throws InvalidConfigException |
||
247 | * @throws NotSupportedException |
||
248 | * |
||
249 | * @return MssqlTableSchema|null DBMS-dependent table metadata, `null` if the table does not exist. |
||
250 | */ |
||
251 | protected function loadTableSchema(string $name): ?MssqlTableSchema |
||
252 | { |
||
253 | $table = new MssqlTableSchema(); |
||
254 | |||
255 | $this->resolveTableNames($table, $name); |
||
256 | $this->findPrimaryKeys($table); |
||
257 | |||
258 | if ($this->findColumns($table)) { |
||
259 | $this->findForeignKeys($table); |
||
260 | |||
261 | return $table; |
||
262 | } |
||
263 | |||
264 | return null; |
||
265 | } |
||
266 | |||
267 | /** |
||
268 | * Loads a primary key for the given table. |
||
269 | * |
||
270 | * @param string $tableName table name. |
||
271 | * |
||
272 | * @throws Exception |
||
273 | * @throws InvalidArgumentException |
||
274 | * @throws InvalidConfigException |
||
275 | * |
||
276 | * @return Constraint|null primary key for the given table, `null` if the table has no primary key. |
||
277 | */ |
||
278 | protected function loadTablePrimaryKey(string $tableName): ?Constraint |
||
279 | { |
||
280 | return $this->loadTableConstraints($tableName, 'primaryKey'); |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * Loads all foreign keys for the given table. |
||
285 | * |
||
286 | * @param string $tableName table name. |
||
287 | * |
||
288 | * @throws Exception |
||
289 | * @throws InvalidArgumentException |
||
290 | * @throws InvalidConfigException |
||
291 | * |
||
292 | * @return ForeignKeyConstraint[] foreign keys for the given table. |
||
293 | */ |
||
294 | protected function loadTableForeignKeys(string $tableName): array |
||
295 | { |
||
296 | return $this->loadTableConstraints($tableName, 'foreignKeys'); |
||
297 | } |
||
298 | |||
299 | /** |
||
300 | * Loads all indexes for the given table. |
||
301 | * |
||
302 | * @param string $tableName table name. |
||
303 | * |
||
304 | * @throws Exception |
||
305 | * @throws InvalidArgumentException |
||
306 | * @throws InvalidConfigException |
||
307 | * |
||
308 | * @return IndexConstraint[] indexes for the given table. |
||
309 | */ |
||
310 | protected function loadTableIndexes(string $tableName): array |
||
311 | { |
||
312 | static $sql = <<<'SQL' |
||
313 | SELECT |
||
314 | [i].[name] AS [name], |
||
315 | [iccol].[name] AS [column_name], |
||
316 | [i].[is_unique] AS [index_is_unique], |
||
317 | [i].[is_primary_key] AS [index_is_primary] |
||
318 | FROM [sys].[indexes] AS [i] |
||
319 | INNER JOIN [sys].[index_columns] AS [ic] |
||
320 | ON [ic].[object_id] = [i].[object_id] AND [ic].[index_id] = [i].[index_id] |
||
321 | INNER JOIN [sys].[columns] AS [iccol] |
||
322 | ON [iccol].[object_id] = [ic].[object_id] AND [iccol].[column_id] = [ic].[column_id] |
||
323 | WHERE [i].[object_id] = OBJECT_ID(:fullName) |
||
324 | ORDER BY [ic].[key_ordinal] ASC |
||
325 | SQL; |
||
326 | |||
327 | $resolvedName = $this->resolveTableName($tableName); |
||
328 | $indexes = $this->getDb()->createCommand($sql, [':fullName' => $resolvedName->getFullName()])->queryAll(); |
||
329 | $indexes = $this->normalizePdoRowKeyCase($indexes, true); |
||
330 | $indexes = ArrayHelper::index($indexes, null, 'name'); |
||
331 | |||
332 | $result = []; |
||
333 | foreach ($indexes as $name => $index) { |
||
334 | $result[] = (new IndexConstraint()) |
||
335 | ->primary((bool) $index[0]['index_is_primary']) |
||
336 | ->unique((bool) $index[0]['index_is_unique']) |
||
337 | ->name($name) |
||
338 | ->columnNames(ArrayHelper::getColumn($index, 'column_name')); |
||
339 | } |
||
340 | |||
341 | return $result; |
||
342 | } |
||
343 | |||
344 | /** |
||
345 | * Loads all unique constraints for the given table. |
||
346 | * |
||
347 | * @param string $tableName table name. |
||
348 | * |
||
349 | * @throws Exception |
||
350 | * @throws InvalidArgumentException |
||
351 | * @throws InvalidConfigException |
||
352 | * |
||
353 | * @return Constraint[] unique constraints for the given table. |
||
354 | */ |
||
355 | protected function loadTableUniques(string $tableName): array |
||
356 | { |
||
357 | return $this->loadTableConstraints($tableName, 'uniques'); |
||
358 | } |
||
359 | |||
360 | /** |
||
361 | * Loads all check constraints for the given table. |
||
362 | * |
||
363 | * @param string $tableName table name. |
||
364 | * |
||
365 | * @throws Exception |
||
366 | * @throws InvalidArgumentException |
||
367 | * @throws InvalidConfigException |
||
368 | * |
||
369 | * @return CheckConstraint[] check constraints for the given table. |
||
370 | */ |
||
371 | protected function loadTableChecks(string $tableName): array |
||
372 | { |
||
373 | return $this->loadTableConstraints($tableName, 'checks'); |
||
374 | } |
||
375 | |||
376 | /** |
||
377 | * Loads all default value constraints for the given table. |
||
378 | * |
||
379 | * @param string $tableName table name. |
||
380 | * |
||
381 | * @throws Exception |
||
382 | * @throws InvalidArgumentException |
||
383 | * @throws InvalidConfigException |
||
384 | * |
||
385 | * @return DefaultValueConstraint[] default value constraints for the given table. |
||
386 | */ |
||
387 | protected function loadTableDefaultValues(string $tableName): array |
||
388 | { |
||
389 | return $this->loadTableConstraints($tableName, 'defaults'); |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Creates a new savepoint. |
||
394 | * |
||
395 | * @param string $name the savepoint name. |
||
396 | * |
||
397 | * @throws Exception |
||
398 | * @throws InvalidConfigException |
||
399 | */ |
||
400 | public function createSavepoint(string $name): void |
||
401 | { |
||
402 | $this->getDb()->createCommand("SAVE TRANSACTION $name")->execute(); |
||
403 | } |
||
404 | |||
405 | /** |
||
406 | * Releases an existing savepoint. |
||
407 | * |
||
408 | * @param string $name the savepoint name. |
||
409 | */ |
||
410 | public function releaseSavepoint(string $name): void |
||
411 | { |
||
412 | /* does nothing as MSSQL does not support this */ |
||
413 | } |
||
414 | |||
415 | /** |
||
416 | * Rolls back to a previously created savepoint. |
||
417 | * |
||
418 | * @param string $name the savepoint name. |
||
419 | * |
||
420 | * @throws Exception |
||
421 | * @throws InvalidConfigException |
||
422 | */ |
||
423 | public function rollBackSavepoint(string $name): void |
||
424 | { |
||
425 | $this->getDb()->createCommand("ROLLBACK TRANSACTION $name")->execute(); |
||
426 | } |
||
427 | |||
428 | /** |
||
429 | * Creates a column schema for the database. |
||
430 | * |
||
431 | * This method may be overridden by child classes to create a DBMS-specific column schema. |
||
432 | * |
||
433 | * @return MssqlColumnSchema column schema instance. |
||
434 | */ |
||
435 | protected function createColumnSchema(): MssqlColumnSchema |
||
438 | } |
||
439 | |||
440 | /** |
||
441 | * Creates a query builder for the MSSQL database. |
||
442 | * |
||
443 | * @return MssqlQueryBuilder query builder interface. |
||
444 | */ |
||
445 | public function createQueryBuilder(): MssqlQueryBuilder |
||
446 | { |
||
447 | return new MssqlQueryBuilder($this->getDb()); |
||
448 | } |
||
449 | |||
450 | /** |
||
451 | * Resolves the table name and schema name (if any). |
||
452 | * |
||
453 | * @param MssqlTableSchema $table the table metadata object. |
||
454 | * @param string $name the table name |
||
455 | */ |
||
456 | protected function resolveTableNames(MssqlTableSchema $table, string $name) |
||
457 | { |
||
458 | $parts = $this->getTableNameParts($name); |
||
459 | $partCount = count($parts); |
||
460 | |||
461 | if ($partCount === 4) { |
||
462 | /* server name, catalog name, schema name and table name passed */ |
||
463 | $table->catalogName($parts[1]); |
||
464 | $table->schemaName($parts[2]); |
||
465 | $table->name($parts[3]); |
||
466 | $table->fullName($table->getCatalogName() . '.' . $table->getSchemaName() . '.' . $table->getName()); |
||
467 | } elseif ($partCount === 3) { |
||
468 | /* catalog name, schema name and table name passed */ |
||
469 | $table->catalogName($parts[0]); |
||
470 | $table->schemaName($parts[1]); |
||
471 | $table->name($parts[2]); |
||
472 | $table->fullName($table->getCatalogName() . '.' . $table->getSchemaName() . '.' . $table->getName()); |
||
473 | } elseif ($partCount === 2) { |
||
474 | /* only schema name and table name passed */ |
||
475 | $table->schemaName($parts[0]); |
||
476 | $table->name($parts[1]); |
||
477 | $table->fullName( |
||
478 | $table->getSchemaName() !== $this->defaultSchema |
||
479 | ? $table->getSchemaName() . '.' . $table->getName() : $table->getName() |
||
480 | ); |
||
481 | } else { |
||
482 | /* only table name passed */ |
||
483 | $table->schemaName($this->defaultSchema); |
||
484 | $table->name($parts[0]); |
||
485 | $table->fullName($table->getName()); |
||
486 | } |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * Loads the column information into a {@see ColumnSchema} object. |
||
491 | * |
||
492 | * @param array $info column information. |
||
493 | * |
||
494 | * @return ColumnSchema the column schema object. |
||
495 | */ |
||
496 | protected function loadColumnSchema(array $info): ColumnSchema |
||
497 | { |
||
498 | $column = $this->createColumnSchema(); |
||
499 | |||
500 | $column->name($info['column_name']); |
||
501 | $column->allowNull($info['is_nullable'] === 'YES'); |
||
502 | $column->dbType($info['data_type']); |
||
503 | $column->enumValues([]); // mssql has only vague equivalents to enum |
||
504 | $column->primaryKey(false); // primary key will be determined in findColumns() method |
||
505 | $column->autoIncrement($info['is_identity'] == 1); |
||
506 | $column->unsigned(stripos($column->getDbType(), 'unsigned') !== false); |
||
507 | $column->comment($info['comment'] ?? ''); |
||
508 | $column->type(self::TYPE_STRING); |
||
509 | |||
510 | if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->getDbType(), $matches)) { |
||
511 | $type = $matches[1]; |
||
512 | |||
513 | if (isset($this->typeMap[$type])) { |
||
514 | $column->type($this->typeMap[$type]); |
||
515 | } |
||
516 | |||
517 | if (!empty($matches[2])) { |
||
518 | $values = explode(',', $matches[2]); |
||
519 | $column->precision((int) $values[0]); |
||
520 | $column->size((int) $values[0]); |
||
521 | |||
522 | if (isset($values[1])) { |
||
523 | $column->scale((int) $values[1]); |
||
524 | } |
||
525 | |||
526 | if ($column->getSize() === 1 && ($type === 'tinyint' || $type === 'bit')) { |
||
527 | $column->type('boolean'); |
||
528 | } elseif ($type === 'bit') { |
||
529 | if ($column->getSize() > 32) { |
||
530 | $column->type('bigint'); |
||
531 | } elseif ($column->getSize() === 32) { |
||
532 | $column->type('integer'); |
||
533 | } |
||
534 | } |
||
535 | } |
||
536 | } |
||
537 | |||
538 | $column->phpType($this->getColumnPhpType($column)); |
||
539 | |||
540 | if ($info['column_default'] === '(NULL)') { |
||
541 | $info['column_default'] = null; |
||
542 | } |
||
543 | |||
544 | if (!$column->isPrimaryKey() && ($column->getType() !== 'timestamp' || $info['column_default'] !== 'CURRENT_TIMESTAMP')) { |
||
545 | $column->defaultValue($column->defaultPhpTypecast($info['column_default'])); |
||
546 | } |
||
547 | |||
548 | return $column; |
||
549 | } |
||
550 | |||
551 | /** |
||
552 | * Collects the metadata of table columns. |
||
553 | * |
||
554 | * @param MssqlTableSchema $table the table metadata. |
||
555 | * |
||
556 | * @throws Exception |
||
557 | * @throws InvalidConfigException |
||
558 | * @throws NotSupportedException |
||
559 | * |
||
560 | * @return bool whether the table exists in the database. |
||
561 | */ |
||
562 | protected function findColumns(MssqlTableSchema $table): bool |
||
563 | { |
||
564 | $columnsTableName = 'INFORMATION_SCHEMA.COLUMNS'; |
||
565 | $whereSql = "[t1].[table_name] = " . $this->getDb()->quoteValue($table->getName()); |
||
566 | |||
567 | if ($table->getCatalogName() !== null) { |
||
568 | $columnsTableName = "{$table->getCatalogName()}.{$columnsTableName}"; |
||
569 | $whereSql .= " AND [t1].[table_catalog] = '{$table->getCatalogName()}'"; |
||
570 | } |
||
571 | |||
572 | if ($table->getSchemaName() !== null) { |
||
573 | $whereSql .= " AND [t1].[table_schema] = '{$table->getSchemaName()}'"; |
||
574 | } |
||
575 | |||
576 | $columnsTableName = $this->quoteTableName($columnsTableName); |
||
577 | |||
578 | $sql = <<<SQL |
||
579 | SELECT |
||
580 | [t1].[column_name], |
||
581 | [t1].[is_nullable], |
||
582 | CASE WHEN [t1].[data_type] IN ('char','varchar','nchar','nvarchar','binary','varbinary') THEN |
||
583 | CASE WHEN [t1].[character_maximum_length] = NULL OR [t1].[character_maximum_length] = -1 THEN |
||
584 | [t1].[data_type] |
||
585 | ELSE |
||
586 | [t1].[data_type] + '(' + LTRIM(RTRIM(CONVERT(CHAR,[t1].[character_maximum_length]))) + ')' |
||
587 | END |
||
588 | ELSE |
||
589 | [t1].[data_type] |
||
590 | END AS 'data_type', |
||
591 | [t1].[column_default], |
||
592 | COLUMNPROPERTY(OBJECT_ID([t1].[table_schema] + '.' + [t1].[table_name]), [t1].[column_name], 'IsIdentity') AS is_identity, |
||
593 | ( |
||
594 | SELECT CONVERT(VARCHAR, [t2].[value]) |
||
595 | FROM [sys].[extended_properties] AS [t2] |
||
596 | WHERE |
||
597 | [t2].[class] = 1 AND |
||
598 | [t2].[class_desc] = 'OBJECT_OR_COLUMN' AND |
||
599 | [t2].[name] = 'MS_Description' AND |
||
600 | [t2].[major_id] = OBJECT_ID([t1].[TABLE_SCHEMA] + '.' + [t1].[table_name]) AND |
||
601 | [t2].[minor_id] = COLUMNPROPERTY(OBJECT_ID([t1].[TABLE_SCHEMA] + '.' + [t1].[TABLE_NAME]), [t1].[COLUMN_NAME], 'ColumnID') |
||
602 | ) as comment |
||
603 | FROM {$columnsTableName} AS [t1] |
||
604 | WHERE {$whereSql} |
||
605 | SQL; |
||
606 | |||
607 | try { |
||
608 | $columns = $this->getDb()->createCommand($sql)->queryAll(); |
||
609 | |||
610 | if (empty($columns)) { |
||
611 | return false; |
||
612 | } |
||
613 | } catch (\Exception $e) { |
||
614 | return false; |
||
615 | } |
||
616 | |||
617 | foreach ($columns as $column) { |
||
618 | $column = $this->loadColumnSchema($column); |
||
619 | foreach ($table->getPrimaryKey() as $primaryKey) { |
||
620 | if (strcasecmp($column->getName(), $primaryKey) === 0) { |
||
621 | $column->primaryKey(true); |
||
622 | break; |
||
623 | } |
||
624 | } |
||
625 | |||
626 | if ($column->isPrimaryKey() && $column->isAutoIncrement()) { |
||
627 | $table->sequenceName(''); |
||
628 | } |
||
629 | |||
630 | $table->columns($column->getName(), $column); |
||
631 | } |
||
632 | |||
633 | return true; |
||
634 | } |
||
635 | |||
636 | /** |
||
637 | * Collects the constraint details for the given table and constraint type. |
||
638 | * |
||
639 | * @param MssqlTableSchema $table |
||
640 | * @param string $type either PRIMARY KEY or UNIQUE. |
||
641 | * |
||
642 | * @throws Exception |
||
643 | * @throws InvalidArgumentException |
||
644 | * @throws InvalidConfigException |
||
645 | * |
||
646 | * @return array each entry contains index_name and field_name. |
||
647 | */ |
||
648 | protected function findTableConstraints(MssqlTableSchema $table, string $type): array |
||
649 | { |
||
650 | $keyColumnUsageTableName = 'INFORMATION_SCHEMA.KEY_COLUMN_USAGE'; |
||
651 | $tableConstraintsTableName = 'INFORMATION_SCHEMA.TABLE_CONSTRAINTS'; |
||
652 | |||
653 | if ($table->getCatalogName() !== null) { |
||
654 | $keyColumnUsageTableName = $table->getCatalogName() . '.' . $keyColumnUsageTableName; |
||
655 | $tableConstraintsTableName = $table->getCatalogName() . '.' . $tableConstraintsTableName; |
||
656 | } |
||
657 | |||
658 | $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName); |
||
659 | $tableConstraintsTableName = $this->quoteTableName($tableConstraintsTableName); |
||
660 | |||
661 | $sql = <<<SQL |
||
662 | SELECT |
||
663 | [kcu].[constraint_name] AS [index_name], |
||
664 | [kcu].[column_name] AS [field_name] |
||
665 | FROM {$keyColumnUsageTableName} AS [kcu] |
||
666 | LEFT JOIN {$tableConstraintsTableName} AS [tc] ON |
||
667 | [kcu].[table_schema] = [tc].[table_schema] AND |
||
668 | [kcu].[table_name] = [tc].[table_name] AND |
||
669 | [kcu].[constraint_name] = [tc].[constraint_name] |
||
670 | WHERE |
||
671 | [tc].[constraint_type] = :type AND |
||
672 | [kcu].[table_name] = :tableName AND |
||
673 | [kcu].[table_schema] = :schemaName |
||
674 | SQL; |
||
675 | |||
676 | return $this->getDb()->createCommand( |
||
677 | $sql, |
||
678 | [ |
||
679 | ':tableName' => $table->getName(), |
||
680 | ':schemaName' => $table->getSchemaName(), |
||
681 | ':type' => $type, |
||
682 | ] |
||
683 | )->queryAll(); |
||
684 | } |
||
685 | |||
686 | /** |
||
687 | * Collects the primary key column details for the given table. |
||
688 | * |
||
689 | * @param MssqlTableSchema $table the table metadata |
||
690 | * |
||
691 | * @throws Exception |
||
692 | * @throws InvalidArgumentException |
||
693 | * @throws InvalidConfigException |
||
694 | */ |
||
695 | protected function findPrimaryKeys(MssqlTableSchema $table): void |
||
696 | { |
||
697 | $result = []; |
||
698 | foreach ($this->findTableConstraints($table, 'PRIMARY KEY') as $row) { |
||
699 | $table->primaryKey($row['field_name']); |
||
700 | } |
||
701 | } |
||
702 | |||
703 | /** |
||
704 | * Collects the foreign key column details for the given table. |
||
705 | * |
||
706 | * @param MssqlTableSchema $table the table metadata |
||
707 | * |
||
708 | * @throws Exception |
||
709 | * @throws InvalidArgumentException |
||
710 | * @throws InvalidConfigException |
||
711 | */ |
||
712 | protected function findForeignKeys(MssqlTableSchema $table): void |
||
713 | { |
||
714 | $object = $table->getName(); |
||
715 | |||
716 | if ($table->getSchemaName() !== null) { |
||
717 | $object = $table->getSchemaName() . '.' . $object; |
||
718 | } |
||
719 | |||
720 | if ($table->getCatalogName() !== null) { |
||
721 | $object = $table->getCatalogName() . '.' . $object; |
||
722 | } |
||
723 | |||
724 | /** |
||
725 | * Please refer to the following page for more details: |
||
726 | * {@see http://msdn2.microsoft.com/en-us/library/aa175805(SQL.80).aspx} |
||
727 | */ |
||
728 | $sql = <<<'SQL' |
||
729 | SELECT |
||
730 | [fk].[name] AS [fk_name], |
||
731 | [cp].[name] AS [fk_column_name], |
||
732 | OBJECT_NAME([fk].[referenced_object_id]) AS [uq_table_name], |
||
733 | [cr].[name] AS [uq_column_name] |
||
734 | FROM |
||
735 | [sys].[foreign_keys] AS [fk] |
||
736 | INNER JOIN [sys].[foreign_key_columns] AS [fkc] ON |
||
737 | [fk].[object_id] = [fkc].[constraint_object_id] |
||
738 | INNER JOIN [sys].[columns] AS [cp] ON |
||
739 | [fk].[parent_object_id] = [cp].[object_id] AND |
||
740 | [fkc].[parent_column_id] = [cp].[column_id] |
||
741 | INNER JOIN [sys].[columns] AS [cr] ON |
||
742 | [fk].[referenced_object_id] = [cr].[object_id] AND |
||
743 | [fkc].[referenced_column_id] = [cr].[column_id] |
||
744 | WHERE |
||
745 | [fk].[parent_object_id] = OBJECT_ID(:object) |
||
746 | SQL; |
||
747 | |||
748 | $rows = $this->getDb()->createCommand($sql, [':object' => $object])->queryAll(); |
||
749 | |||
750 | $table->foreignKeys([]); |
||
751 | foreach ($rows as $row) { |
||
752 | if (!isset($table->getForeignKeys()[$row['fk_name']])) { |
||
753 | $fk[$row['fk_name']][] = $row['uq_table_name']; |
||
754 | $table->foreignKeys($fk); |
||
755 | } |
||
756 | |||
757 | $fk[$row['fk_name']][$row['fk_column_name']] = $row['uq_column_name']; |
||
758 | $table->foreignKeys($fk); |
||
759 | } |
||
760 | } |
||
761 | |||
762 | /** |
||
763 | * Returns all views names in the database. |
||
764 | * |
||
765 | * @param string $schema the schema of the views. Defaults to empty string, meaning the current or default schema. |
||
766 | * |
||
767 | * @throws Exception |
||
768 | * @throws InvalidArgumentException |
||
769 | * @throws InvalidConfigException |
||
770 | * |
||
771 | * @return array all views names in the database. The names have NO schema name prefix. |
||
772 | */ |
||
773 | protected function findViewNames(string $schema = ''): array |
||
774 | { |
||
775 | if ($schema === '') { |
||
776 | $schema = $this->defaultSchema; |
||
777 | } |
||
778 | |||
779 | $sql = <<<'SQL' |
||
780 | SELECT [t].[table_name] |
||
781 | FROM [INFORMATION_SCHEMA].[TABLES] AS [t] |
||
782 | WHERE [t].[table_schema] = :schema AND [t].[table_type] = 'VIEW' |
||
783 | ORDER BY [t].[table_name] |
||
784 | SQL; |
||
785 | |||
786 | $views = $this->getDb()->createCommand($sql, [':schema' => $schema])->queryColumn(); |
||
787 | $views = array_map(static function ($item) { |
||
788 | return '[' . $item . ']'; |
||
789 | }, $views); |
||
790 | |||
791 | return $views; |
||
792 | } |
||
793 | |||
794 | /** |
||
795 | * Returns all unique indexes for the given table. |
||
796 | * |
||
797 | * Each array element is of the following structure: |
||
798 | * |
||
799 | * ```php |
||
800 | * [ |
||
801 | * 'IndexName1' => ['col1' [, ...]], |
||
802 | * 'IndexName2' => ['col2' [, ...]], |
||
803 | * ] |
||
804 | * ``` |
||
805 | * |
||
806 | * @param MssqlTableSchema $table the table metadata. |
||
807 | * |
||
808 | * @throws Exception |
||
809 | * @throws InvalidArgumentException |
||
810 | * @throws InvalidConfigException |
||
811 | * |
||
812 | * @return array all unique indexes for the given table. |
||
813 | */ |
||
814 | public function findUniqueIndexes(MssqlTableSchema $table): array |
||
815 | { |
||
816 | $result = []; |
||
817 | |||
818 | foreach ($this->findTableConstraints($table, 'UNIQUE') as $row) { |
||
819 | $result[$row['index_name']][] = $row['field_name']; |
||
820 | } |
||
821 | |||
822 | return $result; |
||
823 | } |
||
824 | |||
825 | /** |
||
826 | * Loads multiple types of constraints and returns the specified ones. |
||
827 | * |
||
828 | * @param string $tableName table name. |
||
829 | * @param string $returnType return type: |
||
830 | * - primaryKey |
||
831 | * - foreignKeys |
||
832 | * - uniques |
||
833 | * - checks |
||
834 | * - defaults |
||
835 | * |
||
836 | * @throws Exception |
||
837 | * @throws InvalidArgumentException |
||
838 | * @throws InvalidConfigException |
||
839 | * |
||
840 | * @return mixed constraints. |
||
841 | */ |
||
842 | private function loadTableConstraints(string $tableName, string $returnType) |
||
843 | { |
||
844 | static $sql = <<<'SQL' |
||
845 | SELECT |
||
846 | [o].[name] AS [name], |
||
847 | COALESCE([ccol].[name], [dcol].[name], [fccol].[name], [kiccol].[name]) AS [column_name], |
||
848 | RTRIM([o].[type]) AS [type], |
||
849 | OBJECT_SCHEMA_NAME([f].[referenced_object_id]) AS [foreign_table_schema], |
||
850 | OBJECT_NAME([f].[referenced_object_id]) AS [foreign_table_name], |
||
851 | [ffccol].[name] AS [foreign_column_name], |
||
852 | [f].[update_referential_action_desc] AS [on_update], |
||
853 | [f].[delete_referential_action_desc] AS [on_delete], |
||
854 | [c].[definition] AS [check_expr], |
||
855 | [d].[definition] AS [default_expr] |
||
856 | FROM (SELECT OBJECT_ID(:fullName) AS [object_id]) AS [t] |
||
857 | INNER JOIN [sys].[objects] AS [o] |
||
858 | ON [o].[parent_object_id] = [t].[object_id] AND [o].[type] IN ('PK', 'UQ', 'C', 'D', 'F') |
||
859 | LEFT JOIN [sys].[check_constraints] AS [c] |
||
860 | ON [c].[object_id] = [o].[object_id] |
||
861 | LEFT JOIN [sys].[columns] AS [ccol] |
||
862 | ON [ccol].[object_id] = [c].[parent_object_id] AND [ccol].[column_id] = [c].[parent_column_id] |
||
863 | LEFT JOIN [sys].[default_constraints] AS [d] |
||
864 | ON [d].[object_id] = [o].[object_id] |
||
865 | LEFT JOIN [sys].[columns] AS [dcol] |
||
866 | ON [dcol].[object_id] = [d].[parent_object_id] AND [dcol].[column_id] = [d].[parent_column_id] |
||
867 | LEFT JOIN [sys].[key_constraints] AS [k] |
||
868 | ON [k].[object_id] = [o].[object_id] |
||
869 | LEFT JOIN [sys].[index_columns] AS [kic] |
||
870 | ON [kic].[object_id] = [k].[parent_object_id] AND [kic].[index_id] = [k].[unique_index_id] |
||
871 | LEFT JOIN [sys].[columns] AS [kiccol] |
||
872 | ON [kiccol].[object_id] = [kic].[object_id] AND [kiccol].[column_id] = [kic].[column_id] |
||
873 | LEFT JOIN [sys].[foreign_keys] AS [f] |
||
874 | ON [f].[object_id] = [o].[object_id] |
||
875 | LEFT JOIN [sys].[foreign_key_columns] AS [fc] |
||
876 | ON [fc].[constraint_object_id] = [o].[object_id] |
||
877 | LEFT JOIN [sys].[columns] AS [fccol] |
||
878 | ON [fccol].[object_id] = [fc].[parent_object_id] AND [fccol].[column_id] = [fc].[parent_column_id] |
||
879 | LEFT JOIN [sys].[columns] AS [ffccol] |
||
880 | ON [ffccol].[object_id] = [fc].[referenced_object_id] AND [ffccol].[column_id] = [fc].[referenced_column_id] |
||
881 | ORDER BY [kic].[key_ordinal] ASC, [fc].[constraint_column_id] ASC |
||
882 | SQL; |
||
883 | |||
884 | $resolvedName = $this->resolveTableName($tableName); |
||
885 | $constraints = $this->getDb()->createCommand($sql, [':fullName' => $resolvedName->getFullName()])->queryAll(); |
||
886 | $constraints = $this->normalizePdoRowKeyCase($constraints, true); |
||
887 | $constraints = ArrayHelper::index($constraints, null, ['type', 'name']); |
||
888 | $result = [ |
||
889 | 'primaryKey' => null, |
||
890 | 'foreignKeys' => [], |
||
891 | 'uniques' => [], |
||
892 | 'checks' => [], |
||
893 | 'defaults' => [], |
||
894 | ]; |
||
895 | |||
896 | foreach ($constraints as $type => $names) { |
||
897 | foreach ($names as $name => $constraint) { |
||
898 | switch ($type) { |
||
899 | case 'PK': |
||
900 | $result['primaryKey'] = (new Constraint()) |
||
901 | ->name($name) |
||
902 | ->columnNames(ArrayHelper::getColumn($constraint, 'column_name')); |
||
903 | break; |
||
904 | case 'F': |
||
905 | $result['foreignKeys'][] = (new ForeignKeyConstraint()) |
||
906 | ->name($name) |
||
907 | ->columnNames(ArrayHelper::getColumn($constraint, 'column_name')) |
||
908 | ->foreignSchemaName($constraint[0]['foreign_table_schema']) |
||
909 | ->foreignTableName($constraint[0]['foreign_table_name']) |
||
910 | ->foreignColumnNames(ArrayHelper::getColumn($constraint, 'foreign_column_name')) |
||
911 | ->onDelete(str_replace('_', '', $constraint[0]['on_delete'])) |
||
912 | ->onUpdate(str_replace('_', '', $constraint[0]['on_update'])); |
||
913 | break; |
||
914 | case 'UQ': |
||
915 | $result['uniques'][] = (new Constraint()) |
||
916 | ->name($name) |
||
917 | ->columnNames(ArrayHelper::getColumn($constraint, 'column_name')); |
||
918 | break; |
||
919 | case 'C': |
||
920 | $result['checks'][] = (new CheckConstraint()) |
||
921 | ->name($name) |
||
922 | ->columnNames(ArrayHelper::getColumn($constraint, 'column_name')) |
||
923 | ->expression($constraint[0]['check_expr']); |
||
924 | break; |
||
925 | case 'D': |
||
926 | $result['defaults'][] = (new DefaultValueConstraint()) |
||
927 | ->name($name) |
||
928 | ->columnNames(ArrayHelper::getColumn($constraint, 'column_name')) |
||
929 | ->value($constraint[0]['default_expr']); |
||
930 | break; |
||
931 | } |
||
932 | } |
||
933 | } |
||
934 | foreach ($result as $type => $data) { |
||
935 | $this->setTableMetadata($tableName, $type, $data); |
||
936 | } |
||
937 | |||
938 | return $result[$returnType]; |
||
939 | } |
||
940 | |||
941 | /** |
||
942 | * Quotes a column name for use in a query. |
||
943 | * |
||
944 | * If the column name contains prefix, the prefix will also be properly quoted. If the column name is already quoted |
||
945 | * or contains '(', '[[' or '{{', then this method will do nothing. |
||
946 | * |
||
947 | * @param string $name column name. |
||
948 | * |
||
949 | * @return string the properly quoted column name. |
||
950 | * |
||
951 | * {@see quoteSimpleColumnName()} |
||
952 | */ |
||
953 | public function quoteColumnName(string $name): string |
||
960 | } |
||
961 | |||
962 | /** |
||
963 | * Executes the INSERT command, returning primary key values. |
||
964 | * |
||
965 | * @param string $table the table that new rows will be inserted into. |
||
966 | * @param array $columns the column data (name => value) to be inserted into the table. |
||
967 | * |
||
968 | * @throws Exception |
||
969 | * @throws InvalidConfigException |
||
970 | * @throws NotSupportedException |
||
971 | * @throws InvalidCallException |
||
972 | * |
||
973 | * @return array|false primary key values or false if the command fails. |
||
974 | */ |
||
975 | public function insert(string $table, array $columns) |
||
976 | { |
||
977 | $command = $this->getDb()->createCommand()->insert($table, $columns); |
||
1006 | } |
||
1007 | } |
||
1008 |
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.