Completed
Push — master ( 239191...b9c48b )
by Edgard
77:13 queued 37:11
created

Schema::insert()   C

Complexity

Conditions 7
Paths 10

Size

Total Lines 32
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 0
cts 0
cp 0
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 22
nc 10
nop 2
crap 56
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\Exception;
13
use yii\db\TableSchema;
14
15
/**
16
 * Schema represents the Firebird schema information.
17
 *
18
 * @property string[] $indexNames All index names in the Firebird. This property is read-only.
19
 * @property IndexSchema[] $indexSchemas The metadata for all indexes in the Firebird. Each array element is an
20
 * instance of [[IndexSchema]] or its child class. This property is read-only.
21
 * @property array $indexTypes All index types in the Firebird in format: index name => index type. This
22
 * property is read-only.
23
 * @property QueryBuilder $queryBuilder The query builder for this connection. This property is read-only.
24
 *
25
 * @author Edgard Lorraine Messias <[email protected]>
26
 * @since 2.0
27
 */
28
class Schema extends \yii\db\Schema
29
{
30
31
    private $_lastInsertID = null;
32
33
    /**
34
     * @var array map of DB errors and corresponding exceptions
35
     * If left part is found in DB error message exception class from the right part is used.
36
     */
37
    public $exceptionMap = [
38
        'SQLSTATE[23'                                               => 'yii\db\IntegrityException',
39
        'SQLSTATE[HY000]: General error: -803 violation of PRIMARY' => 'yii\db\IntegrityException',
40
    ];
41
    public $reservedWords = [
42
        'ADD',
43
        'ADMIN',
44
        'ALL',
45
        'ALTER',
46
        'AND',
47
        'ANY',
48
        'AS',
49
        'AT',
50
        'AVG',
51
        'BEGIN',
52
        'BETWEEN',
53
        'BIGINT',
54
        'BIT_LENGTH',
55
        'BLOB',
56
        'BOTH',
57
        'BOOLEAN',
58
        'BY',
59
        'CASE',
60
        'CAST',
61
        'CHAR',
62
        'CHAR_LENGTH',
63
        'CHARACTER',
64
        'CHARACTER_LENGTH',
65
        'CHECK',
66
        'CLOSE',
67
        'COLLATE',
68
        'COLUMN',
69
        'COMMIT',
70
        'CONNECT',
71 99
        'CONSTRAINT',
72
        'CORR',
73 99
        'COUNT',
74
        'COVAR_POP',
75
        'CREATE',
76
        'CROSS',
77
        'CURRENT',
78
        'CURRENT_CONNECTION',
79 2
        'CURRENT_DATE',
80
        'CURRENT_ROLE',
81 2
        'CURRENT_TIME',
82
        'CURRENT_TIMESTAMP',
83
        'CURRENT_TRANSACTION',
84 157
        'CURRENT_USER',
85
        'CURSOR',
86 157
        'DATE',
87
        'DAY',
88
        'DEC',
89
        'DECIMAL',
90 157
        'DECLARE',
91 157
        'DEFAULT',
92 43
        'DELETE',
93
        'DELETING',
94
        'DETERMINISTIC',
95 156
        'DISCONNECT',
96
        'DISTINCT',
97
        'DOUBLE',
98 173
        'DROP',
99
        'ELSE',
100 173
        'END',
101 6
        'ESCAPE',
102
        'EXECUTE',
103 173
        'EXISTS',
104
        'EXTERNAL',
105
        'EXRACT',
106 106
        'FALSE',
107
        'FETCH',
108 106
        'FILTER',
109 106
        'FLOAT',
110 106
        'FOR',
111 104
        'FOREIGN',
112 104
        'FROM',
113
        'FULL',
114 7
        'FUNCTION',
115
        'GDSCODE',
116
        'GLOBAL',
117 93
        'GRANT',
118
        'GROUP',
119 93
        'HAVING',
120
        'HOUR',
121
        'IN',
122
        'INDEX',
123
        'INNER',
124
        'INSENSITIVE',
125
        'INSERT',
126
        'INSERTING',
127 93
        'INT',
128
        'INTEGER',
129 93
        'INTO',
130
        'IS',
131
        'JOIN',
132
        'LEADING',
133
        'LEFT',
134
        'LIKE',
135
        'LONG',
136
        'LOWER',
137 106
        'MAX',
138
        'MAXIMUM_SEGMENT',
139 106
        'MERGE',
140 106
        'MIN',
141
        'MINUTE',
142
        'MONTH',
143
        'NATIONAL',
144
        'NATURAL',
145 106
        'NCHAR',
146 106
        'NO',
147
        'NOT',
148 106
        'NULL',
149
        'NUMERIC',
150
        'OCTET_LENGTH',
151
        'OF',
152
        'OFFSET',
153
        'ON',
154
        'OPEN',
155
        'OR',
156 106
        'ORDER',
157
        'OUTER',
158
        'OVER',
159
        'PARAMETER',
160
        'PASSWORD',
161
        'PLAN',
162
        'POSITION',
163
        'POST_EVENT',
164
        'PRECISION',
165
        'PRIMARY',
166
        'PROCEDURE',
167
        'RDB$DB_KEY',
168
        'RDB$RECORD_VERSION',
169
        'REAL',
170
        'RECORD_VERSION',
171
        'RECREATE',
172
        'RECURSIVE',
173
        'REFERENCES',
174 106
        'REGR_AVGX',
175
        'REGR_AVGY',
176
        'REGR_COUNT',
177
        'REGR_INTERCEPT',
178
        'REGR_R2',
179
        'REGR_SLOPE',
180
        'REGR_SXX',
181
        'REGR_SXY',
182
        'REGR_SYY',
183
        'RELEASE',
184 106
        'RETURN',
185
        'RETURNING_VALUES',
186 106
        'RETURNS',
187
        'REVOKE',
188 106
        'RIGHT',
189 106
        'ROLLBACK',
190 106
        'ROW',
191
        'ROWS',
192
        'ROW_COUNT',
193
        'SAVEPOINT',
194
        'SCROLL',
195
        'SECOND',
196
        'SELECT',
197
        'SENSITIVE',
198
        'SET',
199
        'SIMILAR',
200
        'SOME',
201 104
        'SQLCODE',
202
        'SQLSTATE',
203 104
        'START',
204
        'STDDEV_POP',
205
        'STDDEV_SAMP',
206
        'SUM',
207 104
        'TABLE',
208 104
        'THEN',
209 104
        'TIME',
210 104
        'TIMESTAMP',
211 104
        'TO',
212
        'TRAILING',
213 104
        'TRIGGER',
214 104
        'TRIM',
215 104
        'TRUE',
216 87
        'UNION',
217 87
        'UNIQUE',
218 79
        'UNKNOWN',
219 13
        'UPDATE',
220 13
        'UPDATING',
221
        'UPPER',
222
        'USER',
223 104
        'USING',
224 104
        'VALUE',
225 104
        'VALUES',
226
        'VARCHAR',
227
        'VARIABLE',
228 104
        'VARYING',
229
        'VAR_POP',
230
        'VAR_SAMP',
231
        'VIEW',
232
        'WHEN',
233
        'WHERE',
234
        'WHILE',
235 104
        'WITH',
236
        'YEAR',
237 104
    ];
238
239
    /**
240
     * @var array mapping from physical column types (keys) to abstract column types (values)
241
     */
242
    public $typeMap = [
243
        'bigint'             => self::TYPE_BIGINT,
244
        'char'               => self::TYPE_CHAR,
245
        'varchar'            => self::TYPE_STRING,
246 104
        'timestamp'          => self::TYPE_TIMESTAMP,
247
        'decimal'            => self::TYPE_DECIMAL,
248 104
        'float'              => self::TYPE_FLOAT,
249 104
        'blob'               => self::TYPE_BINARY,
250 104
        'integer'            => self::TYPE_INTEGER,
251 104
        'blob sub_type text' => self::TYPE_TEXT,
252 104
        'numeric'            => self::TYPE_DECIMAL,
253 104
        'double precision'   => self::TYPE_DOUBLE,
254
        'smallint'           => self::TYPE_SMALLINT,
255 104
    ];
256
257 104
    /**
258 104
     * Creates a query builder for the database.
259
     * This method may be overridden by child classes to create a DBMS-specific query builder.
260 63
     * @return QueryBuilder query builder instance
261 63
     */
262
    public function createQueryBuilder()
263
    {
264 63
        return new QueryBuilder($this->db);
265
    }
266 104
267 103
    /**
268
     * @inheritdoc
269 104
     */
270
    public function createColumnSchemaBuilder($type, $length = null)
271 104
    {
272
        return new ColumnSchemaBuilder($type, $length);
273
    }
274
275
    public function quoteSimpleTableName($name)
276
    {
277
        if ($this->db->tablePrefix !== '') {
278
            return $name;
279
        }
280
281
        $word = strtoupper(str_replace('%', '', $name));
282
        if (in_array($word, $this->reservedWords)) {
283
            return strpos($name, '"') !== false ? $name : '"' . $name . '"';
284
        }
285
286
        return $name;
287 104
    }
288
289
    public function quoteSimpleColumnName($name)
290 104
    {
291 104
        if (in_array(strtoupper($name), $this->reservedWords)) {
292 88
            return parent::quoteSimpleColumnName($name);
293 88
        }
294 88
        return $name;
295 88
    }
296
297 104
    protected function loadTableSchema($name)
298 104
    {
299 104
        $table = new TableSchema;
300 104
        $this->resolveTableNames($table, $name);
301 104
        if ($this->findColumns($table)) {
302
            $this->findConstraints($table);
303
            return $table;
304
        }
305
        return null;
306
    }
307 104
308 16
    public function getPdoType($data)
309 16
    {
310 16
        static $typeMap = [
311 16
            // php type => PDO type
312 16
            'boolean'  => \PDO::PARAM_INT,
313
            'integer'  => \PDO::PARAM_INT,
314 104
            'string'   => \PDO::PARAM_STR,
315 101
            'resource' => \PDO::PARAM_LOB,
316 47
            'NULL'     => \PDO::PARAM_NULL,
317 47
        ];
318 3
        $type = gettype($data);
319 3
320 3
        return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
321 3
    }
322 3
323 47
    /**
324 46
     *
325 46
     * @param TableSchema $table
326 46
     * @param string $name
327 46
     */
328 46
    protected function resolveTableNames($table, $name)
329
    {
330 4
        $parts = explode('.', str_replace('"', '', $name));
331 4
        if (isset($parts[1])) {
332
            $table->schemaName = $parts[0];
333 47
            $table->name = strtolower($parts[1]);
334 89
            $table->fullName = $this->quoteTableName($table->schemaName) . '.' . $this->quoteTableName($table->name);
335 15
        } else {
336 15
            $table->name = strtolower($parts[0]);
337 15
            $table->fullName = $this->quoteTableName($table->name);
338 15
        }
339 15
    }
340
341 15
    /**
342
     * Collects the table column metadata.
343
     *
344 104
     * @param TableSchema $table the table metadata
345
     * @return boolean whether the table exists in the database
346 104
     */
347 104
    protected function findColumns($table)
348 104
    {
349 104
        // Zoggo - Converted sql to use join syntax
350 104
        // robregonm - Added isAutoInc
351
        $sql = 'SELECT
352
                    rel.rdb$field_name AS fname,
353
                    rel.rdb$default_source AS fdefault,
354
                    fld.rdb$field_type AS fcodtype,
355 104
                    fld.rdb$field_sub_type AS fcodsubtype,
356
                    fld.rdb$field_length AS flength,
357 104
                    fld.rdb$character_length AS fcharlength,
358 104
                    fld.rdb$field_scale AS fscale,
359 63
                    fld.rdb$field_precision AS fprecision,
360 63
                    rel.rdb$null_flag AS fnull,
361 15
                    rel.rdb$description AS fcomment,
362
                    fld.rdb$default_value AS fdefault_value,';
363 63
364
        if (version_compare($this->db->firebird_version, '3.0.0', '>=')) {
365
            $sql .= '
366
                    rel.rdb$generator_name AS fgenerator_name,';
367 104
        }
368
        
369
        $sql .= '
370
                    (SELECT RDB$TRIGGER_SOURCE FROM RDB$TRIGGERS
371
                        WHERE RDB$SYSTEM_FLAG = 0
372
                        AND UPPER(RDB$RELATION_NAME)=UPPER(\'' . $table->name . '\')
373
                        AND RDB$TRIGGER_TYPE = 1
374
                        AND RDB$TRIGGER_INACTIVE = 0
375 104
                        AND (UPPER(REPLACE(RDB$TRIGGER_SOURCE,\' \',\'\')) LIKE \'%NEW.\'||TRIM(rel.rdb$field_name)||\'=GEN_ID%\'
376
                            OR UPPER(REPLACE(RDB$TRIGGER_SOURCE,\' \',\'\')) LIKE \'%NEW.\'||TRIM(rel.rdb$field_name)||\'=NEXTVALUEFOR%\'))
377
                    AS fautoinc
378
                FROM
379
                    rdb$relation_fields rel
380
                    JOIN rdb$fields fld ON rel.rdb$field_source=fld.rdb$field_name
381
                WHERE
382
                    UPPER(rel.rdb$relation_name)=UPPER(\'' . $table->name . '\')
383
                ORDER BY
384
                    rel.rdb$field_position;';
385
        try {
386
            $columns = $this->db->createCommand($sql)->queryAll();
387
            if (empty($columns)) {
388
                return false;
389
            }
390
        } catch (Exception $e) {
391 104
            return false;
392
        }
393 104
        $sql = 'SELECT
394
                    idx.rdb$field_name AS fname
395
                FROM
396
                    rdb$relation_constraints rc
397
                    JOIN rdb$index_segments idx ON idx.rdb$index_name=rc.rdb$index_name
398 104
                WHERE rc.rdb$constraint_type=\'PRIMARY KEY\'
399 104
					AND UPPER(rc.rdb$relation_name)=UPPER(\'' . $table->name . '\')';
400
        try {
401
            $pkeys = $this->db->createCommand($sql)->queryColumn();
402 29
        } catch (Exception $e) {
403 29
            return false;
404
        }
405 29
        $pkeys = array_map('rtrim', $pkeys);
406 29
        $pkeys = array_map('strtolower', $pkeys);
407 29
        foreach ($columns as $key => $column) {
408
            $column = array_map('strtolower', $column);
409
            $columns[$key]['fprimary'] = in_array(rtrim($column['fname']), $pkeys);
410 29
        }
411
        foreach ($columns as $column) {
412 104
            $c = $this->loadColumnSchema($column);
413 104
            if ($table->sequenceName === null && $c->autoIncrement) {
414
                $matches = [];
415 5
                
416
                if (isset($column['fgenerator_name']) && $column['fgenerator_name']) {
417
                    $table->sequenceName = $column['fgenerator_name'];
418
                } elseif (preg_match("/NEW.{$c->name}\s*=\s*GEN_ID\((\w+)/i", $column['fautoinc'], $matches)) {
419
                    $table->sequenceName = $matches[1];
420
                } elseif (preg_match("/NEW.{$c->name}\s*=\s*NEXT\s+VALUE\s+FOR\s+(\w+)/i", $column['fautoinc'], $matches)) {
421
                    $table->sequenceName = $matches[1];
422 5
                }
423
            }
424 5
            $table->columns[$c->name] = $c;
425
            if ($c->isPrimaryKey) {
426
                $table->primaryKey[] = $c->name;
427
            }
428
        }
429 5
        return (count($table->columns) > 0);
430 5
    }
431
432 5
    /**
433
     * @return ColumnSchema
434
     * @throws \yii\base\InvalidConfigException
435
     */
436
    protected function createColumnSchema()
437
    {
438
        return \Yii::createObject('\edgardmessias\db\firebird\ColumnSchema');
439
    }
440
441
    /**
442
     * Creates a table column.
443
     *
444
     * @param array $column column metadata
445
     * @return ColumnSchema normalized column metadata
446
     */
447
    protected function loadColumnSchema($column)
448
    {
449
        $c = $this->createColumnSchema();
450 1
        $c->name = strtolower(rtrim($column['fname']));
451
        $c->allowNull = (int) $column['fnull'] !== 1;
452
        $c->isPrimaryKey = $column['fprimary'];
453
        $c->autoIncrement = (isset($column['fgenerator_name']) && $column['fgenerator_name']) || (boolean) $column['fautoinc'];
454
        $c->comment = $column['fcomment'] === null ? '' : $column['fcomment'];
455
456
        $c->type = self::TYPE_STRING;
457
458 1
        $defaultValue = null;
459 1
        if (!empty($column['fdefault'])) {
460 1
            // remove whitespace, 'DEFAULT ' prefix and surrounding single quotes; all optional
461 1
            if (preg_match("/\s*(DEFAULT\s+){0,1}('(.*)'|(.*))\s*/i", $column['fdefault'], $parts)) {
462 1
                $defaultValue = array_pop($parts);
463 1
            }
464
            // handle escaped single quotes like in "funny''quoted''string"
465 1
            $defaultValue = str_replace('\'\'', '\'', $defaultValue);
466
        }
467
        if ($defaultValue === null) {
468
            $defaultValue = $column['fdefault_value'];
469
        }
470
        $dbType = '';
471
        $baseTypes = [
472
            7   => 'SMALLINT',
473
            8   => 'INTEGER',
474
            16  => 'INT64',
475
            9   => 'QUAD',
476 2
            10  => 'FLOAT',
477
            11  => 'D_FLOAT',
478 2
            17  => 'BOOLEAN',
479 2
            27  => 'DOUBLE PRECISION',
480 1
            12  => 'DATE',
481 1
            13  => 'TIME',
482 1
            35  => 'TIMESTAMP',
483 1
            261 => 'BLOB',
484
            40  => 'CSTRING',
485 1
            45  => 'BLOB_ID',
486
        ];
487 2
        $baseCharTypes = [
488
            37 => 'VARCHAR',
489
            14 => 'CHAR',
490
        ];
491
        if (array_key_exists((int) $column['fcodtype'], $baseTypes)) {
492 13
            $dbType = $baseTypes[(int) $column['fcodtype']];
493
        } elseif (array_key_exists((int) $column['fcodtype'], $baseCharTypes)) {
494 13
            $c->size = (int) $column['fcharlength'];
495 13
            $c->precision = $c->size;
496 13
            $dbType = $baseCharTypes[(int) $column['fcodtype']] . "($c->size)";
497 13
        }
498 13
        switch ((int) $column['fcodtype']) {
499 12
            case 7:
500 12
            case 8:
501 12
                switch ((int) $column['fcodsubtype']) {
502
                    case 1:
503 12
                        $c->precision = (int) $column['fprecision'];
504
                        $c->size = $c->precision;
505
                        $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...
506 13
                        $dbType = "NUMERIC({$c->precision},{$c->scale})";
507 13
                        break;
508 13
                    case 2:
509
                        $c->precision = (int) $column['fprecision'];
510 13
                        $c->size = $c->precision;
511
                        $c->scale = abs((int) $column['fscale']);
512
                        $dbType = "DECIMAL({$c->precision},{$c->scale})";
513 13
                        break;
514 12
                }
515 12
                break;
516 10
            case 16:
517 12
                switch ((int) $column['fcodsubtype']) {
518
                    case 1:
519
                        $c->precision = (int) $column['fprecision'];
520
                        $c->size = $c->precision;
521 13
                        $c->scale = abs((int) $column['fscale']);
522
                        $dbType = "NUMERIC({$c->precision},{$c->scale})";
523
                        break;
524
                    case 2:
525
                        $c->precision = (int) $column['fprecision'];
526
                        $c->size = $c->precision;
527
                        $c->scale = abs((int) $column['fscale']);
528 2
                        $dbType = "DECIMAL({$c->precision},{$c->scale})";
529
                        break;
530 2
                    default:
531
                        $dbType = 'BIGINT';
532
                        break;
533
                }
534 2
                break;
535 2
            case 261:
536
                switch ((int) $column['fcodsubtype']) {
537
                    case 1:
538 1
                        $dbType = 'BLOB SUB_TYPE TEXT';
539 1
                        $c->size = null;
540
                        break;
541
                }
542
                break;
543
        }
544
545
        $c->dbType = strtolower($dbType);
546
547
        $c->type = self::TYPE_STRING;
548
        if (preg_match('/^([\w\ ]+)(?:\(([^\)]+)\))?/', $c->dbType, $matches)) {
549
            $type = strtolower($matches[1]);
550
            if (isset($this->typeMap[$type])) {
551
                $c->type = $this->typeMap[$type];
552
            }
553
        }
554
555
556
        $c->phpType = $this->getColumnPhpType($c);
557
558
        $c->defaultValue = null;
559
        if ($defaultValue !== null) {
560
            if (in_array($c->type, [self::TYPE_DATE, self::TYPE_DATETIME, self::TYPE_TIME, self::TYPE_TIMESTAMP])
561
                    && preg_match('/(CURRENT_|NOW|NULL|TODAY|TOMORROW|YESTERDAY)/i', $defaultValue)) {
562
                $c->defaultValue = new \yii\db\Expression(trim($defaultValue));
563
            } else {
564
                $c->defaultValue = $c->phpTypecast($defaultValue);
565
            }
566
        }
567
568
        return $c;
569
    }
570
571
    /**
572
     * Collects the foreign key column details for the given table.
573
     *
574
     * @param TableSchema $table the table metadata
575
     */
576
    protected function findConstraints($table)
577
    {
578
        // Zoggo - Converted sql to use join syntax
579
        $sql = 'SELECT
580
                    a.rdb$constraint_name as fconstraint,
581
                    c.rdb$relation_name AS ftable,
582
                    d.rdb$field_name AS pfield,
583
                    e.rdb$field_name AS ffield
584
                FROM
585
                    rdb$ref_constraints b
586
                    JOIN rdb$relation_constraints a ON a.rdb$constraint_name=b.rdb$constraint_name
587
                    JOIN rdb$relation_constraints c ON b.rdb$const_name_uq=c.rdb$constraint_name
588
                    JOIN rdb$index_segments d ON c.rdb$index_name=d.rdb$index_name
589
                    JOIN rdb$index_segments e ON a.rdb$index_name=e.rdb$index_name AND e.rdb$field_position = d.rdb$field_position
590
                WHERE
591
                    a.rdb$constraint_type=\'FOREIGN KEY\' AND
592
                    UPPER(a.rdb$relation_name)=UPPER(\'' . $table->name . '\') ';
593
        try {
594
            $fkeys = $this->db->createCommand($sql)->queryAll();
595
        } catch (Exception $e) {
596
            return false;
597
        }
598
599
        $constraints = [];
600
        foreach ($fkeys as $fkey) {
601
            // Zoggo - Added strtolower here to guarantee that values are
602
            // returned lower case. Otherwise gii generates wrong code.
603
            $fkey = array_map('rtrim', $fkey);
604
            $fkey = array_map('strtolower', $fkey);
605
606
            if (!isset($constraints[$fkey['fconstraint']])) {
607
                $constraints[$fkey['fconstraint']] = [
608
                    $fkey['ftable']
609
                ];
610
            }
611
            $constraints[$fkey['fconstraint']][$fkey['ffield']] = $fkey['pfield'];
612
        }
613
        $table->foreignKeys = $constraints;
614
    }
615
616
    protected function findTableNames($schema = '')
617
    {
618
        $sql = 'SELECT
619
                    rdb$relation_name
620
                FROM
621
                    rdb$relations
622
                WHERE
623
                    (rdb$system_flag is null OR rdb$system_flag=0)';
624
        try {
625
            $tables = $this->db->createCommand($sql)->queryColumn();
626
        } catch (Exception $e) {
627
            return false;
628
        }
629
630
        $tables = array_map('rtrim', $tables);
631
        $tables = array_map('strtolower', $tables);
632
633
        return $tables;
634
    }
635
636
    /**
637
     * Returns all unique indexes for the given table.
638
     * Each array element is of the following structure:
639
     *
640
     * ~~~
641
     * [
642
     *  'IndexName1' => ['col1' [, ...]],
643
     *  'IndexName2' => ['col2' [, ...]],
644
     * ]
645
     * ~~~
646
     *
647
     * @param TableSchema $table the table metadata
648
     * @return array all unique indexes for the given table.
649
     * @since 2.0.4
650
     */
651
    public function findUniqueIndexes($table)
652
    {
653
        $query = '
654
SELECT id.RDB$INDEX_NAME as index_name, ids.RDB$FIELD_NAME as column_name
655
FROM RDB$INDICES id
656
INNER JOIN RDB$INDEX_SEGMENTS ids ON ids.RDB$INDEX_NAME = id.RDB$INDEX_NAME
657
WHERE id.RDB$UNIQUE_FLAG = 1
658
AND   id.RDB$SYSTEM_FLAG = 0
659
AND UPPER(id.RDB$RELATION_NAME) = UPPER(\'' . $table->name . '\')
660
ORDER BY id.RDB$RELATION_NAME, id.RDB$INDEX_NAME, ids.RDB$FIELD_POSITION';
661
        $result = [];
662
        $command = $this->db->createCommand($query);
663
        foreach ($command->queryAll() as $row) {
664
            $result[strtolower(rtrim($row['index_name']))][] = strtolower(rtrim($row['column_name']));
665
        }
666
        return $result;
667
    }
668
669
    /**
670
     * Sets the isolation level of the current transaction.
671
     * @param string $level The transaction isolation level to use for this transaction.
672
     * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]]
673
     * and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used
674
     * after `SET TRANSACTION ISOLATION LEVEL`.
675
     * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
676
     */
677
    public function setTransactionIsolationLevel($level)
678
    {
679
        if ($level == \yii\db\Transaction::READ_UNCOMMITTED) {
680
            parent::setTransactionIsolationLevel('READ COMMITTED RECORD_VERSION');
681
        } elseif ($level == \yii\db\Transaction::REPEATABLE_READ) {
682
            parent::setTransactionIsolationLevel('SNAPSHOT');
683
        } elseif ($level == \yii\db\Transaction::SERIALIZABLE) {
684
            parent::setTransactionIsolationLevel('SNAPSHOT TABLE STABILITY');
685
        } else {
686
            parent::setTransactionIsolationLevel($level);
687
        }
688
    }
689
690
    /**
691
     * @inheritdoc
692
     */
693
    public function insert($table, $columns)
694
    {
695
        $this->_lastInsertID = false;
696
        $params = [];
697
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
698
        $returnColumns = $this->getTableSchema($table)->primaryKey;
699
        if (!empty($returnColumns)) {
700
            $returning = [];
701
            foreach ((array) $returnColumns as $name) {
702
                $returning[] = $this->quoteColumnName($name);
703
            }
704
            $sql .= ' RETURNING ' . implode(', ', $returning);
705
        }
706
707
        $command = $this->db->createCommand($sql, $params);
708
        $command->prepare(false);
709
        $result = $command->queryOne();
710
711
        if (!$command->pdoStatement->rowCount()) {
712
            return false;
713
        } else {
714
            if (!empty($returnColumns)) {
715
                foreach ((array) $returnColumns as $name) {
716
                    if ($this->getTableSchema($table)->getColumn($name)->autoIncrement) {
717
                        $this->_lastInsertID = $result[$name];
718
                        break;
719
                    }
720
                }
721
            }
722
            return $result;
723
        }
724
    }
725
726
    /**
727
     * @inheritdoc
728
     */
729
    public function getLastInsertID($sequenceName = '')
730
    {
731
        if (!$this->db->isActive) {
732
            throw new InvalidCallException('DB Connection is not active.');
733
        }
734
        
735
        if ($sequenceName !== '') {
736
            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 736 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...
737
        }
738
739
        if ($this->_lastInsertID !== false) {
740
            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...
741
        }
742
        return null;
743
    }
744
}
745