Skip to content

Commit

Permalink
Refactor ColumnFactory (#327)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored Nov 8, 2024
1 parent b2ae795 commit 9f9ace2
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 130 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
- Enh #312: Refactor `bit` type (@Tigrov)
- Enh #315: Refactor PHP type of `ColumnSchemaInterface` instances (@Tigrov)
- Enh #317: Raise minimum PHP version to `^8.1` with minor refactoring (@Tigrov)
- New #316: Implement `ColumnFactory` class (@Tigrov)
- New #316, #327: Implement `ColumnFactory` class (@Tigrov)
- Enh #319: Separate column type constants (@Tigrov)
- New #320: Realize `ColumnBuilder` class (@Tigrov)
- Enh #321: Update according changes in `ColumnSchemaInterface` (@Tigrov)
- New #322: Add `ColumnDefinitionBuilder` class (@Tigrov)
- Enh #323: Refactor `Dsn` class (@Tigrov)
- Enh #324: Use constructor to create columns and initialize properties (@Tigrov)
- Enh #327: Refactor `Schema::findColumns()` method (@Tigrov)

## 1.2.0 March 21, 2024

Expand Down
28 changes: 6 additions & 22 deletions src/Column/ColumnFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Schema\Column\AbstractColumnFactory;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;
use Yiisoft\Db\Schema\Column\StringColumnSchema;

final class ColumnFactory extends AbstractColumnFactory
{
/**
* Mapping from physical column types (keys) to abstract column types (values).
*
* @var string[]
* @psalm-var array<string, ColumnType::*>
*/
private const TYPE_MAP = [
protected const TYPE_MAP = [
/** Exact numbers */
'bit' => ColumnType::BOOLEAN,
'tinyint' => ColumnType::TINYINT,
Expand Down Expand Up @@ -69,37 +69,21 @@ final class ColumnFactory extends AbstractColumnFactory
'table' => ColumnType::STRING,
];

protected function getType(string $dbType, array $info = []): string
{
return self::TYPE_MAP[$dbType] ?? ColumnType::STRING;
}

public function fromPseudoType(string $pseudoType, array $info = []): ColumnSchemaInterface
{
if ($pseudoType === PseudoType::UUID_PK_SEQ) {
unset($info['type']);
$info['primaryKey'] = true;
$info['autoIncrement'] = true;
if ($pseudoType === PseudoType::UUID_PK_SEQ && !isset($info['defaultValue'])) {
$info['defaultValue'] = new Expression('newsequentialid()');

return new StringColumnSchema(ColumnType::UUID, ...$info);
}

return parent::fromPseudoType($pseudoType, $info);
}

public function fromType(string $type, array $info = []): ColumnSchemaInterface
protected function getColumnClass(string $type, array $info = []): string
{
if ($type === ColumnType::BINARY) {
unset($info['type']);
return new BinaryColumnSchema($type, ...$info);
return BinaryColumnSchema::class;
}

return parent::fromType($type, $info);
}

protected function isDbType(string $dbType): bool
{
return isset(self::TYPE_MAP[$dbType]);
return parent::getColumnClass($type, $info);
}
}
110 changes: 51 additions & 59 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,30 @@
use Yiisoft\Db\Schema\TableSchemaInterface;

use function array_change_key_case;
use function array_fill_keys;
use function array_map;
use function is_array;
use function md5;
use function preg_match;
use function serialize;
use function str_replace;
use function strcasecmp;

/**
* Implements the MSSQL Server specific schema, supporting MSSQL Server 2017 and above.
*
* @psalm-type ColumnArray = array{
* column_name: string,
* column_default: string|null,
* is_nullable: string,
* data_type: string,
* column_default: string|null,
* size: int|string|null,
* numeric_scale: int|string|null,
* is_identity: string,
* is_computed: string,
* comment: null|string,
* size?: int,
* scale?: int,
* comment: string|null,
* primaryKey: bool,
* schema: string|null,
* table: string
* }
* @psalm-type ConstraintArray = array<
* array-key,
Expand Down Expand Up @@ -366,18 +369,19 @@ protected function loadTableDefaultValues(string $tableName): array
*/
private function loadColumnSchema(array $info): ColumnSchemaInterface
{
$columnFactory = $this->getColumnFactory();

$dbType = $info['data_type'];
/** @psalm-var ColumnArray $info */
$column = $columnFactory->fromDefinition($dbType);
/** @psalm-suppress DeprecatedMethod */
$column->name($info['column_name']);
$column->notNull($info['is_nullable'] !== 'YES');
$column->dbType($dbType);
$column->autoIncrement($info['is_identity'] === '1');
$column->computed($info['is_computed'] === '1');
$column->comment($info['comment'] ?? '');
$column = $this->getColumnFactory()->fromDbType($info['data_type'], [
'autoIncrement' => $info['is_identity'] === '1',
'comment' => $info['comment'],
'computed' => $info['is_computed'] === '1',
'name' => $info['column_name'],
'notNull' => $info['is_nullable'] !== 'YES',
'primaryKey' => $info['primaryKey'],
'scale' => $info['numeric_scale'] !== null ? (int) $info['numeric_scale'] : null,
'schema' => $info['schema'],
'size' => $info['size'] !== null ? (int) $info['size'] : null,
'table' => $info['table'],
]);

$column->defaultValue($this->normalizeDefaultValue($info['column_default'], $column));

return $column;
Expand Down Expand Up @@ -420,61 +424,46 @@ private function normalizeDefaultValue(string|null $defaultValue, ColumnSchemaIn
*/
protected function findColumns(TableSchemaInterface $table): bool
{
$columnsTableName = 'INFORMATION_SCHEMA.COLUMNS';
$schemaName = $table->getSchemaName();
$tableName = $table->getName();

$whereParams = [':table_name' => $table->getName()];
$columnsTableName = '[INFORMATION_SCHEMA].[COLUMNS]';
$whereSql = '[t1].[table_name] = :table_name';
$whereParams = [':table_name' => $tableName];

if ($table->getCatalogName() !== null) {
$columnsTableName = "{$table->getCatalogName()}.$columnsTableName";
$columnsTableName = "[{$table->getCatalogName()}].$columnsTableName";
$whereSql .= ' AND [t1].[table_catalog] = :catalog';
$whereParams[':catalog'] = $table->getCatalogName();
}

if ($table->getSchemaName() !== null) {
$whereSql .= " AND [t1].[table_schema] = '{$table->getSchemaName()}'";
if ($schemaName !== null) {
$whereSql .= ' AND [t1].[table_schema] = :schema_name';
$whereParams[':schema_name'] = $schemaName;
}

$columnsTableName = $this->db->getQuoter()->quoteTableName($columnsTableName);

$sql = <<<SQL
SELECT
[t1].[column_name],
[t1].[column_default],
[t1].[is_nullable],
CASE WHEN [t1].[data_type] IN ('char','varchar','nchar','nvarchar','binary','varbinary') THEN
CASE WHEN [t1].[character_maximum_length] = NULL OR [t1].[character_maximum_length] = -1 THEN
[t1].[data_type]
ELSE
[t1].[data_type] + '(' + LTRIM(RTRIM(CONVERT(CHAR,[t1].[character_maximum_length]))) + ')'
END
WHEN [t1].[data_type] IN ('decimal','numeric') THEN
CASE WHEN [t1].[numeric_precision] = NULL OR [t1].[numeric_precision] = -1 THEN
[t1].[data_type]
ELSE
[t1].[data_type] + '(' + LTRIM(RTRIM(CONVERT(CHAR,[t1].[numeric_precision]))) + ',' + LTRIM(RTRIM(CONVERT(CHAR,[t1].[numeric_scale]))) + ')'
END
ELSE
[t1].[data_type]
END AS 'data_type',
[t1].[column_default],
COLUMNPROPERTY(OBJECT_ID([t1].[table_schema] + '.' + [t1].[table_name]), [t1].[column_name], 'IsIdentity') AS is_identity,
COLUMNPROPERTY(OBJECT_ID([t1].[table_schema] + '.' + [t1].[table_name]), [t1].[column_name], 'IsComputed') AS is_computed,
(
SELECT CONVERT(VARCHAR, [t2].[value])
FROM [sys].[extended_properties] AS [t2]
WHERE
[t2].[class] = 1 AND
[t2].[class_desc] = 'OBJECT_OR_COLUMN' AND
[t2].[name] = 'MS_Description' AND
[t2].[major_id] = OBJECT_ID([t1].[TABLE_SCHEMA] + '.' + [t1].[table_name]) AND
[t2].[minor_id] = COLUMNPROPERTY(OBJECT_ID([t1].[TABLE_SCHEMA] + '.' + [t1].[TABLE_NAME]), [t1].[COLUMN_NAME], 'ColumnID')
) as comment
[t1].[data_type],
COALESCE(NULLIF([t1].[character_maximum_length], -1), [t1].[numeric_precision], [t1].[datetime_precision]) AS [size],
[t1].[numeric_scale],
COLUMNPROPERTY(OBJECT_ID([t1].[table_schema] + '.' + [t1].[table_name]), [t1].[column_name], 'IsIdentity') AS [is_identity],
COLUMNPROPERTY(OBJECT_ID([t1].[table_schema] + '.' + [t1].[table_name]), [t1].[column_name], 'IsComputed') AS [is_computed],
[t2].[value] as [comment]
FROM $columnsTableName AS [t1]
LEFT JOIN [sys].[extended_properties] AS [t2]
ON [t2].[class] = 1
AND [t2].[class_desc] = 'OBJECT_OR_COLUMN'
AND [t2].[name] = 'MS_Description'
AND [t2].[major_id] = OBJECT_ID([t1].[table_schema] + '.' + [t1].[table_name])
AND [t2].[minor_id] = COLUMNPROPERTY([t2].[major_id], [t1].[column_name], 'ColumnID')
WHERE $whereSql
SQL;

try {
/** @psalm-var ColumnArray[] $columns */
$columns = $this->db->createCommand($sql, $whereParams)->queryAll();

if (empty($columns)) {
Expand All @@ -484,14 +473,17 @@ protected function findColumns(TableSchemaInterface $table): bool
return false;
}

$primaryKeys = array_fill_keys($table->getPrimaryKey(), true);

foreach ($columns as $info) {
$info = array_change_key_case($info);

/** @psalm-var ColumnArray $info */
$info['primaryKey'] = isset($primaryKeys[$info['column_name']]);
$info['schema'] = $schemaName;
$info['table'] = $tableName;

$column = $this->loadColumnSchema($info);
foreach ($table->getPrimaryKey() as $primaryKey) {
if (strcasecmp($info['column_name'], $primaryKey) === 0) {
$column->primaryKey(true);
break;
}
}

if ($column->isPrimaryKey() && $column->isAutoIncrement()) {
$table->sequenceName('');
Expand Down
3 changes: 2 additions & 1 deletion tests/CommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,8 @@ public function testAlterColumnWithDefaultNull()

$this->assertTrue($fieldCol->isNotNull());
$this->assertNull($fieldCol->getDefaultValue());
$this->assertSame('nvarchar(40)', $fieldCol->getDbType());
$this->assertSame('nvarchar', $fieldCol->getDbType());
$this->assertSame(40, $fieldCol->getSize());

$command->dropTable('column_with_constraint');
}
Expand Down
36 changes: 18 additions & 18 deletions tests/Provider/SchemaProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public static function columns(): array
'notNull' => true,
'autoIncrement' => false,
'enumValues' => null,
'size' => null,
'scale' => null,
'size' => 10,
'scale' => 0,
'defaultValue' => null,
],
'int_col2' => [
Expand All @@ -34,8 +34,8 @@ public static function columns(): array
'notNull' => false,
'autoIncrement' => false,
'enumValues' => null,
'size' => null,
'scale' => null,
'size' => 10,
'scale' => 0,
'defaultValue' => 1,
],
'tinyint_col' => [
Expand All @@ -46,8 +46,8 @@ public static function columns(): array
'notNull' => false,
'autoIncrement' => false,
'enumValues' => null,
'size' => null,
'scale' => null,
'size' => 3,
'scale' => 0,
'defaultValue' => 1,
],
'smallint_col' => [
Expand All @@ -58,13 +58,13 @@ public static function columns(): array
'notNull' => false,
'autoIncrement' => false,
'enumValues' => null,
'size' => null,
'scale' => null,
'size' => 5,
'scale' => 0,
'defaultValue' => 1,
],
'char_col' => [
'type' => 'char',
'dbType' => 'char(100)',
'dbType' => 'char',
'phpType' => 'string',
'primaryKey' => false,
'notNull' => true,
Expand All @@ -76,7 +76,7 @@ public static function columns(): array
],
'char_col2' => [
'type' => 'string',
'dbType' => 'varchar(100)',
'dbType' => 'varchar',
'phpType' => 'string',
'primaryKey' => false,
'notNull' => false,
Expand All @@ -94,13 +94,13 @@ public static function columns(): array
'notNull' => false,
'autoIncrement' => false,
'enumValues' => null,
'size' => null,
'size' => 2147483647,
'scale' => null,
'defaultValue' => null,
],
'float_col' => [
'type' => 'decimal',
'dbType' => 'decimal(4,3)',
'dbType' => 'decimal',
'phpType' => 'float',
'primaryKey' => false,
'notNull' => true,
Expand All @@ -118,7 +118,7 @@ public static function columns(): array
'notNull' => false,
'autoIncrement' => false,
'enumValues' => null,
'size' => null,
'size' => 53,
'scale' => null,
'defaultValue' => 1.23,
],
Expand All @@ -136,7 +136,7 @@ public static function columns(): array
],
'numeric_col' => [
'type' => 'decimal',
'dbType' => 'decimal(5,2)',
'dbType' => 'decimal',
'phpType' => 'float',
'primaryKey' => false,
'notNull' => false,
Expand All @@ -154,7 +154,7 @@ public static function columns(): array
'notNull' => true,
'autoIncrement' => false,
'enumValues' => null,
'size' => null,
'size' => 3,
'scale' => null,
'defaultValue' => '2002-01-01 00:00:00',
],
Expand Down Expand Up @@ -195,13 +195,13 @@ public static function columns(): array
'notNull' => true,
'autoIncrement' => true,
'enumValues' => null,
'size' => null,
'scale' => null,
'size' => 10,
'scale' => 0,
'defaultValue' => null,
],
'type' => [
'type' => 'string',
'dbType' => 'varchar(255)',
'dbType' => 'varchar',
'phpType' => 'string',
'primaryKey' => false,
'notNull' => true,
Expand Down
4 changes: 2 additions & 2 deletions tests/Provider/Type/BinaryProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ final class BinaryProvider
public static function columns(): array
{
return [
['Mybinary1', 'binary(10)', 'mixed', 10, 'CONVERT([binary](10),\'binary\')'],
['Mybinary2', 'binary(1)', 'mixed', 1, 'CONVERT([binary](1),\'b\')'],
['Mybinary1', 'binary', 'mixed', 10, 'CONVERT([binary](10),\'binary\')'],
['Mybinary2', 'binary', 'mixed', 1, 'CONVERT([binary](1),\'b\')'],
];
}
}
Loading

0 comments on commit 9f9ace2

Please sign in to comment.