Passed
Push — master ( 0854c8...b628a1 )
by Edgard
30:59
created

QueryBuilder::buildOrderByAndLimit()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
ccs 9
cts 9
cp 1
rs 9.4285
cc 3
eloc 8
nc 4
nop 4
crap 3
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\InvalidParamException;
12
use yii\base\NotSupportedException;
13
use yii\db\Expression;
14
use yii\db\Query;
15
16
/**
17
 *
18
 * @author Edgard Lorraine Messias <[email protected]>
19
 * @since 2.0
20
 */
21
class QueryBuilder extends \yii\db\QueryBuilder
22
{
23
24
    /**
25
     * @var array mapping from abstract column types (keys) to physical column types (values).
26
     */
27
    public $typeMap = [
28
        Schema::TYPE_PK        => 'integer NOT NULL PRIMARY KEY',
29
        Schema::TYPE_UPK       => 'integer NOT NULL PRIMARY KEY',
30
        Schema::TYPE_BIGPK     => 'bigint NOT NULL PRIMARY KEY',
31
        Schema::TYPE_UBIGPK    => 'bigint NOT NULL PRIMARY KEY',
32
        Schema::TYPE_CHAR      => 'char(1)',
33
        Schema::TYPE_STRING    => 'varchar(255)',
34
        Schema::TYPE_TEXT      => 'blob sub_type text',
35
        Schema::TYPE_SMALLINT  => 'smallint',
36
        Schema::TYPE_INTEGER   => 'integer',
37
        Schema::TYPE_BIGINT    => 'bigint',
38
        Schema::TYPE_FLOAT     => 'float',
39
        Schema::TYPE_DOUBLE    => 'double precision',
40
        Schema::TYPE_DECIMAL   => 'numeric(10,0)',
41
        Schema::TYPE_DATETIME  => 'timestamp',
42
        Schema::TYPE_TIMESTAMP => 'timestamp',
43
        Schema::TYPE_TIME      => 'time',
44
        Schema::TYPE_DATE      => 'date',
45
        Schema::TYPE_BINARY    => 'blob',
46
        Schema::TYPE_BOOLEAN   => 'smallint',
47
        Schema::TYPE_MONEY     => 'numeric(18,4)',
48
    ];
49
    
50
    public function init()
51
    {
52
        if (version_compare($this->db->firebird_version, '3.0.0', '>=')) {
53
            $this->typeMap[Schema::TYPE_PK] = 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY';
54
            $this->typeMap[Schema::TYPE_UPK] = 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY';
55
            $this->typeMap[Schema::TYPE_BIGPK] = 'bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY';
56
            $this->typeMap[Schema::TYPE_UBIGPK] = 'bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY';
57
            $this->typeMap[Schema::TYPE_BOOLEAN] = 'boolean';
58
        }
59 181
        
60
        parent::init();
61 181
    }
62
63 181
    protected function defaultExpressionBuilders()
64
    {
65
        return array_merge(parent::defaultExpressionBuilders(), [
66 181
            'yii\db\Expression' => 'edgardmessias\db\firebird\ExpressionBuilder',
67 181
            'yii\db\conditions\InCondition' => 'edgardmessias\db\firebird\conditions\InConditionBuilder',
68 181
            'yii\db\conditions\LikeCondition' => 'edgardmessias\db\firebird\conditions\LikeConditionBuilder',
69 181
        ]);
70 181
    }
71 181
72
    /**
73
     * Generates a SELECT SQL statement from a [[Query]] object.
74 181
     * @param Query $query the [[Query]] object from which the SQL statement will be generated.
75 181
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
76
     * be included in the result with the additional parameters generated during the query building process.
77 181
     * @return array the generated SQL statement (the first array element) and the corresponding
78 34
     * parameters to be bound to the SQL statement (the second array element). The parameters returned
79 34
     * include those provided in `$params`.
80 34
     */
81
    public function build($query, $params = [])
82
    {
83
        $query = $query->prepare($this);
84 181
85 2
        $params = empty($params) ? $query->params : array_merge($params, $query->params);
86 2
87 2
        $clauses = [
88
            $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
89
            $this->buildFrom($query->from, $params),
90
            $this->buildJoin($query->join, $params),
91
            $this->buildWhere($query->where, $params),
92 181
            $this->buildGroupBy($query->groupBy),
93 181
            $this->buildHaving($query->having, $params),
94 2
        ];
95
96
        $sql = implode($this->separator, array_filter($clauses));
97 181
        $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset);
98
99
        if (!empty($query->orderBy)) {
100
            foreach ($query->orderBy as $expression) {
101
                if ($expression instanceof Expression) {
102
                    $params = array_merge($params, $expression->params);
103 181
                }
104
            }
105 181
        }
106 53
        if (!empty($query->groupBy)) {
107 53
            foreach ($query->groupBy as $expression) {
108 3
                if ($expression instanceof Expression) {
109
                    $params = array_merge($params, $expression->params);
110 52
                }
111 52
            }
112 21
        }
113 21
114
        $union = $this->buildUnion($query->union, $params);
115 52
        if ($union !== '') {
116
            $sql = "$sql{$this->separator}$union";
117
        }
118
119
        return [$sql, $params];
120 181
    }
