Skip to content

Commit

Permalink
Realize column factory (#346)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored Sep 1, 2024
1 parent a59e09c commit 6758b75
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 81 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Chg #339: Replace call of `SchemaInterface::getRawTableName()` to `QuoterInterface::getRawTableName()` (@Tigrov)
- Enh #342: Add JSON overlaps condition builder (@Tigrov)
- Enh #344: Update `bit` type according to main PR yiisoft/db#860 (@Tigrov)
- Enh #346: Implement `ColumnFactory` class (@Tigrov)
- Enh #347, #353: Raise minimum PHP version to `^8.1` with minor refactoring (@Tigrov)
- Bug #349, #352: Restore connection if closed by connection timeout (@Tigrov)

Expand Down
76 changes: 76 additions & 0 deletions src/Column/ColumnFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Mysql\Column;

use Yiisoft\Db\Schema\Column\AbstractColumnFactory;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;
use Yiisoft\Db\Schema\SchemaInterface;

final class ColumnFactory extends AbstractColumnFactory
{
/**
* Mapping from physical column types (keys) to abstract column types (values).
*
* @var string[]
*
* @psalm-suppress MissingClassConstType
*/
private const TYPE_MAP = [
'bit' => SchemaInterface::TYPE_BIT,
'tinyint' => SchemaInterface::TYPE_TINYINT,
'smallint' => SchemaInterface::TYPE_SMALLINT,
'mediumint' => SchemaInterface::TYPE_INTEGER,
'int' => SchemaInterface::TYPE_INTEGER,
'integer' => SchemaInterface::TYPE_INTEGER,
'bigint' => SchemaInterface::TYPE_BIGINT,
'float' => SchemaInterface::TYPE_FLOAT,
'real' => SchemaInterface::TYPE_FLOAT,
'double' => SchemaInterface::TYPE_DOUBLE,
'decimal' => SchemaInterface::TYPE_DECIMAL,
'numeric' => SchemaInterface::TYPE_DECIMAL,
'char' => SchemaInterface::TYPE_CHAR,
'varchar' => SchemaInterface::TYPE_STRING,
'string' => SchemaInterface::TYPE_STRING,
'enum' => SchemaInterface::TYPE_STRING,
'tinytext' => SchemaInterface::TYPE_TEXT,
'mediumtext' => SchemaInterface::TYPE_TEXT,
'longtext' => SchemaInterface::TYPE_TEXT,
'text' => SchemaInterface::TYPE_TEXT,
'varbinary' => SchemaInterface::TYPE_BINARY,
'blob' => SchemaInterface::TYPE_BINARY,
'longblob' => SchemaInterface::TYPE_BINARY,
'year' => SchemaInterface::TYPE_DATE,
'date' => SchemaInterface::TYPE_DATE,
'time' => SchemaInterface::TYPE_TIME,
'datetime' => SchemaInterface::TYPE_DATETIME,
'timestamp' => SchemaInterface::TYPE_TIMESTAMP,
'json' => SchemaInterface::TYPE_JSON,
];

public function fromDefinition(string $definition, array $info = []): ColumnSchemaInterface
{
if (str_starts_with($definition, 'enum(')) {
preg_match('/^enum\(([^)]+)\)\s*/', $definition, $matches);
preg_match_all("/'([^']*)'/", $matches[1], $values);

$info['enum_values'] = $values[1];

return $this->fromDbType('enum', $info);
}

return parent::fromDefinition($definition, $info);
}

protected function getType(string $dbType, array $info = []): string
{
$type = self::TYPE_MAP[$dbType] ?? SchemaInterface::TYPE_STRING;

if ($type === SchemaInterface::TYPE_BIT && isset($info['size']) && $info['size'] === 1) {
return SchemaInterface::TYPE_BOOLEAN;
}

return $type;
}
}
90 changes: 9 additions & 81 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Helper\DbArrayHelper;
use Yiisoft\Db\Mysql\Column\ColumnFactory;
use Yiisoft\Db\Schema\Builder\ColumnInterface;
use Yiisoft\Db\Schema\Column\ColumnFactoryInterface;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;
use Yiisoft\Db\Schema\TableSchemaInterface;

use function array_change_key_case;
use function array_map;
use function array_values;
use function bindec;
use function explode;
use function in_array;
use function is_string;
use function ksort;
Expand Down Expand Up @@ -76,48 +77,16 @@
*/
final class Schema extends AbstractPdoSchema
{
/**
* Mapping from physical column types (keys) to abstract column types (values).
*
* @var string[]
*/
private const TYPE_MAP = [
'tinyint' => self::TYPE_TINYINT,
'bit' => self::TYPE_BIT,
'smallint' => self::TYPE_SMALLINT,
'mediumint' => self::TYPE_INTEGER,
'int' => self::TYPE_INTEGER,
'integer' => self::TYPE_INTEGER,
'bigint' => self::TYPE_BIGINT,
'float' => self::TYPE_FLOAT,
'double' => self::TYPE_DOUBLE,
'real' => self::TYPE_FLOAT,
'decimal' => self::TYPE_DECIMAL,
'numeric' => self::TYPE_DECIMAL,
'tinytext' => self::TYPE_TEXT,
'mediumtext' => self::TYPE_TEXT,
'longtext' => self::TYPE_TEXT,
'longblob' => self::TYPE_BINARY,
'blob' => self::TYPE_BINARY,
'text' => self::TYPE_TEXT,
'varchar' => self::TYPE_STRING,
'string' => self::TYPE_STRING,
'char' => self::TYPE_CHAR,
'datetime' => self::TYPE_DATETIME,
'year' => self::TYPE_DATE,
'date' => self::TYPE_DATE,
'time' => self::TYPE_TIME,
'timestamp' => self::TYPE_TIMESTAMP,
'enum' => self::TYPE_STRING,
'varbinary' => self::TYPE_BINARY,
'json' => self::TYPE_JSON,
];

public function createColumn(string $type, array|int|string $length = null): ColumnInterface
{
return new Column($type, $length);
}

public function getColumnFactory(): ColumnFactoryInterface
{
return new ColumnFactory();
}

/**
* Returns all unique indexes for the given table.
*
Expand Down Expand Up @@ -449,15 +418,9 @@ protected function getCreateTableSql(TableSchemaInterface $table): string
private function loadColumnSchema(array $info): ColumnSchemaInterface
{
$dbType = $info['type'];
$type = $this->getColumnType($dbType, $info);
$isUnsigned = stripos($dbType, 'unsigned') !== false;
/** @psalm-var ColumnInfoArray $info */
$column = $this->createColumnSchema($type, unsigned: $isUnsigned);
$column = $this->getColumnFactory()->fromDefinition($dbType);
$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);
Expand All @@ -470,7 +433,7 @@ private function loadColumnSchema(array $info): ColumnSchemaInterface
empty($extra)
&& !empty($info['extra_default_value'])
&& !str_starts_with($info['extra_default_value'], '\'')
&& in_array($type, [
&& in_array($column->getType(), [
self::TYPE_CHAR, self::TYPE_STRING, self::TYPE_TEXT,
self::TYPE_DATETIME, self::TYPE_TIMESTAMP, self::TYPE_TIME, self::TYPE_DATE,
], true)
Expand All @@ -488,41 +451,6 @@ private 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' && $info['size'] === 1) {
return self::TYPE_BOOLEAN;
}
}
}

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
34 changes: 34 additions & 0 deletions tests/ColumnFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Mysql\Tests;

