From 7a74430747b90689151023af05f1861fa0961799 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Sat, 16 Nov 2024 09:28:05 +0700 Subject: [PATCH] Refactor `normalizeDefaultValue()` (#363) --- CHANGELOG.md | 1 + src/Column/ColumnFactory.php | 55 ++++++++++++++++++++++++ src/Schema.php | 47 +------------------- tests/ColumnFactoryTest.php | 30 +++++++++++-- tests/Provider/ColumnFactoryProvider.php | 19 ++++++++ 5 files changed, 102 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 180b1bbb..7990376e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - New #358: Add `ColumnDefinitionBuilder` class (@Tigrov) - Enh #359: Refactor `Dsn` class (@Tigrov) - Enh #361, #362: Refactor `Schema::findColumns()` method (@Tigrov) +- Enh #363: Refactor `Schema::normalizeDefaultValue()` method and move it to `ColumnFactory` class (@Tigrov) ## 1.2.0 March 21, 2024 diff --git a/src/Column/ColumnFactory.php b/src/Column/ColumnFactory.php index df1cf580..455a6a55 100644 --- a/src/Column/ColumnFactory.php +++ b/src/Column/ColumnFactory.php @@ -5,7 +5,15 @@ namespace Yiisoft\Db\Mysql\Column; use Yiisoft\Db\Constant\ColumnType; +use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Schema\Column\AbstractColumnFactory; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; + +use function bindec; +use function in_array; +use function preg_match; +use function str_starts_with; +use function substr; final class ColumnFactory extends AbstractColumnFactory { @@ -55,4 +63,51 @@ protected function getType(string $dbType, array $info = []): string return parent::getType($dbType, $info); } + + protected function normalizeDefaultValue(string|null $defaultValue, ColumnSchemaInterface $column): mixed + { + if ( + $defaultValue === null + || $column->isPrimaryKey() + || $column->isComputed() + ) { + return null; + } + + return $this->normalizeNotNullDefaultValue($defaultValue, $column); + } + + protected function normalizeNotNullDefaultValue(string $defaultValue, ColumnSchemaInterface $column): mixed + { + if ($defaultValue === '') { + return $column->phpTypecast($defaultValue); + } + + if ( + in_array($column->getType(), [ColumnType::TIMESTAMP, ColumnType::DATETIME, ColumnType::DATE, ColumnType::TIME], true) + && preg_match('/^current_timestamp(?:\((\d*)\))?$/i', $defaultValue, $matches) === 1 + ) { + return new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : '')); + } + + if (!empty($column->getExtra())) { + return new Expression($defaultValue); + } + + if ($defaultValue[0] === "'" && $defaultValue[-1] === "'") { + $value = substr($defaultValue, 1, -1); + $value = str_replace("''", "'", $value); + + return $column->phpTypecast($value); + } + + if ( + str_starts_with($defaultValue, "b'") + && in_array($column->getType(), [ColumnType::BOOLEAN, ColumnType::BIT], true) + ) { + return $column->phpTypecast(bindec(substr($defaultValue, 2, -1))); + } + + return $column->phpTypecast($defaultValue); + } } diff --git a/src/Schema.php b/src/Schema.php index 3ae7259d..b5e56eb3 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -13,7 +13,6 @@ use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; -use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Helper\DbArrayHelper; use Yiisoft\Db\Mysql\Column\ColumnBuilder; use Yiisoft\Db\Mysql\Column\ColumnFactory; @@ -25,18 +24,15 @@ use function array_change_key_case; use function array_map; use function array_values; -use function bindec; use function in_array; use function is_string; use function ksort; use function md5; use function preg_match_all; -use function preg_match; use function serialize; use function str_contains; use function str_ireplace; use function str_starts_with; -use function strtolower; use function substr; use function trim; @@ -417,6 +413,7 @@ private function loadColumnSchema(array $info): ColumnSchemaInterface $column = $this->getColumnFactory()->fromDefinition($info['column_type'], [ 'autoIncrement' => $autoIncrement > 0, 'comment' => $info['column_comment'], + 'defaultValueRaw' => $info['column_default'], 'extra' => $extra, 'name' => $info['column_name'], 'notNull' => $info['is_nullable'] !== 'YES', @@ -426,8 +423,6 @@ private function loadColumnSchema(array $info): ColumnSchemaInterface 'unique' => $info['column_key'] === 'UNI', ]); - $column->defaultValue($this->normalizeDefaultValue($info['column_default'], $column)); - if (str_starts_with($extra, 'DEFAULT_GENERATED')) { $column->extra(trim(substr($extra, 18))); } @@ -435,46 +430,6 @@ private function loadColumnSchema(array $info): ColumnSchemaInterface return $column; } - /** - * Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database. - * - * @param string|null $defaultValue The default value retrieved from the database. - * @param ColumnSchemaInterface $column The column schema object. - * - * @return mixed The normalized default value. - */ - private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $column): mixed - { - if ($defaultValue === null) { - return null; - } - - if ($column->isPrimaryKey()) { - return $column->phpTypecast($defaultValue); - } - - if ( - in_array($column->getType(), [ColumnType::TIMESTAMP, ColumnType::DATETIME, ColumnType::DATE, ColumnType::TIME], true) - && preg_match('/^current_timestamp(?:\((\d*)\))?$/i', $defaultValue, $matches) === 1 - ) { - return new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : '')); - } - - if (!empty($defaultValue) && !empty($column->getExtra())) { - return new Expression($defaultValue); - } - - if (str_starts_with(strtolower((string) $column->getDbType()), 'bit')) { - return $column->phpTypecast(bindec(trim($defaultValue, "b'"))); - } - - if ($defaultValue[0] === "'" && $defaultValue[-1] === "'") { - return $column->phpTypecast(substr($defaultValue, 1, -1)); - } - - return $column->phpTypecast($defaultValue); - } - /** * Loads all check constraints for the given table. * diff --git a/tests/ColumnFactoryTest.php b/tests/ColumnFactoryTest.php index 195346aa..4e5f73cd 100644 --- a/tests/ColumnFactoryTest.php +++ b/tests/ColumnFactoryTest.php @@ -4,6 +4,10 @@ namespace Yiisoft\Db\Mysql\Tests; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use Yiisoft\Db\Constant\ColumnType; +use Yiisoft\Db\Expression\Expression; +use Yiisoft\Db\Mysql\Tests\Provider\ColumnFactoryProvider; use Yiisoft\Db\Mysql\Tests\Support\TestTrait; use Yiisoft\Db\Tests\AbstractColumnFactoryTest; @@ -14,13 +18,13 @@ final class ColumnFactoryTest extends AbstractColumnFactoryTest { use TestTrait; - /** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\ColumnFactoryProvider::dbTypes */ + #[DataProviderExternal(ColumnFactoryProvider::class, 'dbTypes')] public function testFromDbType(string $dbType, string $expectedType, string $expectedInstanceOf): void { parent::testFromDbType($dbType, $expectedType, $expectedInstanceOf); } - /** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\ColumnFactoryProvider::definitions */ + #[DataProviderExternal(ColumnFactoryProvider::class, 'definitions')] public function testFromDefinition( string $definition, string $expectedType, @@ -30,7 +34,7 @@ public function testFromDefinition( parent::testFromDefinition($definition, $expectedType, $expectedInstanceOf, $expectedMethodResults); } - /** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\ColumnFactoryProvider::pseudoTypes */ + #[DataProviderExternal(ColumnFactoryProvider::class, 'pseudoTypes')] public function testFromPseudoType( string $pseudoType, string $expectedType, @@ -40,9 +44,27 @@ public function testFromPseudoType( parent::testFromPseudoType($pseudoType, $expectedType, $expectedInstanceOf, $expectedMethodResults); } - /** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\ColumnFactoryProvider::types */ + #[DataProviderExternal(ColumnFactoryProvider::class, 'types')] public function testFromType(string $type, string $expectedType, string $expectedInstanceOf): void { parent::testFromType($type, $expectedType, $expectedInstanceOf); } + + #[DataProviderExternal(ColumnFactoryProvider::class, 'defaultValueRaw')] + public function testFromTypeDefaultValueRaw(string $type, string|null $defaultValueRaw, mixed $expected): void + { + parent::testFromTypeDefaultValueRaw($type, $defaultValueRaw, $expected); + } + + public function testExpressionDefaultValueRaw(): void + { + $db = $this->getConnection(); + $columnFactory = $db->getSchema()->getColumnFactory(); + + $column = $columnFactory->fromType(ColumnType::DATETIME, ['defaultValueRaw' => 'now()', 'extra' => 'DEFAULT_GENERATED']); + + $this->assertEquals(new Expression('now()'), $column->getDefaultValue()); + + $db->close(); + } } diff --git a/tests/Provider/ColumnFactoryProvider.php b/tests/Provider/ColumnFactoryProvider.php index fe2b1bfa..597644fc 100644 --- a/tests/Provider/ColumnFactoryProvider.php +++ b/tests/Provider/ColumnFactoryProvider.php @@ -5,6 +5,7 @@ namespace Yiisoft\Db\Mysql\Tests\Provider; use Yiisoft\Db\Constant\ColumnType; +use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Schema\Column\BinaryColumnSchema; use Yiisoft\Db\Schema\Column\BitColumnSchema; use Yiisoft\Db\Schema\Column\BooleanColumnSchema; @@ -59,4 +60,22 @@ public static function definitions(): array return $definitions; } + + public static function defaultValueRaw(): array + { + return [ + // type, default value, expected value + [ColumnType::STRING, null, null], + [ColumnType::STRING, '', ''], + [ColumnType::INTEGER, '-1', -1], + [ColumnType::DOUBLE, '0.0', 0.0], + [ColumnType::DOUBLE, '-1.1', -1.1], + [ColumnType::BOOLEAN, "b'1'", true], + [ColumnType::BOOLEAN, "b'0'", false], + [ColumnType::BIT, "b'1011'", 0b1011], + [ColumnType::STRING, "'str''ing'", "str'ing"], + [ColumnType::TIMESTAMP, 'CURRENT_TIMESTAMP', new Expression('CURRENT_TIMESTAMP')], + [ColumnType::TIMESTAMP, 'current_timestamp(3)', new Expression('CURRENT_TIMESTAMP(3)')], + ]; + } }