Completed
Push — master ( 7dfaed...4792f7 )
by Edgard
17:14
created

Schema   C

Complexity

Total Complexity 62

Size/Duplication

Total Lines 481
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 69.39%

Importance

Changes 14
Bugs 5 Features 5
Metric Value
wmc 62
c 14
b 5
f 5
lcom 1
cbo 7
dl 0
loc 481
ccs 136
cts 196
cp 0.6939
rs 5.9493

14 Methods

Rating   Name   Duplication   Size   Complexity  
A createQueryBuilder() 0 4 1
A quoteSimpleTableName() 0 4 2
A quoteSimpleColumnName() 0 4 3
A loadTableSchema() 0 12 2
A resolveTableNames() 0 11 2
A getPdoType() 0 14 2
C loadColumnSchema() 0 34 8
C findColumns() 0 86 10
C findConstraints() 0 71 9
B findUniqueIndexes() 0 57 7
B findTableNames() 0 40 6
A createSavepoint() 0 4 1
B setTransactionIsolationLevel() 0 22 5
A refreshTableSchema() 0 14 4

How to fix   Complexity   

Complex Class

Complex classes like Schema 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Schema, and based on these observations, apply Extract Interface, too.

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
23
{
24
25
    public $typeMap = [
26
        'character'  => self::TYPE_CHAR,
27
        'varchar'    => self::TYPE_STRING,
28
        'char'       => self::TYPE_CHAR,
29
        'clob'       => self::TYPE_TEXT,
30
        'graphic'    => self::TYPE_STRING,
31
        'vargraphic' => self::TYPE_STRING,
32
        'varg'       => self::TYPE_STRING,
33
        'dbclob'     => self::TYPE_TEXT,
34
        'nchar'      => self::TYPE_CHAR,
35
        'nvarchar'   => self::TYPE_STRING,
36
        'nclob'      => self::TYPE_TEXT,
37
        'binary'     => self::TYPE_BINARY,
38
        'varbinary'  => self::TYPE_BINARY,
39
        'varbin'     => self::TYPE_BINARY,
40
        'blob'       => self::TYPE_BINARY,
41
        'smallint'   => self::TYPE_SMALLINT,
42
        'int'        => self::TYPE_INTEGER,
43
        'integer'    => self::TYPE_INTEGER,
44
        'bigint'     => self::TYPE_BIGINT,
45
        'decimal'    => self::TYPE_DECIMAL,
46
        'numeric'    => self::TYPE_DECIMAL,
47
        'real'       => self::TYPE_FLOAT,
48
        'float'      => self::TYPE_FLOAT,
49
        'double'     => self::TYPE_DOUBLE,
50
        'decfloat'   => self::TYPE_FLOAT,
51
        'date'       => self::TYPE_DATE,
52
        'time'       => self::TYPE_TIME,
53
        'timestamp'  => self::TYPE_TIMESTAMP,
54
        'timestmp'   => self::TYPE_TIMESTAMP
55
    ];
56
57
   /**
58
     * @inheritdoc
59
     */
60 114
    public function createQueryBuilder()
61
    {
62 114
        return new QueryBuilder($this->db);
63
    }
64
65
    /**
66
     * @inheritdoc
67
     */
68 151
    public function quoteSimpleTableName($name)
69
    {
70 151
        return strpos($name, '"') !== false ? $name : '"' . $name . '"';
71
    }
72
73
    /**
74
     * @inheritdoc
75
     */
76 182
    public function quoteSimpleColumnName($name)
77
    {
78 182
        return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"';
79
    }
80
81
    /**
82
     * @inheritdoc
83
     */
84 104
    protected function loadTableSchema($name)
85
    {
86 104
        $table = new TableSchema();
87 104
        $this->resolveTableNames($table, $name);
88
89 104
        if ($this->findColumns($table)) {
90 102
            $this->findConstraints($table);
91 102
            return $table;
92
        } else {
93 5
            return null;
94
        }
95
    }
96
97
    /**
98
     * @inheritdoc
99
     */
100 104
    protected function resolveTableNames($table, $name)
101
    {
102 104
        $parts = explode('.', str_replace('"', '', $name));
103 104
        if (isset($parts[1])) {
104
            $table->schemaName = $parts[0];
105
            $table->name = $parts[1];
106
            $table->fullName = $table->schemaName . '.' . $table->name;
107
        } else {
108 104
            $table->fullName = $table->name = $parts[0];
109
        }
110 104
    }
111
112
    /**
113
     * Determines the PDO type for the given PHP data value.
114
     * @param mixed $data the data whose PDO type is to be determined
115
     * @return integer the PDO type
116
     * @see http://www.php.net/manual/en/pdo.constants.php
117
     */
118 125
    public function getPdoType($data)
119
    {
120
        static $typeMap = [
121
            // php type => PDO type
122
            'boolean'  => PDO::PARAM_INT, // PARAM_BOOL is not supported by DB2 PDO
123
            'integer'  => PDO::PARAM_INT,
124
            'string'   => PDO::PARAM_STR,
125
            'resource' => PDO::PARAM_LOB,
126
            'NULL'     => PDO::PARAM_INT, // PDO IBM doesn't support PARAM_NULL
127 125
        ];
128 125
        $type = gettype($data);
129
130 125
        return isset($typeMap[$type]) ? $typeMap[$type] : PDO::PARAM_STR;
131
    }
132
133
    /**
134
     * @inheritdoc
135
     */
136 102
    protected function loadColumnSchema($info)
137
    {
138 102
        $column = $this->createColumnSchema();
139
140 102
        $column->name = $info['name'];
141 102
        $column->dbType = $info['dbtype'];
142 102
        $column->defaultValue = isset($info['defaultvalue']) ? trim($info['defaultvalue'], "''") : null;
143 102
        $column->scale = (int) $info['scale'];
144 102
        $column->size = (int) $info['size'];
145 102
        $column->precision = (int) $info['size'];
146 102
        $column->allowNull = $info['allownull'] === '1';
147 102
        $column->isPrimaryKey = $info['isprimarykey'] === '1';
148 102
        $column->autoIncrement = $info['autoincrement'] === '1';
149 102
        $column->unsigned = false;
150 102
        $column->type = $this->typeMap[strtolower($info['dbtype'])];
151 102
        $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...
152 102
        $column->comment = isset($info['comment']) ? $info['comment'] : null;
153
154 102
        if (preg_match('/(varchar|character|clob|graphic|binary|blob)/i', $info['dbtype'])) {
155 87
            $column->dbType .= '(' . $info['size'] . ')';
156 102
        } elseif (preg_match('/(decimal|double|real)/i', $info['dbtype'])) {
157 55
            $column->dbType .= '(' . $info['size'] . ',' . $info['scale'] . ')';
158 55
        }
159
160 102
        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...
161 13
            if ($column->type === 'timestamp' && $column->defaultValue === 'CURRENT TIMESTAMP') {
162 10
                $column->defaultValue = new Expression($column->defaultValue);
163 10
            }
164 13
        }
165
166 102
        $column->phpType = $this->getColumnPhpType($column);
167
168 102
        return $column;
169
    }
