Passed
Push — master ( 5e5904...566343 )
by Edgard
18:25
created

Schema::loadTableUniques()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 0
cts 0
cp 0
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 19
nc 2
nop 1
crap 6
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\firebird;
10
11
use yii\base\InvalidCallException;
12
use yii\db\CheckConstraint;
13
use yii\db\Constraint;
14
use yii\db\ConstraintFinderInterface;
15
use yii\db\ConstraintFinderTrait;
16
use yii\db\Exception;
17
use yii\db\Expression;
18
use yii\db\ForeignKeyConstraint;
19
use yii\db\Schema as BaseSchema;
20
use yii\db\TableSchema;
21
use yii\db\Transaction;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, edgardmessias\db\firebird\Transaction.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
22
use yii\db\ViewFinderTrait;
23
use yii\helpers\ArrayHelper;
24
25
/**
26
 * Schema represents the Firebird schema information.
27
 *
28
 * @property string[] $indexNames All index names in the Firebird. This property is read-only.
29
 * @property IndexSchema[] $indexSchemas The metadata for all indexes in the Firebird. Each array element is an
30
 * instance of [[IndexSchema]] or its child class. This property is read-only.
31
 * @property array $indexTypes All index types in the Firebird in format: index name => index type. This
32
 * property is read-only.
33
 * @property QueryBuilder $queryBuilder The query builder for this connection. This property is read-only.
34
 *
35
 * @author Edgard Lorraine Messias <[email protected]>
36
 * @since 2.0
37
 */