121
122
    /**
123
     * @inheritdoc
124
     */
125
    public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
126 5
    {
127
        if (is_array($columns)) {
128 5
            foreach ($columns as $i => $column) {
129 5
                if (!is_string($column)) {
130 5
                    continue;
131
                }
132 5
                $matches = [];
133 5
                if (preg_match('/^(COUNT|SUM|AVG|MIN|MAX)\([\{\[]{0,2}(\w+|\*)[\}\]]{0,2}\)$/i', $column, $matches)) {
134 5
                    $function = $matches[1];
135 5
                    $alias = $matches[2] != '*' ? $matches[2] : 'ALL';
136 5
137 5
                    $columns[$i] = "{$column} AS {$function}_{$alias}";
138 5
                }
139 5
            }
140
        }
141 5
142
        return parent::buildSelect($columns, $params, $distinct, $selectOption);
143
    }
144 5
    
145
    public function buildLimit($limit, $offset)
146 5
    {
147
        $sql = '';
148
        if ($this->hasLimit($limit)) {
149
            if ($limit instanceof \yii\db\ExpressionInterface) {
150
                $limit = "($limit)";
151
            }
152 181
            $sql = 'FIRST ' . $limit;
153
        }
154
        if ($this->hasOffset($offset)) {
155 181
            if ($offset instanceof \yii\db\ExpressionInterface) {
156 181
                $offset = "($offset)";
157 34
            }
158
            $sql .= ' SKIP ' . $offset;
159
        }
160 181
161 181
        return ltrim($sql);
162
    }
163 181
164 180
    /**
165
     * @inheritdoc
166
     */
167
    public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset)
168 3
    {
169 2
170 2
        $orderBy = $this->buildOrderBy($orderBy);
171 2
        if ($orderBy !== '') {
172
            $sql .= $this->separator . $orderBy;
173
        }
174
        
175 2
        $limit = $this->buildLimit($limit, $offset);
176 2
        if ($limit !== '') {
177 2
            $sql = preg_replace('/^SELECT /i', 'SELECT ' . $limit . ' ', $sql, 1);
178 2
        }
179
        
180
        return $sql;
181 1
    }
182 1
183 1
    /**
184 1
     * @param array $unions
185
     * @param array $params the binding parameters to be populated
186
     * @return string the UNION clause built from [[Query::$union]].
187
     */
188
    public function buildUnion($unions, &$params)
189
    {
190
        if (empty($unions)) {
191
            return '';
192
        }
193
194
        $result = '';
195
196 181
        foreach ($unions as $i => $union) {
197
            $query = $union['query'];
198 181
            if ($query instanceof Query) {
199 181
                list($unions[$i]['query'], $params) = $this->build($query, $params);
200
            }
201
202 2
            $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . $unions[$i]['query'] . ' ';
203
        }
204 2
205 2
        return trim($result);
206 2
    }