170
171
    /**
172
     * @inheritdoc
173
     */
174 104
    protected function findColumns($table)
175
    {
176
177
178 104
        if ($this->db->isISeries) {
179
            $sql = <<<SQL
180
                SELECT c.column_name AS name,
181
                       c.data_type AS dbtype,
182
                       CAST(c.column_default AS VARCHAR(254)) AS defaultvalue,
183
                       CASE WHEN c.is_nullable = 'Y'         THEN 1 ELSE 0 END AS allownull,
184
                       c.length AS size,
185
                       c.numeric_scale AS scale,
186
                       CASE WHEN c.is_identity = 'YES'      THEN 1 ELSE 0 END AS autoincrement,
187
                       case when x.column<>'' THEN 1 ELSE 0 END AS isprimarykey
188
                FROM qsys2.syscolumns c
189
                left join (
190
                SELECT
191
                column_name As column
192
                FROM qsys2.syscst
193
                INNER JOIN qsys2.syskeycst
194
                  ON qsys2.syscst.constraint_name = qsys2.syskeycst.constraint_name
195
                 AND qsys2.syscst.table_schema = qsys2.syskeycst.table_schema
196
                 AND qsys2.syscst.table_name = qsys2.syskeycst.table_name
197
                WHERE qsys2.syscst.constraint_type = 'PRIMARY KEY'
198
                  AND qsys2.syscst.table_name = :table) x
199
                  on x.column= c.column_name
200
                WHERE UPPER(c.table_name) = :table1
201
                AND c.table_schema = :schema
202
SQL;
203
            $sql .= ' ORDER BY c.ordinal_position';
204
        } else {
205
            $sql = <<<SQL
206
                SELECT
207
                    c.colname AS name,
208
                    c.typename AS dbtype,
209
                    cast(c.default as varchar(254)) AS defaultvalue,
210
                    c.scale AS scale,
211
                    c.length AS size,
212
                    CASE WHEN c.nulls = 'Y'         THEN 1 ELSE 0 END AS allownull,
213
                    CASE WHEN c.keyseq IS NOT NULL  THEN 1 ELSE 0 END AS isprimarykey,
214
                    CASE WHEN c.identity = 'Y'      THEN 1 ELSE 0 END AS autoincrement,
215
                    c.remarks AS comment
216
                FROM
217
                    syscat.columns AS c
218
                WHERE
219
                    c.tabname = :table
220 104
SQL;
221
222 104
            if (isset($table->schemaName)) {
223
                $sql .= ' AND c.tabschema = :schema';
224
            }
225
226 104
            $sql .= ' ORDER BY c.colno';
227
        }
228
229 104
        $command = $this->db->createCommand($sql);
230 104
        $command->bindValue(':table', $table->name);
231 104
        if($this->db->isISeries){
232
            $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...
233
            $command->bindValue(':table1', $table->name);
234
235
        }else {
236 104
            if (isset($table->schemaName)) {
237
                $command->bindValue(':schema', $table->schemaName);
238
            }
239
        }
240 104
        $columns = $command->queryAll();
241 104
        if (empty($columns)) {
242 5
            return false;
243
        }
244
245 102
        foreach ($columns as $info) {
246 102
            if ($this->db->slavePdo->getAttribute(PDO::ATTR_CASE) !== PDO::CASE_LOWER) {
247 101
                $info = array_change_key_case($info, CASE_LOWER);
248 101
            }
249 102
            $column = $this->loadColumnSchema($info);
250 102
            $table->columns[$column->name] = $column;
251 102
            if ($column->isPrimaryKey) {
252 92
                $table->primaryKey[] = $column->name;
253 92
                if ($column->autoIncrement) {
254 89
                    $table->sequenceName = $column->name;
255 89
                }
256 92
            }
257 102
        }
258 102
        return true;
259
    }
