GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( ecf3ef...78a151 )
by Robert
11:40
created

QueryBuilder::oldUpsert()   C

Complexity

Conditions 13
Paths 55

Size

Total Lines 78
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
dl 0
loc 78
ccs 0
cts 60
cp 0
rs 5.1663
c 0
b 0
f 0
cc 13
eloc 60
nc 55
nop 4
crap 182

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\InvalidArgumentException;
11
use yii\db\Constraint;
12
use yii\db\Expression;
13
use yii\db\Query;
14
use yii\db\PdoValue;
15
use yii\helpers\StringHelper;
16
17
/**
18
 * QueryBuilder is the query builder for PostgreSQL databases.
19
 *
20
 * @author Gevik Babakhani <[email protected]>
21
 * @since 2.0
22
 */
23
class QueryBuilder extends \yii\db\QueryBuilder
24
{
25
    /**
26
     * Defines a UNIQUE index for [[createIndex()]].
27
     * @since 2.0.6
28
     */
29
    const INDEX_UNIQUE = 'unique';
30
    /**
31
     * Defines a B-tree index for [[createIndex()]].
32
     * @since 2.0.6
33
     */
34
    const INDEX_B_TREE = 'btree';
35
    /**
36
     * Defines a hash index for [[createIndex()]].
37
     * @since 2.0.6
38
     */
39
    const INDEX_HASH = 'hash';
40
    /**
41
     * Defines a GiST index for [[createIndex()]].
42
     * @since 2.0.6
43
     */
44
    const INDEX_GIST = 'gist';
45
    /**
46
     * Defines a GIN index for [[createIndex()]].
47
     * @since 2.0.6
48
     */
49
    const INDEX_GIN = 'gin';
50
51
    /**
52
     * @var array mapping from abstract column types (keys) to physical column types (values).
53
     */
54
    public $typeMap = [
55
        Schema::TYPE_PK => 'serial NOT NULL PRIMARY KEY',
56
        Schema::TYPE_UPK => 'serial NOT NULL PRIMARY KEY',
57
        Schema::TYPE_BIGPK => 'bigserial NOT NULL PRIMARY KEY',
58
        Schema::TYPE_UBIGPK => 'bigserial NOT NULL PRIMARY KEY',
59
        Schema::TYPE_CHAR => 'char(1)',
60
        Schema::TYPE_STRING => 'varchar(255)',
61
        Schema::TYPE_TEXT => 'text',
62
        Schema::TYPE_SMALLINT => 'smallint',
63
        Schema::TYPE_INTEGER => 'integer',
64
        Schema::TYPE_BIGINT => 'bigint',
65
        Schema::TYPE_FLOAT => 'double precision',
66
        Schema::TYPE_DOUBLE => 'double precision',
67
        Schema::TYPE_DECIMAL => 'numeric(10,0)',
68
        Schema::TYPE_DATETIME => 'timestamp(0)',
69
        Schema::TYPE_TIMESTAMP => 'timestamp(0)',
70
        Schema::TYPE_TIME => 'time(0)',
71
        Schema::TYPE_DATE => 'date',
72
        Schema::TYPE_BINARY => 'bytea',
73
        Schema::TYPE_BOOLEAN => 'boolean',
74
        Schema::TYPE_MONEY => 'numeric(19,4)',
75
        Schema::TYPE_JSON => 'jsonb',
76
    ];
77
78
    /**
79
     * {@inheritdoc}
80
     */
81 508
    protected function defaultConditionClasses()
82
    {
83 508
        return array_merge(parent::defaultConditionClasses(), [
84 508
            'ILIKE' => 'yii\db\conditions\LikeCondition',
85
            'NOT ILIKE' => 'yii\db\conditions\LikeCondition',
86
            'OR ILIKE' => 'yii\db\conditions\LikeCondition',
87
            'OR NOT ILIKE' => 'yii\db\conditions\LikeCondition',
88
        ]);
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 508
    protected function defaultExpressionBuilders()
95
    {
96 508
        return array_merge(parent::defaultExpressionBuilders(), [
97 508
            'yii\db\ArrayExpression' => 'yii\db\pgsql\ArrayExpressionBuilder',
98
            'yii\db\JsonExpression' => 'yii\db\pgsql\JsonExpressionBuilder',
99
        ]);
100
    }
101
102
    /**
103
     * Builds a SQL statement for creating a new index.
104
     * @param string $name the name of the index. The name will be properly quoted by the method.
105
     * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
106
     * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
107
     * separate them with commas or use an array to represent them. Each column name will be properly quoted
108
     * by the method, unless a parenthesis is found in the name.
109
     * @param bool|string $unique whether to make this a UNIQUE index constraint. You can pass `true` or [[INDEX_UNIQUE]] to create
110
     * a unique index, `false` to make a non-unique index using the default index type, or one of the following constants to specify
111
     * the index method to use: [[INDEX_B_TREE]], [[INDEX_HASH]], [[INDEX_GIST]], [[INDEX_GIN]].
112
     * @return string the SQL statement for creating a new index.
113
     * @see http://www.postgresql.org/docs/8.2/static/sql-createindex.html
114
     */
115 6
    public function createIndex($name, $table, $columns, $unique = false)
116
    {
117 6
        if ($unique === self::INDEX_UNIQUE || $unique === true) {
118 4
            $index = false;
119 4
            $unique = true;
120
        } else {
121 3
            $index = $unique;
122 3
            $unique = false;
123
        }
124
125 6
        return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ') .
126 6
        $this->db->quoteTableName($name) . ' ON ' .
127 6
        $this->db->quoteTableName($table) .
128 6
        ($index !== false ? " USING $index" : '') .
129 6
        ' (' . $this->buildColumns($columns) . ')';
130
    }
131
132
    /**
133
     * Builds a SQL statement for dropping an index.
134
     * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
135
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
136
     * @return string the SQL statement for dropping an index.
137
     */
138 2
    public function dropIndex($name, $table)
139
    {
140 2
        return 'DROP INDEX ' . $this->db->quoteTableName($name);
141
    }
142
143
    /**
144
     * Builds a SQL statement for renaming a DB table.
145
     * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
146
     * @param string $newName the new table name. The name will be properly quoted by the method.
147
     * @return string the SQL statement for renaming a DB table.
148
     */
149 1
    public function renameTable($oldName, $newName)
150
    {
151 1
        return 'ALTER TABLE ' . $this->db->quoteTableName($oldName) . ' RENAME TO ' . $this->db->quoteTableName($newName);
152
    }
153
154
    /**
155
     * Creates a SQL statement for resetting the sequence value of a table's primary key.
156
     * The sequence will be reset such that the primary key of the next new row inserted
157
     * will have the specified value or 1.
158
     * @param string $tableName the name of the table whose primary key sequence will be reset
159
     * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
160
     * the next new row's primary key will have a value 1.
161
     * @return string the SQL statement for resetting sequence
162
     * @throws InvalidArgumentException if the table does not exist or there is no sequence associated with the table.
163
     */
164 5
    public function resetSequence($tableName, $value = null)
165
    {
166 5
        $table = $this->db->getTableSchema($tableName);
167 5
        if ($table !== null && $table->sequenceName !== null) {
168
            // c.f. http://www.postgresql.org/docs/8.1/static/functions-sequence.html
169 5
            $sequence = $this->db->quoteTableName($table->sequenceName);
170 5
            $tableName = $this->db->quoteTableName($tableName);
171 5
            if ($value === null) {
172 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...
173 1
                $value = "(SELECT COALESCE(MAX({$key}),0) FROM {$tableName})+1";
174
            } else {
175 5
                $value = (int) $value;
176
            }
177
178 5
            return "SELECT SETVAL('$sequence',$value,false)";
179
        } elseif ($table === null) {
180
            throw new InvalidArgumentException("Table not found: $tableName");
181
        }
182
183
        throw new InvalidArgumentException("There is not sequence associated with table '$tableName'.");
184
    }
185
186
    /**
187
     * Builds a SQL statement for enabling or disabling integrity check.
188
     * @param bool $check whether to turn on or off the integrity check.
189
     * @param string $schema the schema of the tables.
190
     * @param string $table the table name.
191
     * @return string the SQL statement for checking integrity
192
     */
193
    public function checkIntegrity($check = true, $schema = '', $table = '')
194
    {
195
        $enable = $check ? 'ENABLE' : 'DISABLE';
196
        $schema = $schema ?: $this->db->getSchema()->defaultSchema;
197
        $tableNames = $table ? [$table] : $this->db->getSchema()->getTableNames($schema);
198
        $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...
199
        $tableNames = array_diff($tableNames, $viewNames);
200
        $command = '';
201
202
        foreach ($tableNames as $tableName) {
203
            $tableName = $this->db->quoteTableName("{$schema}.{$tableName}");
204
            $command .= "ALTER TABLE $tableName $enable TRIGGER ALL; ";
205
        }
206
207
        // enable to have ability to alter several tables
208
        $this->db->getMasterPdo()->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true);
209
210
        return $command;
211
    }
212
213
    /**
214
     * Builds a SQL statement for truncating a DB table.
215
     * Explicitly restarts identity for PGSQL to be consistent with other databases which all do this by default.
216
     * @param string $table the table to be truncated. The name will be properly quoted by the method.
217
     * @return string the SQL statement for truncating a DB table.
218
     */
219 1
    public function truncateTable($table)
220
    {
221 1
        return 'TRUNCATE TABLE ' . $this->db->quoteTableName($table) . ' RESTART IDENTITY';
222
    }
223
224
    /**
225
     * Builds a SQL statement for changing the definition of a column.
226
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
227
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
228
     * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
229
     * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
230
     * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
231
     * will become 'varchar(255) not null'. You can also use PostgreSQL-specific syntax such as `SET NOT NULL`.
232
     * @return string the SQL statement for changing the definition of a column.
233
     */
234 2
    public function alterColumn($table, $column, $type)
235
    {
236
        // https://github.com/yiisoft/yii2/issues/4492
237
        // http://www.postgresql.org/docs/9.1/static/sql-altertable.html
238 2
        if (!preg_match('/^(DROP|SET|RESET)\s+/i', $type)) {
239 2
            $type = 'TYPE ' . $this->getColumnType($type);
240
        }
241
242 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
243 2
            . $this->db->quoteColumnName($column) . ' ' . $type;
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249 189
    public function insert($table, $columns, &$params)
250
    {
251 189
        return parent::insert($table, $this->normalizeTableRowData($table, $columns), $params);
252
    }
253
254
    /**
255
     * @inheritdoc
256
     * @see https://www.postgresql.org/docs/9.5/static/sql-insert.html#SQL-ON-CONFLICT
257
     * @see https://stackoverflow.com/questions/1109061/insert-on-duplicate-update-in-postgresql/8702291#8702291
258
     */
259 22
    public function upsert($table, $insertColumns, $updateColumns, &$params)
260
    {
261 22
        $insertColumns = $this->normalizeTableRowData($table, $insertColumns);
262 22
        if (!is_bool($updateColumns)) {
263 7
            $updateColumns = $this->normalizeTableRowData($table, $updateColumns);
264
        }
265 22
        if (version_compare($this->db->getServerVersion(), '9.5', '<')) {
266
            return $this->oldUpsert($table, $insertColumns, $updateColumns, $params);
267
        }
268
269 22
        return $this->newUpsert($table, $insertColumns, $updateColumns, $params);
270
    }
271
272
    /**
273
     * [[upsert()]] implementation for PostgreSQL 9.5 or higher.
274
     * @param string $table
275
     * @param array|Query $insertColumns
276
     * @param array|bool $updateColumns
277
     * @param array $params
278
     * @return string
279
     */
280 22
    private function newUpsert($table, $insertColumns, $updateColumns, &$params)
281
    {
282 22
        $insertSql = $this->insert($table, $insertColumns, $params);
283 22
        list($uniqueNames, , $updateNames) = $this->prepareUpsertColumns($table, $insertColumns, $updateColumns);
284 22
        if (empty($uniqueNames)) {
285 3
            return $insertSql;
286
        }
287
288 19
        if ($updateColumns === false) {
289 4
            return "$insertSql ON CONFLICT DO NOTHING";
290
        }
291
292 15
        if ($updateColumns === true) {
293 9
            $updateColumns = [];
294 9
            foreach ($updateNames as $name) {
295 9
                $updateColumns[$name] = new Expression('EXCLUDED.' . $this->db->quoteColumnName($name));
296
            }
297
        }
298 15
        list($updates, $params) = $this->prepareUpdateSets($table, $updateColumns, $params);
0 ignored issues
show
Bug introduced by
It seems like $updateColumns can also be of type boolean; however, yii\db\QueryBuilder::prepareUpdateSets() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
299 15
        return $insertSql . ' ON CONFLICT (' . implode(', ', $uniqueNames) . ') DO UPDATE SET ' . implode(', ', $updates);
300
    }
301
302
    /**
303
     * [[upsert()]] implementation for PostgreSQL older than 9.5.
304
     * @param string $table
305
     * @param array|Query $insertColumns
306
     * @param array|bool $updateColumns
307
     * @param array $params
308
     * @return string
309
     */
310
    private function oldUpsert($table, $insertColumns, $updateColumns, &$params)
311
    {
312
        /** @var Constraint[] $constraints */
313
        list($uniqueNames, $insertNames, $updateNames) = $this->prepareUpsertColumns($table, $insertColumns, $updateColumns, $constraints);
314
        if (empty($uniqueNames)) {
315
            return $this->insert($table, $insertColumns, $params);
316
        }
317
318
        /** @var Schema $schema */
319
        $schema = $this->db->getSchema();
320
        if (!$insertColumns instanceof Query) {
321
            $tableSchema = $schema->getTableSchema($table);
322
            $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
323
            foreach ($insertColumns as $name => $value) {
324
                // NULLs and numeric values must be type hinted in order to be used in SET assigments
325
                // NVM, let's cast them all
326
                if (isset($columnSchemas[$name])) {
327
                    $phName = self::PARAM_PREFIX . count($params);
328
                    $params[$phName] = $value;
329
                    $insertColumns[$name] = new Expression("CAST($phName AS {$columnSchemas[$name]->dbType})");
330
                }
331
            }
332
        }
333
        list(, $placeholders, $values, $params) = $this->prepareInsertValues($table, $insertColumns, $params);
334
        $updateCondition = ['or'];
335
        $insertCondition = ['or'];
336
        $quotedTableName = $schema->quoteTableName($table);
337
        foreach ($constraints as $constraint) {
338
            $constraintUpdateCondition = ['and'];
339
            $constraintInsertCondition = ['and'];
340
            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...
341
                $quotedName = $schema->quoteColumnName($name);
342
                $constraintUpdateCondition[] = "$quotedTableName.$quotedName=\"EXCLUDED\".$quotedName";
343
                $constraintInsertCondition[] = "\"upsert\".$quotedName=\"EXCLUDED\".$quotedName";
344
            }
345
            $updateCondition[] = $constraintUpdateCondition;
346
            $insertCondition[] = $constraintInsertCondition;
347
        }
348
        $withSql = 'WITH "EXCLUDED" (' . implode(', ', $insertNames)
349
            . ') AS (' . (!empty($placeholders) ? 'VALUES (' . implode(', ', $placeholders) . ')' : ltrim($values, ' ')) . ')';
350
        if ($updateColumns === false) {
351
            $selectSubQuery = (new Query())
352
                ->select(new Expression('1'))
353
                ->from($table)
354
                ->where($updateCondition);
355
            $insertSelectSubQuery = (new Query())
356
                ->select($insertNames)
357
                ->from('EXCLUDED')
358
                ->where(['not exists', $selectSubQuery]);
359
            $insertSql = $this->insert($table, $insertSelectSubQuery, $params);
360
            return "$withSql $insertSql";
361
        }
362
363
        if ($updateColumns === true) {
364
            $updateColumns = [];
365
            foreach ($updateNames as $name) {
366
                $quotedName = $this->db->quoteColumnName($name);
367
                if (strrpos($quotedName, '.') === false) {
368
                    $quotedName = '"EXCLUDED".' . $quotedName;
369
                }
370
                $updateColumns[$name] = new Expression($quotedName);
371
            }
372
        }
373
        list($updates, $params) = $this->prepareUpdateSets($table, $updateColumns, $params);
0 ignored issues
show
Bug introduced by
It seems like $updateColumns can also be of type boolean; however, yii\db\QueryBuilder::prepareUpdateSets() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
374
        $updateSql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $updates)
375
            . ' FROM "EXCLUDED" ' . $this->buildWhere($updateCondition, $params)
376
            . ' RETURNING ' . $this->db->quoteTableName($table) .'.*';
377
        $selectUpsertSubQuery = (new Query())
378
            ->select(new Expression('1'))
379
            ->from('upsert')
380
            ->where($insertCondition);
381
        $insertSelectSubQuery = (new Query())
382
            ->select($insertNames)
383
            ->from('EXCLUDED')
384
            ->where(['not exists', $selectUpsertSubQuery]);
385
        $insertSql = $this->insert($table, $insertSelectSubQuery, $params);
386
        return "$withSql, \"upsert\" AS ($updateSql) $insertSql";
387
    }