207 2
208
    /**
209
     * @inheritdoc
210 2
     */
211
    public function prepareInsertValues($table, $columns, $params = [])
212
    {
213 2
        $schema = $this->db->getSchema();
214
        $tableSchema = $schema->getTableSchema($table);
215
        $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
216
        
217
        //Empty insert
218
        if (empty($columns) && !empty($columnSchemas)) {
219
            $columns = [];
220
            foreach ($columnSchemas as $columnSchema) {
221 3
                if (!$columnSchema->autoIncrement) {
222
                    $columns[$columnSchema->name] = $columnSchema->defaultValue;
223 3
                }
224
            }
225
        }
226
227
        if (is_array($columns)) {
228
            foreach ($columns as $name => $value) {
229 3
                if ($value instanceof \yii\db\ExpressionInterface) {
230
                    continue;
231 3
                }
232
                if ($value instanceof \yii\db\PdoValue) {
233
                    continue;
234 3
                }
235
                if (isset($columnSchemas[$name]) && in_array($columnSchemas[$name]->type, [Schema::TYPE_TEXT, Schema::TYPE_BINARY])) {
236
                    $columns[$name] = [$value, \PDO::PARAM_LOB];
237
                }
238
            }
239
        }
240 23
241
        return parent::prepareInsertValues($table, $columns, $params);
242 23
    }
243 23
    
244 23
    /**
245
     * @inheritdoc
246
     */
247
    protected function prepareInsertSelectSubQuery($columns, $schema, $params = [])
248
    {
249
        /**
250 23
         * @see https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-dml-insert.html#fblangref25-dml-insert-select-unstable
251 1
         */
252 1
        if (version_compare($this->db->firebird_version, '3.0.0', '<')) {
253 1
            throw new NotSupportedException('Firebird < 3.0.0 has the "Unstable Cursor" problem');
254 1
        }
255
256
        return parent::prepareInsertSelectSubQuery($columns, $schema, $params);
257
    }
258
259 23
    /**
260 23
     * @inheritdoc
261 23
     */
262 1
    public function prepareUpdateSets($table, $columns, $params = [])
263 23
    {
264 23
        $schema = $this->db->getSchema();
265
        $tableSchema = $schema->getTableSchema($table);
266
        $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
267
268
        foreach ($columns as $name => $value) {
269 23
            if ($value instanceof \yii\db\ExpressionInterface) {
270
                continue;
271
            }
272
            if ($value instanceof \yii\db\PdoValue) {
273
                continue;
274
            }
275
            if (isset($columnSchemas[$name]) && in_array($columnSchemas[$name]->type, [Schema::TYPE_TEXT, Schema::TYPE_BINARY])) {
276
                $columns[$name] = [$value, \PDO::PARAM_LOB];
277
            }
278
        }
279
        return parent::prepareUpdateSets($table, $columns, $params);
280
    }
281
282
    /**
283
     * @inheritdoc
284
     */
285
    public function batchInsert($table, $columns, $rows, &$params = [])
286
    {
287
        if (empty($rows)) {
288
            return '';
289
        }
290 13
291
        $schema = $this->db->getSchema();
292 13
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
293 13
            $columnSchemas = $tableSchema->columns;
294 13
        } else {
295
            $columnSchemas = [];
296
        }
297
298 13
        $values = [];