260
261
    /**
262
     * @inheritdoc
263
     */
264 102
    protected function findConstraints($table)
265
    {
266 102
        if ($this->db->isISeries) {
267
            $sql = <<<SQL
268
            SELECT
269
              parent.table_name AS tablename,
270
              parent.column_name AS pk,
271
              child.column_name AS fk
272
            FROM qsys2.syskeycst child
273
            INNER JOIN qsys2.sysrefcst crossref
274
                ON child.constraint_schema = crossref.constraint_schema
275
               AND child.constraint_name = crossref.constraint_name
276
            INNER JOIN qsys2.syskeycst parent
277
                ON crossref.unique_constraint_schema = parent.constraint_schema
278
               AND crossref.unique_constraint_name = parent.constraint_name
279
            INNER JOIN qsys2.syscst coninfo
280
                ON child.constraint_name = coninfo.constraint_name
281
            WHERE UPPER(child.table_name) = :table
282
              AND coninfo.constraint_type = 'FOREIGN KEY'
283
              AND child.table_schema = :schema
284
SQL;
285
286
        } else {
287
            $sql = <<<SQL
288
            SELECT
289
                pk.tabname AS tablename,
290
                fk.colname AS fk,
291
                pk.colname AS pk
292
            FROM
293
                syscat.references AS ref
294
            INNER JOIN
295
                syscat.keycoluse AS fk ON ref.constname = fk.constname
296
            INNER JOIN
297
                syscat.keycoluse AS pk ON ref.refkeyname = pk.constname AND pk.colseq = fk.colseq
298
            WHERE
299
                fk.tabname = :table
300 102
SQL;
301
302 102
            if (isset($table->schemaName)) {
303
                $sql .= ' AND fk.tabschema = :schema';
304
            }
305
        }
306 102
        $command = $this->db->createCommand($sql);
307 102
        $command->bindValue(':table', $table->name);
308 102
        if($this->db->isISeries){
309
            $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...
310
        }else {
311 102
            if (isset($table->schemaName)) {
312
                $command->bindValue(':schema', $table->schemaName);
313
            }
314
        }
315
316 102
        $results = $command->queryAll();
317 102
        $foreignKeys = [];
318 102
        foreach ($results as $result) {
319 49
            if ($this->db->slavePdo->getAttribute(PDO::ATTR_CASE) !== PDO::CASE_LOWER) {
320 48
                $result = array_change_key_case($result, CASE_LOWER);
321 48
            }
322 49
            $tablename = $result['tablename'];
323 49
            $fk = $result['fk'];
324 49
            $pk = $result['pk'];
325 49
            $foreignKeys[$tablename][$fk] = $pk;
326 102
        }
327 102
        foreach ($foreignKeys as $tablename => $keymap) {
328 49
            $constraint = [$tablename];
329 49
            foreach ($keymap as $fk => $pk) {
330 49
                $constraint[$fk] = $pk;
331 49
            }
332 49
            $table->foreignKeys[] = $constraint;
333 102
        }
334 102
    }
