From 39872b193b0dfeadd4fd6b2629101a8b61e18a36 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Wed, 1 Nov 2023 10:37:04 +0700 Subject: [PATCH] Refactor DMLQueryBuilder (#746) * Refactor DMLQueryBuilder * Get uniques using `getTableIndexes()` and `getTableUniques()` * Fix @psalm-var * Fix #61 (point 2) * Fix #61 (point 2) add test * Improve test * Remove methods with `NotSupportedException` * Fix test issues * Fix test issues * Revert "Remove methods with `NotSupportedException`" * Add line to CHANGELOG.md * Change order of checks * Improve performance of quoting column names up to 10% using `array_map()` * Update CHANGELOG.md * remove `Generator` in `DMLQueryBuilderInterface` * Update psalm * Apply suggestions from code review Co-authored-by: Sergei Predvoditelev * Apply StyleCI * Remove `Generator` from other places * Return and deprecate `getTypecastValue()` method and `$table` parameter * Fix psalm * Fix psalm * Update CHANGELOG.md --------- Co-authored-by: Sergei Predvoditelev --- CHANGELOG.md | 5 + psalm.xml | 3 + src/Command/CommandInterface.php | 4 + src/QueryBuilder/AbstractDMLQueryBuilder.php | 246 ++++++++---------- src/QueryBuilder/AbstractQueryBuilder.php | 3 +- src/QueryBuilder/DMLQueryBuilderInterface.php | 12 +- tests/AbstractQueryBuilderTest.php | 3 +- tests/Db/QueryBuilder/QueryBuilderTest.php | 3 +- tests/Provider/CommandProvider.php | 29 ++- tests/Provider/QueryBuilderProvider.php | 19 ++ 10 files changed, 163 insertions(+), 164 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63bf93448..34e983ff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## 1.1.2 under development +- Enh #746: Refactor `AbstractDMLQueryBuilder` (@Tigrov) +- Bug #746: Fix `AbstractDMLQueryBuilder::upsert()` when unique index is not at the first position of inserted values (@Tigrov) +- Bug #746: Typecast values in `AbstractDMLQueryBuilder::batchInsert()` if column names with table name and brackets (@Tigrov) +- Bug #746, #61: Typecast values in `AbstractDMLQueryBuilder::batchInsert()` if values with string keys (@Tigrov) +- Enh #746: Enhanced documentation of `batchInsert()` and `update()` methods of `DMLQueryBuilderInterface` interface (@Tigrov) - Bug #751: Fix collected debug actions (@xepozz) - Chg #755: Deprecate `TableSchemaInterface::compositeForeignKey()` (@Tigrov) - Enh #756: Refactor `Quoter` (@Tigrov) diff --git a/psalm.xml b/psalm.xml index 23bfcce17..54a52e176 100644 --- a/psalm.xml +++ b/psalm.xml @@ -14,4 +14,7 @@ + + + diff --git a/src/Command/CommandInterface.php b/src/Command/CommandInterface.php index 955e28c3b..9c3244201 100644 --- a/src/Command/CommandInterface.php +++ b/src/Command/CommandInterface.php @@ -177,6 +177,8 @@ public function alterColumn(string $table, string $column, ColumnInterface|strin * @throws Exception * @throws InvalidArgumentException * + * @psalm-param iterable> $rows + * * Note: The method will quote the `table` and `column` parameters before using them in the generated SQL. */ public function batchInsert(string $table, array $columns, iterable $rows): static; @@ -820,6 +822,8 @@ public function update(string $table, array $columns, array|string $condition = * @throws JsonException * @throws NotSupportedException * + * @psalm-param array|QueryInterface $insertColumns + * * Note: The method will quote the `table` and `insertColumns`, `updateColumns` parameters before using it in the * generated SQL. */ diff --git a/src/QueryBuilder/AbstractDMLQueryBuilder.php b/src/QueryBuilder/AbstractDMLQueryBuilder.php index fad42c957..1224e503f 100644 --- a/src/QueryBuilder/AbstractDMLQueryBuilder.php +++ b/src/QueryBuilder/AbstractDMLQueryBuilder.php @@ -4,7 +4,6 @@ namespace Yiisoft\Db\QueryBuilder; -use Generator; use JsonException; use Yiisoft\Db\Constraint\Constraint; use Yiisoft\Db\Constraint\IndexConstraint; @@ -26,10 +25,8 @@ use function array_merge; use function array_unique; use function array_values; -use function count; use function implode; use function in_array; -use function is_array; use function is_string; use function json_encode; use function preg_match; @@ -52,28 +49,23 @@ public function __construct( ) { } - public function batchInsert(string $table, array $columns, iterable|Generator $rows, array &$params = []): string + public function batchInsert(string $table, array $columns, iterable $rows, array &$params = []): string { if (empty($rows)) { return ''; } - if (($tableSchema = $this->schema->getTableSchema($table)) !== null) { - $columnSchemas = $tableSchema->getColumns(); - } else { - $columnSchemas = []; - } - - $mappedNames = $this->getNormalizeColumnNames($table, $columns); $values = []; + $columns = $this->getNormalizeColumnNames('', $columns); + $columnSchemas = $this->schema->getTableSchema($table)?->getColumns() ?? []; - /** @psalm-var array> $rows */ foreach ($rows as $row) { + $i = 0; $placeholders = []; - foreach ($row as $index => $value) { - if (isset($columns[$index], $mappedNames[$columns[$index]], $columnSchemas[$mappedNames[$columns[$index]]])) { - /** @psalm-var mixed $value */ - $value = $this->getTypecastValue($value, $columnSchemas[$mappedNames[$columns[$index]]]); + + foreach ($row as $value) { + if (isset($columns[$i], $columnSchemas[$columns[$i]])) { + $value = $columnSchemas[$columns[$i]]->dbTypecast($value); } if ($value instanceof ExpressionInterface) { @@ -81,6 +73,8 @@ public function batchInsert(string $table, array $columns, iterable|Generator $r } else { $placeholders[] = $this->queryBuilder->bindParam($value, $params); } + + ++$i; } $values[] = '(' . implode(', ', $placeholders) . ')'; } @@ -89,12 +83,12 @@ public function batchInsert(string $table, array $columns, iterable|Generator $r return ''; } - foreach ($columns as $i => $name) { - $columns[$i] = $this->quoter->quoteColumnName($mappedNames[$name]); - } + $columns = array_map( + [$this->quoter, 'quoteColumnName'], + $columns, + ); - return 'INSERT INTO ' - . $this->quoter->quoteTableName($table) + return 'INSERT INTO ' . $this->quoter->quoteTableName($table) . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values); } @@ -108,17 +102,11 @@ public function delete(string $table, array|string $condition, array &$params): public function insert(string $table, QueryInterface|array $columns, array &$params = []): string { - /** - * @psalm-var string[] $names - * @psalm-var string[] $placeholders - * @psalm-var string $values - */ [$names, $placeholders, $values, $params] = $this->prepareInsertValues($table, $columns, $params); - return 'INSERT INTO ' - . $this->quoter->quoteTableName($table) + return 'INSERT INTO ' . $this->quoter->quoteTableName($table) . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '') - . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : $values); + . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' ' . $values); } public function insertWithReturningPks(string $table, QueryInterface|array $columns, array &$params = []): string @@ -133,10 +121,9 @@ public function resetSequence(string $table, int|string|null $value = null): str public function update(string $table, array $columns, array|string $condition, array &$params = []): string { - /** @psalm-var string[] $lines */ [$lines, $params] = $this->prepareUpdateSets($table, $columns, $params); + $sql = 'UPDATE ' . $this->quoter->quoteTableName($table) . ' SET ' . implode(', ', $lines); - /** @psalm-var array $params */ $where = $this->queryBuilder->buildWhere($condition, $params); return $where === '' ? $sql : $sql . ' ' . $where; @@ -163,20 +150,21 @@ public function upsert( * @throws InvalidConfigException * @throws NotSupportedException * - * @return array Array of column names, values, and params. + * @return array Array of quoted column names, values, and params. + * @psalm-return array{0: string[], 1: string, 2: array} */ protected function prepareInsertSelectSubQuery(QueryInterface $columns, array $params = []): array { - if (empty($columns->getSelect()) || in_array('*', $columns->getSelect(), true)) { + /** @psalm-var string[] $select */ + $select = $columns->getSelect(); + + if (empty($select) || in_array('*', $select, true)) { throw new InvalidArgumentException('Expected select query object with enumerated (named) parameters'); } [$values, $params] = $this->queryBuilder->build($columns, $params); $names = []; - $values = ' ' . $values; - /** @psalm-var string[] $select */ - $select = $columns->getSelect(); foreach ($select as $title => $field) { if (is_string($title)) { @@ -204,37 +192,41 @@ protected function prepareInsertSelectSubQuery(QueryInterface $columns, array $p * @throws InvalidConfigException * @throws InvalidArgumentException * @throws NotSupportedException + * + * @return array Array of quoted column names, placeholders, values, and params. + * @psalm-return array{0: string[], 1: string[], 2: string, 3: array} */ protected function prepareInsertValues(string $table, array|QueryInterface $columns, array $params = []): array { - $tableSchema = $this->schema->getTableSchema($table); - $columnSchemas = $tableSchema !== null ? $tableSchema->getColumns() : []; - $names = []; - $placeholders = []; - $values = ' DEFAULT VALUES'; + if (empty($columns)) { + return [[], [], 'DEFAULT VALUES', []]; + } if ($columns instanceof QueryInterface) { [$names, $values, $params] = $this->prepareInsertSelectSubQuery($columns, $params); - } else { - $columns = $this->normalizeColumnNames($table, $columns); - /** - * @psalm-var mixed $value - * @psalm-var array $columns - */ - foreach ($columns as $name => $value) { - $names[] = $this->quoter->quoteColumnName($name); - /** @var mixed $value */ - $value = $this->getTypecastValue($value, $columnSchemas[$name] ?? null); + return [$names, [], $values, $params]; + } - if ($value instanceof ExpressionInterface) { - $placeholders[] = $this->queryBuilder->buildExpression($value, $params); - } else { - $placeholders[] = $this->queryBuilder->bindParam($value, $params); - } + $names = []; + $placeholders = []; + $columns = $this->normalizeColumnNames('', $columns); + $columnSchemas = $this->schema->getTableSchema($table)?->getColumns() ?? []; + + foreach ($columns as $name => $value) { + $names[] = $this->quoter->quoteColumnName($name); + + if (isset($columnSchemas[$name])) { + $value = $columnSchemas[$name]->dbTypecast($value); + } + + if ($value instanceof ExpressionInterface) { + $placeholders[] = $this->queryBuilder->buildExpression($value, $params); + } else { + $placeholders[] = $this->queryBuilder->bindParam($value, $params); } } - return [$names, $placeholders, $values, $params]; + return [$names, $placeholders, '', $params]; } /** @@ -244,21 +236,20 @@ protected function prepareInsertValues(string $table, array|QueryInterface $colu * @throws InvalidConfigException * @throws InvalidArgumentException * @throws NotSupportedException + * + * @psalm-return array{0: string[], 1: array} */ protected function prepareUpdateSets(string $table, array $columns, array $params = []): array { - $tableSchema = $this->schema->getTableSchema($table); - $columnSchemas = $tableSchema !== null ? $tableSchema->getColumns() : []; $sets = []; - $columns = $this->normalizeColumnNames($table, $columns); + $columns = $this->normalizeColumnNames('', $columns); + $columnSchemas = $this->schema->getTableSchema($table)?->getColumns() ?? []; - /** - * @psalm-var array $columns - * @psalm-var mixed $value - */ foreach ($columns as $name => $value) { - /** @psalm-var mixed $value */ - $value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value; + if (isset($columnSchemas[$name])) { + $value = $columnSchemas[$name]->dbTypecast($value); + } + if ($value instanceof ExpressionInterface) { $placeholder = $this->queryBuilder->buildExpression($value, $params); } else { @@ -272,7 +263,7 @@ protected function prepareUpdateSets(string $table, array $columns, array $param } /** - * Prepare column names and placeholders for "upsert" operation. + * Prepare column names and constraints for "upsert" operation. * * @throws Exception * @throws InvalidArgumentException @@ -280,7 +271,11 @@ protected function prepareUpdateSets(string $table, array $columns, array $param * @throws JsonException * @throws NotSupportedException * + * @psalm-param array|QueryInterface $insertColumns * @psalm-param Constraint[] $constraints + * + * @return array Array of unique, insert and update quoted column names. + * @psalm-return array{0: string[], 1: string[], 2: string[]|null} */ protected function prepareUpsertColumns( string $table, @@ -288,42 +283,28 @@ protected function prepareUpsertColumns( QueryInterface|bool|array $updateColumns, array &$constraints = [] ): array { - $insertNames = []; - - if (!$insertColumns instanceof QueryInterface) { - $insertColumns = $this->normalizeColumnNames($table, $insertColumns); - } - - if (is_array($updateColumns)) { - $updateColumns = $this->normalizeColumnNames($table, $updateColumns); - } - if ($insertColumns instanceof QueryInterface) { - /** @psalm-var list $insertNames */ [$insertNames] = $this->prepareInsertSelectSubQuery($insertColumns); } else { - /** @psalm-var array $insertColumns */ - foreach ($insertColumns as $key => $_value) { - $insertNames[] = $this->quoter->quoteColumnName($key); - } + $insertNames = $this->getNormalizeColumnNames('', array_keys($insertColumns)); + + $insertNames = array_map( + [$this->quoter, 'quoteColumnName'], + $insertNames, + ); } - /** @psalm-var string[] $uniqueNames */ $uniqueNames = $this->getTableUniqueColumnNames($table, $insertNames, $constraints); - foreach ($uniqueNames as $key => $name) { - $insertNames[$key] = $this->quoter->quoteColumnName($name); + if ($updateColumns === true) { + return [$uniqueNames, $insertNames, array_diff($insertNames, $uniqueNames)]; } - if ($updateColumns !== true) { - return [$uniqueNames, $insertNames, null]; - } - - return [$uniqueNames, $insertNames, array_diff($insertNames, $uniqueNames)]; + return [$uniqueNames, $insertNames, null]; } /** - * Returns all column names belonging to constraints enforcing uniqueness (`PRIMARY KEY`, `UNIQUE INDEX`, etc.) + * Returns all quoted column names belonging to constraints enforcing uniqueness (`PRIMARY KEY`, `UNIQUE INDEX`, etc.) * for the named table removing constraints which didn't cover the specified column list. * * The column list will be unique by column names. @@ -335,7 +316,7 @@ protected function prepareUpsertColumns( * * @throws JsonException * - * @return array The column list. + * @return string[] The quoted column names. * * @psalm-param Constraint[] $constraints */ @@ -365,9 +346,8 @@ private function getTableUniqueColumnNames(string $name, array $columns, array & */ $constraints = array_combine( array_map( - static function (Constraint $constraint) { - $columns = $constraint->getColumnNames() ?? []; - $columns = is_array($columns) ? $columns : [$columns]; + static function (Constraint $constraint): string { + $columns = (array) $constraint->getColumnNames(); sort($columns, SORT_STRING); return json_encode($columns, JSON_THROW_ON_ERROR); }, @@ -383,18 +363,16 @@ static function (Constraint $constraint) { $constraints = array_values( array_filter( $constraints, - static function (Constraint $constraint) use ($quoter, $columns, &$columnNames) { - /** @psalm-var string[]|string $getColumnNames */ - $getColumnNames = $constraint->getColumnNames() ?? []; - $constraintColumnNames = []; - - if (is_array($getColumnNames)) { - foreach ($getColumnNames as $columnName) { - $constraintColumnNames[] = $quoter->quoteColumnName($columnName); - } - } + static function (Constraint $constraint) use ($quoter, $columns, &$columnNames): bool { + /** @psalm-var string[] $constraintColumnNames */ + $constraintColumnNames = (array) $constraint->getColumnNames(); + + $constraintColumnNames = array_map( + [$quoter, 'quoteColumnName'], + $constraintColumnNames, + ); - $result = !array_diff($constraintColumnNames, $columns); + $result = empty(array_diff($constraintColumnNames, $columns)); if ($result) { $columnNames = array_merge((array) $columnNames, $constraintColumnNames); @@ -405,12 +383,14 @@ static function (Constraint $constraint) use ($quoter, $columns, &$columnNames) ) ); - /** @psalm-var Constraint[] $columnNames */ + /** @psalm-var string[] $columnNames */ return array_unique($columnNames); } /** * @return mixed The typecast value of the given column. + * + * @deprecated will be removed in version 2.0.0 */ protected function getTypecastValue(mixed $value, ColumnSchemaInterface $columnSchema = null): mixed { @@ -422,61 +402,39 @@ protected function getTypecastValue(mixed $value, ColumnSchemaInterface $columnS } /** - * Normalizes the column names for the given table. + * Normalizes the column names. * - * @param string $table The table to save the data into. - * @param array $columns The column data (name => value) to save into the table or instance of - * {@see QueryInterface} to perform `INSERT INTO ... SELECT` SQL statement. Passing of {@see QueryInterface}. + * @param string $table Not used. Could be empty string. Will be removed in version 2.0.0. + * @param array $columns The column data (name => value). * * @return array The normalized column names (name => value). + * + * @psalm-return array */ protected function normalizeColumnNames(string $table, array $columns): array { - /** @var string[] $columnList */ - $columnList = array_keys($columns); - $mappedNames = $this->getNormalizeColumnNames($table, $columnList); - - /** @psalm-var array $normalizedColumns */ - $normalizedColumns = []; - - /** - * @psalm-var string $name - * @psalm-var mixed $value - */ - foreach ($columns as $name => $value) { - $mappedName = $mappedNames[$name] ?? $name; - /** @psalm-var mixed */ - $normalizedColumns[$mappedName] = $value; - } + /** @var string[] $columnNames */ + $columnNames = array_keys($columns); + $normalizedNames = $this->getNormalizeColumnNames('', $columnNames); - return $normalizedColumns; + return array_combine($normalizedNames, $columns); } /** - * Get a map of normalized columns + * Get normalized column names * - * @param string $table The table to save the data into. - * @param string[] $columns The column data (name => value) to save into the table or instance of - * {@see QueryInterface} to perform `INSERT INTO ... SELECT` SQL statement. Passing of {@see QueryInterface}. + * @param string $table Not used. Could be empty string. Will be removed in version 2.0.0. + * @param string[] $columns The column names. * - * @return string[] Map of normalized columns. + * @return string[] Normalized column names. */ protected function getNormalizeColumnNames(string $table, array $columns): array { $normalizedNames = []; - $rawTableName = $this->schema->getRawTableName($table); foreach ($columns as $name) { - $parts = $this->quoter->getTableNameParts($name, true); - - if (count($parts) === 2 && $this->schema->getRawTableName($parts[0]) === $rawTableName) { - $normalizedName = $parts[count($parts) - 1]; - } else { - $normalizedName = $name; - } - $normalizedName = $this->quoter->ensureColumnName($normalizedName); - - $normalizedNames[$name] = $normalizedName; + $normalizedName = $this->quoter->ensureColumnName($name); + $normalizedNames[] = $this->quoter->unquoteSimpleColumnName($normalizedName); } return $normalizedNames; diff --git a/src/QueryBuilder/AbstractQueryBuilder.php b/src/QueryBuilder/AbstractQueryBuilder.php index f93c13bc9..53971f27b 100644 --- a/src/QueryBuilder/AbstractQueryBuilder.php +++ b/src/QueryBuilder/AbstractQueryBuilder.php @@ -4,7 +4,6 @@ namespace Yiisoft\Db\QueryBuilder; -use Generator; use Yiisoft\Db\Command\CommandInterface; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Query\QueryInterface; @@ -109,7 +108,7 @@ public function alterColumn(string $table, string $column, ColumnInterface|strin return $this->ddlBuilder->alterColumn($table, $column, $type); } - public function batchInsert(string $table, array $columns, iterable|Generator $rows, array &$params = []): string + public function batchInsert(string $table, array $columns, iterable $rows, array &$params = []): string { return $this->dmlBuilder->batchInsert($table, $columns, $rows, $params); } diff --git a/src/QueryBuilder/DMLQueryBuilderInterface.php b/src/QueryBuilder/DMLQueryBuilderInterface.php index 060682381..fdac752aa 100644 --- a/src/QueryBuilder/DMLQueryBuilderInterface.php +++ b/src/QueryBuilder/DMLQueryBuilderInterface.php @@ -4,7 +4,6 @@ namespace Yiisoft\Db\QueryBuilder; -use Generator; use JsonException; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidArgumentException; @@ -33,8 +32,8 @@ interface DMLQueryBuilderInterface * ``` * * @param string $table The table to insert new rows into. - * @param string[] $columns The column names. - * @param Generator|iterable $rows The rows to batch-insert into the table. + * @param string[] $columns The column names of the table. + * @param iterable $rows The rows to batch-insert into the table. * @param array $params The binding parameters. This parameter exists. * * @throws Exception @@ -43,12 +42,13 @@ interface DMLQueryBuilderInterface * @return string The batch INSERT SQL statement. * * @psalm-param string[] $columns + * @psalm-param iterable> $rows * * Note: * - That the values in each row must match the corresponding column names. * - The method will escape the column names, and quote the values to insert. */ - public function batchInsert(string $table, array $columns, iterable|Generator $rows, array &$params = []): string; + public function batchInsert(string $table, array $columns, iterable $rows, array &$params = []): string; /** * Creates a `DELETE` SQL statement. @@ -149,7 +149,7 @@ public function resetSequence(string $table, int|string|null $value = null): str * ``` * * @param string $table The table to update. - * @param array $columns The column data (name => value) to update. + * @param array $columns The column data (name => value) to update the table. * @param array|string $condition The condition to put in the `WHERE` part. Please refer to * {@see Query::where()} On how to specify condition. * @param array $params The binding parameters that will be modified by this method so that they can be bound to @@ -194,6 +194,8 @@ public function update(string $table, array $columns, array|string $condition, a * @throws JsonException * @throws NotSupportedException If this isn't supported by the underlying DBMS. * + * @psalm-param array|QueryInterface $insertColumns + * * Note: The method will escape the table and column names. */ public function upsert( diff --git a/tests/AbstractQueryBuilderTest.php b/tests/AbstractQueryBuilderTest.php index 31de64c71..d6de07f63 100644 --- a/tests/AbstractQueryBuilderTest.php +++ b/tests/AbstractQueryBuilderTest.php @@ -5,7 +5,6 @@ namespace Yiisoft\Db\Tests; use Closure; -use Generator; use JsonException; use PHPUnit\Framework\TestCase; use stdClass; @@ -211,7 +210,7 @@ public function testAlterColumn(): void * * @psalm-param array $columns */ - public function testBatchInsert(string $table, array $columns, iterable|Generator $rows, string $expected): void + public function testBatchInsert(string $table, array $columns, iterable $rows, string $expected): void { $db = $this->getConnection(); diff --git a/tests/Db/QueryBuilder/QueryBuilderTest.php b/tests/Db/QueryBuilder/QueryBuilderTest.php index 79a1e382b..93356917f 100644 --- a/tests/Db/QueryBuilder/QueryBuilderTest.php +++ b/tests/Db/QueryBuilder/QueryBuilderTest.php @@ -4,7 +4,6 @@ namespace Yiisoft\Db\Tests\Db\QueryBuilder; -use Generator; use JsonException; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidArgumentException; @@ -48,7 +47,7 @@ public function testAddDefaultValue(): void /** * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::batchInsert */ - public function testBatchInsert(string $table, array $columns, iterable|Generator $rows, string $expected): void + public function testBatchInsert(string $table, array $columns, iterable $rows, string $expected): void { $db = $this->getConnection(); diff --git a/tests/Provider/CommandProvider.php b/tests/Provider/CommandProvider.php index 7f6999d80..5d2ae6176 100644 --- a/tests/Provider/CommandProvider.php +++ b/tests/Provider/CommandProvider.php @@ -301,16 +301,10 @@ public static function batchInsert(): array ':qp3' => true, ], ], - 'wrongBehavior' => [ + 'table name with column name with brackets' => [ '{{%type}}', ['{{%type}}.[[int_col]]', '[[float_col]]', 'char_col', 'bool_col'], 'values' => [['0', '0.0', 'Kyiv {{city}}, Ukraine', false]], - /** - * Test covers potentially wrong behavior and marks it as expected!. - * - * In case table name or table column is passed with curly or square bracelets, QueryBuilder can not - * determine the table schema and typecast values properly. - */ 'expected' => DbHelper::replaceQuotes( << [ - ':qp0' => '0', - ':qp1' => '0.0', + ':qp0' => 0, + ':qp1' => 0.0, ':qp2' => 'Kyiv {{city}}, Ukraine', ':qp3' => false, ], @@ -345,6 +339,23 @@ public static function batchInsert(): array ':qp3' => false, ], ], + 'with associative values' => [ + 'type', + ['int_col', 'float_col', 'char_col', 'bool_col'], + 'values' => [['int' => '1.0', 'float' => '2', 'char' => 10, 'bool' => 1]], + 'expected' => DbHelper::replaceQuotes( + << [ + ':qp0' => 1, + ':qp1' => 2.0, + ':qp2' => '10', + ':qp3' => true, + ], + ], ]; } diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index 0871a113b..8e04fc7b5 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -213,6 +213,18 @@ public static function batchInsert(): array ), [':qp0' => null], ], + 'column table names are not checked' => [ + '{{%type}}', + ['{{%type}}.[[bool_col]]', '{{%another_table}}.[[bool_col2]]'], + [[true, false]], + 'expected' => DbHelper::replaceQuotes( + << null, ':qp1' => null], + ], 'empty-sql' => [ '{{%type}}', [], @@ -1107,6 +1119,13 @@ public static function upsert(): array '', [':qp0' => 'test@example.com', ':qp1' => 'bar {{city}}', ':qp2' => 1, ':qp3' => null], ], + 'regular values with unique at not the first position' => [ + 'T_upsert', + ['address' => 'bar {{city}}', 'email' => 'test@example.com', 'status' => 1, 'profile_id' => null], + true, + '', + [':qp0' => 'bar {{city}}', ':qp1' => 'test@example.com', ':qp2' => 1, ':qp3' => null], + ], 'regular values with update part' => [ 'T_upsert', ['email' => 'test@example.com', 'address' => 'bar {{city}}', 'status' => 1, 'profile_id' => null],