Completed
Push — fix-unique-exists-expressions ( b0719b...8467f4 )
by Alexander
07:52
created

QueryBuilder   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 313
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 79.77%

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 6
dl 0
loc 313
rs 8.295
c 0
b 0
f 0
ccs 71
cts 89
cp 0.7977

11 Methods

Rating   Name   Duplication   Size   Complexity  
A dropIndex() 0 4 1
A renameTable() 0 4 1
A truncateTable() 0 4 1
A insert() 0 4 1
A update() 0 4 1
A alterColumn() 0 10 2
B normalizeTableRowData() 0 17 7
B resetSequence() 0 21 5
C batchInsert() 0 44 13
B createIndex() 0 16 5
B checkIntegrity() 0 19 5

How to fix   Complexity   

Complex Class

Complex classes like QueryBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QueryBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db\pgsql;
9
10
use yii\base\InvalidParamException;
11
12
/**
13
 * QueryBuilder is the query builder for PostgreSQL databases.
14
 *
15
 * @author Gevik Babakhani <[email protected]>
16
 * @since 2.0
17
 */
18
class QueryBuilder extends \yii\db\QueryBuilder
19
{
20
    /**
21
     * Defines a UNIQUE index for [[createIndex()]].
22
     * @since 2.0.6
23
     */
24
    const INDEX_UNIQUE = 'unique';
25
    /**
26
     * Defines a B-tree index for [[createIndex()]].
27
     * @since 2.0.6
28
     */
29
    const INDEX_B_TREE = 'btree';
30
    /**
31
     * Defines a hash index for [[createIndex()]].
32
     * @since 2.0.6
33
     */
34
    const INDEX_HASH = 'hash';
35
    /**
36
     * Defines a GiST index for [[createIndex()]].
37
     * @since 2.0.6
38
     */
39
    const INDEX_GIST = 'gist';
40
    /**
41
     * Defines a GIN index for [[createIndex()]].
42
     * @since 2.0.6
43
     */
44
    const INDEX_GIN = 'gin';
45
46
    /**
47
     * @var array mapping from abstract column types (keys) to physical column types (values).
48
     */
49
    public $typeMap = [
50
        Schema::TYPE_PK => 'serial NOT NULL PRIMARY KEY',
51
        Schema::TYPE_UPK => 'serial NOT NULL PRIMARY KEY',
52
        Schema::TYPE_BIGPK => 'bigserial NOT NULL PRIMARY KEY',
53
        Schema::TYPE_UBIGPK => 'bigserial NOT NULL PRIMARY KEY',
54
        Schema::TYPE_CHAR => 'char(1)',
55
        Schema::TYPE_STRING => 'varchar(255)',
56
        Schema::TYPE_TEXT => 'text',
57
        Schema::TYPE_SMALLINT => 'smallint',
58
        Schema::TYPE_INTEGER => 'integer',
59
        Schema::TYPE_BIGINT => 'bigint',
60
        Schema::TYPE_FLOAT => 'double precision',
61
        Schema::TYPE_DOUBLE => 'double precision',
62
        Schema::TYPE_DECIMAL => 'numeric(10,0)',
63
        Schema::TYPE_DATETIME => 'timestamp(0)',
64
        Schema::TYPE_TIMESTAMP => 'timestamp(0)',
65
        Schema::TYPE_TIME => 'time(0)',
66
        Schema::TYPE_DATE => 'date',
67
        Schema::TYPE_BINARY => 'bytea',
68
        Schema::TYPE_BOOLEAN => 'boolean',
69
        Schema::TYPE_MONEY => 'numeric(19,4)',
70
    ];
71
72
    /**
73
     * @var array map of query condition to builder methods.
74
     * These methods are used by [[buildCondition]] to build SQL conditions from array syntax.
75
     */
76
    protected $conditionBuilders = [
77
        'NOT' => 'buildNotCondition',
78
        'AND' => 'buildAndCondition',
79
        'OR' => 'buildAndCondition',
80
        'BETWEEN' => 'buildBetweenCondition',
81
        'NOT BETWEEN' => 'buildBetweenCondition',
82
        'IN' => 'buildInCondition',
83
        'NOT IN' => 'buildInCondition',
84
        'LIKE' => 'buildLikeCondition',
85
        'ILIKE' => 'buildLikeCondition',
86
        'NOT LIKE' => 'buildLikeCondition',
87
        'NOT ILIKE' => 'buildLikeCondition',
88
        'OR LIKE' => 'buildLikeCondition',
89
        'OR ILIKE' => 'buildLikeCondition',
90
        'OR NOT LIKE' => 'buildLikeCondition',
91
        'OR NOT ILIKE' => 'buildLikeCondition',
92
        'EXISTS' => 'buildExistsCondition',
93
        'NOT EXISTS' => 'buildExistsCondition',
94
    ];
95
96
97
    /**
98
     * Builds a SQL statement for creating a new index.
99
     * @param string $name the name of the index. The name will be properly quoted by the method.
100
     * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
101
     * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
102
     * separate them with commas or use an array to represent them. Each column name will be properly quoted
103
     * by the method, unless a parenthesis is found in the name.
104
     * @param bool|string $unique whether to make this a UNIQUE index constraint. You can pass `true` or [[INDEX_UNIQUE]] to create
105
     * a unique index, `false` to make a non-unique index using the default index type, or one of the following constants to specify
106
     * the index method to use: [[INDEX_B_TREE]], [[INDEX_HASH]], [[INDEX_GIST]], [[INDEX_GIN]].
107
     * @return string the SQL statement for creating a new index.
108
     * @see http://www.postgresql.org/docs/8.2/static/sql-createindex.html
109
     */
110 5
    public function createIndex($name, $table, $columns, $unique = false)
111
    {
112 5
        if ($unique === self::INDEX_UNIQUE || $unique === true) {
113 1
            $index = false;
114 1
            $unique = true;
115
        } else {
116 4
            $index = $unique;
117 4
            $unique = false;
118
        }
119
120 5
        return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ') .
121 5
        $this->db->quoteTableName($name) . ' ON ' .
122 5
        $this->db->quoteTableName($table) .
123 5
        ($index !== false ? " USING $index" : '') .
124 5
        ' (' . $this->buildColumns($columns) . ')';
125
    }
126
127
    /**
128
     * Builds a SQL statement for dropping an index.
129
     * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
130
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
131
     * @return string the SQL statement for dropping an index.
132
     */
133
    public function dropIndex($name, $table)
134
    {
135
        return 'DROP INDEX ' . $this->db->quoteTableName($name);
136
    }
137
138
    /**
139
     * Builds a SQL statement for renaming a DB table.
140
     * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
141
     * @param string $newName the new table name. The name will be properly quoted by the method.
142
     * @return string the SQL statement for renaming a DB table.
143
     */
144 1
    public function renameTable($oldName, $newName)
145
    {
146 1
        return 'ALTER TABLE ' . $this->db->quoteTableName($oldName) . ' RENAME TO ' . $this->db->quoteTableName($newName);
147
    }
148
149
    /**
150
     * Creates a SQL statement for resetting the sequence value of a table's primary key.
151
     * The sequence will be reset such that the primary key of the next new row inserted
152
     * will have the specified value or 1.
153
     * @param string $tableName the name of the table whose primary key sequence will be reset
154
     * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
155
     * the next new row's primary key will have a value 1.
156
     * @return string the SQL statement for resetting sequence
157
     * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
158
     */
159 3
    public function resetSequence($tableName, $value = null)
160
    {
161 3
        $table = $this->db->getTableSchema($tableName);
162 3
        if ($table !== null && $table->sequenceName !== null) {
163
            // c.f. http://www.postgresql.org/docs/8.1/static/functions-sequence.html
164 3
            $sequence = $this->db->quoteTableName($table->sequenceName);
165 3
            $tableName = $this->db->quoteTableName($tableName);
166 3
            if ($value === null) {
167 1
                $key = $this->db->quoteColumnName(reset($table->primaryKey));
0 ignored issues
show
Security Bug introduced by
It seems like reset($table->primaryKey) targeting reset() can also be of type false; however, yii\db\Connection::quoteColumnName() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
168 1
                $value = "(SELECT COALESCE(MAX({$key}),0) FROM {$tableName})+1";
169
            } else {
170 3
                $value = (int) $value;
171
            }
172
173 3
            return "SELECT SETVAL('$sequence',$value,false)";
174
        } elseif ($table === null) {
175
            throw new InvalidParamException("Table not found: $tableName");
176
        } else {
177
            throw new InvalidParamException("There is not sequence associated with table '$tableName'.");
178
        }
179
    }
180
181
    /**
182
     * Builds a SQL statement for enabling or disabling integrity check.
183
     * @param bool $check whether to turn on or off the integrity check.
184
     * @param string $schema the schema of the tables.
185
     * @param string $table the table name.
186
     * @return string the SQL statement for checking integrity
187
     */
188
    public function checkIntegrity($check = true, $schema = '', $table = '')
189
    {
190
        $enable = $check ? 'ENABLE' : 'DISABLE';
191
        $schema = $schema ?: $this->db->getSchema()->defaultSchema;
192
        $tableNames = $table ? [$table] : $this->db->getSchema()->getTableNames($schema);
193
        $viewNames = $this->db->getSchema()->getViewNames($schema);
0 ignored issues
show
Documentation Bug introduced by
The method getViewNames does not exist on object<yii\db\Schema>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
194
        $tableNames = array_diff($tableNames, $viewNames);
195
        $command = '';
196
197
        foreach ($tableNames as $tableName) {
198
            $tableName = $this->db->quoteTableName("{$schema}.{$tableName}");
199
            $command .= "ALTER TABLE $tableName $enable TRIGGER ALL; ";
200
        }
201
202
        // enable to have ability to alter several tables
203
        $this->db->getMasterPdo()->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true);
204
205
        return $command;
206
    }