use Yiisoft\Db\Mysql\Tests\Support\TestTrait;
use Yiisoft\Db\Tests\AbstractColumnFactoryTest;

/**
* @group mysql
*/
final class ColumnFactoryTest extends AbstractColumnFactoryTest
{
use TestTrait;

/** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\ColumnFactoryProvider::dbTypes */
public function testFromDbType(string $dbType, string $expectedType, string $expectedInstanceOf): void
{
parent::testFromDbType($dbType, $expectedType, $expectedInstanceOf);
}

/** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\ColumnFactoryProvider::definitions */
public function testFromDefinition(string $definition, string $expectedType, string $expectedInstanceOf, array $expectedInfo = []): void
{
parent::testFromDefinition($definition, $expectedType, $expectedInstanceOf, $expectedInfo);
}

/** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\ColumnFactoryProvider::types */
public function testFromType(string $type, string $expectedType, string $expectedInstanceOf): void
{
parent::testFromType($type, $expectedType, $expectedInstanceOf);
}
}
61 changes: 61 additions & 0 deletions tests/Provider/ColumnFactoryProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Mysql\Tests\Provider;

use Yiisoft\Db\Schema\Column\BinaryColumnSchema;
use Yiisoft\Db\Schema\Column\BitColumnSchema;
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;

