Schema::resolveTableName()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * @link http://www.yiiframework.com/
5
 * @copyright Copyright (c) 2008 Yii Software LLC
6
 * @license http://www.yiiframework.com/license/
7
 */
8
9
namespace edgardmessias\db\ibm\db2;
10
11
use PDO;
12
use yii\db\Expression;
13
use yii\db\TableSchema;
14
use yii\db\Transaction;
15
16
/**
17
 * @author Edgard Messias <[email protected]>
18
 * @author Nikita Verkhovin <[email protected]>
19
 * @since 1.0
20
 */
21
22
class Schema extends \yii\db\Schema implements \yii\db\ConstraintFinderInterface
23
{
24
    use \yii\db\ViewFinderTrait;
25
    use \yii\db\ConstraintFinderTrait;
26
27
    public $typeMap = [
28
        'character'  => self::TYPE_CHAR,
29
        'varchar'    => self::TYPE_STRING,
30
        'char'       => self::TYPE_CHAR,
31
        'clob'       => self::TYPE_TEXT,
32
        'graphic'    => self::TYPE_STRING,
33
        'vargraphic' => self::TYPE_STRING,
34
        'varg'       => self::TYPE_STRING,
35
        'dbclob'     => self::TYPE_TEXT,
36
        'nchar'      => self::TYPE_CHAR,
37
        'nvarchar'   => self::TYPE_STRING,
38
        'nclob'      => self::TYPE_TEXT,
39
        'binary'     => self::TYPE_BINARY,
40
        'varbinary'  => self::TYPE_BINARY,
41
        'varbin'     => self::TYPE_BINARY,
42
        'blob'       => self::TYPE_BINARY,
43
        'smallint'   => self::TYPE_SMALLINT,
44
        'int'        => self::TYPE_INTEGER,
45
        'integer'    => self::TYPE_INTEGER,
46
        'bigint'     => self::TYPE_BIGINT,
47
        'decimal'    => self::TYPE_DECIMAL,
48
        'numeric'    => self::TYPE_DECIMAL,
49
        'real'       => self::TYPE_FLOAT,
50
        'float'      => self::TYPE_FLOAT,
51
        'double'     => self::TYPE_DOUBLE,
52
        'decfloat'   => self::TYPE_FLOAT,
53
        'date'       => self::TYPE_DATE,
54
        'time'       => self::TYPE_TIME,
55
        'timestamp'  => self::TYPE_TIMESTAMP,
56
        'timestmp'   => self::TYPE_TIMESTAMP
57
    ];
58
59
   /**
60
     * @inheritdoc
61
     */
62 181
    public function createQueryBuilder()
63
    {
64 181
        return new QueryBuilder($this->db);
65
    }
66
67
    /**
68
     * @inheritdoc
69
     */
70 268
    public function quoteSimpleTableName($name)
71
    {
72 268
        return strpos($name, '"') !== false ? $name : '"' . $name . '"';
73
    }
74
75
    /**
76
     * @inheritdoc
77
     */
78 295
    public function quoteSimpleColumnName($name)
79
    {
80 295
        return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"';
81
    }
82
83
    /**
84
     * @inheritdoc
85
     */
86 196
    protected function loadTableSchema($name)
87
    {
88 196
        $table = new TableSchema();
89 196
        $this->resolveTableNames($table, $name);
90
91 196
        if ($this->findColumns($table)) {
92 189
            $this->findConstraints($table);
93 189
            return $table;
94
        } else {
95 14
            return null;
96
        }
97
    }
98
99
    /**
100
     * @inheritdoc
101
     */
102 256
    protected function resolveTableNames($table, $name)
103
    {
104 256
        $parts = explode('.', str_replace('"', '', $name));
105 256
        if (isset($parts[1])) {
106
            $table->schemaName = $parts[0];
107
            $table->name = $parts[1];
108
            $table->fullName = $table->schemaName . '.' . $table->name;
109
        } else {
110 256
            $table->fullName = $table->name = $parts[0];
111
        }
112 256
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117 83
    protected function resolveTableName($name)
118
    {
119 83
        $resolvedName = new TableSchema();
120 83
        $this->resolveTableNames($resolvedName, $name);
121 83
        return $resolvedName;
122
    }
123
124
    /**
125
     * Determines the PDO type for the given PHP data value.
126
     * @param mixed $data the data whose PDO type is to be determined
127
     * @return integer the PDO type
128
     * @see http://www.php.net/manual/en/pdo.constants.php
129
     */
130 283
    public function getPdoType($data)
131
    {
132
        static $typeMap = [
133
            // php type => PDO type
134
            'boolean'  => PDO::PARAM_INT, // PARAM_BOOL is not supported by DB2 PDO
135
            'integer'  => PDO::PARAM_INT,
136
            'string'   => PDO::PARAM_STR,
137
            'resource' => PDO::PARAM_LOB,
138
            'NULL'     => PDO::PARAM_INT, // PDO IBM doesn't support PARAM_NULL
139 283
        ];
140 283
        $type = gettype($data);
141
142 283
        return isset($typeMap[$type]) ? $typeMap[$type] : PDO::PARAM_STR;
143
    }
144
145
    /**
146
     * @inheritdoc
147
     */
148 189
    protected function loadColumnSchema($info)
149
    {
150 189
        $column = $this->createColumnSchema();
151
152 189
        $column->name = $info['name'];
153 189
        $column->dbType = $info['dbtype'];
154 189
        $column->defaultValue = isset($info['defaultvalue']) ? trim($info['defaultvalue'], "''") : null;
155 189
        $column->scale = (int) $info['scale'];
156 189
        $column->size = (int) $info['size'];
157 189
        $column->precision = (int) $info['size'];
158 189
        $column->allowNull = $info['allownull'] === '1';
159 189
        $column->isPrimaryKey = $info['isprimarykey'] === '1';
160 189
        $column->autoIncrement = $info['autoincrement'] === '1';
161 189
        $column->unsigned = false;
162 189
        $column->type = $this->typeMap[strtolower($info['dbtype'])];
163 189
        $column->enumValues = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $enumValues.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
164 189
        $column->comment = isset($info['comment']) ? $info['comment'] : null;
165
166 189
        if (preg_match('/(varchar|character|clob|graphic|binary|blob)/i', $info['dbtype'])) {
167 165
            $column->dbType .= '(' . $info['size'] . ')';
168 189
        } elseif (preg_match('/(decimal|double|real)/i', $info['dbtype'])) {
169 84
            $column->dbType .= '(' . $info['size'] . ',' . $info['scale'] . ')';
170 84
        }
171
172 189
        if ($column->defaultValue) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $column->defaultValue of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
173 28
            if ($column->type === 'timestamp' && $column->defaultValue === 'CURRENT TIMESTAMP') {
174 23
                $column->defaultValue = new Expression($column->defaultValue);
175 23
            }
176 28
        }
177
178 189
        $column->phpType = $this->getColumnPhpType($column);
179
180 189
        return $column;
181
    }
182
183
    /**
184
     * @inheritdoc
185
     */
186 196
    protected function findColumns($table)
187
    {
188
189
190 196
        if ($this->db->isISeries) {
191
            $sql = <<<SQL
192
                SELECT c.column_name AS name,
193
                       c.data_type AS dbtype,
194
                       CAST(c.column_default AS VARCHAR(254)) AS defaultvalue,
195
                       CASE WHEN c.is_nullable = 'Y'         THEN 1 ELSE 0 END AS allownull,
196
                       c.length AS size,
197
                       c.numeric_scale AS scale,
198
                       CASE WHEN c.is_identity = 'YES'      THEN 1 ELSE 0 END AS autoincrement,
199
                       case when x.column<>'' THEN 1 ELSE 0 END AS isprimarykey
200
                FROM qsys2.syscolumns c
201
                left join (
202
                SELECT
203
                column_name As column
204
                FROM qsys2.syscst
205
                INNER JOIN qsys2.syskeycst
206
                  ON qsys2.syscst.constraint_name = qsys2.syskeycst.constraint_name
207
                 AND qsys2.syscst.table_schema = qsys2.syskeycst.table_schema
208
                 AND qsys2.syscst.table_name = qsys2.syskeycst.table_name
209
                WHERE qsys2.syscst.constraint_type = 'PRIMARY KEY'
210
                  AND qsys2.syscst.table_name = :table) x
211
                  on x.column= c.column_name
212
                WHERE UPPER(c.table_name) = :table1
213
                AND c.table_schema = :schema
214
SQL;
215
            $sql .= ' ORDER BY c.ordinal_position';
216
        } else {
217
            $sql = <<<SQL
218
                SELECT
219
                    c.colname AS name,
220
                    c.typename AS dbtype,
221
                    cast(c.default as varchar(254)) AS defaultvalue,
222
                    c.scale AS scale,
223
                    c.length AS size,
224
                    CASE WHEN c.nulls = 'Y'         THEN 1 ELSE 0 END AS allownull,
225
                    CASE WHEN c.keyseq IS NOT NULL  THEN 1 ELSE 0 END AS isprimarykey,
226
                    CASE WHEN c.identity = 'Y'      THEN 1 ELSE 0 END AS autoincrement,
227
                    c.remarks AS comment
228
                FROM
229
                    syscat.columns AS c
230
                WHERE
231
                    c.tabname = :table
232 196
SQL;
233
234 196
            if (isset($table->schemaName)) {
235
                $sql .= ' AND c.tabschema = :schema';
236
            }
237
238 196
            $sql .= ' ORDER BY c.colno';
239
        }
240
241 196
        $command = $this->db->createCommand($sql);
242 196
        $command->bindValue(':table', $table->name);
243 196
        if($this->db->isISeries){
244
            $command->bindValue(':schema', $this->db->defaultSchema);
0 ignored issues
show
Bug introduced by
The property defaultSchema does not seem to exist. Did you mean schema?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
245
            $command->bindValue(':table1', $table->name);
246
247
        }else {
248 196
            if (isset($table->schemaName)) {
249
                $command->bindValue(':schema', $table->schemaName);
250
            }
251
        }
252 196
        $columns = $command->queryAll();
253 196
        if (empty($columns)) {
254 14
            return false;
255
        }
256
        
257 189
        $columns = $this->normalizePdoRowKeyCase($columns, true);
258
259 189
        foreach ($columns as $info) {
260 189
            $column = $this->loadColumnSchema($info);
261 189
            $table->columns[$column->name] = $column;
262 189
            if ($column->isPrimaryKey) {
263 164
                $table->primaryKey[] = $column->name;
264 164
                if ($column->autoIncrement) {
265 161
                    $table->sequenceName = $column->name;
266 161
                }
267 164
            }
268 189
        }
269 189
        return true;
270
    }
271
272
    /**
273
     * @inheritdoc
274
     */
275 189
    protected function findConstraints($table)
276
    {
277 189
        if ($this->db->isISeries) {
278
            $sql = <<<SQL
279
            SELECT
280
              child.constraint_name as name,
281
              parent.table_name AS tablename,
282
              parent.column_name AS pk,
283
              child.column_name AS fk
284
            FROM qsys2.syskeycst child
285
            INNER JOIN qsys2.sysrefcst crossref
286
                ON child.constraint_schema = crossref.constraint_schema
287
               AND child.constraint_name = crossref.constraint_name
288
            INNER JOIN qsys2.syskeycst parent
289
                ON crossref.unique_constraint_schema = parent.constraint_schema
290
               AND crossref.unique_constraint_name = parent.constraint_name
291
            INNER JOIN qsys2.syscst coninfo
292
                ON child.constraint_name = coninfo.constraint_name
293
            WHERE UPPER(child.table_name) = :table
294
              AND coninfo.constraint_type = 'FOREIGN KEY'
295
              AND child.table_schema = :schema
296
SQL;
297
298
        } else {
299
            $sql = <<<SQL
300
            SELECT
301
                fk.constname as name,
302
                pk.tabname AS tablename,
303
                fk.colname AS fk,
304
                pk.colname AS pk
305
            FROM
306
                syscat.references AS ref
307
            INNER JOIN
308
                syscat.keycoluse AS fk ON ref.constname = fk.constname
309
            INNER JOIN
310
                syscat.keycoluse AS pk ON ref.refkeyname = pk.constname AND pk.colseq = fk.colseq
311
            WHERE
312
                fk.tabname = :table
313 189
SQL;
314
315 189
            if (isset($table->schemaName)) {
316
                $sql .= ' AND fk.tabschema = :schema';
317
            }
318
        }
319 189
        $command = $this->db->createCommand($sql);
320 189
        $command->bindValue(':table', $table->name);
321 189
        if($this->db->isISeries){
322
            $command->bindValue(':schema', $this->db->defaultSchema);
0 ignored issues
show
Bug introduced by
The property defaultSchema does not seem to exist. Did you mean schema?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
323
        }else {
324 189
            if (isset($table->schemaName)) {
325
                $command->bindValue(':schema', $table->schemaName);
326
            }
327
        }
328
329 189
        $constraints = $command->queryAll();
330 189
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
331
332 189
        $constraints = \yii\helpers\ArrayHelper::index($constraints, null, ['name']);
333
334 189
        foreach ($constraints as $name => $constraint) {
335 66
            $fks = \yii\helpers\ArrayHelper::getColumn($constraint, 'fk');
336 66
            $pks = \yii\helpers\ArrayHelper::getColumn($constraint, 'pk');
337
338 66
            $tablename = $constraint[0]['tablename'];
339
            
340 66
            $keymap = array_combine($fks, $pks);
341
342 66
            $foreignKeys = [$tablename];
343 66
            foreach ($keymap as $fk => $pk) {
344 66
                $foreignKeys[$fk] = $pk;
345 66
            }
346
347 66
            $table->foreignKeys[$name] = $foreignKeys;
348 189
        }
349 189
    }
350
351
    /**
352
     * @inheritdoc
353
     */
354 1
    public function findUniqueIndexes($table)
355
    {
356
357 1
        if ($this->db->isISeries) {
358
            $sql = <<<SQL
359
                SELECT
360
                qsys2.syskeycst.constraint_name As indexname,
361
                qsys2.syskeycst.column_name As column
362
                FROM qsys2.syscst
363
                INNER JOIN qsys2.syskeycst
364
                  ON qsys2.syscst.constraint_name = qsys2.syskeycst.constraint_name
365
                 AND qsys2.syscst.table_schema = qsys2.syskeycst.table_schema
366
                 AND qsys2.syscst.table_name = qsys2.syskeycst.table_name
367
                WHERE qsys2.syscst.constraint_type = 'PRIMARY KEY'
368
                  AND qsys2.syscst.table_name = :table
369
                  AND qsys2.syscst.table_schema = :schema
370
                  ORDER BY qsys2.syskeycst.column_position
371
SQL;
372
        }else {
373
            $sql = <<<SQL
374
            SELECT
375
                i.indname AS indexname,
376
                ic.colname AS column
377
            FROM
378
                syscat.indexes AS i
379
            INNER JOIN
380
                syscat.indexcoluse AS ic ON i.indname = ic.indname
381
            WHERE
382
                i.tabname = :table
383 1
SQL;
384
385 1
            if (isset($table->schemaName)) {
386
                $sql .= ' AND tabschema = :schema';
387
            }
388
389 1
            $sql .= ' ORDER BY ic.colseq';
390
        }
391 1
        $command = $this->db->createCommand($sql);
392 1
        $command->bindValue(':table', $table->name);
393
394 1
        if($this->db->isISeries){
395
            $command->bindValue(':schema', $this->db->defaultSchema);
0 ignored issues
show
Bug introduced by
The property defaultSchema does not seem to exist. Did you mean schema?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
396
        }else{
397 1
            if (isset($table->schemaName)) {
398
                $command->bindValue(':schema', $table->schemaName);
399
            }
400
        }
401 1
        $results = $command->queryAll();
402 1
        $results = $this->normalizePdoRowKeyCase($results, true);
403
404 1
        $indexes = [];
405 1
        foreach ($results as $result) {
406 1
            $indexes[$result['indexname']][] = $result['column'];
407 1
        }
408 1
        return $indexes;
409
    }
410
411
    /**
412
     * @inheritdoc
413
     */
414 5
    protected function findTableNames($schema = '')
415
    {
416
417 5
        if ($schema === '' && $this->db->isISeries) {
418
                $schema= $this->db->defaultSchema;
0 ignored issues
show
Bug introduced by
The property defaultSchema does not seem to exist. Did you mean schema?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
419
        }
420
421 5
        if ($this->db->isISeries) {
422
            $sql = <<<SQL
423
                SELECT TABLE_NAME as tabname
424
                FROM QSYS2.SYSTABLES
425
                WHERE TABLE_TYPE IN ('P','T','V')
426
                  AND SYSTEM_TABLE = 'N'
427
                  AND TABLE_SCHEMA = :schema
428
                ORDER BY TABLE_NAME
429
SQL;
430
        }else {
431
432
            $sql = <<<SQL
433
            SELECT
434
                t.tabname
435
            FROM
436
                syscat.tables AS t
437
            WHERE
438
                t.type in ('P','T', 'V') AND
439
                t.ownertype != 'S'
440 5
SQL;
441
442 5
            if ($schema !== '') {
443
                $sql .= ' AND t.tabschema = :schema';
444
            }
445
        }
446 5
        $command = $this->db->createCommand($sql);
447
448 5
        if ($schema !== '') {
449
            $command->bindValue(':schema', $schema);
450
        }
451
452 5
        return $command->queryColumn();
453
    }
454
    
455
    /**
456
     * @inheritdoc
457
     */
458 1
    protected function findSchemaNames()
459
    {
460
461
        $sql = <<<SQL
462
        SELECT
463
            t.schemaname
464
        FROM
465
            syscat.schemata AS t
466
        WHERE
467
            t.definertype != 'S' AND
468
            t.definer != 'DB2INST1'
469 1
SQL;
470
471 1
        $command = $this->db->createCommand($sql);
472
        
473 1
        $schemas = $command->queryColumn();
474
475 1
        return array_map('trim', $schemas);
476
    }
477
    
478
    /**
479
     * Creates a new savepoint.
480
     * @param string $name the savepoint name
481
     */
482 1
    public function createSavepoint($name)
483
    {
484 1
        $this->db->createCommand("SAVEPOINT $name ON ROLLBACK RETAIN CURSORS")->execute();
485 1
    }
486
487
    /**
488
     * Sets the isolation level of the current transaction.
489
     * @param string $level The transaction isolation level to use for this transaction.
490
     * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]]
491
     * and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used
492
     * after `SET TRANSACTION ISOLATION LEVEL`.
493
     * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
494
     */
495 2
    public function setTransactionIsolationLevel($level)
496
    {
497 2
        $sql = 'SET CURRENT ISOLATION ';
498
        switch ($level) {
499 2
            case Transaction::READ_UNCOMMITTED:
500 2
                $sql .= 'UR';
501 2
                break;
502 1
            case Transaction::READ_COMMITTED:
503 1
                $sql .= 'CS';
504 1
                break;
505 1
            case Transaction::REPEATABLE_READ:
506 1
                $sql .= 'RS';
507 1
                break;
508 1
            case Transaction::SERIALIZABLE:
509 1
                $sql .= 'RR';
510 1
                break;
511
            default:
512
                $sql .= $level;
513
        }
514
        
515 2
        $this->db->createCommand($sql)->execute();
516 2
    }
517
    
518
    /**
519
     * Refreshes the particular table schema.
520
     * This method cleans up cached table schema so that it can be re-created later
521
     * to reflect the database schema change.
522
     * @param string $name table name.
523
     * @since 2.0.6
524
     */
525 22
    public function refreshTableSchema($name)
526
    {
527 22
        if ($name) {
528
            try {
529 22
                $sql = "CALL ADMIN_CMD ('REORG TABLE " . $this->db->quoteTableName($name) . "')";
530 22
                $this->db->createCommand($sql)->execute();
531 22
            } catch (\Exception $ex) {
532
                // Do not throw error on table which doesn't exist (-2211)
533
                // Do not throw error on view (-2212)
534 7
                $code = isset($ex->errorInfo[1]) ? $ex->errorInfo[1] : 0;
0 ignored issues
show
Bug introduced by
The property errorInfo does not seem to exist in Exception.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
535 7
                if (!in_array($code, [-2211, -2212])) {
536
                    throw new \Exception($ex->getMessage(), $ex->getCode(), $ex->getPrevious());
537
                }
538
            }
539 22
        }
540
541 22
        parent::refreshTableSchema($name);
542 22
    }
543
544
    protected function findViewNames($schema = '') {
545
        $sql = <<<SQL
546
            SELECT t.viewname
547
            FROM syscat.views AS t
548
            WHERE t.ownertype != 'S'
549
SQL;
550
551
        if ($schema !== '') {
552
            $sql .= ' AND t.viewschema = :schema';
553
        }
554
        $command = $this->db->createCommand($sql);
555
556
        if ($schema !== '') {
557
            $command->bindValue(':schema', $schema);
558
        }
559
560
        return $command->queryColumn();
561
    }
562
563 30
    protected function loadTablePrimaryKey($tableName) {
564 30
        $resolvedName = $this->resolveTableName($tableName);
565
566
        $sql = <<<SQL
567
            SELECT CASE
568
                     WHEN i.indschema = 'SYSIBM' THEN NULL
569
                     ELSE i.indname
570
                   END AS name,
571
                   t.colname AS column_name
572
            FROM syscat.columns AS t
573
              LEFT JOIN syscat.indexes AS i
574
                     ON i.tabschema = t.tabschema
575
                    AND i.tabname = t.tabname
576
              INNER JOIN syscat.indexcoluse AS ic
577
                      ON ic.indschema = i.indschema
578
                     AND ic.indname = i.indname
579
                     AND ic.colname = t.colname
580
            WHERE t.keyseq IS NOT NULL
581
            AND   i.ownertype != 'S'
582
            AND   t.tabname = :table
583 30
SQL;
584
585 30
        if ($resolvedName->sequenceName) {
586
            $sql .= ' AND t.tabschema = :schema';
587
        }
588
589 30
        $sql .= ' ORDER BY t.keyseq';
590
591 30
        $command = $this->db->createCommand($sql);
592
593 30
        $command->bindValue(':table', $resolvedName->name);
594
595 30
        if ($resolvedName->sequenceName) {
596
            $command->bindValue(':schema', $resolvedName->sequenceName);
597
        }
598
        
599 30
        $constraints = $command->queryAll();
600
        
601 30
        if (empty($constraints)) {
602 4
            return null;
603
        }
604
        
605 27
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
606
        
607 27
        $columns = \yii\helpers\ArrayHelper::getColumn($constraints, 'column_name');
608
        
609 27
        return new \yii\db\Constraint([
610 27
            'name' => $constraints[0]['name'],
611 27
            'columnNames' => $columns,
612 27
        ]);
613
    }
614
615 30
    protected function loadTableUniques($tableName) {
616 30
        $resolvedName = $this->resolveTableName($tableName);
617
618
        $sql = <<<SQL
619
            SELECT i.indname AS name,
620
                   ic.colname AS column_name
621
            FROM syscat.indexes AS i
622
              INNER JOIN syscat.indexcoluse AS ic
623
                      ON ic.indschema = i.indschema
624
                     AND ic.indname = i.indname
625
            WHERE i.ownertype != 'S'
626
            AND i.indschema != 'SYSIBM'
627
            AND i.uniquerule = 'U'
628
            AND i.tabname = :table
629 30
SQL;
630
631 30
        if ($resolvedName->sequenceName) {
632
            $sql .= ' AND i.tabschema = :schema';
633
        }
634
635 30
        $sql .= ' ORDER BY ic.colseq';
636
637 30
        $command = $this->db->createCommand($sql);
638
639 30
        $command->bindValue(':table', $resolvedName->name);
640
641 30
        if ($resolvedName->sequenceName) {
642
            $command->bindValue(':schema', $resolvedName->sequenceName);
643
        }
644
        
645 30
        $constraints = $command->queryAll();
646 30
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
647 30
        $constraints = \yii\helpers\ArrayHelper::index($constraints, null, ['name']);
648 30
        $result = [];
649 30
        foreach ($constraints as $name => $constraint) {
650 10
            $columns = \yii\helpers\ArrayHelper::getColumn($constraint, 'column_name');
651
652 10
            $result[] = new \yii\db\Constraint([
653 10
                'name' => $name,
654 10
                'columnNames' => $columns,
655 10
            ]);
656 30
        }
657 30
        return $result;
658
    }
659
660 13
    protected function loadTableChecks($tableName) {
661 13
        $resolvedName = $this->resolveTableName($tableName);
662
663
        $sql = <<<SQL
664
            SELECT c.constname AS name,
665
                   cc.colname AS column_name,
666
                   c.text AS check_expr
667
            FROM syscat.checks AS c
668
              INNER JOIN syscat.colchecks cc
669
                      ON cc.constname = c.constname
670
            WHERE c.ownertype != 'S'
671
            AND   c.tabname = :table
672 13
SQL;
673
674 13
        if ($resolvedName->sequenceName) {
675
            $sql .= ' AND c.tabschema = :schema';
676
        }
677
678 13
        $command = $this->db->createCommand($sql);
679
680 13
        $command->bindValue(':table', $resolvedName->name);
681
682 13
        if ($resolvedName->sequenceName) {
683
            $command->bindValue(':schema', $resolvedName->sequenceName);
684
        }
685
        
686 13
        $constraints = $command->queryAll();
687 13
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
688 13
        $constraints = \yii\helpers\ArrayHelper::index($constraints, null, ['name']);
689 13
        $result = [];
690 13
        foreach ($constraints as $name => $constraint) {
691 4
            $columns = \yii\helpers\ArrayHelper::getColumn($constraint, 'column_name');
692 4
            $check_expr = $constraint[0]['check_expr'];
693 4
            $result[] = new \yii\db\CheckConstraint([
694 4
                'name' => strtolower(trim($name)),
695 4
                'columnNames' => $columns,
696 4
                'expression' => $check_expr,
697 4
            ]);
698 13
        }
699 13
        return $result;
700
    }
701
702 13
    protected function loadTableDefaultValues($tableName) {
703 13
        $resolvedName = $this->resolveTableName($tableName);
704
705
        $sql = <<<SQL
706
            SELECT c.colname AS column_name,
707
                   c.default AS default_value
708
            FROM syscat.columns AS c
709
            WHERE c.default IS NOT NULL
710
            AND c.tabname = :table
711 13
SQL;
712
713 13
        if ($resolvedName->sequenceName) {
714
            $sql .= ' AND c.tabschema = :schema';
715
        }
716
717 13
        $sql .= ' ORDER BY c.colno';
718
719 13
        $command = $this->db->createCommand($sql);
720
721 13
        $command->bindValue(':table', $resolvedName->name);
722
723 13
        if ($resolvedName->sequenceName) {
724
            $command->bindValue(':schema', $resolvedName->sequenceName);
725
        }
726
        
727 13
        $constraints = $command->queryAll();
728 13
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
729
730 13
        $result = [];
731 13
        foreach ($constraints as $constraint) {
732 10
            $columns = [$constraint['column_name']];
733 10
            $default_value = $constraint['default_value'];
734 10
            $result[] = new \yii\db\DefaultValueConstraint([
735 10
                'columnNames' => $columns,
736 10
                'value' => $default_value,
737 10
            ]);
738 13
        }
739 13
        return $result;
740
    }
741
742 4
    protected function loadTableForeignKeys($tableName) {
743 4
        $resolvedName = $this->resolveTableName($tableName);
744
745
        $sql = <<<SQL
746
            SELECT ref.constname AS name,
747
                   fk.colname AS column_name,
748
                   ref.reftabschema AS ref_schema,
749
                   ref.reftabname AS ref_table,
750
                   pk.colname AS ref_column,
751
                   ref.deleterule AS on_delete,
752
                   ref.updaterule AS on_update
753
            FROM syscat.references AS ref
754
              INNER JOIN syscat.keycoluse AS fk
755
                      ON ref.constname = fk.constname
756
              INNER JOIN syscat.keycoluse AS pk
757
                      ON ref.refkeyname = pk.constname
758
                     AND pk.colseq = fk.colseq
759
            WHERE ref.tabname = :table
760 4
SQL;
761
762 4
        if ($resolvedName->sequenceName) {
763
            $sql .= ' AND ref.tabschema = :schema';
764
        }
765
766 4
        $sql .= ' ORDER BY fk.colseq';
767
768 4
        $command = $this->db->createCommand($sql);
769
770 4
        $command->bindValue(':table', $resolvedName->name);
771
772 4
        if ($resolvedName->sequenceName) {
773
            $command->bindValue(':schema', $resolvedName->sequenceName);
774
        }
775
        
776 4
        $constraints = $command->queryAll();
777 4
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
778 4
        $constraints = \yii\helpers\ArrayHelper::index($constraints, null, ['name']);
779 4
        $result = [];
780 4
        foreach ($constraints as $name => $constraint) {
781 4
            $columns = \yii\helpers\ArrayHelper::getColumn($constraint, 'column_name');
782 4
            $foreignColumnNames = \yii\helpers\ArrayHelper::getColumn($constraint, 'ref_column');
783
784 4
            $foreignSchemaName = $constraint[0]['ref_schema'];
785 4
            $foreignTableName = $constraint[0]['ref_table'];
786 4
            $onDelete = $constraint[0]['on_delete'];
787 4
            $onUpdate = $constraint[0]['on_update'];
788
            
789
            static $onRuleMap = [
790
                'C' => 'CASCADE',
791
                'N' => 'SET NULL',
792
                'R' => 'RESTRICT',
793 4
            ];
794
795 4
            $result[] = new \yii\db\ForeignKeyConstraint([
796 4
                'name' => $name,
797 4
                'columnNames' => $columns,
798 4
                'foreignSchemaName' => $foreignSchemaName,
799 4
                'foreignTableName' => $foreignTableName,
800 4
                'foreignColumnNames' => $foreignColumnNames,
801 4
                'onUpdate' => isset($onRuleMap[$onUpdate]) ? $onRuleMap[$onUpdate] : null,
802 4
                'onDelete' => isset($onRuleMap[$onDelete]) ? $onRuleMap[$onDelete] : null,
803 4
            ]);
804 4
        }
805 4
        return $result;
806
    }
807
808 27
    protected function loadTableIndexes($tableName) {
809 27
        $resolvedName = $this->resolveTableName($tableName);
810
811
        $sql = <<<SQL
812
            SELECT i.indname AS name,
813
                   ic.colname AS column_name,
814
                   CASE i.uniquerule
815
                     WHEN 'U' THEN 1
816
                     WHEN 'P' THEN 1
817
                     ELSE 0
818
                   END AS index_is_unique,
819
                   CASE i.uniquerule
820
                     WHEN 'P' THEN 1
821
                     ELSE 0
822
                   END AS index_is_primary
823
            FROM syscat.indexes AS i
824
              INNER JOIN syscat.indexcoluse AS ic
825
                      ON ic.indschema = i.indschema
826
                     AND ic.indname = i.indname
827
            WHERE i.ownertype != 'S'
828
            AND i.tabname = :table
829 27
SQL;
830
831 27
        if ($resolvedName->sequenceName) {
832
            $sql .= ' AND i.tabschema = :schema';
833
        }
834
835 27
        $sql .= ' ORDER BY ic.colseq';
836
837 27
        $command = $this->db->createCommand($sql);
838
839 27
        $command->bindValue(':table', $resolvedName->name);
840
841 27
        if ($resolvedName->sequenceName) {
842
            $command->bindValue(':schema', $resolvedName->sequenceName);
843
        }
844
        
845 27
        $constraints = $command->queryAll();
846 27
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
847 27
        $constraints = \yii\helpers\ArrayHelper::index($constraints, null, ['name']);
848 27
        $result = [];
849 27
        foreach ($constraints as $name => $constraint) {
850 24
            $columns = \yii\helpers\ArrayHelper::getColumn($constraint, 'column_name');
851
852 24
            $isUnique = $constraint[0]['index_is_unique'];
853 24
            $isPrimary = $constraint[0]['index_is_primary'];
854
            
855 24
            $result[] = new \yii\db\IndexConstraint([
856 24
                'name' => $name,
857 24
                'columnNames' => $columns,
858 24
                'isUnique' => !!$isUnique,
859 24
                'isPrimary' => !!$isPrimary,
860 24
            ]);
861 27
        }
862 27
        return $result;
863
    }
864
865 252
    protected function normalizePdoRowKeyCase(array $row, $multiple)
866
    {
867 252
        if ($this->db->getSlavePdo()->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) {
868 20
            return $row;
869
        }
870
871 232
        if ($multiple) {
872 232
            return array_map(function (array $row) {
873 220
                return array_change_key_case($row, CASE_LOWER);
874 232
            }, $row);
875
        }
876
877
        return array_change_key_case($row, CASE_LOWER);
878
    }
879
}
880