Skip to content

Commit

Permalink
ColumnSchema classes for performance of typecasting (#236)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored May 30, 2024
1 parent 74594d9 commit a8529da
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 80 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from `$table, $columns, $rows` to `$table, $rows, $columns = []` (@Tigrov)
- Enh #260: Support `Traversable` values for `DMLQueryBuilder::batchInsert()` method with empty columns (@Tigrov)
- Enh #255: Implement `SqlParser` and `ExpressionBuilder` driver classes (@Tigrov)
- Enh #236: Implement `ColumnSchemaInterface` classes according to the data type of database table columns
for type casting performance. Related with yiisoft/db#752 (@Tigrov)
- Chg #272: Replace call of `SchemaInterface::getRawTableName()` to `QuoterInterface::getRawTableName()` (@Tigrov)

## 1.3.0 March 21, 2024
Expand Down
30 changes: 30 additions & 0 deletions src/Column/BinaryColumnSchema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Oracle\Column;

use Yiisoft\Db\Command\ParamInterface;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Schema\Column\BinaryColumnSchema as BaseBinaryColumnSchema;

use function is_string;

final class BinaryColumnSchema extends BaseBinaryColumnSchema
{
public function dbTypecast(mixed $value): mixed
{
if ($this->getDbType() === 'BLOB') {
if ($value instanceof ParamInterface && is_string($value->getValue())) {
/** @var string */
$value = $value->getValue();
}

if (is_string($value)) {
return new Expression('TO_BLOB(UTL_RAW.CAST_TO_RAW(:value))', ['value' => $value]);
}
}

return parent::dbTypecast($value);
}
}
58 changes: 0 additions & 58 deletions src/ColumnSchema.php

This file was deleted.

4 changes: 3 additions & 1 deletion src/DMLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\AbstractDMLQueryBuilder;

use function array_key_first;
use function array_map;
use function implode;
use function count;
Expand Down Expand Up @@ -145,7 +146,8 @@ protected function prepareInsertValues(string $table, array|QueryInterface $colu
if (!empty($tableSchema->getPrimaryKey())) {
$columns = $tableSchema->getPrimaryKey();
} else {
$columns = [current($tableSchema->getColumns())->getName()];
/** @var list<string> $columns */
$columns = [array_key_first($tableSchema->getColumns())];
}

foreach ($columns as $name) {
Expand Down
58 changes: 39 additions & 19 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Helper\DbArrayHelper;
use Yiisoft\Db\Oracle\Column\BinaryColumnSchema;
use Yiisoft\Db\Schema\Builder\ColumnInterface;
use Yiisoft\Db\Schema\ColumnSchemaInterface;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;
use Yiisoft\Db\Schema\TableSchemaInterface;

use function array_change_key_case;
Expand Down Expand Up @@ -405,14 +406,14 @@ protected function findColumns(TableSchemaInterface $table): bool
return false;
}

/** @psalm-var string[][] $columns */
foreach ($columns as $column) {
/** @psalm-var ColumnInfoArray $column */
$column = array_change_key_case($column);
/** @psalm-var string[][] $info */
foreach ($columns as $info) {
/** @psalm-var ColumnInfoArray $info */
$info = array_change_key_case($info);

$c = $this->createColumnSchema($column);
$column = $this->loadColumnSchema($info);

$table->column($c->getName(), $c);
$table->column($info['column_name'], $column);
}

return true;
Expand Down Expand Up @@ -448,28 +449,43 @@ protected function getTableSequenceName(string $tableName): string|null
}

/**
* Creates ColumnSchema instance.
* Loads the column information into a {@see ColumnSchemaInterface} object.
*
* @psalm-param ColumnInfoArray $info
* @param array $info The column information.
*
* @return ColumnSchemaInterface The column schema object.
*
* @psalm-param ColumnInfoArray $info The column information.
*/
protected function createColumnSchema(array $info): ColumnSchemaInterface
private function loadColumnSchema(array $info): ColumnSchemaInterface
{
$column = new ColumnSchema($info['column_name']);
$dbType = $info['data_type'];
$type = $this->extractColumnType($dbType, $info);

$column = $this->createColumnSchema($type);
$column->name($info['column_name']);
$column->allowNull($info['nullable'] === 'Y');
$column->comment($info['column_comment']);
$column->primaryKey((bool) $info['is_pk']);
$column->autoIncrement($info['identity_column'] === 'YES');
$column->size((int) $info['data_length']);
$column->precision($info['data_precision'] !== null ? (int) $info['data_precision'] : null);
$column->scale($info['data_scale'] !== null ? (int) $info['data_scale'] : null);
$column->dbType($info['data_type']);
$column->type($this->extractColumnType($column));
$column->phpType($this->getColumnPhpType($column));
$column->dbType($dbType);
$column->defaultValue($this->normalizeDefaultValue($info['data_default'], $column));

return $column;
}

protected function createColumnSchemaFromPhpType(string $phpType, string $type): ColumnSchemaInterface
{
if ($phpType === self::PHP_TYPE_RESOURCE) {
return new BinaryColumnSchema($type, $phpType);
}

return parent::createColumnSchemaFromPhpType($phpType, $type);
}

/**
* Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database.
*
Expand Down Expand Up @@ -645,16 +661,20 @@ public function findUniqueIndexes(TableSchemaInterface $table): array
/**
* Extracts the data type for the given column.
*
* @param ColumnSchemaInterface $column The column schema object.
* @param string $dbType The database data type
* @param array $info Column information.
* @psalm-param ColumnInfoArray $info
*
* @return string The abstract column type.
*/
private function extractColumnType(ColumnSchemaInterface $column): string
private function extractColumnType(string $dbType, array $info): string
{
$dbType = strtolower((string) $column->getDbType());
$dbType = strtolower($dbType);

if ($dbType === 'number') {
return match ($column->getScale()) {
$scale = $info['data_scale'] !== null ? (int) $info['data_scale'] : null;

return match ($scale) {
null => self::TYPE_DOUBLE,
0 => self::TYPE_INTEGER,
default => self::TYPE_DECIMAL,
Expand All @@ -663,7 +683,7 @@ private function extractColumnType(ColumnSchemaInterface $column): string

$dbType = preg_replace('/\([^)]+\)/', '', $dbType);

if ($dbType === 'interval day to second' && $column->getPrecision() > 0) {
if ($dbType === 'interval day to second' && $info['data_precision'] > 0) {
return self::TYPE_STRING;
}

Expand Down
46 changes: 44 additions & 2 deletions tests/ColumnSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@

namespace Yiisoft\Db\Oracle\Tests;

use PHPUnit\Framework\TestCase;
use PDO;
use Yiisoft\Db\Command\Param;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Oracle\Column\BinaryColumnSchema;
use Yiisoft\Db\Oracle\Tests\Support\TestTrait;
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\Schema\Column\DoubleColumnSchema;
use Yiisoft\Db\Schema\Column\IntegerColumnSchema;
use Yiisoft\Db\Schema\Column\StringColumnSchema;
use Yiisoft\Db\Tests\Common\CommonColumnSchemaTest;

use function str_repeat;

/**
* @group oracle
*/
final class ColumnSchemaTest extends TestCase
final class ColumnSchemaTest extends CommonColumnSchemaTest
{
use TestTrait;

Expand Down Expand Up @@ -62,4 +68,40 @@ public function testPhpTypeCast(): void

$db->close();
}

public function testColumnSchemaInstance()
{
$db = $this->getConnection(true);
$schema = $db->getSchema();
$tableSchema = $schema->getTableSchema('type');

$this->assertInstanceOf(IntegerColumnSchema::class, $tableSchema->getColumn('int_col'));
$this->assertInstanceOf(StringColumnSchema::class, $tableSchema->getColumn('char_col'));
$this->assertInstanceOf(DoubleColumnSchema::class, $tableSchema->getColumn('float_col'));
$this->assertInstanceOf(BinaryColumnSchema::class, $tableSchema->getColumn('blob_col'));
}

/** @dataProvider \Yiisoft\Db\Oracle\Tests\Provider\ColumnSchemaProvider::predefinedTypes */
public function testPredefinedType(string $className, string $type, string $phpType)
{
parent::testPredefinedType($className, $type, $phpType);
}

/** @dataProvider \Yiisoft\Db\Oracle\Tests\Provider\ColumnSchemaProvider::dbTypecastColumns */
public function testDbTypecastColumns(string $className, array $values)
{
parent::testDbTypecastColumns($className, $values);
}

public function testBinaryColumnSchema()
{
$binaryCol = new BinaryColumnSchema();
$binaryCol->dbType('BLOB');

$this->assertInstanceOf(Expression::class, $binaryCol->dbTypecast("\x10\x11\x12"));
$this->assertInstanceOf(
Expression::class,
$binaryCol->dbTypecast(new Param("\x10\x11\x12", PDO::PARAM_LOB)),
);
}
}
26 changes: 26 additions & 0 deletions tests/Provider/ColumnSchemaProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Oracle\Tests\Provider;

use Yiisoft\Db\Oracle\Column\BinaryColumnSchema;

class ColumnSchemaProvider extends \Yiisoft\Db\Tests\Provider\ColumnSchemaProvider
{
public static function predefinedTypes(): array
{
$values = parent::predefinedTypes();
$values['binary'][0] = BinaryColumnSchema::class;

return $values;
}

public static function dbTypecastColumns(): array
{
$values = parent::dbTypecastColumns();
$values['binary'][0] = BinaryColumnSchema::class;

return $values;
}
}

0 comments on commit a8529da

Please sign in to comment.