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 #303

Merged
merged 12 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 @@ -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 @@ -99,7 +82,7 @@
*
* @var string[]
*/
private const TYPE_MAP = [

Check failure on line 85 in src/Schema.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

MissingClassConstType

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

Check failure on line 85 in src/Schema.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

MissingClassConstType

src/Schema.php:85:19: MissingClassConstType: Class constant "Yiisoft\Db\Mysql\Schema::TYPE_MAP" should have a declared type. (see https://psalm.dev/359)
'tinyint' => self::TYPE_TINYINT,
'bit' => self::TYPE_INTEGER,
'smallint' => self::TYPE_SMALLINT,
Expand Down Expand Up @@ -195,8 +178,7 @@
// 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 @@
]
)->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 @@

/** @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 @@
*
* @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 @@
}

$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 @@
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 @@
$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
Loading