Skip to content

Add option for typecasting when insert or update values. #949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 23, 2025
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
- Enh #925, #951: Add callback to `Query::all()` and `Query::one()` methods (@Tigrov, @vjik)
- New #954: Add `DbArrayHelper::arrange()` method (@Tigrov)
- Chg #956: Remove nullable from `PdoConnectionInterface::getActivePdo()` result (@vjik)
- New #949: Add option for typecasting when insert or update values (@Tigrov)

## 1.3.0 March 21, 2024

Expand Down
7 changes: 7 additions & 0 deletions src/Command/AbstractCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ abstract class AbstractCommand implements CommandInterface
*/
protected string|null $refreshTableName = null;
protected Closure|null $retryHandler = null;
protected bool $dbTypecasting = true;
/**
* @var string The SQL statement to execute.
*/
Expand Down Expand Up @@ -252,6 +253,12 @@ public function createView(string $viewName, QueryInterface|string $subQuery): s
return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
}

public function dbTypecasting(bool $dbTypecasting = true): static
{
$this->dbTypecasting = $dbTypecasting;
return $this;
}

public function delete(string $table, array|string $condition = '', array $params = []): static
{
$sql = $this->getQueryBuilder()->delete($table, $condition, $params);
Expand Down
5 changes: 5 additions & 0 deletions src/Command/CommandInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,11 @@ public function createTable(string $table, array $columns, ?string $options = nu
*/
public function createView(string $viewName, QueryInterface|string $subQuery): static;

/**
* Enables or disables typecasting of values when inserting or updating records.
*/
public function dbTypecasting(bool $dbTypecasting = true): static;

/**
* Creates a DELETE command.
*
Expand Down
8 changes: 8 additions & 0 deletions src/Debug/CommandInterfaceProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ public function createView(string $viewName, QueryInterface|string $subQuery): s
return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector);
}

/**
* @psalm-suppress MixedArgument
*/
public function dbTypecasting(bool $dbTypecasting = true): static
{
return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector);
}

/**
* @psalm-suppress MixedArgument
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Driver/Pdo/AbstractPdoCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@

protected function getQueryBuilder(): QueryBuilderInterface
{
return $this->db->getQueryBuilder();
return $this->db->getQueryBuilder()->withTypecasting($this->dbTypecasting);

Check warning on line 171 in src/Driver/Pdo/AbstractPdoCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/Pdo/AbstractPdoCommand.php#L171

Added line #L171 was not covered by tests
}

protected function getQueryMode(int $queryMode): string
Expand Down
15 changes: 12 additions & 3 deletions src/QueryBuilder/AbstractDMLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
*/
abstract class AbstractDMLQueryBuilder implements DMLQueryBuilderInterface
{
protected bool $typecasting = true;

public function __construct(
protected QueryBuilderInterface $queryBuilder,
protected QuoterInterface $quoter,
Expand Down Expand Up @@ -145,6 +147,13 @@ public function upsert(
throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.');
}

public function withTypecasting(bool $typecasting = true): static
{
$new = clone $this;
$new->typecasting = $typecasting;
return $new;
}

/**
* Prepare traversable for batch insert.
*
Expand Down Expand Up @@ -186,7 +195,7 @@ protected function prepareBatchInsertValues(string $table, iterable $rows, array
/** @var string[] $names */
$names = array_values($columnNames);
$keys = array_fill_keys($names, false);
$columns = $this->schema->getTableSchema($table)?->getColumns() ?? [];
$columns = $this->typecasting ? $this->schema->getTableSchema($table)?->getColumns() ?? [] : [];

foreach ($rows as $row) {
$i = 0;
Expand Down Expand Up @@ -339,7 +348,7 @@ protected function prepareInsertValues(string $table, array|QueryInterface $colu
$names = [];
$placeholders = [];
$columns = $this->normalizeColumnNames($columns);
$tableColumns = $this->schema->getTableSchema($table)?->getColumns() ?? [];
$tableColumns = $this->typecasting ? $this->schema->getTableSchema($table)?->getColumns() ?? [] : [];

foreach ($columns as $name => $value) {
$names[] = $this->quoter->quoteColumnName($name);
Expand Down Expand Up @@ -373,7 +382,7 @@ protected function prepareUpdateSets(string $table, array $columns, array $param
{
$sets = [];
$columns = $this->normalizeColumnNames($columns);
$tableColumns = $this->schema->getTableSchema($table)?->getColumns() ?? [];
$tableColumns = $this->typecasting ? $this->schema->getTableSchema($table)?->getColumns() ?? [] : [];

foreach ($columns as $name => $value) {
if (isset($tableColumns[$name])) {
Expand Down
7 changes: 7 additions & 0 deletions src/QueryBuilder/AbstractQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,13 @@ public function upsert(
return $this->dmlBuilder->upsert($table, $insertColumns, $updateColumns, $params);
}

public function withTypecasting(bool $typecasting = true): static
{
$new = clone $this;
$new->dmlBuilder = $new->dmlBuilder->withTypecasting($typecasting);
return $new;
}

/**
* Converts a resource value to its SQL representation or throws an exception if conversion is not possible.
*
Expand Down
2 changes: 2 additions & 0 deletions src/QueryBuilder/DMLQueryBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,6 @@ public function upsert(
bool|array $updateColumns,
array &$params
): string;

public function withTypecasting(bool $typecasting = true): static;
}
97 changes: 97 additions & 0 deletions tests/Common/CommonCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1506,6 +1506,72 @@ public function testInsertToBlob(): void
$db->close();
}

public function testInsertWithoutTypecasting(): void
{
$db = $this->getConnection(true);
$command = $db->createCommand();

$values = [
'int_col' => '1',
'char_col' => 'test',
'float_col' => '3.14',
'bool_col' => '1',
];

$command->insert('{{type}}', $values);

$this->assertSame([
':qp0' => 1,
':qp1' => 'test',
':qp2' => 3.14,
':qp3' => $db->getDriverName() === 'oci' ? '1' : true,
], $command->getParams());

$command->dbTypecasting(false)->insert('{{type}}', $values);

$this->assertSame([
':qp0' => '1',
':qp1' => 'test',
':qp2' => '3.14',
':qp3' => '1',
], $command->getParams());

$db->close();
}

public function testInsertBatchWithoutTypecasting(): void
{
$db = $this->getConnection(true);
$command = $db->createCommand();

$values = [
'int_col' => '1',
'char_col' => 'test',
'float_col' => '3.14',
'bool_col' => '1',
];

$command->insertBatch('{{type}}', [$values]);

$this->assertSame([
':qp0' => 1,
':qp1' => 'test',
':qp2' => 3.14,
':qp3' => $db->getDriverName() === 'oci' ? '1' : true,
], $command->getParams());

$command->dbTypecasting(false)->insertBatch('{{type}}', [$values]);

$this->assertSame([
':qp0' => '1',
':qp1' => 'test',
':qp2' => '3.14',
':qp3' => '1',
], $command->getParams());

$db->close();
}

/**
* @throws Exception
* @throws InvalidConfigException
Expand Down Expand Up @@ -1985,6 +2051,37 @@ public function testUpdate(
$db->close();
}

public function testUpdateWithoutTypecasting(): void
{
$db = $this->getConnection(true);
$command = $db->createCommand();

$values = [
'int_col' => '1',
'char_col' => 'test',
'float_col' => '3.14',
'bool_col' => '1',
];

$command->update('{{type}}', $values);

$this->assertSame([
':qp0' => 1,
':qp1' => 'test',
':qp2' => 3.14,
':qp3' => $db->getDriverName() === 'oci' ? '1' : true,
], $command->getParams());

$command->dbTypecasting(false)->update('{{type}}', $values);

$this->assertSame([
':qp0' => '1',
':qp1' => 'test',
':qp2' => '3.14',
':qp3' => '1',
], $command->getParams());
}

/**
* @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::upsert
*
Expand Down
115 changes: 109 additions & 6 deletions tests/Common/CommonQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@

abstract class CommonQueryBuilderTest extends AbstractQueryBuilderTest
{
private function createTebleWithColumn(CommandInterface $command, string|ColumnInterface $column)
{
try {
$command->dropTable('build_column_definition_primary_key')->execute();
} catch (Exception) {
}

$command->createTable('build_column_definition_primary_key', ['id' => $column])->execute();
}

public function getBuildColumnDefinitionProvider(): array
{
return QueryBuilderProvider::buildColumnDefinition();
Expand Down Expand Up @@ -60,13 +70,106 @@ public function testCreateTableWithBuildColumnDefinition(): void
$command->createTable('build_column_definition', $columns)->execute();
}

private function createTebleWithColumn(CommandInterface $command, string|ColumnInterface $column)
public function testInsertWithoutTypecasting(): void
{
try {
$command->dropTable('build_column_definition_primary_key')->execute();
} catch (Exception) {
}
$db = $this->getConnection(true);
$qb = $db->getQueryBuilder();

$values = [
'int_col' => '1',
'char_col' => 'test',
'float_col' => '3.14',
'bool_col' => '1',
];

$params = [];
$qb->insert('{{type}}', $values, $params);

$this->assertSame([
':qp0' => 1,
':qp1' => 'test',
':qp2' => 3.14,
':qp3' => $db->getDriverName() === 'oci' ? '1' : true,
], $params);

$params = [];
$qb->withTypecasting(false)->insert('{{type}}', $values, $params);

$this->assertSame([
':qp0' => '1',
':qp1' => 'test',
':qp2' => '3.14',
':qp3' => '1',
], $params);

$db->close();
}

$command->createTable('build_column_definition_primary_key', ['id' => $column])->execute();
public function testInsertBatchWithoutTypecasting(): void
{
$db = $this->getConnection(true);
$qb = $db->getQueryBuilder();

$values = [
'int_col' => '1',
'char_col' => 'test',
'float_col' => '3.14',
'bool_col' => '1',
];

$params = [];
$qb->insertBatch('{{type}}', [$values], [], $params);

$this->assertSame([
':qp0' => 1,
':qp1' => 'test',
':qp2' => 3.14,
':qp3' => $db->getDriverName() === 'oci' ? '1' : true,
], $params);

$params = [];
$qb->withTypecasting(false)->insertBatch('{{type}}', [$values], [], $params);

$this->assertSame([
':qp0' => '1',
':qp1' => 'test',
':qp2' => '3.14',
':qp3' => '1',
], $params);

$db->close();
}

public function testUpdateWithoutTypecasting(): void
{
$db = $this->getConnection(true);
$qb = $db->getQueryBuilder();

$values = [
'int_col' => '1',
'char_col' => 'test',
'float_col' => '3.14',
'bool_col' => '1',
];

$params = [];
$qb->update('{{type}}', $values, [], $params);

$this->assertSame([
':qp0' => 1,
':qp1' => 'test',
':qp2' => 3.14,
':qp3' => $db->getDriverName() === 'oci' ? '1' : true,
], $params);

$params = [];
$qb->withTypecasting(false)->update('{{type}}', $values, [], $params);

$this->assertSame([
':qp0' => '1',
':qp1' => 'test',
':qp2' => '3.14',
':qp3' => '1',
], $params);
}
}
16 changes: 16 additions & 0 deletions tests/Db/Command/CommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -751,4 +751,20 @@ public function testProfilerData(?string $sql = null): void
);
parent::testProfilerData();
}

public function testDbTypecasting(): void
{
$db = $this->getConnection();
$command = $db->createCommand();

$this->assertTrue(Assert::getInaccessibleProperty($command, 'dbTypecasting'));

$command->dbTypecasting(false);

$this->assertFalse(Assert::getInaccessibleProperty($command, 'dbTypecasting'));

$command->dbTypecasting();

$this->assertTrue(Assert::getInaccessibleProperty($command, 'dbTypecasting'));
}
}
Loading
Loading