final class ColumnFactoryProvider extends \Yiisoft\Db\Tests\Provider\ColumnFactoryProvider
{
public static function dbTypes(): array
{
return [
// db type, expected abstract type, expected instance of
['bit', 'bit', BitColumnSchema::class],
['tinyint', 'tinyint', IntegerColumnSchema::class],
['smallint', 'smallint', IntegerColumnSchema::class],
['mediumint', 'integer', IntegerColumnSchema::class],
['int', 'integer', IntegerColumnSchema::class],
['integer', 'integer', IntegerColumnSchema::class],
['bigint', 'bigint', IntegerColumnSchema::class],
['float', 'float', DoubleColumnSchema::class],
['real', 'float', DoubleColumnSchema::class],
['double', 'double', DoubleColumnSchema::class],
['decimal', 'decimal', DoubleColumnSchema::class],
['numeric', 'decimal', DoubleColumnSchema::class],
['char', 'char', StringColumnSchema::class],
['varchar', 'string', StringColumnSchema::class],
['string', 'string', StringColumnSchema::class],
['enum', 'string', StringColumnSchema::class],
['tinytext', 'text', StringColumnSchema::class],
['mediumtext', 'text', StringColumnSchema::class],
['longtext', 'text', StringColumnSchema::class],
['text', 'text', StringColumnSchema::class],
['varbinary', 'binary', BinaryColumnSchema::class],
['blob', 'binary', BinaryColumnSchema::class],
['longblob', 'binary', BinaryColumnSchema::class],
['year', 'date', StringColumnSchema::class],
['date', 'date', StringColumnSchema::class],
['time', 'time', StringColumnSchema::class],
['datetime', 'datetime', StringColumnSchema::class],
['timestamp', 'timestamp', StringColumnSchema::class],
['json', 'json', JsonColumnSchema::class],
];
}

public static function definitions(): array
{
$definitions = parent::definitions();

$definitions[] = ['bit(1)', 'boolean', BooleanColumnSchema::class, ['getSize' => 1]];

return $definitions;
}
}
9 changes: 9 additions & 0 deletions tests/SchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Mysql\Column;
use Yiisoft\Db\Mysql\Column\ColumnFactory;
use Yiisoft\Db\Mysql\Schema;
use Yiisoft\Db\Mysql\Tests\Support\TestTrait;
use Yiisoft\Db\Query\Query;
Expand Down Expand Up @@ -563,4 +564,12 @@ public function testInsertDefaultValues()
'numeric_col' => '-33.22',
], $row);
}

public function testGetColumnFactory(): void
{
$db = $this->getConnection();
$factory = $db->getSchema()->getColumnFactory();

$this->assertInstanceOf(ColumnFactory::class, $factory);
}
}

0 comments on commit 6758b75

Please sign in to comment.