Skip to content

Commit a8529da

Browse files
authored
ColumnSchema classes for performance of typecasting (#236)
1 parent 74594d9 commit a8529da

File tree

7 files changed

+144
-80
lines changed

7 files changed

+144
-80
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from `$table, $columns, $rows` to `$table, $rows, $columns = []` (@Tigrov)
77
- Enh #260: Support `Traversable` values for `DMLQueryBuilder::batchInsert()` method with empty columns (@Tigrov)
88
- Enh #255: Implement `SqlParser` and `ExpressionBuilder` driver classes (@Tigrov)
9+
- Enh #236: Implement `ColumnSchemaInterface` classes according to the data type of database table columns
10+
for type casting performance. Related with yiisoft/db#752 (@Tigrov)
911
- Chg #272: Replace call of `SchemaInterface::getRawTableName()` to `QuoterInterface::getRawTableName()` (@Tigrov)
1012

1113
## 1.3.0 March 21, 2024

src/Column/BinaryColumnSchema.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Oracle\Column;
6+
7+
use Yiisoft\Db\Command\ParamInterface;
8+
use Yiisoft\Db\Expression\Expression;
9+
use Yiisoft\Db\Schema\Column\BinaryColumnSchema as BaseBinaryColumnSchema;
10+
11+
use function is_string;
12+
13+
final class BinaryColumnSchema extends BaseBinaryColumnSchema
14+
{
15+
public function dbTypecast(mixed $value): mixed
16+
{
17+
if ($this->getDbType() === 'BLOB') {
18+
if ($value instanceof ParamInterface && is_string($value->getValue())) {
19+
/** @var string */
20+
$value = $value->getValue();
21+
}
22+
23+
if (is_string($value)) {
24+
return new Expression('TO_BLOB(UTL_RAW.CAST_TO_RAW(:value))', ['value' => $value]);
25+
}
26+
}
27+
28+
return parent::dbTypecast($value);
29+
}
30+
}

src/ColumnSchema.php

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/DMLQueryBuilder.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Yiisoft\Db\Query\QueryInterface;
1111
use Yiisoft\Db\QueryBuilder\AbstractDMLQueryBuilder;
1212

13+
use function array_key_first;
1314
use function array_map;
1415
use function implode;
1516
use function count;
@@ -145,7 +146,8 @@ protected function prepareInsertValues(string $table, array|QueryInterface $colu
145146
if (!empty($tableSchema->getPrimaryKey())) {
146147
$columns = $tableSchema->getPrimaryKey();
147148
} else {
148-
$columns = [current($tableSchema->getColumns())->getName()];
149+
/** @var list<string> $columns */
150+
$columns = [array_key_first($tableSchema->getColumns())];
149151
}
150152

151153
foreach ($columns as $name) {

src/Schema.php

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
use Yiisoft\Db\Exception\NotSupportedException;
1818
use Yiisoft\Db\Expression\Expression;
1919
use Yiisoft\Db\Helper\DbArrayHelper;
20+
use Yiisoft\Db\Oracle\Column\BinaryColumnSchema;
2021
use Yiisoft\Db\Schema\Builder\ColumnInterface;
21-
use Yiisoft\Db\Schema\ColumnSchemaInterface;
22+
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;
2223
use Yiisoft\Db\Schema\TableSchemaInterface;
2324

2425
use function array_change_key_case;
@@ -405,14 +406,14 @@ protected function findColumns(TableSchemaInterface $table): bool
405406
return false;
406407
}
407408

408-
/** @psalm-var string[][] $columns */
409-
foreach ($columns as $column) {
410-
/** @psalm-var ColumnInfoArray $column */
411-
$column = array_change_key_case($column);
409+
/** @psalm-var string[][] $info */
410+
foreach ($columns as $info) {
411+
/** @psalm-var ColumnInfoArray $info */
412+
$info = array_change_key_case($info);
412413

413-
$c = $this->createColumnSchema($column);
414+
$column = $this->loadColumnSchema($info);
414415

415-
$table->column($c->getName(), $c);
416+
$table->column($info['column_name'], $column);
416417
}
417418

418419
return true;
@@ -448,28 +449,43 @@ protected function getTableSequenceName(string $tableName): string|null
448449
}
449450

450451
/**
451-
* Creates ColumnSchema instance.
452+
* Loads the column information into a {@see ColumnSchemaInterface} object.
452453
*
453-
* @psalm-param ColumnInfoArray $info
454+
* @param array $info The column information.
455+
*
456+
* @return ColumnSchemaInterface The column schema object.
457+
*
458+
* @psalm-param ColumnInfoArray $info The column information.
454459
*/
455-
protected function createColumnSchema(array $info): ColumnSchemaInterface
460+
private function loadColumnSchema(array $info): ColumnSchemaInterface
456461
{
457-
$column = new ColumnSchema($info['column_name']);
462+
$dbType = $info['data_type'];
463+
$type = $this->extractColumnType($dbType, $info);
464+
465+
$column = $this->createColumnSchema($type);
466+
$column->name($info['column_name']);
458467
$column->allowNull($info['nullable'] === 'Y');
459468
$column->comment($info['column_comment']);
460469
$column->primaryKey((bool) $info['is_pk']);
461470
$column->autoIncrement($info['identity_column'] === 'YES');
462471
$column->size((int) $info['data_length']);
463472
$column->precision($info['data_precision'] !== null ? (int) $info['data_precision'] : null);
464473
$column->scale($info['data_scale'] !== null ? (int) $info['data_scale'] : null);
465-
$column->dbType($info['data_type']);
466-
$column->type($this->extractColumnType($column));
467-
$column->phpType($this->getColumnPhpType($column));
474+
$column->dbType($dbType);
468475
$column->defaultValue($this->normalizeDefaultValue($info['data_default'], $column));
469476

470477
return $column;
471478
}
472479

480+
protected function createColumnSchemaFromPhpType(string $phpType, string $type): ColumnSchemaInterface
481+
{
482+
if ($phpType === self::PHP_TYPE_RESOURCE) {
483+
return new BinaryColumnSchema($type, $phpType);
484+
}
485+
486+
return parent::createColumnSchemaFromPhpType($phpType, $type);
487+
}
488+
473489
/**
474490
* Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database.
475491
*
@@ -645,16 +661,20 @@ public function findUniqueIndexes(TableSchemaInterface $table): array
645661
/**
646662
* Extracts the data type for the given column.
647663
*
648-
* @param ColumnSchemaInterface $column The column schema object.
664+
* @param string $dbType The database data type
665+
* @param array $info Column information.
666+
* @psalm-param ColumnInfoArray $info
649667
*
650668
* @return string The abstract column type.
651669
*/
652-
private function extractColumnType(ColumnSchemaInterface $column): string
670+
private function extractColumnType(string $dbType, array $info): string
653671
{
654-
$dbType = strtolower((string) $column->getDbType());
672+
$dbType = strtolower($dbType);
655673

656674
if ($dbType === 'number') {
657-
return match ($column->getScale()) {
675+
$scale = $info['data_scale'] !== null ? (int) $info['data_scale'] : null;
676+
677+
return match ($scale) {
658678
null => self::TYPE_DOUBLE,
659679
0 => self::TYPE_INTEGER,
660680
default => self::TYPE_DECIMAL,
@@ -663,7 +683,7 @@ private function extractColumnType(ColumnSchemaInterface $column): string
663683

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

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

tests/ColumnSchemaTest.php

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@
44

55
namespace Yiisoft\Db\Oracle\Tests;
66

7-
use PHPUnit\Framework\TestCase;
7+
use PDO;
8+
use Yiisoft\Db\Command\Param;
89
use Yiisoft\Db\Expression\Expression;
10+
use Yiisoft\Db\Oracle\Column\BinaryColumnSchema;
911
use Yiisoft\Db\Oracle\Tests\Support\TestTrait;
1012
use Yiisoft\Db\Query\Query;
13+
use Yiisoft\Db\Schema\Column\DoubleColumnSchema;
14+
use Yiisoft\Db\Schema\Column\IntegerColumnSchema;
15+
use Yiisoft\Db\Schema\Column\StringColumnSchema;
16+
use Yiisoft\Db\Tests\Common\CommonColumnSchemaTest;
1117

1218
use function str_repeat;
1319

1420
/**
1521
* @group oracle
1622
*/
17-
final class ColumnSchemaTest extends TestCase
23+
final class ColumnSchemaTest extends CommonColumnSchemaTest
1824
{
1925
use TestTrait;
2026

@@ -62,4 +68,40 @@ public function testPhpTypeCast(): void
6268

6369
$db->close();
6470
}
71+
72+
public function testColumnSchemaInstance()
73+
{
74+
$db = $this->getConnection(true);
75+
$schema = $db->getSchema();
76+
$tableSchema = $schema->getTableSchema('type');
77+
78+
$this->assertInstanceOf(IntegerColumnSchema::class, $tableSchema->getColumn('int_col'));
79+
$this->assertInstanceOf(StringColumnSchema::class, $tableSchema->getColumn('char_col'));
80+
$this->assertInstanceOf(DoubleColumnSchema::class, $tableSchema->getColumn('float_col'));
81+
$this->assertInstanceOf(BinaryColumnSchema::class, $tableSchema->getColumn('blob_col'));
82+
}
83+
84+
/** @dataProvider \Yiisoft\Db\Oracle\Tests\Provider\ColumnSchemaProvider::predefinedTypes */
85+
public function testPredefinedType(string $className, string $type, string $phpType)
86+
{
87+
parent::testPredefinedType($className, $type, $phpType);
88+
}
89+
90+
/** @dataProvider \Yiisoft\Db\Oracle\Tests\Provider\ColumnSchemaProvider::dbTypecastColumns */
91+
public function testDbTypecastColumns(string $className, array $values)
92+
{
93+
parent::testDbTypecastColumns($className, $values);
94+
}
95+
96+
public function testBinaryColumnSchema()
97+
{
98+
$binaryCol = new BinaryColumnSchema();
99+
$binaryCol->dbType('BLOB');
100+
101+
$this->assertInstanceOf(Expression::class, $binaryCol->dbTypecast("\x10\x11\x12"));
102+
$this->assertInstanceOf(
103+
Expression::class,
104+
$binaryCol->dbTypecast(new Param("\x10\x11\x12", PDO::PARAM_LOB)),
105+
);
106+
}
65107
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Oracle\Tests\Provider;
6+
7+
use Yiisoft\Db\Oracle\Column\BinaryColumnSchema;
8+
9+
class ColumnSchemaProvider extends \Yiisoft\Db\Tests\Provider\ColumnSchemaProvider
10+
{
11+
public static function predefinedTypes(): array
12+
{
13+
$values = parent::predefinedTypes();
14+
$values['binary'][0] = BinaryColumnSchema::class;
15+
16+
return $values;
17+
}
18+
19+
public static function dbTypecastColumns(): array
20+
{
21+
$values = parent::dbTypecastColumns();
22+
$values['binary'][0] = BinaryColumnSchema::class;
23+
24+
return $values;
25+
}
26+
}

0 commit comments

Comments
 (0)