207
208
    /**
209
     * Builds a SQL statement for truncating a DB table.
210
     * Explicitly restarts identity for PGSQL to be consistent with other databases which all do this by default.
211
     * @param string $table the table to be truncated. The name will be properly quoted by the method.
212
     * @return string the SQL statement for truncating a DB table.
213
     */
214 5
    public function truncateTable($table)
215
    {
216 5
        return 'TRUNCATE TABLE ' . $this->db->quoteTableName($table) . ' RESTART IDENTITY';
217
    }
218
219
    /**
220
     * Builds a SQL statement for changing the definition of a column.
221
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
222
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
223
     * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
224
     * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
225
     * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
226
     * will become 'varchar(255) not null'. You can also use PostgreSQL-specific syntax such as `SET NOT NULL`.
227
     * @return string the SQL statement for changing the definition of a column.
228
     */
229 2
    public function alterColumn($table, $column, $type)
230
    {
231
        // https://github.com/yiisoft/yii2/issues/4492
232
        // http://www.postgresql.org/docs/9.1/static/sql-altertable.html
233 2
        if (!preg_match('/^(DROP|SET|RESET)\s+/i', $type)) {
234 2
            $type = 'TYPE ' . $this->getColumnType($type);
235
        }
236 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
237 2
            . $this->db->quoteColumnName($column) . ' ' . $type;
238
    }
