Skip to content

Commit

Permalink
Refactor ColumnFactory (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored Nov 8, 2024
1 parent ccb8b73 commit 0630eb6
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 225 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
- Chg #339: Replace call of `SchemaInterface::getRawTableName()` to `QuoterInterface::getRawTableName()` (@Tigrov)
- New #342: Add JSON overlaps condition builder (@Tigrov)
- Enh #344: Update `bit` type according to main PR yiisoft/db#860 (@Tigrov)
- New #346: Implement `ColumnFactory` class (@Tigrov)
- New #346, #361: 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)
- Enh #354: Separate column type constants (@Tigrov)
- New #355: Realize `ColumnBuilder` class (@Tigrov)
- Enh #357: Update according changes in `ColumnSchemaInterface` (@Tigrov)
- New #358: Add `ColumnDefinitionBuilder` class (@Tigrov)
- Enh #359: Refactor `Dsn` class (@Tigrov)
- Enh #361: Refactor `Schema::findColumns()` method (@Tigrov)

## 1.2.0 March 21, 2024

Expand Down
16 changes: 4 additions & 12 deletions src/Column/ColumnFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ final class ColumnFactory extends AbstractColumnFactory
* Mapping from physical column types (keys) to abstract column types (values).
*
* @var string[]
*
* @psalm-suppress MissingClassConstType
* @psalm-var array<string, ColumnType::*>
*/
private const TYPE_MAP = [
protected const TYPE_MAP = [
'bit' => ColumnType::BIT,
'tinyint' => ColumnType::TINYINT,
'smallint' => ColumnType::SMALLINT,
Expand Down Expand Up @@ -50,17 +49,10 @@ final class ColumnFactory extends AbstractColumnFactory

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

if ($type === ColumnType::BIT && isset($info['size']) && $info['size'] === 1) {
if ($dbType === 'bit' && isset($info['size']) && $info['size'] === 1) {
return ColumnType::BOOLEAN;
}

return $type;
}

protected function isDbType(string $dbType): bool
{
return isset(self::TYPE_MAP[$dbType]);
return parent::getType($dbType, $info);
}
}
163 changes: 72 additions & 91 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,26 @@
use function preg_match_all;
use function preg_match;
use function serialize;
use function stripos;
use function str_contains;
use function str_ireplace;
use function str_starts_with;
use function strtolower;
use function substr;
use function trim;

/**
* Implements MySQL, MariaDB specific schema, supporting MySQL Server 5.7, MariaDB Server 10.4 and higher.
*
* @psalm-type ColumnInfoArray = array{
* field: string,
* type: string,
* collation: string|null,
* null: string,
* key: string,
* default: string|null,
* @psalm-type ColumnArray = array{
* column_name: string,
* column_default: string|null,
* is_nullable: string,
* column_type: string,
* column_key: string,
* extra: string,
* extra_default_value: string|null,
* privileges: string,
* comment: string,
* enum_values?: string[],
* size?: int,
* scale?: int,
* column_comment: string,
* schema: string,
* table: string
* }
* @psalm-type RowConstraint = array{
* constraint_name: string,
Expand Down Expand Up @@ -140,63 +139,56 @@ public function findUniqueIndexes(TableSchemaInterface $table): array
*/
protected function findColumns(TableSchemaInterface $table): bool
{
$tableName = $table->getFullName() ?? '';
$sql = 'SHOW FULL COLUMNS FROM ' . $this->db->getQuoter()->quoteTableName($tableName);

try {
$columns = $this->db->createCommand($sql)->queryAll();
// Chapter 1: crutches for MariaDB. {@see https://github.com/yiisoft/yii2/issues/19747}
$columnsExtra = [];
if (str_contains($this->db->getServerVersion(), 'MariaDB')) {
$rows = $this->db->createCommand(
<<<SQL
SELECT `COLUMN_NAME` as name,`COLUMN_DEFAULT` as default_value
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = COALESCE(:schemaName, DATABASE()) AND TABLE_NAME = :tableName
SQL ,
[
':schemaName' => $table->getSchemaName(),
':tableName' => $table->getName(),
]
)->queryAll();
/** @psalm-var string[] $cols */
foreach ($rows as $cols) {
$columnsExtra[$cols['name']] = $cols['default_value'];
}
}
} catch (Exception $e) {
$previous = $e->getPrevious();

if ($previous && str_contains($previous->getMessage(), 'SQLSTATE[42S02')) {
/**
* The table doesn't exist.
*
* @link https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html#error_er_bad_table_error
*/
return false;
}

throw $e;
$schemaName = $table->getSchemaName();
$tableName = $table->getName();

$columns = $this->db->createCommand(
<<<SQL
SELECT
`COLUMN_NAME`,
`COLUMN_DEFAULT`,
`IS_NULLABLE`,
`COLUMN_TYPE`,
`COLUMN_KEY`,
`EXTRA`,
`COLUMN_COMMENT`
FROM `INFORMATION_SCHEMA`.`COLUMNS`
WHERE `TABLE_SCHEMA` = COALESCE(:schemaName, DATABASE())
AND `TABLE_NAME` = :tableName
SQL,
[
':schemaName' => $schemaName,
':tableName' => $tableName,
]
)->queryAll();

if (empty($columns)) {
return false;
}

$jsonColumns = $this->getJsonColumns($table);
$isMariaDb = str_contains($this->db->getServerVersion(), 'MariaDB');

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

$info['extra_default_value'] = $columnsExtra[$info['field']] ?? '';
$info['schema'] = $schemaName;
$info['table'] = $tableName;

if (in_array($info['field'], $jsonColumns, true)) {
$info['type'] = ColumnType::JSON;
if (in_array($info['column_name'], $jsonColumns, true)) {
$info['column_type'] = ColumnType::JSON;
}

if ($isMariaDb && $info['column_default'] === 'NULL') {
$info['column_default'] = null;
}

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

if ($column->isPrimaryKey()) {
$table->primaryKey($info['field']);
$table->primaryKey($info['column_name']);
if ($column->isAutoIncrement()) {
$table->sequenceName('');
}
Expand Down Expand Up @@ -415,43 +407,28 @@ protected function getCreateTableSql(TableSchemaInterface $table): string
*
* @return ColumnSchemaInterface The column schema object.
*
* @psalm-param ColumnInfoArray $info The column information.
* @psalm-param ColumnArray $info The column information.
*/
private function loadColumnSchema(array $info): ColumnSchemaInterface
{
$columnFactory = $this->getColumnFactory();

$dbType = $info['type'];
/** @psalm-var ColumnInfoArray $info */
$column = $columnFactory->fromDefinition($dbType);
/** @psalm-suppress DeprecatedMethod */
$column->name($info['field']);
$column->notNull($info['null'] !== 'YES');
$column->primaryKey($info['key'] === 'PRI');
$column->autoIncrement(stripos($info['extra'], 'auto_increment') !== false);
$column->unique($info['key'] === 'UNI');
$column->comment($info['comment']);
$column->dbType($dbType);

// 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(), [
ColumnType::CHAR, ColumnType::STRING, ColumnType::TEXT,
ColumnType::DATETIME, ColumnType::TIMESTAMP, ColumnType::TIME, ColumnType::DATE,
], true)
) {
$extra = 'DEFAULT_GENERATED';
}

$column->extra($extra);
$column->defaultValue($this->normalizeDefaultValue($info['default'], $column));
$extra = trim(str_ireplace('auto_increment', '', $info['extra'], $autoIncrement));

$column = $this->getColumnFactory()->fromDefinition($info['column_type'], [
'autoIncrement' => $autoIncrement > 0,
'comment' => $info['column_comment'],
'extra' => $extra,
'name' => $info['column_name'],
'notNull' => $info['is_nullable'] !== 'YES',
'primaryKey' => $info['column_key'] === 'PRI',
'schema' => $info['schema'],
'table' => $info['table'],
'unique' => $info['column_key'] === 'UNI',
]);

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

if (str_starts_with($extra, 'DEFAULT_GENERATED')) {
$column->extra(trim(strtoupper(substr($extra, 18))));
$column->extra(trim(substr($extra, 18)));
}

return $column;
Expand Down Expand Up @@ -490,6 +467,10 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf
return $column->phpTypecast(bindec(trim($defaultValue, "b'")));
}

if ($defaultValue[0] === "'" && $defaultValue[-1] === "'") {
return $column->phpTypecast(substr($defaultValue, 1, -1));
}

return $column->phpTypecast($defaultValue);
}

Expand Down Expand Up @@ -820,7 +801,7 @@ private function getJsonColumns(TableSchemaInterface $table): array
{
$sql = $this->getCreateTableSql($table);
$result = [];
$regexp = '/json_valid\([\`"](.+)[\`"]\s*\)/mi';
$regexp = '/json_valid\([`"](.+)[`"]\s*\)/mi';

if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER) > 0) {
foreach ($matches as $match) {
Expand Down
Loading

0 comments on commit 0630eb6

Please sign in to comment.