Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use new column definition builder #374

Merged
merged 11 commits into from
Dec 23, 2024
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 @@
. $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 @@
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)';

Check warning on line 100 in src/Column/ColumnDefinitionBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/Column/ColumnDefinitionBuilder.php#L99-L100

Added lines #L99 - L100 were not covered by tests
}

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
Loading