299 13
        foreach ($rows as $row) {
300 2
            $vs = [];
301 11
            foreach ($row as $i => $value) {
302 13
                if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) {
303
                    $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
304
                }
305 13
                if (is_string($value)) {
306
                    $value = $schema->quoteValue($value);
307
                } elseif (is_float($value)) {
308
                    // ensure type cast always has . as decimal separator in all locales
309
                    $value = \yii\helpers\StringHelper::floatToString($value);
310
                } elseif ($value === false) {
311 8
                    $value = 0;
312
                } elseif ($value === null) {
313 8
                    $value = 'NULL';
314 2
                } elseif ($value instanceof ExpressionInterface) {
0 ignored issues
show
Bug introduced by
The class edgardmessias\db\firebird\ExpressionInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
315
                    $value = $this->buildExpression($value, $params);
316
                }
317 7
                $vs[] = $value;
318 7
            }
319 7
            $values[] = 'INSERT INTO ' . $schema->quoteTableName($table)
320
                    . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $vs) . ');';
321
        }
322
323
        foreach ($columns as $i => $name) {
324 7
            $columns[$i] = $schema->quoteColumnName($name);
325 7
        }
326 7
327 7
        return 'EXECUTE block AS BEGIN ' . implode(' ', $values) . ' END;';
328 7
    }
329 4
    
330
    /**
331 7
     * {@inheritdoc}
332 4
     * @see https://www.firebirdsql.org/refdocs/langrefupd21-update-or-insert.html
333 4
     * @see https://www.firebirdsql.org/refdocs/langrefupd21-merge.html
334 1
     */
335 4
    public function upsert($table, $insertColumns, $updateColumns, &$params)
336 3
    {
337
        /** @var Constraint[] $constraints */
338 7
        list($uniqueNames, $insertNames, $updateNames) = $this->prepareUpsertColumns($table, $insertColumns, $updateColumns, $constraints);
339
        if (empty($uniqueNames)) {
340 7
            return 'UPDATE OR ' . $this->insert($table, $insertColumns, $params);
341 7
        }
342
343
        $onCondition = ['or'];
344 7
        $quotedTableName = $this->db->quoteTableName($table);
345 6
        foreach ($constraints as $constraint) {
346
            $constraintCondition = ['and'];
347
            foreach ($constraint->columnNames as $name) {
0 ignored issues
show
Bug introduced by
The expression $constraint->columnNames of type array<integer,string>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
348 7
                $quotedName = $this->db->quoteColumnName($name);
349
                $constraintCondition[] = "$quotedTableName.$quotedName=\"EXCLUDED\".$quotedName";
350
            }
351
            $onCondition[] = $constraintCondition;
352
        }
353
        $on = $this->buildCondition($onCondition, $params);
354 1
        list(, $placeholders, $values, $params) = $this->prepareInsertValues($table, $insertColumns, $params);
355
        if (!empty($placeholders)) {
356 1
            /** @var Schema $schema */
357
            $schema = $this->db->getSchema();
358
            $tableSchema = $schema->getTableSchema($table);
359
            $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
360
            
361
            $usingSelectValues = [];
362 2
            foreach ($insertNames as $index => $name) {
363
                if (isset($columnSchemas[$name])) {
364 2
                    $usingSelectValues[$name] = new Expression("CAST({$placeholders[$index]} AS {$columnSchemas[$name]->dbType})");
365
                }
366
            }
367
            $usingSubQuery = (new Query())
368
                ->select($usingSelectValues)
369
                ->from('RDB$DATABASE');
370 1
            list($usingValues, $params) = $this->build($usingSubQuery, $params);
371
        }
372 1
        $mergeSql = 'MERGE INTO ' . $this->db->quoteTableName($table) . ' '
373 1
            . 'USING (' . (isset($usingValues) ? $usingValues : ltrim($values, ' ')) . ') "EXCLUDED" '
374
            . "ON ($on)";
375
        $insertValues = [];
376
        foreach ($insertNames as $name) {
377
            $quotedName = $this->db->quoteColumnName($name);
378
            if (strrpos($quotedName, '.') === false) {
379 1
                $quotedName = '"EXCLUDED".' . $quotedName;
380
            }
381 1
            $insertValues[] = $quotedName;
382 1
        }
383 1
        $insertSql = 'INSERT (' . implode(', ', $insertNames) . ')'
384
            . ' VALUES (' . implode(', ', $insertValues) . ')';
385
        if ($updateColumns === false) {
386
            return "$mergeSql WHEN NOT MATCHED THEN $insertSql";
387
        }
388
389 3
        if ($updateColumns === true) {
390
            $updateColumns = [];
391 3
            foreach ($updateNames as $name) {
392 3
                $quotedName = $this->db->quoteColumnName($name);
393 3
                if (strrpos($quotedName, '.') === false) {
394
                    $quotedName = '"EXCLUDED".' . $quotedName;
395 3
                }
396
                $updateColumns[$name] = new Expression($quotedName);
397 3
            }
398
        }
399 3
        list($updates, $params) = $this->prepareUpdateSets($table, $updateColumns, $params);
400
        $updateSql = 'UPDATE SET ' . implode(', ', $updates);
401 3
        return "$mergeSql WHEN MATCHED THEN $updateSql WHEN NOT MATCHED THEN $insertSql";
402 3
    }
