diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a62824d..819079bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) - New #945: Add option for typecasting values retrieved from DB (@Tigrov) - Enh #941: Add the ability for user-defined type casting (@Tigrov) - Enh #961: Added `setHaving()` as a forced method to overwrite `having()` (@lav45) diff --git a/UPGRADE.md b/UPGRADE.md index a4c1903f8..13bc24522 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -120,6 +120,7 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace - `QueryInterface::resultCallback()` - allows to use a callback, to be called on all rows of the query result; - `QueryInterface::getResultCallback()` - returns the callback to be called on all rows of the query result or `null` if the callback is not set; +- `QueryInterface::withTypecasting()` - enables or disables typecasting of values when retrieving records from DB; - `QueryPartsInterface::setWhere()` - overwrites the `WHERE` part of the query; - `QueryPartsInterface::setHaving()` - overwrites the `HAVING` part of the query; - `ConnectionInterface::getColumnFactory()` - returns the column factory object for concrete DBMS; @@ -129,17 +130,18 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace - `QueryBuilderInterface::prepareValue()` - converts a value to its SQL representation; - `QueryBuilderInterface::getColumnFactory()` - returns the column factory object for concrete DBMS; - `QueryBuilderInterface::getServerInfo()` - returns `ServerInfoInterface` instance which provides server information; +- `DMLQueryBuilderInterface::withTypecasting()` - enables or disables typecasting of values when inserting or updating + records in DB; - `LikeConditionInterface::getCaseSensitive()` - returns whether the comparison is case-sensitive; - `SchemaInterface::hasTable()` - returns whether the specified table exists in database; - `SchemaInterface::hasSchema()` - returns whether the specified schema exists in database; - `SchemaInterface::hasView()` - returns whether the specified view exists in database; - `DbArrayHelper::arrange()` - arranges an array by specified keys; +- `CommandInterface::withDbTypecasting()` - enables or disables typecasting of values when inserting or updating records; - `CommandInterface::withPhpTypecasting()` - enables or disables typecasting of values when retrieving records from DB; -- `AbstractCommand::withPhpTypecasting()` - enables or disables typecasting of values when retrieving records from DB; -- `QueryInterface::withTypecasting()` - enables or disables typecasting of values when retrieving records from DB; -- `Query::withTypecasting()` - enables or disables typecasting of values when retrieving records from DB; +- `CommandInterface::withTypecasting()` - enables or disables typecasting of values when inserting, updating + or retrieving records from DB; - `SchemaInterface::getResultColumn()` - returns the column instance for the column metadata received from the query; -- `AbstractSchema::getResultColumn()` - returns the column instance for the column metadata received from the query; - `AbstractSchema::getResultColumnCacheKey()` - returns the cache key for the column metadata received from the query; - `AbstractSchema::loadResultColumn()` - creates a new column instance according to the column metadata from the query; diff --git a/docs/guide/en/schema/typecasting.md b/docs/guide/en/schema/typecasting.md index 00ffb7f33..242be663c 100644 --- a/docs/guide/en/schema/typecasting.md +++ b/docs/guide/en/schema/typecasting.md @@ -17,8 +17,8 @@ flowchart LR When saving a value to the database, the value must be in the correct type. For example, if saving a value to a column that is of type `bit`, the value must be an `integer` or `string` depends on DBMS. -To ensure that the value is saved in the correct type, `ColumnInterface::dbTypecast()` method can be used to cast -the value. Majority of the DB library methods, such as `CommandInterface::insert()`, automatically convert the type. +By default `update()`, `upsert()`, `insert()`, `insertBatch()` and `insertWithReturningPks()` methods +of `CommandInterface` and `QueryBuilderInterface` instances cast the values to the correct type. ```php use Yiisoft\Db\Connection\ConnectionInterface; @@ -33,7 +33,45 @@ $command->execute(); ``` In the example above, the value of `is_active` is a `boolean`, but the column `is_active` can be of type `bit`. -The `CommandInterface::insert()` method will automatically cast the value to the correct type. +The `CommandInterface::insert()` method by default cast the value to the correct type. + +### Using `ColumnInterface::dbTypecast()` + +To ensure that the value is saved in the correct type, `ColumnInterface::dbTypecast()` method can be used to cast +the value. + +```php +$isActive = true; +// Cast the value to the correct database type +$isActive = $db->getTableSchema('customer')->getColumn('is_active')->phpTypecast($isActive); + +$command = $db->createCommand(); +$command->setSql('INSERT INTO {{customer}} (name, is_active) VALUES (:name, :is_active)', [ + ':name' => 'John Doe', + ':is_active' => $isActive, +]); +$command->execute(); +``` + +In the example above, the value of `is_active` is casted to the correct database type before it is saved. + +### Using `QueryBuilderInterface::withTypecasting()` or `CommandInterface::withDbTypecasting()` + +You can also use `QueryBuilderInterface::withTypecasting()` or `CommandInterface::withDbTypecasting()` methods to +enable or disable type casting. + +```php +$command = $db->createCommand()->withDbTypecasting(false); +$command->insert('customer', [ + 'name' => 'John Doe', + 'is_active' => 1, +]); +$command->execute(); +``` + +In the example above, the value of `is_active` is not casted to the correct type before it is saved. The value +is saved as an integer `1` instead of a `bit` or a `bool`. This is useful when you want to save the value as is or +the value is already in the correct type. ## Casting values retrieved from the database @@ -47,9 +85,6 @@ To ensure that the value is returned in the correct type, you can use `ColumnInt the value, in the example above, to a `float`. ```php -use Yiisoft\Db\Connection\ConnectionInterface; - -/** @var ConnectionInterface $db */ $command = $db->createCommand('SELECT * FROM {{customer}} WHERE id = 1'); $row = $command->queryOne(); diff --git a/src/Command/AbstractCommand.php b/src/Command/AbstractCommand.php index dc32c42ed..66d4c4a40 100644 --- a/src/Command/AbstractCommand.php +++ b/src/Command/AbstractCommand.php @@ -120,6 +120,7 @@ abstract class AbstractCommand implements CommandInterface */ protected string|null $refreshTableName = null; protected Closure|null $retryHandler = null; + protected bool $dbTypecasting = true; protected bool $phpTypecasting = false; /** * @var string The SQL statement to execute. @@ -457,13 +458,6 @@ public function queryScalar(): bool|string|null|int|float return is_scalar($result) ? $result : null; } - public function withPhpTypecasting(bool $phpTypecasting = true): static - { - $new = clone $this; - $new->phpTypecasting = $phpTypecasting; - return $new; - } - public function renameColumn(string $table, string $oldName, string $newName): static { $sql = $this->getQueryBuilder()->renameColumn($table, $oldName, $newName); @@ -529,6 +523,28 @@ public function upsert( return $this->setSql($sql)->bindValues($params); } + public function withDbTypecasting(bool $dbTypecasting = true): static + { + $new = clone $this; + $new->dbTypecasting = $dbTypecasting; + return $new; + } + + public function withPhpTypecasting(bool $phpTypecasting = true): static + { + $new = clone $this; + $new->phpTypecasting = $phpTypecasting; + return $new; + } + + public function withTypecasting(bool $typecasting = true): static + { + $new = clone $this; + $new->dbTypecasting = $typecasting; + $new->phpTypecasting = $typecasting; + return $new; + } + /** * @return QueryBuilderInterface The query builder instance. */ diff --git a/src/Command/CommandInterface.php b/src/Command/CommandInterface.php index 1deb6901e..3918eec00 100644 --- a/src/Command/CommandInterface.php +++ b/src/Command/CommandInterface.php @@ -619,11 +619,6 @@ public function insert(string $table, QueryInterface|array $columns): static; */ public function insertWithReturningPks(string $table, array $columns): array|false; - /** - * Returns a copy of the instance with enabled or disabled typecasting of values when retrieving records from DB. - */ - public function withPhpTypecasting(bool $phpTypecasting = true): static; - /** * Prepares the SQL statement to be executed. * @@ -894,4 +889,20 @@ public function upsert( bool|array $updateColumns = true, array $params = [] ): static; + + /** + * Returns copy of the instance with enabled or disabled typecasting of values when inserting or updating records. + */ + public function withDbTypecasting(bool $dbTypecasting = true): static; + + /** + * Returns a copy of the instance with enabled or disabled typecasting of values when retrieving records from DB. + */ + public function withPhpTypecasting(bool $phpTypecasting = true): static; + + /** + * Returns a copy of the instance with enabled or disabled typecasting of values when inserting, updating + * or retrieving records from DB. + */ + public function withTypecasting(bool $typecasting = true): static; } diff --git a/src/Debug/CommandInterfaceProxy.php b/src/Debug/CommandInterfaceProxy.php index 2ac0d22b9..9aba7a846 100644 --- a/src/Debug/CommandInterfaceProxy.php +++ b/src/Debug/CommandInterfaceProxy.php @@ -321,14 +321,6 @@ public function insertWithReturningPks(string $table, array $columns): array|fal return $this->decorated->{__FUNCTION__}(...func_get_args()); } - /** - * @psalm-suppress MixedArgument - */ - public function withPhpTypecasting(bool $phpTypecasting = true): static - { - return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); - } - public function prepare(?bool $forRead = null): void { $this->decorated->{__FUNCTION__}(...func_get_args()); @@ -506,6 +498,30 @@ public function upsert( return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } + /** + * @psalm-suppress MixedArgument + */ + public function withDbTypecasting(bool $dbTypecasting = true): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + /** + * @psalm-suppress MixedArgument + */ + public function withPhpTypecasting(bool $phpTypecasting = true): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + + /** + * @psalm-suppress MixedArgument + */ + public function withTypecasting(bool $typecasting = true): static + { + return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); + } + private function collectQueryStart(string $id, string $line): void { $this->collector->collectQueryStart( diff --git a/src/Driver/Pdo/AbstractPdoCommand.php b/src/Driver/Pdo/AbstractPdoCommand.php index 0bf099852..9b988a41c 100644 --- a/src/Driver/Pdo/AbstractPdoCommand.php +++ b/src/Driver/Pdo/AbstractPdoCommand.php @@ -170,7 +170,7 @@ protected function bindPendingParams(): void protected function getQueryBuilder(): QueryBuilderInterface { - return $this->db->getQueryBuilder(); + return $this->db->getQueryBuilder()->withTypecasting($this->dbTypecasting); } protected function getQueryMode(int $queryMode): string diff --git a/src/QueryBuilder/AbstractDMLQueryBuilder.php b/src/QueryBuilder/AbstractDMLQueryBuilder.php index c28368ed4..b13c24982 100644 --- a/src/QueryBuilder/AbstractDMLQueryBuilder.php +++ b/src/QueryBuilder/AbstractDMLQueryBuilder.php @@ -55,6 +55,8 @@ */ abstract class AbstractDMLQueryBuilder implements DMLQueryBuilderInterface { + protected bool $typecasting = true; + public function __construct( protected QueryBuilderInterface $queryBuilder, protected QuoterInterface $quoter, @@ -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. * @@ -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; @@ -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); @@ -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])) { diff --git a/src/QueryBuilder/AbstractQueryBuilder.php b/src/QueryBuilder/AbstractQueryBuilder.php index c3bffadca..768cbfdcc 100644 --- a/src/QueryBuilder/AbstractQueryBuilder.php +++ b/src/QueryBuilder/AbstractQueryBuilder.php @@ -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. * diff --git a/src/QueryBuilder/DMLQueryBuilderInterface.php b/src/QueryBuilder/DMLQueryBuilderInterface.php index 463d7ccd9..ef7e104d6 100644 --- a/src/QueryBuilder/DMLQueryBuilderInterface.php +++ b/src/QueryBuilder/DMLQueryBuilderInterface.php @@ -227,4 +227,6 @@ public function upsert( bool|array $updateColumns, array &$params ): string; + + public function withTypecasting(bool $typecasting = true): static; } diff --git a/tests/Common/CommonCommandTest.php b/tests/Common/CommonCommandTest.php index 6071278c7..2f5a08b83 100644 --- a/tests/Common/CommonCommandTest.php +++ b/tests/Common/CommonCommandTest.php @@ -1525,6 +1525,74 @@ 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 = $command->withDbTypecasting(false); + $command->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 = $command->withDbTypecasting(false); + $command->insertBatch('{{type}}', [$values]); + + $this->assertSame([ + ':qp0' => '1', + ':qp1' => 'test', + ':qp2' => '3.14', + ':qp3' => '1', + ], $command->getParams()); + + $db->close(); + } + /** * @throws Exception * @throws InvalidConfigException @@ -2004,6 +2072,38 @@ 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 = $command->withDbTypecasting(false); + $command->update('{{type}}', $values); + + $this->assertSame([ + ':qp0' => '1', + ':qp1' => 'test', + ':qp2' => '3.14', + ':qp3' => '1', + ], $command->getParams()); + } + /** * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::upsert * diff --git a/tests/Common/CommonQueryBuilderTest.php b/tests/Common/CommonQueryBuilderTest.php index 14187c179..d0600b21e 100644 --- a/tests/Common/CommonQueryBuilderTest.php +++ b/tests/Common/CommonQueryBuilderTest.php @@ -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(); @@ -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); } } diff --git a/tests/Db/Command/CommandTest.php b/tests/Db/Command/CommandTest.php index a3b43a8a8..35faa58e4 100644 --- a/tests/Db/Command/CommandTest.php +++ b/tests/Db/Command/CommandTest.php @@ -752,6 +752,22 @@ public function testProfilerData(?string $sql = null): void parent::testProfilerData(); } + public function testWithDbTypecasting(): void + { + $db = $this->getConnection(); + $command = $db->createCommand(); + + $this->assertTrue(Assert::getInaccessibleProperty($command, 'dbTypecasting')); + + $command = $command->withDbTypecasting(false); + + $this->assertFalse(Assert::getInaccessibleProperty($command, 'dbTypecasting')); + + $command = $command->withDbTypecasting(); + + $this->assertTrue(Assert::getInaccessibleProperty($command, 'dbTypecasting')); + } + public function testWithPhpTypecasting(): void { $db = $this->getConnection(); @@ -767,4 +783,23 @@ public function testWithPhpTypecasting(): void $this->assertFalse(Assert::getInaccessibleProperty($command, 'phpTypecasting')); } + + public function testWithTypecasting(): void + { + $db = $this->getConnection(); + $command = $db->createCommand(); + + $this->assertTrue(Assert::getInaccessibleProperty($command, 'dbTypecasting')); + $this->assertFalse(Assert::getInaccessibleProperty($command, 'phpTypecasting')); + + $command = $command->withTypecasting(false); + + $this->assertFalse(Assert::getInaccessibleProperty($command, 'dbTypecasting')); + $this->assertFalse(Assert::getInaccessibleProperty($command, 'phpTypecasting')); + + $command = $command->withTypecasting(); + + $this->assertTrue(Assert::getInaccessibleProperty($command, 'dbTypecasting')); + $this->assertTrue(Assert::getInaccessibleProperty($command, 'phpTypecasting')); + } } diff --git a/tests/Db/QueryBuilder/QueryBuilderTest.php b/tests/Db/QueryBuilder/QueryBuilderTest.php index 8ace1896e..7ab337ca8 100644 --- a/tests/Db/QueryBuilder/QueryBuilderTest.php +++ b/tests/Db/QueryBuilder/QueryBuilderTest.php @@ -15,6 +15,7 @@ use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\Schema\Column\ColumnBuilder; use Yiisoft\Db\Tests\AbstractQueryBuilderTest; +use Yiisoft\Db\Tests\Support\Assert; use Yiisoft\Db\Tests\Support\DbHelper; use Yiisoft\Db\Tests\Support\Stub\QueryBuilder; use Yiisoft\Db\Tests\Support\TestTrait; @@ -372,4 +373,39 @@ public function testJsonColumn(): void $qb->alterColumn('json_table', 'json_col', $column), ); } + + public function testWithTypecasting(): void + { + $db = $this->getConnection(); + $qb = $db->getQueryBuilder(); + + $dmlBuilder = Assert::getInaccessibleProperty($qb, 'dmlBuilder'); + $typecasting = Assert::getInaccessibleProperty($dmlBuilder, 'typecasting'); + + $this->assertTrue($typecasting); + + $dmlBuilder = $dmlBuilder->withTypecasting(false); + $typecasting = Assert::getInaccessibleProperty($dmlBuilder, 'typecasting'); + + $this->assertFalse($typecasting); + + $dmlBuilder = $dmlBuilder->withTypecasting(); + $typecasting = Assert::getInaccessibleProperty($dmlBuilder, 'typecasting'); + + $this->assertTrue($typecasting); + + $qb = $qb->withTypecasting(false); + $dmlBuilder = Assert::getInaccessibleProperty($qb, 'dmlBuilder'); + $typecasting = Assert::getInaccessibleProperty($dmlBuilder, 'typecasting'); + + $this->assertFalse($typecasting); + + $qb = $qb->withTypecasting(); + $dmlBuilder = Assert::getInaccessibleProperty($qb, 'dmlBuilder'); + $typecasting = Assert::getInaccessibleProperty($dmlBuilder, 'typecasting'); + + $this->assertTrue($typecasting); + + $db->close(); + } }