Skip to content

Commit

Permalink
Refactor normalizeDefaultValue() (#363)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored Nov 16, 2024
1 parent b8ffabc commit 7a74430
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 50 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
55 changes: 55 additions & 0 deletions src/Column/ColumnFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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);
}
}
47 changes: 1 addition & 46 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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',
Expand All @@ -426,55 +423,13 @@ 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)));
}

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.
*
Expand Down
30 changes: 26 additions & 4 deletions tests/ColumnFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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();
}
}
19 changes: 19 additions & 0 deletions tests/Provider/ColumnFactoryProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)')],
];
}
}

0 comments on commit 7a74430

Please sign in to comment.