Skip to content

Commit

Permalink
Refactor normalizeDefaultValue() (#370)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored Nov 16, 2024
1 parent 1364341 commit 7054c55
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 79 deletions.
File renamed without changes.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
- Enh #353: Update `bit` type according to main PR yiisoft/db#860 (@Tigrov)
- Enh #354: Refactor PHP type of `ColumnSchemaInterface` instances (@Tigrov)
- Enh #356: Raise minimum PHP version to `^8.1` with minor refactoring (@Tigrov)
- New #355, #368: Implement `ColumnFactory` class (@Tigrov)
- New #355, #368, #370: Implement `ColumnFactory` class (@Tigrov)
- Enh #359: Separate column type constants (@Tigrov)
- Enh #359: Remove `Schema::TYPE_ARRAY` and `Schema::TYPE_STRUCTURED` constants (@Tigrov)
- New #360: Realize `ColumnBuilder` class (@Tigrov)
- Enh #362: Update according changes in `ColumnSchemaInterface` (@Tigrov)
- New #364: Add `ColumnDefinitionBuilder` class (@Tigrov)
- Enh #365: Refactor `Dsn` class (@Tigrov)
- Enh #366: Use constructor to create columns and initialize properties (@Tigrov)
- Enh #370: Refactor `Schema::normalizeDefaultValue()` method and move it to `ColumnFactory` class (@Tigrov)

## 1.3.0 March 21, 2024

Expand Down
42 changes: 42 additions & 0 deletions src/Column/ColumnFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@

use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Schema\Column\AbstractColumnFactory;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;

use function preg_replace;
use function str_starts_with;
use function substr;

use const PHP_INT_SIZE;

/**
Expand Down Expand Up @@ -114,6 +119,26 @@ final class ColumnFactory extends AbstractColumnFactory
'jsonb' => ColumnType::JSON,
];

public function fromType(string $type, array $info = []): ColumnSchemaInterface
{
$column = parent::fromType($type, $info);

if ($column instanceof StructuredColumnSchema) {
/** @psalm-var array|null $defaultValue */
$defaultValue = $column->getDefaultValue();

if (is_array($defaultValue)) {
foreach ($column->getColumns() as $structuredColumnName => $structuredColumn) {
if (isset($defaultValue[$structuredColumnName])) {
$structuredColumn->defaultValue($defaultValue[$structuredColumnName]);
}
}
}
}

return $column;
}

protected function getColumnClass(string $type, array $info = []): string
{
return match ($type) {
Expand All @@ -131,4 +156,21 @@ protected function getColumnClass(string $type, array $info = []): string
default => parent::getColumnClass($type, $info),
};
}

protected function normalizeNotNullDefaultValue(string $defaultValue, ColumnSchemaInterface $column): mixed
{
$value = preg_replace("/::[^:']+$/", '$1', $defaultValue);

if (str_starts_with($value, "B'") && $value[-1] === "'") {
return $column->phpTypecast(substr($value, 2, -1));
}

$value = parent::normalizeNotNullDefaultValue($value, $column);

if ($value instanceof Expression) {
return new Expression($defaultValue);
}

return $value;
}
}
2 changes: 1 addition & 1 deletion src/Column/SequenceColumnSchemaTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ trait SequenceColumnSchemaTrait
/**
* @var string|null Name of an associated sequence if column is auto incremental.
*/
private string|null $sequenceName = null;
protected string|null $sequenceName = null;

public function getSequenceName(): string|null
{
Expand Down
87 changes: 14 additions & 73 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@
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\Pgsql\Column\ColumnFactory;
use Yiisoft\Db\Pgsql\Column\SequenceColumnSchemaInterface;
use Yiisoft\Db\Schema\Builder\ColumnInterface;
use Yiisoft\Db\Schema\Column\ColumnFactoryInterface;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;
use Yiisoft\Db\Schema\Column\StructuredColumnSchema;
use Yiisoft\Db\Schema\TableSchemaInterface;

use function array_change_key_case;
Expand All @@ -34,7 +32,6 @@
use function in_array;
use function is_string;
use function preg_match;
use function preg_replace;
use function str_replace;
use function str_starts_with;
use function substr;
Expand Down Expand Up @@ -760,92 +757,36 @@ private function loadColumnSchema(array $info): ColumnSchemaInterface
$columnInfo['columns'] = $structured->getColumns();
}

