Skip to content

Commit

Permalink
Use new column definition builder (#374)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored Dec 23, 2024
1 parent 3481791 commit 900b897
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 255 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- Enh #370: Refactor `Schema::normalizeDefaultValue()` method and move it to `ColumnFactory` class (@Tigrov)
- New #373: Override `QueryBuilder::prepareBinary()` method (@Tigrov)
- Chg #375: Update `QueryBuilder` constructor (@Tigrov)
- Enh #374: Use `ColumnDefinitionBuilder` to generate table column SQL representation (@Tigrov)

## 1.3.0 March 21, 2024

Expand Down
4 changes: 4 additions & 0 deletions src/Column.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
*
* Provides a fluent interface, which means that the methods can be chained together to create a column schema with
* many properties in a single line of code.
*
* @psalm-suppress DeprecatedClass
*
* @deprecated Use {@see StringColumnSchema} or other column classes instead. Will be removed in 2.0.0.
*/
final class Column extends AbstractColumn
{
Expand Down
22 changes: 20 additions & 2 deletions src/Column/ColumnDefinitionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
use Yiisoft\Db\QueryBuilder\AbstractColumnDefinitionBuilder;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;

use function version_compare;

final class ColumnDefinitionBuilder extends AbstractColumnDefinitionBuilder
{
protected const GENERATE_UUID_EXPRESSION = 'gen_random_uuid()';

protected const TYPES_WITH_SIZE = [
'bit',
'bit varying',
Expand Down Expand Up @@ -47,6 +47,12 @@ public function build(ColumnSchemaInterface $column): string
. $this->buildExtra($column);
}

public function buildAlter(ColumnSchemaInterface $column): string
{
return $this->buildType($column)
. $this->buildExtra($column);
}

protected function buildType(ColumnSchemaInterface $column): string
{
if ($column instanceof \Yiisoft\Db\Schema\Column\ArrayColumnSchema) {
Expand Down Expand Up @@ -84,4 +90,16 @@ protected function getDbType(ColumnSchemaInterface $column): string
default => 'varchar',
};
}

protected function getDefaultUuidExpression(): string
{
$serverVersion = $this->queryBuilder->getServerInfo()->getVersion();

if (version_compare($serverVersion, '13', '<')) {
return "uuid_in(overlay(overlay(md5(now()::text || random()::text) placing '4' from 13) placing"
. ' to_hex(floor(4 * random() + 8)::int)::text from 17)::cstring)';
}

return 'gen_random_uuid()';
}
}
58 changes: 27 additions & 31 deletions src/DDLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder;
use Yiisoft\Db\Schema\Builder\ColumnInterface;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;

use function array_diff;
use function array_unshift;
use function explode;
use function implode;
use function is_string;
use function preg_match;
use function preg_replace;
use function str_contains;
Expand All @@ -27,7 +28,7 @@ public function addDefaultValue(string $table, string $name, string $column, mix
throw new NotSupportedException(__METHOD__ . ' is not supported by PostgreSQL.');
}

public function alterColumn(string $table, string $column, ColumnInterface|string $type): string
public function alterColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): string
{
$columnName = $this->quoter->quoteColumnName($column);
$tableName = $this->quoter->quoteTableName($table);
Expand All @@ -40,47 +41,42 @@ public function alterColumn(string $table, string $column, ColumnInterface|strin
* @link https://github.com/yiisoft/yii2/issues/4492
* @link https://www.postgresql.org/docs/9.1/static/sql-altertable.html
*/
if (preg_match('/^(DROP|SET|RESET|USING)\s+/i', $type)) {
return "ALTER TABLE $tableName ALTER COLUMN $columnName $type";
if (is_string($type)) {
if (preg_match('/^(DROP|SET|RESET|USING)\s+/i', $type) === 1) {
return "ALTER TABLE $tableName ALTER COLUMN $columnName $type";
}

$type = $this->schema->getColumnFactory()->fromDefinition($type);
}

/** @psalm-suppress DeprecatedMethod */
$type = 'TYPE ' . $this->queryBuilder->getColumnType($type);
$multiAlterStatement = [];
$constraintPrefix = preg_replace('/[^a-z0-9_]/i', '', $table . '_' . $column);
$columnDefinitionBuilder = $this->queryBuilder->getColumnDefinitionBuilder();

if (preg_match('/\s+DEFAULT\s+(["\']?\w*["\']?)/i', $type, $matches)) {
$type = preg_replace('/\s+DEFAULT\s+(["\']?\w*["\']?)/i', '', $type);
$multiAlterStatement[] = "ALTER COLUMN $columnName SET DEFAULT $matches[1]";
}
$multiAlterStatement = ["ALTER COLUMN $columnName TYPE " . $columnDefinitionBuilder->buildAlter($type)];

$type = preg_replace('/\s+NOT\s+NULL/i', '', $type, -1, $count);
if ($type->hasDefaultValue()) {
$defaultValue = $type->dbTypecast($type->getDefaultValue());
$defaultValue = $this->queryBuilder->prepareValue($defaultValue);

if ($count > 0) {
$multiAlterStatement[] = "ALTER COLUMN $columnName SET NOT NULL";
} else {
/** remove extra null if any */
$type = preg_replace('/\s+NULL/i', '', $type, -1, $count);
if ($count > 0) {
$multiAlterStatement[] = "ALTER COLUMN $columnName DROP NOT NULL";
}
$multiAlterStatement[] = "ALTER COLUMN $columnName SET DEFAULT $defaultValue";
}

if (preg_match('/\s+CHECK\s+\((.+)\)/i', $type, $matches)) {
$type = preg_replace('/\s+CHECK\s+\((.+)\)/i', '', $type);
$multiAlterStatement[] = "ADD CONSTRAINT {$constraintPrefix}_check CHECK ($matches[1])";
}
match ($type->isNotNull()) {
true => $multiAlterStatement[] = "ALTER COLUMN $columnName SET NOT NULL",
false => $multiAlterStatement[] = "ALTER COLUMN $columnName DROP NOT NULL",
default => null,
};

$type = preg_replace('/\s+UNIQUE/i', '', $type, -1, $count);
$check = $type->getCheck();
if (!empty($check)) {
$constraintPrefix = preg_replace('/\W/', '', $table . '_' . $column);
$multiAlterStatement[] = "ADD CONSTRAINT {$constraintPrefix}_check CHECK ($check)";
}

if ($count > 0) {
if ($type->isUnique()) {
$multiAlterStatement[] = "ADD UNIQUE ($columnName)";
}

/** add what's left at the beginning */
array_unshift($multiAlterStatement, "ALTER COLUMN $columnName $type");

return 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $multiAlterStatement);
return "ALTER TABLE $tableName " . implode(', ', $multiAlterStatement);
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ final class Schema extends AbstractPdoSchema
/** @deprecated Use {@see ColumnBuilder} instead. Will be removed in 2.0. */
public function createColumn(string $type, array|int|string $length = null): ColumnInterface
{
/** @psalm-suppress DeprecatedClass */
return new Column($type, $length);
}

Expand Down
26 changes: 0 additions & 26 deletions tests/ColumnSchemaBuilderTest.php

This file was deleted.

28 changes: 0 additions & 28 deletions tests/Provider/ColumnSchemaBuilderProvider.php

This file was deleted.

45 changes: 45 additions & 0 deletions tests/Provider/QueryBuilderProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,45 @@
use Yiisoft\Db\Tests\Support\TraversableObject;

use function array_replace;
use function version_compare;

final class QueryBuilderProvider extends \Yiisoft\Db\Tests\Provider\QueryBuilderProvider
{
use TestTrait;

protected static string $driverName = 'pgsql';

public static function alterColumn(): array
{
return [
['SET NOT null', 'ALTER TABLE "foo1" ALTER COLUMN "bar" SET NOT null'],
['drop default', 'ALTER TABLE "foo1" ALTER COLUMN "bar" drop default'],
['reset xyz', 'ALTER TABLE "foo1" ALTER COLUMN "bar" reset xyz'],
['string', 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255)'],
['varchar(255)', 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255)'],
['string NOT NULL', 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255), ALTER COLUMN "bar" SET NOT NULL'],
['string NULL', 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255), ALTER COLUMN "bar" DROP NOT NULL'],
['string DEFAULT NULL', 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255), ALTER COLUMN "bar" SET DEFAULT NULL'],
["string DEFAULT ''", 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255), ALTER COLUMN "bar" SET DEFAULT \'\''],
['timestamp(0) DEFAULT now()', 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE timestamp(0), ALTER COLUMN "bar" SET DEFAULT now()'],
['string CHECK (char_length(bar) > 5)', 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255), ADD CONSTRAINT foo1_bar_check CHECK (char_length(bar) > 5)'],
['string(30) UNIQUE', 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(30), ADD UNIQUE ("bar")'],
['varchar(255) USING bar::varchar', 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255) USING bar::varchar'],
['varchar(255) using cast("bar" as varchar)', 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255) using cast("bar" as varchar)'],
[ColumnBuilder::string(), 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255)'],
[ColumnBuilder::string()->notNull(), 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255), ALTER COLUMN "bar" SET NOT NULL'],
[ColumnBuilder::string()->null(), 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255), ALTER COLUMN "bar" DROP NOT NULL'],
[ColumnBuilder::string()->defaultValue(null), 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255), ALTER COLUMN "bar" SET DEFAULT NULL'],
[ColumnBuilder::string()->defaultValue(''), 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255), ALTER COLUMN "bar" SET DEFAULT \'\''],
[ColumnBuilder::string()->null()->defaultValue('xxx'), 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255), ALTER COLUMN "bar" SET DEFAULT \'xxx\', ALTER COLUMN "bar" DROP NOT NULL'],
[ColumnBuilder::timestamp()->defaultValue(new Expression('now()')), 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE timestamp(0), ALTER COLUMN "bar" SET DEFAULT now()'],
[ColumnBuilder::string()->check('char_length(bar) > 5'), 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255), ADD CONSTRAINT foo1_bar_check CHECK (char_length(bar) > 5)'],
[ColumnBuilder::string(30)->unique(), 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(30), ADD UNIQUE ("bar")'],
[ColumnBuilder::string()->extra('USING bar::varchar'), 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255) USING bar::varchar'],
[ColumnBuilder::string()->extra('using cast("bar" as varchar)'), 'ALTER TABLE "foo1" ALTER COLUMN "bar" TYPE varchar(255) using cast("bar" as varchar)'],
];
}

public static function buildCondition(): array
{
$buildCondition = parent::buildCondition();
Expand Down Expand Up @@ -591,6 +623,19 @@ public static function buildColumnDefinition(): array
$values['scale(2)'][0] = 'numeric(10,2)';
$values['integer(8)->scale(2)'][0] = 'integer';

$db = self::getDb();
$serverVersion = self::getDb()->getServerInfo()->getVersion();
$db->close();

if (version_compare($serverVersion, '13', '<')) {
$uuidExpression = "uuid_in(overlay(overlay(md5(now()::text || random()::text) placing '4' from 13) placing"
. ' to_hex(floor(4 * random() + 8)::int)::text from 17)::cstring)';

$values[PseudoType::UUID_PK][0] = "uuid PRIMARY KEY DEFAULT $uuidExpression";
$values[PseudoType::UUID_PK_SEQ][0] = "uuid PRIMARY KEY DEFAULT $uuidExpression";
$values['uuidPrimaryKey()'][0] = "uuid PRIMARY KEY DEFAULT $uuidExpression";
}

return $values;
}

Expand Down
Loading

0 comments on commit 900b897

Please sign in to comment.