Skip to content
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

ColumnSchema classes for performance of typecasting #273

Merged
merged 10 commits into from
May 30, 2024
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 All @@ -77,7 +80,7 @@
*
* @var string[]
*/
private const TYPE_MAP = [

Check failure on line 83 in src/Schema.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

MissingClassConstType

src/Schema.php:83:19: MissingClassConstType: Class constant "Yiisoft\Db\Sqlite\Schema::TYPE_MAP" should have a declared type. (see https://psalm.dev/359)

Check failure on line 83 in src/Schema.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

MissingClassConstType

src/Schema.php:83:19: MissingClassConstType: Class constant "Yiisoft\Db\Sqlite\Schema::TYPE_MAP" should have a declared type. (see https://psalm.dev/359)
'tinyint' => self::TYPE_TINYINT,
'bit' => self::TYPE_SMALLINT,
'boolean' => self::TYPE_BOOLEAN,
Expand Down Expand Up @@ -370,10 +373,10 @@
}

$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 @@
*
* @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 @@
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'));
}
}
Loading