403 2
404 2
    /**
405 2
     * @inheritdoc
406 2
     */
407
    public function renameTable($oldName, $newName)
408
    {
409
        throw new \yii\base\NotSupportedException($this->db->getDriverName() . ' does not support rename table.');
410 3
    }
411 3
    
412 3
    /**
413
     * @inheritdoc
414 3
     */
415
    public function truncateTable($table)
416
    {
417
        return 'DELETE FROM ' . $this->db->quoteTableName($table);
418
    }
419
    
420
    /**
421
     * @inheritdoc
422
     */
423
    public function dropColumn($table, $column)
424
    {
425
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
426
            . ' DROP ' . $this->db->quoteColumnName($column);
427
    }
428
    
429
    /**
430
     * @inheritdoc
431
     */
432
    public function renameColumn($table, $oldName, $newName)
433
    {
434
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
435
            . ' ALTER ' . $this->db->quoteColumnName($oldName)
436
            . ' TO ' . $this->db->quoteColumnName($newName);
437
    }
438
    
439
    /**
440
     * @inheritdoc
441
     */
442
    public function alterColumn($table, $column, $type)
443
    {
444
        $schema = $this->db->getSchema();
445 3
        $tableSchema = $schema->getTableSchema($table);
446 2
        $columnSchema = $tableSchema->getColumn($column);
447
        
448
        $allowNullNewType = !preg_match('/not +null/i', $type);
449 2
        
450 2
        $type = preg_replace('/ +(not)? *null/i', '', $type);
451 2
        
452
        $hasType = false;
453
        
454
        $matches = [];
455
        if (isset($this->typeMap[$type])) {
456
            $hasType = true;
457 2
        } elseif (preg_match('/^(\w+)[\( ]/', $type, $matches)) {
458 2
            if (isset($this->typeMap[$matches[1]])) {
459 2
                $hasType = true;
460
            }
461 2
        }
462 2
        
463
        $baseSql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
464
        . ' ALTER ' . $this->db->quoteColumnName($column)
465
        . (($hasType) ? ' TYPE ' : ' ') .  $this->getColumnType($type);
466
        
467
        if (version_compare($this->db->firebird_version, '3.0.0', '>=')) {
468
            $nullSql = false;
469 1
            
470
            if ($columnSchema->allowNull != $allowNullNewType) {
471 1
                $nullSql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
472
                . ' ALTER ' . $this->db->quoteColumnName($column)
473
                . ($allowNullNewType ? ' DROP' : ' SET')
474
                . ' NOT NULL';
475
            }
476
477 1
            $sql = 'EXECUTE BLOCK AS BEGIN'
478
                . ' EXECUTE STATEMENT ' . $this->db->quoteValue($baseSql) . ';';
479 1
            
480 1
            /**
481
             * In any case (whichever option you choose), make sure that the column doesn't have any NULLs.
482
             * Firebird will not check it for you. Later when you backup the database, everything is fine,
483 1
             * but restore will fail as the NOT NULL column has NULLs in it. To be safe, each time you change from NULL to NOT NULL.
484
             */
485
            if (!$allowNullNewType) {
486
                $sql .= ' UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . $this->db->quoteColumnName($column) . ' = 0'
487 1
                    . ' WHERE ' . $this->db->quoteColumnName($column) . ' IS NULL;';
488 1
            }
489
490
            if ($nullSql) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $nullSql of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
491 1
                $sql .= ' EXECUTE STATEMENT ' . $this->db->quoteValue($nullSql) . ';';
492 1
            }
493 1
494 1
            $sql .= ' END';
495 1
            return $sql;
496 1
        }