$column = $columnFactory->fromType(ColumnType::STRUCTURED, $columnInfo);
} else {
$column = $columnFactory->fromDbType($dbType, $columnInfo);
$columnInfo['type'] = ColumnType::STRUCTURED;
}

$dimension = (int) $info['dimension'];

if ($dimension > 0) {
$columnInfo['column'] = $column;
$columnInfo['column'] = $columnFactory->fromDbType($dbType, $columnInfo);
$columnInfo['dimension'] = $dimension;
$columnInfo['defaultValueRaw'] = $info['column_default'];

$column = $columnFactory->fromType(ColumnType::ARRAY, $columnInfo);
return $columnFactory->fromType(ColumnType::ARRAY, $columnInfo);
}

$defaultValue = $info['column_default'];

/**
* pg_get_serial_sequence() doesn't track DEFAULT value change.
* GENERATED BY IDENTITY columns always have a null default value.
*/
$defaultValue = $info['column_default'];

if ($column instanceof SequenceColumnSchemaInterface) {
if (
$defaultValue !== null
&& preg_match("/^nextval\('([^']+)/", $defaultValue, $matches) === 1
) {
$column->sequenceName($matches[1]);
} elseif ($info['sequence_name'] !== null) {
$column->sequenceName($this->resolveTableName($info['sequence_name'])->getFullName());
}
}

$column->defaultValue($this->normalizeDefaultValue($defaultValue, $column));