388
389
    /**
390
     * {@inheritdoc}
391
     */
392 39
    public function update($table, $columns, $condition, &$params)
393
    {
394 39
        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...
395
    }
396
397
    /**
398
     * Normalizes data to be saved into the table, performing extra preparations and type converting, if necessary.
399
     *
400
     * @param string $table the table that data will be saved into.
401
     * @param array|Query $columns the column data (name => value) to be saved into the table or instance
402
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
403
     * Passing of [[yii\db\Query|Query]] is available since version 2.0.11.
404
     * @return array normalized columns
405
     * @since 2.0.9
406
     */
407 201
    private function normalizeTableRowData($table, $columns)
408
    {
409 201
        if ($columns instanceof Query) {
410 14
            return $columns;
411
        }
412
413 193
        if (($tableSchema = $this->db->getSchema()->getTableSchema($table)) !== null) {
414 193
            $columnSchemas = $tableSchema->columns;
415 193
            foreach ($columns as $name => $value) {
416 192
                if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && is_string($value)) {
417 192
                    $columns[$name] = new PdoValue($value, \PDO::PARAM_LOB); // explicitly setup PDO param type for binary column
418
                }
419
            }
420
        }
421
422 193
        return $columns;
423
    }
424
425
    /**
426
     * {@inheritdoc}
427
     */
