Skip to content

Commit

Permalink
ColumnSchema classes for performance of typecasting (#273)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored May 30, 2024
1 parent 85a1939 commit 0d9db18
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 144 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## 2.0.0 under development

- Enh #289: Implement `SqlParser` and `ExpressionBuilder` driver classes (@Tigrov)
- Enh #273: Implement `ColumnSchemaInterface` classes according to the data type of database table columns
for type casting performance. Related with yiisoft/db#752 (@Tigrov)
- Chg #307: Replace call of `SchemaInterface::getRawTableName()` to `QuoterInterface::getRawTableName()` (@Tigrov)

## 1.2.0 March 21, 2024
Expand Down
85 changes: 0 additions & 85 deletions src/ColumnSchema.php

This file was deleted.

101 changes: 53 additions & 48 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Helper\DbArrayHelper;
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_column;
Expand Down Expand Up @@ -67,7 +67,10 @@
* type:string,
* notnull:string,
* dflt_value:string|null,
* pk:string
* pk:string,
* size?: int,
* precision?: int,
* scale?: int,
* }
*/
final class Schema extends AbstractPdoSchema
Expand Down Expand Up @@ -370,10 +373,10 @@ protected function findColumns(TableSchemaInterface $table): bool
}

$column = $this->loadColumnSchema($info);
$table->column($column->getName(), $column);
$table->column($info['name'], $column);

if ($column->isPrimaryKey()) {
$table->primaryKey($column->getName());
$table->primaryKey($info['name']);
}
}

Expand Down Expand Up @@ -469,49 +472,63 @@ public function getSchemaDefaultValues(string $schema = '', bool $refresh = fals
*
* @return ColumnSchemaInterface The column schema object.
*
* @psalm-param array{cid:string, name:string, type:string, notnull:string, dflt_value:string|null, pk:string} $info
* @psalm-param ColumnInfo $info
*/
protected function loadColumnSchema(array $info): ColumnSchemaInterface
private function loadColumnSchema(array $info): ColumnSchemaInterface
{
$column = $this->createColumnSchema($info['name']);
$dbType = strtolower($info['type']);
$type = $this->getColumnType($dbType, $info);
$isUnsigned = str_contains($dbType, 'unsigned');
/** @psalm-var ColumnInfo $info */
$column = $this->createColumnSchema($type, unsigned: $isUnsigned);
$column->name($info['name']);
$column->size($info['size'] ?? null);
$column->precision($info['precision'] ?? null);
$column->scale($info['scale'] ?? null);
$column->allowNull(!$info['notnull']);
$column->primaryKey($info['pk'] != '0');
$column->dbType(strtolower($info['type']));
$column->unsigned(str_contains($column->getDbType() ?? '', 'unsigned'));
$column->type(self::TYPE_STRING);
$column->primaryKey((bool) $info['pk']);
$column->dbType($dbType);
$column->defaultValue($this->normalizeDefaultValue($info['dflt_value'], $column));

if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $column->getDbType() ?? '', $matches)) {
$type = strtolower($matches[1]);
return $column;
}

if (isset(self::TYPE_MAP[$type])) {
$column->type(self::TYPE_MAP[$type]);
}
/**
* Get the abstract data type for the database data type.
*
* @param string $dbType The database data type
* @param array $info Column information.
*
* @return string The abstract data type.
*/
private function getColumnType(string $dbType, array &$info): string
{
preg_match('/^(\w*)(?:\(([^)]+)\))?/', $dbType, $matches);
$dbType = strtolower($matches[1]);

if (!empty($matches[2])) {
$values = explode(',', $matches[2]);
$column->precision((int) $values[0]);
$column->size((int) $values[0]);
if (!empty($matches[2])) {
$values = explode(',', $matches[2], 2);
$info['size'] = (int) $values[0];
$info['precision'] = (int) $values[0];

if (isset($values[1])) {
$column->scale((int) $values[1]);
}
if (isset($values[1])) {
$info['scale'] = (int) $values[1];
}

if (($type === 'tinyint' || $type === 'bit') && $column->getSize() === 1) {
$column->type(self::TYPE_BOOLEAN);
} elseif ($type === 'bit') {
if ($column->getSize() > 32) {
$column->type(self::TYPE_BIGINT);
} elseif ($column->getSize() === 32) {
$column->type(self::TYPE_INTEGER);
}
}
if (($dbType === 'tinyint' || $dbType === 'bit') && $info['size'] === 1) {
return self::TYPE_BOOLEAN;
}
}

$column->phpType($this->getColumnPhpType($column));
$column->defaultValue($this->normalizeDefaultValue($info['dflt_value'], $column));
if ($dbType === 'bit') {
return match (true) {
$info['size'] === 32 => self::TYPE_INTEGER,
$info['size'] > 32 => self::TYPE_BIGINT,
default => self::TYPE_SMALLINT,
};
}
}

return $column;
return self::TYPE_MAP[$dbType] ?? self::TYPE_STRING;
}

/**
Expand Down Expand Up @@ -627,18 +644,6 @@ private function loadTableConstraints(string $tableName, string $returnType): Co
return $result[$returnType];
}

/**
* Creates a column schema for the database.
*
* This method may be overridden by child classes to create a DBMS-specific column schema.
*
* @param string $name Name of the column.
*/
private function createColumnSchema(string $name): ColumnSchemaInterface
{
return new ColumnSchema($name);
}

/**
* @throws Exception
* @throws InvalidConfigException
Expand Down
29 changes: 18 additions & 11 deletions tests/ColumnSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@
namespace Yiisoft\Db\Sqlite\Tests;

use PDO;
use PHPUnit\Framework\TestCase;
use Yiisoft\Db\Command\Param;
use Yiisoft\Db\Expression\JsonExpression;
use Yiisoft\Db\Sqlite\ColumnSchema;
use Yiisoft\Db\Schema\SchemaInterface;
use Yiisoft\Db\Schema\Column\BinaryColumnSchema;
use Yiisoft\Db\Schema\Column\BooleanColumnSchema;
use Yiisoft\Db\Schema\Column\DoubleColumnSchema;
use Yiisoft\Db\Schema\Column\IntegerColumnSchema;
use Yiisoft\Db\Schema\Column\JsonColumnSchema;
use Yiisoft\Db\Schema\Column\StringColumnSchema;
use Yiisoft\Db\Sqlite\Tests\Support\TestTrait;
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\Tests\Common\CommonColumnSchemaTest;

use function str_repeat;

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

Expand Down Expand Up @@ -75,13 +78,17 @@ public function testPhpTypeCast(): void
$db->close();
}

public function testTypeCastJson(): void
public function testColumnSchemaInstance()
{
$columnSchema = new ColumnSchema('json_col');
$columnSchema->dbType(SchemaInterface::TYPE_JSON);
$columnSchema->type(SchemaInterface::TYPE_JSON);
$db = $this->getConnection(true);
$schema = $db->getSchema();
$tableSchema = $schema->getTableSchema('type');

$this->assertSame(['a' => 1], $columnSchema->phpTypeCast('{"a":1}'));
$this->assertEquals(new JsonExpression(['a' => 1], SchemaInterface::TYPE_JSON), $columnSchema->dbTypeCast(['a' => 1]));
$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'));
$this->assertInstanceOf(BooleanColumnSchema::class, $tableSchema->getColumn('bool_col'));
$this->assertInstanceOf(JsonColumnSchema::class, $tableSchema->getColumn('json_col'));
}
}

0 comments on commit 0d9db18

Please sign in to comment.