497
498
        if ($columnSchema->allowNull == $allowNullNewType) {
499 1
            return $baseSql;
500
        } else {
501
            $sql = 'EXECUTE BLOCK AS BEGIN'
502 1
                . ' EXECUTE STATEMENT ' . $this->db->quoteValue($baseSql) . ';'
503 1
                . ' UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = ' . ($allowNullNewType ? 'NULL' : '1')
504
                . ' WHERE UPPER(RDB$FIELD_NAME) = UPPER(\'' . $column . '\') AND UPPER(RDB$RELATION_NAME) = UPPER(\'' . $table . '\');';
505
            /**
506 1
             * In any case (whichever option you choose), make sure that the column doesn't have any NULLs.
507
             * Firebird will not check it for you. Later when you backup the database, everything is fine,
508
             * but restore will fail as the NOT NULL column has NULLs in it. To be safe, each time you change from NULL to NOT NULL.
509
             */
510
            if (!$allowNullNewType) {
511
                $sql .= ' UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . $this->db->quoteColumnName($column) . ' = 0'
512 5
                    . ' WHERE ' . $this->db->quoteColumnName($column) . ' IS NULL;';
513
            }
514 5
            $sql .= ' END';
515
            return $sql;
516 5
        }
517 5
    }
518
    
519
    /**
520
     * @inheritdoc
521 5
     */
522
    public function dropIndex($name, $table)
523 4
    {
524
        return 'DROP INDEX ' . $this->db->quoteTableName($name);
525
    }
526
    
527 4
    /**
528 4
     * {@inheritdoc}
529
     */
530
    public function addDefaultValue($name, $table, $column, $value)
531
    {
532
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
533
            . $this->db->quoteColumnName($column) . ' SET DEFAULT ' . $this->db->quoteValue($value);
534 4
    }
535 4
536 4
    /**
537 4
     * {@inheritdoc}
538
     */
539
    public function dropDefaultValue($name, $table)
540 5
    {
541
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
542
            . $this->db->quoteColumnName($name) . ' DROP DEFAULT';
543
    }
544 1
545
    /**
546
     * @inheritdoc
547
     */
548
    public function resetSequence($table, $value = null)
549
    {
550 3
        $tableSchema = $this->db->getTableSchema($table);
551
        if ($tableSchema === null) {
552 3
            throw new InvalidParamException("Table not found: $table");
0 ignored issues
show
Deprecated Code introduced by
The class yii\base\InvalidParamException has been deprecated with message: since 2.0.14. Use [[InvalidArgumentException]] instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
553
        }
554 3
        if ($tableSchema->sequenceName === null) {
555 3
            throw new InvalidParamException("There is not sequence associated with table '$table'.");
0 ignored issues
show
Deprecated Code introduced by
The class yii\base\InvalidParamException has been deprecated with message: since 2.0.14. Use [[InvalidArgumentException]] instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
556 1
        }
557
558
        if ($value !== null) {
559
            $value = (int) $value;
560
        } else {
561
            // use master connection to get the biggest PK value
562 2
            $value = $this->db->useMaster(function (Connection $db) use ($tableSchema) {
563 2
                $key = false;
564 2
                foreach ($tableSchema->primaryKey as $name) {
565
                    if ($tableSchema->columns[$name]->autoIncrement) {
566 2
                        $key = $name;
567
                        break;
568
                    }
569
                }
570
                if ($key === false) {
571
                    return 0;
572
                }
573
                return $db->createCommand("SELECT MAX({$this->db->quoteColumnName($key)}) FROM {$this->db->quoteTableName($tableSchema->name)}")->queryScalar();
574
            }) + 1;
575
        }
576 3
577
        return "ALTER SEQUENCE {$this->db->quoteColumnName($tableSchema->sequenceName)} RESTART WITH $value";
578 3
    }