239
240
    /**
241
     * @inheritdoc
242
     */
243 100
    public function insert($table, $columns, &$params)
244
    {
245 100
        return parent::insert($table, $this->normalizeTableRowData($table, $columns), $params);
246
    }
247
248
    /**
249
     * @inheritdoc
250
     */
251 20
    public function update($table, $columns, $condition, &$params)
252
    {
253 20
        return parent::update($table, $this->normalizeTableRowData($table, $columns), $condition, $params);
0 ignored issues
show
Bug introduced by
It seems like $this->normalizeTableRowData($table, $columns) targeting yii\db\pgsql\QueryBuilder::normalizeTableRowData() can also be of type object<yii\db\Query>; however, yii\db\QueryBuilder::update() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
254
    }
255
256
    /**
257
     * Normalizes data to be saved into the table, performing extra preparations and type converting, if necessary.
258
     * @param string $table the table that data will be saved into.
259
     * @param array|\yii\db\Query $columns the column data (name => value) to be saved into the table or instance
260
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
261
     * Passing of [[yii\db\Query|Query]] is available since version 2.0.11.
262
     * @return array normalized columns
263
     * @since 2.0.9
264
     */
265 110
    private function normalizeTableRowData($table, $columns)
266
    {
267 110
        if ($columns instanceof \yii\db\Query) {
268 5
            return $columns;
269
        }
270
271 107
        if (($tableSchema = $this->db->getSchema()->getTableSchema($table)) !== null) {
272 107
            $columnSchemas = $tableSchema->columns;
273 107
            foreach ($columns as $name => $value) {
274 106
                if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && is_string($value)) {
275 45
                    $columns[$name] = [$value, \PDO::PARAM_LOB]; // explicitly setup PDO param type for binary column
276
                }
277
            }
278
        }
279
280 107
        return $columns;
281
    }
282
283
    /**
284
     * @inheritdoc
285
     */
286 13
    public function batchInsert($table, $columns, $rows)
287
    {
288 13
        if (empty($rows)) {
289 2
            return '';
290
        }
291
292 12
        $schema = $this->db->getSchema();
293 12
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
294 12
            $columnSchemas = $tableSchema->columns;
295
        } else {
296
            $columnSchemas = [];
297
        }
298
299 12
        $values = [];
300 12
        foreach ($rows as $row) {
301 11
            $vs = [];
302 11
            foreach ($row as $i => $value) {
303 11
                if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) {
304 8
                    $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
305
                }
306 11
                if (is_string($value)) {
307 5
                    $value = $schema->quoteValue($value);
308 7
                } elseif ($value === true) {
309 3
                    $value = 'TRUE';
310 7
                } elseif ($value === false) {
311 5
                    $value = 'FALSE';
312 4
                } elseif ($value === null) {
313 3
                    $value = 'NULL';
314
                }
315 11
                $vs[] = $value;
316
            }
317 11
            $values[] = '(' . implode(', ', $vs) . ')';
318
        }
319 12
        if (empty($values)) {
320 1
            return '';
321
        }
322
323 11
        foreach ($columns as $i => $name) {
324 10
            $columns[$i] = $schema->quoteColumnName($name);
325
        }
326
327 11
        return 'INSERT INTO ' . $schema->quoteTableName($table)
328 11
        . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
329
    }
330
}
331