Completed
Push — master ( fab53b...ab36cb )
by Dmitry
106:39 queued 103:10
created

QueryBuilder::insert()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 3
crap 1
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 1
    public function createIndex($name, $table, $columns, $unique = false)
111
    {
112 1
        if ($unique === self::INDEX_UNIQUE || $unique === true) {
113 1
            $index = false;
114 1
            $unique = true;
115 1
        } else {
116
            $index = $unique;
117
            $unique = false;
118
        }
119
120 1
        return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ') .
121 1
        $this->db->quoteTableName($name) . ' ON ' .
122 1
        $this->db->quoteTableName($table) .
123 1
        ($index !== false ? " USING $index" : '') .
124 1
        ' (' . $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 1
            } 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 2
    public function checkIntegrity($check = true, $schema = '', $table = '')
189
    {
190 2
        $enable = $check ? 'ENABLE' : 'DISABLE';
191 2
        $schema = $schema ? $schema : $this->db->getSchema()->defaultSchema;
192 2
        $tableNames = $table ? [$table] : $this->db->getSchema()->getTableNames($schema);
193 2
        $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 2
        $tableNames = array_diff($tableNames, $viewNames);
195 2
        $command = '';
196
197 2
        foreach ($tableNames as $tableName) {
198 2
            $tableName = '"' . $schema . '"."' . $tableName . '"';
199 2
            $command .= "ALTER TABLE $tableName $enable TRIGGER ALL; ";
200 2
        }
201
202
        // enable to have ability to alter several tables
203 2
        $this->db->getMasterPdo()->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true);
204
205 2
        return $command;
206
    }
207
208
    /**
209
     * Builds a SQL statement for changing the definition of a column.
210
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
211
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
212
     * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
213
     * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
214
     * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
215
     * will become 'varchar(255) not null'. You can also use PostgreSQL-specific syntax such as `SET NOT NULL`.
216
     * @return string the SQL statement for changing the definition of a column.
217
     */
218 2
    public function alterColumn($table, $column, $type)
219
    {
220
        // https://github.com/yiisoft/yii2/issues/4492
221
        // http://www.postgresql.org/docs/9.1/static/sql-altertable.html
222 2
        if (!preg_match('/^(DROP|SET|RESET)\s+/i', $type)) {
223 2
            $type = 'TYPE ' . $this->getColumnType($type);
224 2
        }
225 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
226 2
            . $this->db->quoteColumnName($column) . ' ' . $type;
227
    }
228
229
    /**
230
     * @inheritdoc
231
     */
232 46
    public function insert($table, $columns, &$params)
233
    {
234 46
        return parent::insert($table, $this->normalizeTableRowData($table, $columns), $params);
235
    }
236
237
    /**
238
     * @inheritdoc
239
     */
240 14
    public function update($table, $columns, $condition, &$params)
241
    {
242 14
        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...
243
    }
244
245
    /**
246
     * Normalizes data to be saved into the table, performing extra preparations and type converting, if necessary.
247
     * @param string $table the table that data will be saved into.
248
     * @param array|\yii\db\Query $columns the column data (name => value) to be saved into the table or instance
249
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
250
     * Passing of [[yii\db\Query|Query]] is available since version 2.0.11.
251
     * @return array normalized columns
252
     * @since 2.0.9
253
     */
254 56
    private function normalizeTableRowData($table, $columns)
255
    {
256 56
        if ($columns instanceof \yii\db\Query) {
257 5
            return $columns;
258
        }
259
260 53
        if (($tableSchema = $this->db->getSchema()->getTableSchema($table)) !== null) {
261 52
            $columnSchemas = $tableSchema->columns;
262 52
            foreach ($columns as $name => $value) {
263 51
                if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && is_string($value)) {
264 1
                    $columns[$name] = [$value, \PDO::PARAM_LOB]; // explicitly setup PDO param type for binary column
265 1
                }
266 52
            }
267 52
        }
268
269 53
        return $columns;
270
    }
271
272
    /**
273
     * @inheritdoc
274
     */
275 11
    public function batchInsert($table, $columns, $rows)
276
    {
277 11
        if (empty($rows)) {
278 2
            return '';
279
        }
280
281 10
        $schema = $this->db->getSchema();
282 10
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
283 10
            $columnSchemas = $tableSchema->columns;
284 10
        } else {
285
            $columnSchemas = [];
286
        }
287
288 10
        $values = [];
289 10
        foreach ($rows as $row) {
290 10
            $vs = [];
291 10
            foreach ($row as $i => $value) {
292 10
                if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) {
293 7
                    $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
294 7
                }
295 10
                if (is_string($value)) {
296 4
                    $value = $schema->quoteValue($value);
297 10
                } elseif ($value === true) {
298 3
                    $value = 'TRUE';
299 7
                } elseif ($value === false) {
300 5
                    $value = 'FALSE';
301 7
                } elseif ($value === null) {
302 3
                    $value = 'NULL';
303 3
                }
304 10
                $vs[] = $value;
305 10
            }
306 10
            $values[] = '(' . implode(', ', $vs) . ')';
307 10
        }
308
309 10
        foreach ($columns as $i => $name) {
310 9
            $columns[$i] = $schema->quoteColumnName($name);
311 10
        }
312
313 10
        return 'INSERT INTO ' . $schema->quoteTableName($table)
314 10
        . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
315
    }
316
}
317