335
336
    /**
337
     * @inheritdoc
338
     */
339
    public function findUniqueIndexes($table)
340
    {
341
342
        if ($this->db->isISeries) {
343
            $sql = <<<SQL
344
                SELECT
345
                qsys2.syskeycst.constraint_name As indexname,
346
                qsys2.syskeycst.column_name As column
347
                FROM qsys2.syscst
348
                INNER JOIN qsys2.syskeycst
349
                  ON qsys2.syscst.constraint_name = qsys2.syskeycst.constraint_name
350
                 AND qsys2.syscst.table_schema = qsys2.syskeycst.table_schema
351
                 AND qsys2.syscst.table_name = qsys2.syskeycst.table_name
352
                WHERE qsys2.syscst.constraint_type = 'PRIMARY KEY'
353
                  AND qsys2.syscst.table_name = :table
354
                  AND qsys2.syscst.table_schema = :schema
355
                  ORDER BY qsys2.syskeycst.column_position
356
SQL;
357
        }else {
358
            $sql = <<<SQL
359
            SELECT
360
                i.indname AS indexname,
361
                ic.colname AS column
362
            FROM
363
                syscat.indexes AS i
364
            INNER JOIN
365
                syscat.indexcoluse AS ic ON i.indname = ic.indname
366
            WHERE
367
                i.tabname = :table
368
SQL;
369
370
            if (isset($table->schemaName)) {
371
                $sql .= ' AND tabschema = :schema';
372
            }
373
374
            $sql .= ' ORDER BY ic.colseq';
375
        }
376
        $command = $this->db->createCommand($sql);
377
        $command->bindValue(':table', $table->name);
378
379
        if($this->db->isISeries){
380
            $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...
381
        }else{
382
            if (isset($table->schemaName)) {
383
                $command->bindValue(':schema', $table->schemaName);
384
            }
385
        }
386
        $results = $command->queryAll();
387
        $indexes = [];
388
        foreach ($results as $result) {
389
            if ($this->db->slavePdo->getAttribute(PDO::ATTR_CASE) !== PDO::CASE_LOWER) {
390
                $result = array_change_key_case($result, CASE_LOWER);
391
            }
392
            $indexes[$result['indexname']][] = $result['column'];
393
        }
394
        return $indexes;
395
    }