579
    
580
    /**
581
     * @inheritdoc
582
     */
583
    public function createTable($table, $columns, $options = null)
584
    {
585
        $sql = parent::createTable($table, $columns, $options);
586
        
587
        if (version_compare($this->db->firebird_version, '3.0.0', '>=')) {
588
            return $sql;
589
        }
590
591
        foreach ($columns as $name => $type) {
592
            if (!is_string($name)) {
593
                continue;
594
            }
595
            
596
            if (strpos($type, Schema::TYPE_PK) === 0 || strpos($type, Schema::TYPE_BIGPK) === 0) {
597
                $sqlTrigger = <<<SQLTRIGGER
598
CREATE TRIGGER tr_{$table}_{$name} FOR {$this->db->quoteTableName($table)}
599
ACTIVE BEFORE INSERT POSITION 0
600
AS
601
BEGIN
602
    if (NEW.{$this->db->quoteColumnName($name)} is NULL) then NEW.{$this->db->quoteColumnName($name)} = NEXT VALUE FOR seq_{$table}_{$name};
603
END
604
SQLTRIGGER;
605
                
606
                $sqlBlock = <<<SQL
607
EXECUTE block AS
608
BEGIN
609
    EXECUTE STATEMENT {$this->db->quoteValue($sql)};
610
    EXECUTE STATEMENT {$this->db->quoteValue("CREATE SEQUENCE seq_{$table}_{$name}")};
611
    EXECUTE STATEMENT {$this->db->quoteValue($sqlTrigger)};
612
END;
613
SQL;
614
615
                return $sqlBlock;
616
            }
617
        }
618
        
619
        return $sql;
620
    }
621
    
622
    /**
623
     * @inheritdoc
624
     */
625
    public function dropTable($table)
626
    {
627
        $sql = parent::dropTable($table);
628
        
629
        $tableSchema = $this->db->getTableSchema($table);
630
        if ($tableSchema === null || $tableSchema->sequenceName === null) {
631
            return $sql;
632
        }
633
        
634
        /**
635
         * Not drop sequence for sequence "GENERATED BY DEFAULT AS IDENTITY"
636
         */
637
        if (version_compare($this->db->firebird_version, '3.0.0', '>=')) {
638
            $sqlUserSquence = 'SELECT 1 FROM RDB$GENERATORS
639
                WHERE RDB$SYSTEM_FLAG = 0 AND RDB$GENERATOR_NAME = :name';
640
            
641
            $is_user_sequence = $this->db->createCommand($sqlUserSquence, [':name' => $tableSchema->sequenceName])->queryScalar();
642
            
643
            if (!$is_user_sequence) {
644
                return $sql;
645
            }
646
        }
647
        
648
        $sqlBlock = <<<SQL
649
EXECUTE block AS
650
BEGIN
651
    EXECUTE STATEMENT {$this->db->quoteValue($sql)};
652
    EXECUTE STATEMENT {$this->db->quoteValue("DROP SEQUENCE {$tableSchema->sequenceName}")};
653
END;
654
SQL;
655
                return $sqlBlock;
656
    }
657
658
    /**
659
     * Creates a SELECT EXISTS() SQL statement.
660
     * @param string $rawSql the subquery in a raw form to select from.
661
     * @return string the SELECT EXISTS() SQL statement.
662
     *
663
     * @since 2.0.8
664
     */
665
    public function selectExists($rawSql)
666
    {
667
        return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END FROM RDB$DATABASE';
668
    }
669
}
670