Skip to content

Commit

Permalink
ColumnSchema classes for performance of typecasting (#303)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored May 30, 2024
1 parent 45037f4 commit b177c8b
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 208 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

- Chg #297: Remove `QueryBuilder::getColumnType()` child method as legacy code (@Tigrov)
- Enh #300: Refactor insert default values (@Tigrov)
- Enh #303: Implement `ColumnSchemaInterface` classes according to the data type of database table columns
for type casting performance. Related with yiisoft/db#752 (@Tigrov)
- Enh #309: Move methods from `Command` to `AbstractPdoCommand` class (@Tigrov)
- Bug #302: Refactor `DMLQueryBuilder`, related with yiisoft/db#746 (@Tigrov)

Expand Down
87 changes: 0 additions & 87 deletions src/ColumnSchema.php

This file was deleted.

151 changes: 61 additions & 90 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Yiisoft\Db\Mysql;

use JsonException;
use Throwable;
use Yiisoft\Db\Constraint\Constraint;
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
Expand All @@ -16,7 +15,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_change_key_case;
Expand All @@ -39,26 +38,6 @@
/**
* Implements MySQL, MariaDB specific schema, supporting MySQL Server 5.7, MariaDB Server 10.4 and higher.
*
* @psalm-type ColumnArray = array{
* table_schema: string,
* table_name: string,
* column_name: string,
* data_type: string,
* type_type: string|null,
* character_maximum_length: int,
* column_comment: string|null,
* modifier: int,
* is_nullable: bool,
* column_default: mixed,
* is_autoinc: bool,
* sequence_name: string|null,
* enum_values: array<array-key, float|int|string>|string|null,
* numeric_precision: int|null,
* numeric_scale: int|null,
* size: string|null,
* is_pkey: bool|null,
* dimension: int
* }
* @psalm-type ColumnInfoArray = array{
* field: string,
* type: string,
Expand All @@ -69,7 +48,11 @@
* extra: string,
* extra_default_value: string|null,
* privileges: string,
* comment: string
* comment: string,
* enum_values?: string[],
* size?: int,
* precision?: int,
* scale?: int,
* }
* @psalm-type RowConstraint = array{
* constraint_name: string,
Expand Down Expand Up @@ -195,8 +178,7 @@ protected function findColumns(TableSchemaInterface $table): bool
// Chapter 1: crutches for MariaDB. {@see https://github.com/yiisoft/yii2/issues/19747}
$columnsExtra = [];
if (str_contains($this->db->getServerVersion(), 'MariaDB')) {
/** @psalm-var array[] $columnsExtra */
$columnsExtra = $this->db->createCommand(
$rows = $this->db->createCommand(
<<<SQL
SELECT `COLUMN_NAME` as name,`COLUMN_DEFAULT` as default_value
FROM INFORMATION_SCHEMA.COLUMNS
Expand All @@ -208,7 +190,7 @@ protected function findColumns(TableSchemaInterface $table): bool
]
)->queryAll();
/** @psalm-var string[] $cols */
foreach ($columnsExtra as $cols) {
foreach ($rows as $cols) {
$columnsExtra[$cols['name']] = $cols['default_value'];
}
}
Expand All @@ -231,20 +213,20 @@ protected function findColumns(TableSchemaInterface $table): bool

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

$info['extra_default_value'] = $columnsExtra[(string) $info['field']] ?? '';
$info['extra_default_value'] = $columnsExtra[$info['field']] ?? '';

if (in_array($info['field'], $jsonColumns, true)) {
$info['type'] = self::TYPE_JSON;
}

/** @psalm-var ColumnInfoArray $info */
$column = $this->loadColumnSchema($info);
$table->column($column->getName(), $column);
$table->column($info['field'], $column);

if ($column->isPrimaryKey()) {
$table->primaryKey($column->getName());
$table->primaryKey($info['field']);
if ($column->isAutoIncrement()) {
$table->sequenceName('');
}
Expand Down Expand Up @@ -469,72 +451,35 @@ protected function getCreateTableSql(TableSchemaInterface $table): string
*
* @param array $info The column information.
*
* @throws JsonException
*
* @return ColumnSchemaInterface The column schema object.
*
* @psalm-param ColumnInfoArray $info The column information.
*/
protected function loadColumnSchema(array $info): ColumnSchemaInterface
private function loadColumnSchema(array $info): ColumnSchemaInterface
{
$dbType = $info['type'];

$column = $this->createColumnSchema($info['field']);

$type = $this->getColumnType($dbType, $info);
$isUnsigned = stripos($dbType, 'unsigned') !== false;
/** @psalm-var ColumnInfoArray $info */
$column = $this->createColumnSchema($type, unsigned: $isUnsigned);
$column->name($info['field']);
$column->enumValues($info['enum_values'] ?? null);
$column->size($info['size'] ?? null);
$column->precision($info['precision'] ?? null);
$column->scale($info['scale'] ?? null);
$column->allowNull($info['null'] === 'YES');
$column->primaryKey(str_contains($info['key'], 'PRI'));
$column->autoIncrement(stripos($info['extra'], 'auto_increment') !== false);
$column->comment($info['comment']);
$column->dbType($dbType);
$column->unsigned(stripos($dbType, 'unsigned') !== false);
$column->type(self::TYPE_STRING);

if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $dbType, $matches)) {
$type = strtolower($matches[1]);

if (isset(self::TYPE_MAP[$type])) {
$column->type(self::TYPE_MAP[$type]);
}

if (!empty($matches[2])) {
if ($type === 'enum') {
preg_match_all("/'[^']*'/", $matches[2], $values);

foreach ($values[0] as $i => $value) {
$values[$i] = trim($value, "'");
}

$column->enumValues($values);
} else {
$values = explode(',', $matches[2]);
$column->precision((int) $values[0]);
$column->size((int) $values[0]);

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

if ($type === 'bit') {
if ($column->getSize() === 1) {
$column->type(self::TYPE_BOOLEAN);
} elseif ($column->getSize() > 32) {
$column->type(self::TYPE_BIGINT);
} elseif ($column->getSize() === 32) {
$column->type(self::TYPE_INTEGER);
}
}
}
}
}

// Chapter 2: crutches for MariaDB {@see https://github.com/yiisoft/yii2/issues/19747}
$extra = $info['extra'];
if (
empty($extra)
&& !empty($info['extra_default_value'])
&& !str_starts_with($info['extra_default_value'], '\'')
&& in_array($column->getType(), [
&& in_array($type, [
self::TYPE_CHAR, self::TYPE_STRING, self::TYPE_TEXT,
self::TYPE_DATETIME, self::TYPE_TIMESTAMP, self::TYPE_TIME, self::TYPE_DATE,
], true)
Expand All @@ -543,7 +488,6 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface
}

$column->extra($extra);
$column->phpType($this->getColumnPhpType($column));
$column->defaultValue($this->normalizeDefaultValue($info['default'], $column));

if (str_starts_with($extra, 'DEFAULT_GENERATED')) {
Expand All @@ -553,6 +497,45 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface
return $column;
}

/**
* 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])) {
if ($dbType === 'enum') {
preg_match_all("/'([^']*)'/", $matches[2], $values);
$info['enum_values'] = $values[1];
} else {
$values = explode(',', $matches[2], 2);
$info['size'] = (int) $values[0];
$info['precision'] = (int) $values[0];

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

if ($dbType === 'bit') {
return match (true) {
$info['size'] === 1 => self::TYPE_BOOLEAN,
$info['size'] > 32 => self::TYPE_BIGINT,
default => self::TYPE_INTEGER,
};
}
}
}

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

/**
* Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database.
*
Expand Down Expand Up @@ -907,18 +890,6 @@ protected function resolveTableCreateSql(TableSchemaInterface $table): void
$table->createSql($sql);
}

/**
* 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): ColumnSchema
{
return new ColumnSchema($name);
}

/**
* @throws Exception
* @throws InvalidConfigException
Expand Down
Loading

0 comments on commit b177c8b

Please sign in to comment.