38
class Schema extends BaseSchema implements ConstraintFinderInterface
39
{
40
    use ViewFinderTrait;
41
    use ConstraintFinderTrait;
42
43
    private $_lastInsertID = null;
44
45
    /**
46
     * @var array map of DB errors and corresponding exceptions
47
     * If left part is found in DB error message exception class from the right part is used.
48
     */
49
    public $exceptionMap = [
50
        'SQLSTATE[23'                                               => 'yii\db\IntegrityException',
51
        'SQLSTATE[HY000]: General error: -803 violation of PRIMARY' => 'yii\db\IntegrityException',
52
    ];
53
    public $reservedWords = [
54
        'ADD',
55
        'ADMIN',
56
        'ALL',
57
        'ALTER',
58
        'AND',
59
        'ANY',
60
        'AS',
61
        'AT',
62
        'AVG',
63
        'BEGIN',
64
        'BETWEEN',
65
        'BIGINT',
66
        'BIT_LENGTH',
67
        'BLOB',
68
        'BOTH',
69
        'BOOLEAN',
70
        'BY',
71 99
        'CASE',
72
        'CAST',
73 99
        'CHAR',
74
        'CHAR_LENGTH',
75
        'CHARACTER',
76
        'CHARACTER_LENGTH',
77
        'CHECK',
78
        'CLOSE',
79 2
        'COLLATE',
80
        'COLUMN',
81 2
        'COMMIT',
82
        'CONNECT',
83
        'CONSTRAINT',
84 157
        'CORR',
85
        'COUNT',
86 157
        'COVAR_POP',
87
        'CREATE',
88
        'CROSS',
89
        'CURRENT',
90 157
        'CURRENT_CONNECTION',
91 157
        'CURRENT_DATE',
92 43
        'CURRENT_ROLE',
93
        'CURRENT_TIME',
94
        'CURRENT_TIMESTAMP',
95 156
        'CURRENT_TRANSACTION',
96
        'CURRENT_USER',
97
        'CURSOR',
98 173
        'DATE',
99
        'DAY',
100 173
        'DEC',
101 6
        'DECIMAL',
102
        'DECLARE',
103 173
        'DEFAULT',
104
        'DELETE',
105
        'DELETING',
106 106
        'DETERMINISTIC',
107
        'DISCONNECT',
108 106
        'DISTINCT',
109 106
        'DOUBLE',
110 106
        'DROP',
111 104
        'ELSE',
112 104
        'END',
113
        'ESCAPE',
114 7
        'EXECUTE',
115
        'EXISTS',
116
        'EXTERNAL',
117 93
        'EXRACT',
118
        'FALSE',
119 93
        'FETCH',
120
        'FILTER',
121
        'FLOAT',
122
        'FOR',
123
        'FOREIGN',
124
        'FROM',
125
        'FULL',
126
        'FUNCTION',
127 93
        'GDSCODE',
128
        'GLOBAL',
129 93
        'GRANT',
130
        'GROUP',
131
        'HAVING',
132
        'HOUR',
133
        'IN',
134
        'INDEX',
135
        'INNER',
136
        'INSENSITIVE',
137 106
        'INSERT',
138
        'INSERTING',
139 106
        'INT',
140 106
        'INTEGER',
141
        'INTO',
142
        'IS',
143
        'JOIN',
144
        'LEADING',
145 106
        'LEFT',
146 106
        'LIKE',
147
        'LONG',
148 106
        'LOWER',
149
        'MAX',
150
        'MAXIMUM_SEGMENT',
151
        'MERGE',
152
        'MIN',
153
        'MINUTE',
154
        'MONTH',
155
        'NATIONAL',
156 106
        'NATURAL',
157
        'NCHAR',
158
        'NO',
159
        'NOT',
160
        'NULL',
161
        'NUMERIC',
162
        'OCTET_LENGTH',
163
        'OF',
164
        'OFFSET',
165
        'ON',
166
        'OPEN',
167
        'OR',
168
        'ORDER',
169
        'OUTER',
170
        'OVER',
171
        'PARAMETER',
172
        'PASSWORD',
173
        'PLAN',
174 106
        'POSITION',
175
        'POST_EVENT',
176
        'PRECISION',
177
        'PRIMARY',
178
        'PROCEDURE',
179
        'RDB$DB_KEY',
180
        'RDB$RECORD_VERSION',
181
        'REAL',
182
        'RECORD_VERSION',
183
        'RECREATE',
184 106
        'RECURSIVE',
185
        'REFERENCES',
186 106
        'REGR_AVGX',
187
        'REGR_AVGY',
188 106
        'REGR_COUNT',
189 106
        'REGR_INTERCEPT',
190 106
        'REGR_R2',
191
        'REGR_SLOPE',
192
        'REGR_SXX',
193
        'REGR_SXY',
194
        'REGR_SYY',
195
        'RELEASE',
196
        'RETURN',
197
        'RETURNING_VALUES',
198
        'RETURNS',
199
        'REVOKE',
200
        'RIGHT',
201 104
        'ROLLBACK',
202
        'ROW',
203 104
        'ROWS',
204
        'ROW_COUNT',
205
        'SAVEPOINT',
206
        'SCROLL',
207 104
        'SECOND',
208 104
        'SELECT',
209 104
        'SENSITIVE',
210 104
        'SET',
211 104
        'SIMILAR',
212
        'SOME',
213 104
        'SQLCODE',
214 104
        'SQLSTATE',
215 104
        'START',
216 87
        'STDDEV_POP',
217 87
        'STDDEV_SAMP',
218 79
        'SUM',
219 13
        'TABLE',
220 13
        'THEN',
221
        'TIME',
222
        'TIMESTAMP',
223 104
        'TO',
224 104
        'TRAILING',
225 104
        'TRIGGER',
226
        'TRIM',
227
        'TRUE',
228 104
        'UNION',
229
        'UNIQUE',
230
        'UNKNOWN',
231
        'UPDATE',
232
        'UPDATING',
233
        'UPPER',
234
        'USER',
235 104
        'USING',
236
        'VALUE',
237 104
        'VALUES',
238
        'VARCHAR',
239
        'VARIABLE',
240
        'VARYING',
241
        'VAR_POP',
242
        'VAR_SAMP',
243
        'VIEW',
244
        'WHEN',
245
        'WHERE',
246 104
        'WHILE',
247
        'WITH',
248 104
        'YEAR',
249 104
    ];
250 104
251 104
    /**
252 104
     * @var array mapping from physical column types (keys) to abstract column types (values)
253 104
     */
254
    public $typeMap = [
255 104
        'bigint'             => self::TYPE_BIGINT,
256
        'char'               => self::TYPE_CHAR,
257 104
        'varchar'            => self::TYPE_STRING,
258 104
        'timestamp'          => self::TYPE_TIMESTAMP,
259
        'decimal'            => self::TYPE_DECIMAL,
260 63
        'float'              => self::TYPE_FLOAT,
261 63
        'blob'               => self::TYPE_BINARY,
262
        'integer'            => self::TYPE_INTEGER,
263
        'blob sub_type text' => self::TYPE_TEXT,
264 63
        'numeric'            => self::TYPE_DECIMAL,
265
        'double precision'   => self::TYPE_DOUBLE,
266 104
        'smallint'           => self::TYPE_SMALLINT,
267 103
    ];
268
269 104
    /**
270
     * {@inheritdoc}
271 104
     */
272
    protected function resolveTableName($name)
273
    {
274
        $resolvedName = new TableSchema();
275
        $this->resolveTableNames($resolvedName, $name);
276
        return $resolvedName;
277
    }
278
279
    /**
280
     * Creates a query builder for the database.
281
     * This method may be overridden by child classes to create a DBMS-specific query builder.
282
     * @return QueryBuilder query builder instance
283
     */
284
    public function createQueryBuilder()
285
    {
286
        return new QueryBuilder($this->db);
287 104
    }
288
289
    /**
290 104
     * @inheritdoc
291 104
     */
292 88
    public function createColumnSchemaBuilder($type, $length = null)
293 88
    {
294 88
        return new ColumnSchemaBuilder($type, $length);
295 88
    }
296
297 104
    public function quoteSimpleTableName($name)
298 104
    {
299 104
        if ($this->db->tablePrefix !== '') {
300 104
            return $name;
301 104
        }
302
303
        $word = strtoupper(str_replace('%', '', $name));
304
        if (in_array($word, $this->reservedWords)) {
305
            return strpos($name, '"') !== false ? $name : '"' . $name . '"';
306
        }
307 104
308 16
        return $name;
309 16
    }
310 16
311 16
    public function quoteSimpleColumnName($name)
312 16
    {
313
        if (in_array(strtoupper($name), $this->reservedWords)) {
314 104
            return parent::quoteSimpleColumnName($name);
315 101
        }
316 47
        return $name;
317 47
    }
318 3
319 3
    protected function loadTableSchema($name)
320 3
    {
321 3
        $table = $this->resolveTableName($name);
322 3
        if ($this->findColumns($table)) {
323 47
            $this->findConstraints($table);
324 46
            return $table;
325 46
        }
326 46
        return null;
327 46
    }
328 46
329
    public function getPdoType($data)
330 4
    {
331 4
        static $typeMap = [
332
            // php type => PDO type
333 47
            'boolean'  => \PDO::PARAM_INT,
334 89
            'integer'  => \PDO::PARAM_INT,
335 15
            'string'   => \PDO::PARAM_STR,
336 15
            'resource' => \PDO::PARAM_LOB,
337 15
            'NULL'     => \PDO::PARAM_NULL,
338 15
        ];
339 15
        $type = gettype($data);
340
341 15
        return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
342
    }
343
344 104
    /**
345
     *
346 104
     * @param TableSchema $table
347 104
     * @param string $name
348 104
     */
349 104
    protected function resolveTableNames($table, $name)
350 104
    {
351
        $parts = explode('.', str_replace('"', '', $name));
352
        if (isset($parts[1])) {
353
            $table->schemaName = $parts[0];
354
            $table->name = strtolower($parts[1]);
355 104
            $table->fullName = $this->quoteTableName($table->schemaName) . '.' . $this->quoteTableName($table->name);
356
        } else {
357 104
            $table->name = strtolower($parts[0]);
358 104
            $table->fullName = $this->quoteTableName($table->name);
359 63
        }
360 63
    }
361 15
362
    /**
363 63
     * Collects the table column metadata.
364
     *
365
     * @param TableSchema $table the table metadata
366
     * @return boolean whether the table exists in the database
367 104
     */
368
    protected function findColumns($table)
369
    {
370
        // Zoggo - Converted sql to use join syntax
371
        // robregonm - Added isAutoInc
372
        $sql = 'SELECT
373
                    rel.rdb$field_name AS fname,
374
                    rel.rdb$default_source AS fdefault,
375 104
                    fld.rdb$field_type AS fcodtype,
376
                    fld.rdb$field_sub_type AS fcodsubtype,
377
                    fld.rdb$field_length AS flength,
378
                    fld.rdb$character_length AS fcharlength,
379
                    fld.rdb$field_scale AS fscale,
380
                    fld.rdb$field_precision AS fprecision,
381
                    rel.rdb$null_flag AS fnull,
382
                    rel.rdb$description AS fcomment,
383
                    fld.rdb$default_value AS fdefault_value,';
384
385
        if (version_compare($this->db->firebird_version, '3.0.0', '>=')) {
386
            $sql .= '
387
                    rel.rdb$generator_name AS fgenerator_name,';
388
        }
389
390
        $sql .= '
391 104
                    (SELECT RDB$TRIGGER_SOURCE FROM RDB$TRIGGERS
392
                        WHERE RDB$SYSTEM_FLAG = 0
393 104
                        AND UPPER(RDB$RELATION_NAME)=UPPER(\'' . $table->name . '\')
394
                        AND RDB$TRIGGER_TYPE = 1
395
                        AND RDB$TRIGGER_INACTIVE = 0
396
                        AND (UPPER(REPLACE(RDB$TRIGGER_SOURCE,\' \',\'\')) LIKE \'%NEW.\'||TRIM(rel.rdb$field_name)||\'=GEN_ID%\'
397
                            OR UPPER(REPLACE(RDB$TRIGGER_SOURCE,\' \',\'\')) LIKE \'%NEW.\'||TRIM(rel.rdb$field_name)||\'=NEXTVALUEFOR%\'))
398 104
                    AS fautoinc
399 104
                FROM
400
                    rdb$relation_fields rel
401
                    JOIN rdb$fields fld ON rel.rdb$field_source=fld.rdb$field_name
402 29
                WHERE
403 29
                    UPPER(rel.rdb$relation_name)=UPPER(\'' . $table->name . '\')
404
                ORDER BY
405 29
                    rel.rdb$field_position;';
406 29
        try {
407 29
            $columns = $this->db->createCommand($sql)->queryAll();
408
            if (empty($columns)) {
409
                return false;
410 29
            }
411
        } catch (Exception $e) {
412 104
            return false;
413 104
        }
414
        $sql = 'SELECT
415 5
                    idx.rdb$field_name AS fname
416
                FROM
417
                    rdb$relation_constraints rc
418
                    JOIN rdb$index_segments idx ON idx.rdb$index_name=rc.rdb$index_name
419
                WHERE rc.rdb$constraint_type=\'PRIMARY KEY\'
420
					AND UPPER(rc.rdb$relation_name)=UPPER(\'' . $table->name . '\')';
421
        try {
422 5
            $pkeys = $this->db->createCommand($sql)->queryColumn();
423
        } catch (Exception $e) {
424 5
            return false;
425
        }
426
        $pkeys = array_map('rtrim', $pkeys);
427
        $pkeys = array_map('strtolower', $pkeys);
428
        foreach ($columns as $key => $column) {
429 5
            $column = array_map('strtolower', $column);
430 5
            $columns[$key]['fprimary'] = in_array(rtrim($column['fname']), $pkeys);
431
        }
432 5
        foreach ($columns as $column) {
433
            $c = $this->loadColumnSchema($column);
434
            if ($table->sequenceName === null && $c->autoIncrement) {
435
                $matches = [];
436
437
                if (isset($column['fgenerator_name']) && $column['fgenerator_name']) {
438
                    $table->sequenceName = $column['fgenerator_name'];
439
                } elseif (preg_match("/NEW.{$c->name}\s*=\s*GEN_ID\((\w+)/i", $column['fautoinc'], $matches)) {
440
                    $table->sequenceName = $matches[1];
441
                } elseif (preg_match("/NEW.{$c->name}\s*=\s*NEXT\s+VALUE\s+FOR\s+(\w+)/i", $column['fautoinc'], $matches)) {
442
                    $table->sequenceName = $matches[1];
443
                }
444
            }
445
            $table->columns[$c->name] = $c;
446
            if ($c->isPrimaryKey) {
447
                $table->primaryKey[] = $c->name;
448
            }
449
        }
450 1
        return (count($table->columns) > 0);
451
    }
452
453
    /**
454
     * @return ColumnSchema
455
     * @throws \yii\base\InvalidConfigException
456
     */
457
    protected function createColumnSchema()
458 1
    {
459 1
        return \Yii::createObject('\edgardmessias\db\firebird\ColumnSchema');
460 1
    }
461 1
462 1
    /**
463 1
     * Creates a table column.
464
     *
465 1
     * @param array $column column metadata
466
     * @return ColumnSchema normalized column metadata
467
     */
468
    protected function loadColumnSchema($column)
469
    {
470
        $c = $this->createColumnSchema();
471
        $c->name = strtolower(rtrim($column['fname']));
472
        $c->allowNull = (int) $column['fnull'] !== 1;
473
        $c->isPrimaryKey = $column['fprimary'];
474
        $c->autoIncrement = (isset($column['fgenerator_name']) && $column['fgenerator_name']) || (boolean) $column['fautoinc'];
475
        $c->comment = $column['fcomment'] === null ? '' : $column['fcomment'];
476 2
477
        $c->type = self::TYPE_STRING;
478 2
479 2
        $defaultValue = null;
480 1
        if (!empty($column['fdefault'])) {
481 1
            // remove whitespace, 'DEFAULT ' prefix and surrounding single quotes; all optional
482 1
            if (preg_match("/\s*(DEFAULT\s+){0,1}('(.*)'|(.*))\s*/i", $column['fdefault'], $parts)) {
483 1
                $defaultValue = array_pop($parts);
484
            }
485 1
            // handle escaped single quotes like in "funny''quoted''string"
486
            $defaultValue = str_replace('\'\'', '\'', $defaultValue);
487 2
        }
488
        if ($defaultValue === null) {
489
            $defaultValue = $column['fdefault_value'];
490
        }
491
        $dbType = '';
492 13
        $baseTypes = [
493
            7   => 'SMALLINT',
494 13
            8   => 'INTEGER',
495 13
            16  => 'INT64',
496 13
            9   => 'QUAD',
497 13
            10  => 'FLOAT',
498 13
            11  => 'D_FLOAT',
499 12
            17  => 'BOOLEAN',
500 12
            27  => 'DOUBLE PRECISION',
501 12
            12  => 'DATE',
502
            13  => 'TIME',
503 12
            35  => 'TIMESTAMP',
504
            261 => 'BLOB',
505
            40  => 'CSTRING',
506 13
            45  => 'BLOB_ID',
507 13
        ];
508 13
        $baseCharTypes = [
509
            37 => 'VARCHAR',
510 13
            14 => 'CHAR',
511
        ];
512
        if (array_key_exists((int) $column['fcodtype'], $baseTypes)) {
513 13
            $dbType = $baseTypes[(int) $column['fcodtype']];
514 12
        } elseif (array_key_exists((int) $column['fcodtype'], $baseCharTypes)) {
515 12
            $c->size = (int) $column['fcharlength'];
516 10
            $c->precision = $c->size;
517 12
            $dbType = $baseCharTypes[(int) $column['fcodtype']] . "($c->size)";
518
        }
519
        switch ((int) $column['fcodtype']) {
520
            case 7:
521 13
            case 8:
522
                switch ((int) $column['fcodsubtype']) {
523
                    case 1:
524
                        $c->precision = (int) $column['fprecision'];
525
                        $c->size = $c->precision;
526
                        $c->scale = abs((int) $column['fscale']);
0 ignored issues
show
Documentation Bug introduced by
It seems like abs((int) $column['fscale']) can also be of type double. However, the property $scale is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
527
                        $dbType = "NUMERIC({$c->precision},{$c->scale})";
528 2
                        break;
529
                    case 2:
530 2
                        $c->precision = (int) $column['fprecision'];
531
                        $c->size = $c->precision;
532
                        $c->scale = abs((int) $column['fscale']);
533
                        $dbType = "DECIMAL({$c->precision},{$c->scale})";
534 2
                        break;
535 2
                }
536
                break;
537
            case 16:
538 1
                switch ((int) $column['fcodsubtype']) {
539 1
                    case 1:
540
                        $c->precision = (int) $column['fprecision'];
541
                        $c->size = $c->precision;
542
                        $c->scale = abs((int) $column['fscale']);
543
                        $dbType = "NUMERIC({$c->precision},{$c->scale})";
544
                        break;
545
                    case 2:
546
                        $c->precision = (int) $column['fprecision'];
547
                        $c->size = $c->precision;
548
                        $c->scale = abs((int) $column['fscale']);
549
                        $dbType = "DECIMAL({$c->precision},{$c->scale})";
550
                        break;
551
                    default:
552
                        $dbType = 'BIGINT';
553
                        break;
554
                }
555
                break;
556
            case 261:
557
                switch ((int) $column['fcodsubtype']) {
558
                    case 1:
559
                        $dbType = 'BLOB SUB_TYPE TEXT';
560
                        $c->size = null;
561
                        break;
562
                }
563
                break;
564
        }
565
566
        $c->dbType = strtolower($dbType);
567
568
        $c->type = self::TYPE_STRING;
569
        if (preg_match('/^([\w\ ]+)(?:\(([^\)]+)\))?/', $c->dbType, $matches)) {
570
            $type = strtolower($matches[1]);
571
            if (isset($this->typeMap[$type])) {
572
                $c->type = $this->typeMap[$type];
573
            }
574
        }
575
576
577
        $c->phpType = $this->getColumnPhpType($c);
578
579
        $c->defaultValue = null;
580
        if ($defaultValue !== null) {
581
            if (in_array($c->type, [self::TYPE_DATE, self::TYPE_DATETIME, self::TYPE_TIME, self::TYPE_TIMESTAMP])
582
                    && preg_match('/(CURRENT_|NOW|NULL|TODAY|TOMORROW|YESTERDAY)/i', $defaultValue)) {
583
                $c->defaultValue = new Expression(trim($defaultValue));
584
            } else {
585
                $c->defaultValue = $c->phpTypecast($defaultValue);
586
            }
587
        }
588
589
        return $c;
590
    }
591
592
    /**
593
     * Collects the foreign key column details for the given table.
594
     *
595
     * @param TableSchema $table the table metadata
596
     */
597
    protected function findConstraints($table)
598
    {
599
        // Zoggo - Converted sql to use join syntax
600
        $sql = 'SELECT
601
                    a.rdb$constraint_name as fconstraint,
602
                    c.rdb$relation_name AS ftable,
603
                    d.rdb$field_name AS pfield,
604
                    e.rdb$field_name AS ffield
605
                FROM
606
                    rdb$ref_constraints b
607
                    JOIN rdb$relation_constraints a ON a.rdb$constraint_name=b.rdb$constraint_name
608
                    JOIN rdb$relation_constraints c ON b.rdb$const_name_uq=c.rdb$constraint_name
609
                    JOIN rdb$index_segments d ON c.rdb$index_name=d.rdb$index_name
610
                    JOIN rdb$index_segments e ON a.rdb$index_name=e.rdb$index_name AND e.rdb$field_position = d.rdb$field_position
611
                WHERE
612
                    a.rdb$constraint_type=\'FOREIGN KEY\' AND
613
                    UPPER(a.rdb$relation_name)=UPPER(\'' . $table->name . '\') ';
614
        try {
615
            $fkeys = $this->db->createCommand($sql)->queryAll();
616
        } catch (Exception $e) {
617
            return false;
618
        }
619
620
        $constraints = [];
621
        foreach ($fkeys as $fkey) {
622
            // Zoggo - Added strtolower here to guarantee that values are
623
            // returned lower case. Otherwise gii generates wrong code.
624
            $fkey = array_map('rtrim', $fkey);
625
            $fkey = array_map('strtolower', $fkey);
626
627
            if (!isset($constraints[$fkey['fconstraint']])) {
628
                $constraints[$fkey['fconstraint']] = [
629
                    $fkey['ftable']
630
                ];
631
            }
632
            $constraints[$fkey['fconstraint']][$fkey['ffield']] = $fkey['pfield'];
633
        }
634
        $table->foreignKeys = $constraints;
635
    }
636
637
    protected function findTableNames($schema = '')
638
    {
639
        $sql = 'SELECT
640
                    rdb$relation_name
641
                FROM
642
                    rdb$relations
643
                WHERE
644
                    (rdb$system_flag is null OR rdb$system_flag=0)';
645
        try {
646
            $tables = $this->db->createCommand($sql)->queryColumn();
647
        } catch (Exception $e) {
648
            return false;
649
        }
650
651
        $tables = array_map('rtrim', $tables);
652
        $tables = array_map('strtolower', $tables);
653
654
        return $tables;
655
    }
656
657
    /**
658
     * Returns all unique indexes for the given table.
659
     * Each array element is of the following structure:
660
     *
661
     * ~~~
662
     * [
663
     *  'IndexName1' => ['col1' [, ...]],
664
     *  'IndexName2' => ['col2' [, ...]],
665
     * ]
666
     * ~~~
667
     *
668
     * @param TableSchema $table the table metadata
669
     * @return array all unique indexes for the given table.
670
     * @since 2.0.4
671
     */
672
    public function findUniqueIndexes($table)
673
    {
674
        $query = '
675
SELECT id.RDB$INDEX_NAME as index_name, ids.RDB$FIELD_NAME as column_name
676
FROM RDB$INDICES id
677
INNER JOIN RDB$INDEX_SEGMENTS ids ON ids.RDB$INDEX_NAME = id.RDB$INDEX_NAME
678
WHERE id.RDB$UNIQUE_FLAG = 1
679
AND   id.RDB$SYSTEM_FLAG = 0
680
AND UPPER(id.RDB$RELATION_NAME) = UPPER(\'' . $table->name . '\')
681
ORDER BY id.RDB$RELATION_NAME, id.RDB$INDEX_NAME, ids.RDB$FIELD_POSITION';
682
        $result = [];
683
        $command = $this->db->createCommand($query);
684
        foreach ($command->queryAll() as $row) {
685
            $result[strtolower(rtrim($row['index_name']))][] = strtolower(rtrim($row['column_name']));
686
        }
687
        return $result;
688
    }
689
690
    /**
691
     * Sets the isolation level of the current transaction.
692
     * @param string $level The transaction isolation level to use for this transaction.
693
     * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]]
694
     * and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used
695
     * after `SET TRANSACTION ISOLATION LEVEL`.
696
     * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
697
     */
698
    public function setTransactionIsolationLevel($level)
699
    {
700
        if ($level == Transaction::READ_UNCOMMITTED) {
701
            parent::setTransactionIsolationLevel('READ COMMITTED RECORD_VERSION');
702
        } elseif ($level == Transaction::REPEATABLE_READ) {
703
            parent::setTransactionIsolationLevel('SNAPSHOT');
704
        } elseif ($level == Transaction::SERIALIZABLE) {
705
            parent::setTransactionIsolationLevel('SNAPSHOT TABLE STABILITY');
706
        } else {
707
            parent::setTransactionIsolationLevel($level);
708
        }
709
    }
710
711
    /**
712
     * @inheritdoc
713
     */
714
    public function insert($table, $columns)
715
    {
716
        $this->_lastInsertID = false;
717
        $params = [];
718
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
719
        $returnColumns = $this->getTableSchema($table)->primaryKey;
720
        if (!empty($returnColumns)) {
721
            $returning = [];
722
            foreach ((array) $returnColumns as $name) {
723
                $returning[] = $this->quoteColumnName($name);
724
            }
725
            $sql .= ' RETURNING ' . implode(', ', $returning);
726
        }
727
728
        $command = $this->db->createCommand($sql, $params);
729
        $command->prepare(false);
730
        $result = $command->queryOne();
731
732
        if (!$command->pdoStatement->rowCount()) {
733
            return false;
734
        } else {
735
            if (!empty($returnColumns)) {
736
                foreach ((array) $returnColumns as $name) {
737
                    if ($this->getTableSchema($table)->getColumn($name)->autoIncrement) {
738
                        $this->_lastInsertID = $result[$name];
739
                        break;
740
                    }
741
                }
742
            }
743
            return $result;
744
        }
745
    }
746
747
    /**
748
     * @inheritdoc
749
     */
750
    public function getLastInsertID($sequenceName = '')
751
    {
752
        if (!$this->db->isActive) {
753
            throw new InvalidCallException('DB Connection is not active.');
754
        }
755
756
        if ($sequenceName !== '') {
757
            return $this->db->createCommand('SELECT GEN_ID(' . $this->db->quoteTableName($sequenceName) . ', 0 ) FROM RDB$DATABASE;')->queryScalar();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->db->createCommand...BASE;')->queryScalar(); of type string|null|false adds false to the return on line 757 which is incompatible with the return type of the parent method yii\db\Schema::getLastInsertID of type string. It seems like you forgot to handle an error condition.
Loading history...
758
        }
759
760
        if ($this->_lastInsertID !== false) {
761
            return $this->_lastInsertID;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->_lastInsertID; (false) is incompatible with the return type of the parent method yii\db\Schema::getLastInsertID of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
762
        }
763
        return null;
764
    }
765
766
    protected function loadTablePrimaryKey($tableName)
767
    {
768
        static $sql = <<<'SQL'
769
SELECT RC.RDB$CONSTRAINT_NAME AS NAME, IDX.RDB$FIELD_NAME AS COLUMN_NAME
770
FROM RDB$RELATION_CONSTRAINTS RC
771
  JOIN RDB$INDEX_SEGMENTS IDX
772
    ON IDX.RDB$INDEX_NAME = RC.RDB$INDEX_NAME
773
WHERE RC.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY'
774
AND   UPPER(RC.RDB$RELATION_NAME) = UPPER(:tableName)
775
SQL;
776
777
        $resolvedName = $this->resolveTableName($tableName);
778
        $constraints = $this->db->createCommand($sql, [
779
            ':tableName' => $resolvedName->name,
780
        ])->queryAll();
781
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
782
        $constraints = ArrayHelper::index($constraints, null, ['name']);
783
784
        foreach ($constraints as $name => $constraint) {
785
            $columns = ArrayHelper::getColumn($constraint, 'column_name');
786
            $columns = array_map('trim', $columns);
787
            $columns = array_map('strtolower', $columns);
788
            return new Constraint([
789
                'name' => strtolower(trim($name)),
790
                'columnNames' => $columns,
791
            ]);
792
        }
793
794
        return null;
795
    }
796
797
    protected function loadTableUniques($tableName)
798
    {
799
        static $sql = <<<'SQL'
800
SELECT RC.RDB$CONSTRAINT_NAME AS NAME, IDX.RDB$FIELD_NAME AS COLUMN_NAME
801
FROM RDB$RELATION_CONSTRAINTS RC
802
  JOIN RDB$INDEX_SEGMENTS IDX
803
    ON IDX.RDB$INDEX_NAME = RC.RDB$INDEX_NAME
804
WHERE RC.RDB$CONSTRAINT_TYPE = 'UNIQUE'
805
AND   UPPER(RC.RDB$RELATION_NAME) = UPPER(:tableName)
806
SQL;
807
808
        $resolvedName = $this->resolveTableName($tableName);
809
        $constraints = $this->db->createCommand($sql, [
810
            ':tableName' => $resolvedName->name,
811
        ])->queryAll();
812
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
813
        $constraints = ArrayHelper::index($constraints, null, ['name']);
814
815
        $result = [];
816
        foreach ($constraints as $name => $rows) {
817
            $columns = ArrayHelper::getColumn($rows, 'column_name');
818
            $columns = array_map('trim', $columns);
819
            $columns = array_map('strtolower', $columns);
820
            $result[] = new Constraint([
821
                'name' => strtolower(trim($name)),
822
                'columnNames' => $columns,
823
            ]);
824
        }
825
826
        return $result;
827
    }
828
829
    protected function loadTableChecks($tableName)
830
    {
831
        // DISTINCT not work on blob, need cast to varchar
832
        static $sql = <<<'SQL'
833
SELECT DISTINCT RC.RDB$CONSTRAINT_NAME AS NAME,
834
       DEP.RDB$FIELD_NAME AS COLUMN_NAME,
835
       CAST(TRIG.RDB$TRIGGER_SOURCE AS VARCHAR(32765)) AS CHECK_EXPR
836
       FROM RDB$RELATION_CONSTRAINTS RC
837
  JOIN RDB$CHECK_CONSTRAINTS CH_CONST
838
    ON CH_CONST.RDB$CONSTRAINT_NAME = RC.RDB$CONSTRAINT_NAME
839
  JOIN RDB$TRIGGERS TRIG
840
    ON TRIG.RDB$TRIGGER_NAME = CH_CONST.RDB$TRIGGER_NAME
841
  JOIN RDB$DEPENDENCIES DEP
842
    ON DEP.RDB$DEPENDENT_NAME = TRIG.RDB$TRIGGER_NAME
843
   AND DEP.RDB$DEPENDED_ON_NAME = TRIG.RDB$RELATION_NAME
844
WHERE RC.RDB$CONSTRAINT_TYPE = 'CHECK'
845
AND   UPPER(RC.RDB$RELATION_NAME) = UPPER(:tableName)
846
SQL;
847
848
        $resolvedName = $this->resolveTableName($tableName);
849
        $constraints = $this->db->createCommand($sql, [
850
            ':tableName' => $resolvedName->name,
851
        ])->queryAll();
852
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
853
        $constraints = ArrayHelper::index($constraints, null, ['name']);
854
855
        $result = [];
856
        foreach ($constraints as $name => $constraint) {
857
            $columns = ArrayHelper::getColumn($constraint, 'column_name');
858
            $columns = array_map('trim', $columns);
859
            $columns = array_map('strtolower', $columns);
860
861
            $check_expr = $constraint[0]['check_expr'];
862
            $check_expr = preg_replace('/^\s*CHECK\s*/i', '', $check_expr); // remove "CHECK " at begin
863
            $check_expr = preg_replace('/^\((.*)\)$/i', '\1', $check_expr); // remove bracket () at begin and end
864
865
            $result[] = new CheckConstraint([
866
                'name' => strtolower(trim($name)),
867
                'columnNames' => $columns,
868
                'expression' => $check_expr,
869
            ]);
870
        }
871
872
        return $result;
873
    }
874
875
    protected function loadTableDefaultValues($tableName)
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
876
    {
877
        return [];
878
    }
879
880
    protected function loadTableForeignKeys($tableName)
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
881
    {
882
        return [];
883
    }
884
885
    protected function loadTableIndexes($tableName)
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
886
    {
887
        return [];
888
    }
889
890
    protected function findViewNames($schema = '')
0 ignored issues
show
Unused Code introduced by
The parameter $schema is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
891
    {
892
    }
893
}
894