396
397
    /**
398
     * @inheritdoc
399
     */
400 5
    protected function findTableNames($schema = '')
401
    {
402
403 5
        if ($schema === '' && $this->db->isISeries) {
404
                $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...
405
        }
406
407 5
        if ($this->db->isISeries) {
408
            $sql = <<<SQL
409
                SELECT TABLE_NAME as tabname
410
                FROM QSYS2.SYSTABLES
411
                WHERE TABLE_TYPE IN ('P','T','V')
412
                  AND SYSTEM_TABLE = 'N'
413
                  AND TABLE_SCHEMA = :schema
414
                ORDER BY TABLE_NAME
415
SQL;
416
        }else {
417
418
            $sql = <<<SQL
419
            SELECT
420
                t.tabname
421
            FROM
422
                syscat.tables AS t
423
            WHERE
424
                t.type in ('P','T', 'V') AND
425
                t.ownertype != 'S'
426 5
SQL;
427
428 5
            if ($schema !== '') {
429
                $sql .= ' AND t.tabschema = :schema';
430
            }
431
        }
432 5
        $command = $this->db->createCommand($sql);
433
434 5
        if ($schema !== '') {
435
            $command->bindValue(':schema', $schema);
436
        }
437
438 5
        return $command->queryColumn();
439
    }
440
    
441
    /**
442
     * Creates a new savepoint.
443
     * @param string $name the savepoint name
444
     */
445 1
    public function createSavepoint($name)
446
    {
447 1
        $this->db->createCommand("SAVEPOINT $name ON ROLLBACK RETAIN CURSORS")->execute();
448 1
    }
449
450
    /**
451
     * Sets the isolation level of the current transaction.
452
     * @param string $level The transaction isolation level to use for this transaction.
453
     * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]]
454
     * and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used
455
     * after `SET TRANSACTION ISOLATION LEVEL`.
456
     * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
457
     */
458 2
    public function setTransactionIsolationLevel($level)
459
    {
460 2
        $sql = 'SET CURRENT ISOLATION ';
461
        switch ($level) {
462 2
            case Transaction::READ_UNCOMMITTED:
463 2
                $sql .= 'UR';
464 2
                break;
465 1
            case Transaction::READ_COMMITTED:
466 1
                $sql .= 'CS';
467 1
                break;
468 1
            case Transaction::REPEATABLE_READ:
469 1
                $sql .= 'RS';
470 1
                break;
471 1
            case Transaction::SERIALIZABLE:
472 1
                $sql .= 'RR';
473 1
                break;
474
            default:
475
                $sql .= $level;
476
        }
477
        
478 2
        $this->db->createCommand($sql)->execute();
479 2
    }
480
    
481
    /**
482
     * Refreshes the particular table schema.
483
     * This method cleans up cached table schema so that it can be re-created later
484
     * to reflect the database schema change.
485
     * @param string $name table name.
486
     * @since 2.0.6
487
     */
488 6
    public function refreshTableSchema($name)
489
    {
490
        try {
491 6
            $sql = "CALL ADMIN_CMD ('REORG TABLE " . $this->db->quoteTableName($name) . "')";
492 6
            $this->db->createCommand($sql)->execute();
493 6
        } catch (\Exception $ex) {
494
            // Do not throw error on table which doesn't exist
495 3
            if (!(isset($ex->errorInfo[1]) && $ex->errorInfo[1] === -2211)) {
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...
496
                throw new \Exception($ex->getMessage(), $ex->getCode(), $ex->getPrevious());
497
            }
498
        }
499
500 6
        parent::refreshTableSchema($name);
501 6
    }
502
}
503