if ($column instanceof StructuredColumnSchema) {
/** @psalm-var array|null $defaultValue */
$defaultValue = $column->getDefaultValue();

if (is_array($defaultValue)) {
foreach ($column->getColumns() as $structuredColumnName => $structuredColumn) {
if (isset($defaultValue[$structuredColumnName])) {
$structuredColumn->defaultValue($defaultValue[$structuredColumnName]);
}
}
}
}

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|null $defaultValue, ColumnSchemaInterface $column): mixed
{
if (
$defaultValue === null
|| $column->isPrimaryKey()
|| str_starts_with($defaultValue, 'NULL::')
) {
return null;
if ($defaultValue !== null && preg_match("/^nextval\('([^']+)/", $defaultValue, $matches) === 1) {
$defaultValue = null;
$columnInfo['sequenceName'] = $matches[1];
} elseif ($info['sequence_name'] !== null) {
$columnInfo['sequenceName'] = $this->resolveTableName($info['sequence_name'])->getFullName();
}

if ($column->getType() === ColumnType::BOOLEAN && in_array($defaultValue, ['true', 'false'], true)) {
return $defaultValue === 'true';
}

if (
in_array($column->getType(), [ColumnType::TIMESTAMP, ColumnType::DATE, ColumnType::TIME], true)
&& in_array(strtoupper($defaultValue), ['NOW()', 'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME'], true)
) {
return new Expression($defaultValue);
}

$value = preg_replace("/^B?['(](.*?)[)'](?:::[^:]+)?$/s", '$1', $defaultValue);
$value = str_replace("''", "'", $value);

if ($column->getType() === ColumnType::BINARY && str_starts_with($value, '\\x')) {
return hex2bin(substr($value, 2));
}
$columnInfo['defaultValueRaw'] = $defaultValue;

return $column->phpTypecast($value);
/** @psalm-suppress InvalidArgument */
return $columnFactory->fromDbType($dbType, $columnInfo);
}

/**
Expand Down
16 changes: 12 additions & 4 deletions tests/ColumnFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace Yiisoft\Db\Pgsql\Tests;

use PHPUnit\Framework\Attributes\DataProviderExternal;
use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Pgsql\Column\ArrayColumnSchema;
use Yiisoft\Db\Pgsql\Tests\Provider\ColumnFactoryProvider;
use Yiisoft\Db\Pgsql\Tests\Support\TestTrait;
use Yiisoft\Db\Tests\AbstractColumnFactoryTest;

Expand All @@ -16,7 +18,7 @@ final class ColumnFactoryTest extends AbstractColumnFactoryTest
{
use TestTrait;

/** @dataProvider \Yiisoft\Db\Pgsql\Tests\Provider\ColumnFactoryProvider::dbTypes */
#[DataProviderExternal(ColumnFactoryProvider::class, 'dbTypes')]
public function testFromDbType(string $dbType, string $expectedType, string $expectedInstanceOf): void
{
parent::testFromDbType($dbType, $expectedType, $expectedInstanceOf);
Expand All @@ -35,7 +37,7 @@ public function testFromDbType(string $dbType, string $expectedType, string $exp
$db->close();
}

/** @dataProvider \Yiisoft\Db\Pgsql\Tests\Provider\ColumnFactoryProvider::definitions */
#[DataProviderExternal(ColumnFactoryProvider::class, 'definitions')]
public function testFromDefinition(
string $definition,
string $expectedType,
Expand All @@ -45,7 +47,7 @@ public function testFromDefinition(
parent::testFromDefinition($definition, $expectedType, $expectedInstanceOf, $expectedMethodResults);
}

/** @dataProvider \Yiisoft\Db\Pgsql\Tests\Provider\ColumnFactoryProvider::pseudoTypes */
#[DataProviderExternal(ColumnFactoryProvider::class, 'pseudoTypes')]
public function testFromPseudoType(
string $pseudoType,
string $expectedType,
Expand All @@ -55,7 +57,7 @@ public function testFromPseudoType(
parent::testFromPseudoType($pseudoType, $expectedType, $expectedInstanceOf, $expectedMethodResults);
}

/** @dataProvider \Yiisoft\Db\Pgsql\Tests\Provider\ColumnFactoryProvider::types */
#[DataProviderExternal(ColumnFactoryProvider::class, 'types')]
public function testFromType(string $type, string $expectedType, string $expectedInstanceOf): void
{
parent::testFromType($type, $expectedType, $expectedInstanceOf);
Expand All @@ -72,4 +74,10 @@ public function testFromType(string $type, string $expectedType, string $expecte

$db->close();
}

#[DataProviderExternal(ColumnFactoryProvider::class, 'defaultValueRaw')]
public function testFromTypeDefaultValueRaw(string $type, string|null $defaultValueRaw, mixed $expected): void
{
parent::testFromTypeDefaultValueRaw($type, $defaultValueRaw, $expected);
}
}
18 changes: 18 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\Pgsql\Tests\Provider;

use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Pgsql\Column\BinaryColumnSchema;
use Yiisoft\Db\Pgsql\Column\BitColumnSchema;
use Yiisoft\Db\Pgsql\Column\BooleanColumnSchema;
Expand Down Expand Up @@ -100,4 +101,21 @@ public static function pseudoTypes(): array

return $result;
}

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

$defaultValueRaw[] = [ColumnType::TEXT, 'NULL::"text"', null];
$defaultValueRaw[] = [ColumnType::TEXT, '(NULL)::"text"', null];
$defaultValueRaw[] = [ColumnType::TEXT, "'str''ing'::\"text\"", "str'ing"];
$defaultValueRaw[] = [ColumnType::TEXT, "'str::ing'::\"text\"", 'str::ing'];
$defaultValueRaw[] = [ColumnType::INTEGER, '(-1)::"int"', -1];
$defaultValueRaw[] = [ColumnType::BIT, "B'1011'::\"bit\"", 0b1011];
$defaultValueRaw[] = [ColumnType::STRING, "'\\x737472696e67'", '\\x737472696e67'];
$defaultValueRaw[] = [ColumnType::BINARY, "'\\x737472696e67'::bytea", 'string'];
$defaultValueRaw[] = [ColumnType::BINARY, '(1 + 2)::int', new Expression('(1 + 2)::int')];

return $defaultValueRaw;
}
}

0 comments on commit 7054c55

Please sign in to comment.