428 16
    public function batchInsert($table, $columns, $rows)
429
    {
430 16
        if (empty($rows)) {
431 2
            return '';
432
        }
433
434 15
        $schema = $this->db->getSchema();
435 15
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
436 12
            $columnSchemas = $tableSchema->columns;
437
        } else {
438 3
            $columnSchemas = [];
439
        }
440
441 15
        $values = [];
442 15
        foreach ($rows as $row) {
443 14
            $vs = [];
444 14
            foreach ($row as $i => $value) {
445 14
                if (isset($columns[$i], $columnSchemas[$columns[$i]])) {
446 9
                    $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
447
                }
448 14
                if (is_string($value)) {
449 8
                    $value = $schema->quoteValue($value);
450 9
                } elseif (is_float($value)) {
451
                    // ensure type cast always has . as decimal separator in all locales
452 1
                    $value = StringHelper::floatToString($value);
453 9
                } elseif ($value === true) {
454 4
                    $value = 'TRUE';
455 9
                } elseif ($value === false) {
456 7
                    $value = 'FALSE';
457 6
                } elseif ($value === null) {
458 4
                    $value = 'NULL';
459
                }
460 14
                $vs[] = $value;
461
            }
462 14
            $values[] = '(' . implode(', ', $vs) . ')';
463
        }
464 15
        if (empty($values)) {
465 1
            return '';
466
        }
467
468 14
        foreach ($columns as $i => $name) {
469 13
            $columns[$i] = $schema->quoteColumnName($name);
470
        }
471
472 14
        return 'INSERT INTO ' . $schema->quoteTableName($table)
473 14
        . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
474
    }
475
}
476