diff --git a/CHANGELOG.md b/CHANGELOG.md index e11cbd3e4..ff995a0d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,16 +35,18 @@ - Enh #865: Raise minimum PHP version to `^8.1` with minor refactoring (@Tigrov, @vjik) - Enh #798: Allow `QueryInterface::one()` and `QueryInterface::all()` to return objects (@darkdef, @Tigrov) - Enh #872: Use `#[\SensitiveParameter]` attribute to mark sensitive parameters (@heap-s) -- New #864: Realize column factory (@Tigrov) +- New #864, #897, #898: Realize column factory (@Tigrov) - Enh #875: Ignore "Packets out of order..." warnings in `AbstractPdoCommand::internalExecute()` method (@Tigrov) - Enh #877: Separate column type constants (@Tigrov) -- Enh #878: Realize `ColumnBuilder` class (@Tigrov) +- New #878: Realize `ColumnBuilder` class (@Tigrov) +- New #878, #900: Realize `ColumnDefinitionParser` class (@Tigrov) - Enh #881: Refactor `ColumnSchemaInterface` and `AbstractColumnSchema` (@Tigrov) - New #882: Move `ArrayColumnSchema` and `StructuredColumnSchema` classes from `db-pgsql` package (@Tigrov) -- New #883: Add `ColumnDefinitionBuilder` class and `QueryBuilderInterface::buildColumnDefinition()` method (@Tigrov) +- New #883, #901: Add `ColumnDefinitionBuilder` class and `QueryBuilderInterface::buildColumnDefinition()` method (@Tigrov) - Enh #885: Refactor `AbstractDsn` class (@Tigrov) - Chg #889: Update `AbstractDMLQueryBuilder::insertBatch()` method (@Tigrov) - Enh #890: Add properties of `AbstractColumnSchema` class to constructor (@Tigrov) +- New #899: Add `ColumnSchemaInterface::hasDefaultValue()` and `ColumnSchemaInterface::null()` methods (@Tigrov) ## 1.3.0 March 21, 2024 diff --git a/UPGRADE.md b/UPGRADE.md index a22e6df49..2b6379bbc 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -72,9 +72,11 @@ and the following changes were made: - `reference(ForeignKeyConstraint|null $reference)` method is added; - `getReference()` method is added; - `notNull(bool $notNull = true)` method is added; +- `null()` method is added; - `isNotNull()` method is added; - `unique(bool $unique = true)` method is added; - `isUnique()` method is added; +- `hasDefaultValue()` method is added; - all `AbstractColumnSchema` class properties except `$type` moved to constructor; - added `DEFAULT_TYPE` constant to `AbstractColumnSchema` class; - added method chaining. diff --git a/src/QueryBuilder/AbstractColumnDefinitionBuilder.php b/src/QueryBuilder/AbstractColumnDefinitionBuilder.php index 6682de87d..dee3a1424 100644 --- a/src/QueryBuilder/AbstractColumnDefinitionBuilder.php +++ b/src/QueryBuilder/AbstractColumnDefinitionBuilder.php @@ -125,9 +125,7 @@ protected function buildDefault(ColumnSchemaInterface $column): string return ' DEFAULT ' . static::GENERATE_UUID_EXPRESSION; } - if ($column->isAutoIncrement() && $column->getType() !== ColumnType::UUID - || $column->getDefaultValue() === null - ) { + if ($column->isAutoIncrement() && $column->getType() !== ColumnType::UUID) { return ''; } @@ -147,21 +145,21 @@ protected function buildDefault(ColumnSchemaInterface $column): string */ protected function buildDefaultValue(ColumnSchemaInterface $column): string|null { - $value = $column->dbTypecast($column->getDefaultValue()); - - if ($value === null) { + if (!$column->hasDefaultValue()) { return null; } - if ($value instanceof ExpressionInterface) { - return $this->queryBuilder->buildExpression($value); - } + $value = $column->dbTypecast($column->getDefaultValue()); /** @var string */ return match (gettype($value)) { GettypeResult::INTEGER => (string) $value, GettypeResult::DOUBLE => (string) $value, GettypeResult::BOOLEAN => $value ? 'TRUE' : 'FALSE', + GettypeResult::NULL => $column->isNotNull() !== true ? 'NULL' : null, + GettypeResult::OBJECT => $value instanceof ExpressionInterface + ? $this->queryBuilder->buildExpression($value) + : $this->queryBuilder->quoter()->quoteValue((string) $value), default => $this->queryBuilder->quoter()->quoteValue((string) $value), }; } @@ -186,7 +184,11 @@ protected function buildExtra(ColumnSchemaInterface $column): string */ protected function buildNotNull(ColumnSchemaInterface $column): string { - return $column->isNotNull() ? ' NOT NULL' : ''; + return match ($column->isNotNull()) { + true => ' NOT NULL', + false => ' NULL', + default => '', + }; } /** @@ -274,11 +276,7 @@ protected function buildOnUpdate(string $onUpdate): string */ protected function buildType(ColumnSchemaInterface $column): string { - $dbType = $column->getDbType(); - - if ($dbType === null) { - $dbType = $this->getDbType($column); - } + $dbType = $this->getDbType($column); if (empty($dbType) || $dbType[-1] === ')' diff --git a/src/Schema/Column/AbstractColumnFactory.php b/src/Schema/Column/AbstractColumnFactory.php index a58789c1c..05fa0b537 100644 --- a/src/Schema/Column/AbstractColumnFactory.php +++ b/src/Schema/Column/AbstractColumnFactory.php @@ -6,8 +6,15 @@ use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Constant\PseudoType; +use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Syntax\ColumnDefinitionParser; +use function array_diff_key; +use function is_numeric; +use function preg_match; +use function str_replace; +use function substr; + use const PHP_INT_SIZE; /** @@ -18,29 +25,18 @@ abstract class AbstractColumnFactory implements ColumnFactoryInterface { /** - * Get the abstract database type for a database column type. - * - * @param string $dbType The database column type. - * @param array $info The column information. + * The mapping from physical column types (keys) to abstract column types (values). * - * @return string The abstract database type. + * @var string[] * - * @psalm-param ColumnInfo $info - * @psalm-return ColumnType::* - */ - abstract protected function getType(string $dbType, array $info = []): string; - - /** - * Checks if the column type is a database type. + * @psalm-var array */ - abstract protected function isDbType(string $dbType): bool; + protected const TYPE_MAP = []; public function fromDbType(string $dbType, array $info = []): ColumnSchemaInterface { - unset($info['dbType']); - $type = $info['type'] ?? $this->getType($dbType, $info); - unset($info['type']); $info['dbType'] = $dbType; + $type = $info['type'] ?? $this->getType($dbType, $info); return $this->fromType($type, $info); } @@ -60,12 +56,10 @@ public function fromDefinition(string $definition, array $info = []): ColumnSche $info += $definitionInfo; if ($this->isDbType($type)) { - unset($info['dbType']); return $this->fromDbType($type, $info); } if ($this->isType($type)) { - unset($info['type']); return $this->fromType($type, $info); } @@ -73,64 +67,113 @@ public function fromDefinition(string $definition, array $info = []): ColumnSche return $this->fromPseudoType($type, $info); } - unset($info['dbType']); return $this->fromDbType($type, $info); } - /** - * @psalm-suppress MixedArgument - * @psalm-suppress InvalidArgument - * @psalm-suppress InvalidNamedArgument - */ public function fromPseudoType(string $pseudoType, array $info = []): ColumnSchemaInterface { $info['primaryKey'] = true; $info['autoIncrement'] = true; - return match ($pseudoType) { - PseudoType::PK => new IntegerColumnSchema(ColumnType::INTEGER, ...$info), - PseudoType::UPK => new IntegerColumnSchema(ColumnType::INTEGER, ...[...$info, 'unsigned' => true]), - PseudoType::BIGPK => PHP_INT_SIZE !== 8 - ? new BigIntColumnSchema(ColumnType::BIGINT, ...$info) - : new IntegerColumnSchema(ColumnType::BIGINT, ...$info), - PseudoType::UBIGPK => new BigIntColumnSchema(ColumnType::BIGINT, ...[...$info, 'unsigned' => true]), - PseudoType::UUID_PK => new StringColumnSchema(ColumnType::UUID, ...$info), - PseudoType::UUID_PK_SEQ => new StringColumnSchema(ColumnType::UUID, ...$info), + if ($pseudoType === PseudoType::UPK || $pseudoType === PseudoType::UBIGPK) { + $info['unsigned'] = true; + } + + $type = match ($pseudoType) { + PseudoType::PK => ColumnType::INTEGER, + PseudoType::UPK => ColumnType::INTEGER, + PseudoType::BIGPK => ColumnType::BIGINT, + PseudoType::UBIGPK => ColumnType::BIGINT, + PseudoType::UUID_PK => ColumnType::UUID, + PseudoType::UUID_PK_SEQ => ColumnType::UUID, }; + + return $this->fromType($type, $info); + } + + public function fromType(string $type, array $info = []): ColumnSchemaInterface + { + unset($info['type']); + + if ($type === ColumnType::ARRAY && empty($info['column']) && !empty($info['dbType'])) { + /** @psalm-suppress ArgumentTypeCoercion */ + $info['column'] = $this->fromDbType( + $info['dbType'], + array_diff_key($info, ['dimension' => 1, 'defaultValueRaw' => 1]) + ); + } + + $columnClass = $this->getColumnClass($type, $info); + + $column = new $columnClass($type, ...$info); + + if (isset($info['defaultValueRaw'])) { + $column->defaultValue($this->normalizeDefaultValue($info['defaultValueRaw'], $column)); + } + + return $column; } /** - * @psalm-suppress InvalidNamedArgument + * Returns the column definition parser. */ - public function fromType(string $type, array $info = []): ColumnSchemaInterface + protected function columnDefinitionParser(): ColumnDefinitionParser + { + return new ColumnDefinitionParser(); + } + + /** + * @psalm-param ColumnType::* $type + * @param ColumnInfo $info + * + * @psalm-return class-string + */ + protected function getColumnClass(string $type, array $info = []): string { return match ($type) { - ColumnType::BOOLEAN => new BooleanColumnSchema($type, ...$info), - ColumnType::BIT => new BitColumnSchema($type, ...$info), - ColumnType::TINYINT => new IntegerColumnSchema($type, ...$info), - ColumnType::SMALLINT => new IntegerColumnSchema($type, ...$info), + ColumnType::BOOLEAN => BooleanColumnSchema::class, + ColumnType::BIT => BitColumnSchema::class, + ColumnType::TINYINT => IntegerColumnSchema::class, + ColumnType::SMALLINT => IntegerColumnSchema::class, ColumnType::INTEGER => PHP_INT_SIZE !== 8 && !empty($info['unsigned']) - ? new BigIntColumnSchema($type, ...$info) - : new IntegerColumnSchema($type, ...$info), + ? BigIntColumnSchema::class + : IntegerColumnSchema::class, ColumnType::BIGINT => PHP_INT_SIZE !== 8 || !empty($info['unsigned']) - ? new BigIntColumnSchema($type, ...$info) - : new IntegerColumnSchema($type, ...$info), - ColumnType::DECIMAL => new DoubleColumnSchema($type, ...$info), - ColumnType::FLOAT => new DoubleColumnSchema($type, ...$info), - ColumnType::DOUBLE => new DoubleColumnSchema($type, ...$info), - ColumnType::BINARY => new BinaryColumnSchema($type, ...$info), - ColumnType::STRUCTURED => new StructuredColumnSchema($type, ...$info), - ColumnType::JSON => new JsonColumnSchema($type, ...$info), - default => new StringColumnSchema($type, ...$info), + ? BigIntColumnSchema::class + : IntegerColumnSchema::class, + ColumnType::DECIMAL => DoubleColumnSchema::class, + ColumnType::FLOAT => DoubleColumnSchema::class, + ColumnType::DOUBLE => DoubleColumnSchema::class, + ColumnType::BINARY => BinaryColumnSchema::class, + ColumnType::ARRAY => ArrayColumnSchema::class, + ColumnType::STRUCTURED => StructuredColumnSchema::class, + ColumnType::JSON => JsonColumnSchema::class, + default => StringColumnSchema::class, }; } /** - * Returns the column definition parser. + * Get the abstract database type for a database column type. + * + * @param string $dbType The database column type. + * @param array $info The column information. + * + * @return string The abstract database type. + * + * @psalm-param ColumnInfo $info + * @psalm-return ColumnType::* */ - protected function columnDefinitionParser(): ColumnDefinitionParser + protected function getType(string $dbType, array $info = []): string { - return new ColumnDefinitionParser(); + return static::TYPE_MAP[$dbType] ?? ColumnType::STRING; + } + + /** + * Checks if the column type is a database type. + */ + protected function isDbType(string $dbType): bool + { + return isset(static::TYPE_MAP[$dbType]); } /** @@ -184,4 +227,57 @@ protected function isType(string $type): bool default => false, }; } + + /** + * Converts column's default value according to {@see ColumnSchemaInterface::getPhpType()} 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. + */ + protected function normalizeDefaultValue(string|null $defaultValue, ColumnSchemaInterface $column): mixed + { + if ( + $defaultValue === null + || $defaultValue === '' + || $column->isPrimaryKey() + || $column->isComputed() + || preg_match('/^\(?NULL\b/i', $defaultValue) === 1 + ) { + return null; + } + + return $this->normalizeNotNullDefaultValue($defaultValue, $column); + } + + /** + * Converts a not null default value according to {@see ColumnSchemaInterface::getPhpType()}. + */ + protected function normalizeNotNullDefaultValue(string $defaultValue, ColumnSchemaInterface $column): mixed + { + $value = $defaultValue; + + if ($value[0] === '(' && $value[-1] === ')') { + $value = substr($value, 1, -1); + } + + if (is_numeric($value)) { + return $column->phpTypecast($value); + } + + if ($value[0] === "'" && $value[-1] === "'") { + $value = substr($value, 1, -1); + $value = str_replace("''", "'", $value); + + return $column->phpTypecast($value); + } + + return match ($value) { + 'true' => true, + 'false' => false, + default => new Expression($defaultValue), + }; + } } diff --git a/src/Schema/Column/AbstractColumnSchema.php b/src/Schema/Column/AbstractColumnSchema.php index 377d0f952..626063a36 100644 --- a/src/Schema/Column/AbstractColumnSchema.php +++ b/src/Schema/Column/AbstractColumnSchema.php @@ -6,9 +6,9 @@ use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Constant\PhpType; - use Yiisoft\Db\Constraint\ForeignKeyConstraint; +use function array_key_exists; use function property_exists; /** @@ -43,6 +43,11 @@ abstract class AbstractColumnSchema implements ColumnSchemaInterface */ protected const DEFAULT_TYPE = ColumnType::STRING; + /** + * @var mixed $defaultValue The default value of the column. + */ + private mixed $defaultValue; + /** * @var string The column abstract type * @psalm-var ColumnType::* @@ -56,12 +61,11 @@ abstract class AbstractColumnSchema implements ColumnSchemaInterface * @param string|null $comment The column's comment. * @param bool $computed Whether the column is a computed column. * @param string|null $dbType The column's database type. - * @param mixed $defaultValue The default value of the column. * @param array|null $enumValues The list of possible values for an ENUM column. * @param string|null $extra Any extra information that needs to be appended to the column's definition. * @param bool $primaryKey Whether the column is a primary key. * @param string|null $name The column's name. - * @param bool $notNull Whether the column is not nullable. + * @param bool|null $notNull Whether the column is not nullable. * @param ForeignKeyConstraint|null $reference The foreign key constraint. * @param int|null $scale The number of digits to the right of the decimal point. * @param int|null $size The column's size. @@ -79,12 +83,11 @@ public function __construct( private string|null $comment = null, private bool $computed = false, private string|null $dbType = null, - private mixed $defaultValue = null, private array|null $enumValues = null, private string|null $extra = null, private bool $primaryKey = false, private string|null $name = null, - private bool $notNull = false, + private bool|null $notNull = null, private ForeignKeyConstraint|null $reference = null, private int|null $scale = null, private int|null $size = null, @@ -94,6 +97,11 @@ public function __construct( ) { $this->type = $type ?? static::DEFAULT_TYPE; + if (array_key_exists('defaultValue', $args)) { + $this->defaultValue = $args['defaultValue']; + unset($args['defaultValue']); + } + /** @var array $args */ foreach ($args as $property => $value) { if (property_exists($this, $property)) { @@ -159,31 +167,37 @@ public function extra(string|null $extra): static return $this; } + /** @psalm-mutation-free */ public function getCheck(): string|null { return $this->check; } + /** @psalm-mutation-free */ public function getComment(): string|null { return $this->comment; } + /** @psalm-mutation-free */ public function getDbType(): string|null { return $this->dbType; } + /** @psalm-mutation-free */ public function getDefaultValue(): mixed { - return $this->defaultValue; + return $this->defaultValue ?? null; } + /** @psalm-mutation-free */ public function getEnumValues(): array|null { return $this->enumValues; } + /** @psalm-mutation-free */ public function getExtra(): string|null { return $this->extra; @@ -191,6 +205,7 @@ public function getExtra(): string|null /** * @deprecated Will be removed in version 2.0. + * @psalm-mutation-free */ public function getName(): string|null { @@ -199,70 +214,89 @@ public function getName(): string|null /** * @deprecated Use {@see getSize()} instead. Will be removed in version 2.0. + * @psalm-mutation-free */ public function getPrecision(): int|null { return $this->getSize(); } + /** @psalm-mutation-free */ public function getPhpType(): string { return PhpType::MIXED; } + /** @psalm-mutation-free */ public function getReference(): ForeignKeyConstraint|null { return $this->reference; } + /** @psalm-mutation-free */ public function getScale(): int|null { return $this->scale; } + /** @psalm-mutation-free */ public function getSize(): int|null { return $this->size; } + /** @psalm-mutation-free */ public function getType(): string { return $this->type; } + /** @psalm-mutation-free */ + public function hasDefaultValue(): bool + { + return property_exists($this, 'defaultValue'); + } + /** * @deprecated Use {@see isNotNull()} instead. Will be removed in version 2.0. + * @psalm-mutation-free */ public function isAllowNull(): bool { return !$this->isNotNull(); } + /** @psalm-mutation-free */ public function isAutoIncrement(): bool { return $this->autoIncrement; } + /** @psalm-mutation-free */ public function isComputed(): bool { return $this->computed; } - public function isNotNull(): bool + /** @psalm-mutation-free */ + public function isNotNull(): bool|null { return $this->notNull; } + /** @psalm-mutation-free */ public function isPrimaryKey(): bool { return $this->primaryKey; } + /** @psalm-mutation-free */ public function isUnique(): bool { return $this->unique; } + /** @psalm-mutation-free */ public function isUnsigned(): bool { return $this->unsigned; @@ -277,12 +311,18 @@ public function name(string|null $name): static return $this; } - public function notNull(bool $notNull = true): static + public function notNull(bool|null $notNull = true): static { $this->notNull = $notNull; return $this; } + public function null(): static + { + $this->notNull = false; + return $this; + } + /** * @deprecated Use {@see size()} instead. Will be removed in version 2.0. */ diff --git a/src/Schema/Column/ArrayColumnSchema.php b/src/Schema/Column/ArrayColumnSchema.php index 7f60ea92f..0d3e24149 100644 --- a/src/Schema/Column/ArrayColumnSchema.php +++ b/src/Schema/Column/ArrayColumnSchema.php @@ -85,12 +85,14 @@ public function dimension(int $dimension): static * @return int the dimension of the array. * * @psalm-return positive-int + * @psalm-mutation-free */ public function getDimension(): int { return $this->dimension; } + /** @psalm-mutation-free */ public function getPhpType(): string { return PhpType::ARRAY; diff --git a/src/Schema/Column/BigIntColumnSchema.php b/src/Schema/Column/BigIntColumnSchema.php index 30e0b6227..b082f2f83 100644 --- a/src/Schema/Column/BigIntColumnSchema.php +++ b/src/Schema/Column/BigIntColumnSchema.php @@ -41,6 +41,7 @@ public function dbTypecast(mixed $value): int|string|ExpressionInterface|null }; } + /** @psalm-mutation-free */ public function getPhpType(): string { return PhpType::STRING; diff --git a/src/Schema/Column/BitColumnSchema.php b/src/Schema/Column/BitColumnSchema.php index a2cdc30d7..9979afbac 100644 --- a/src/Schema/Column/BitColumnSchema.php +++ b/src/Schema/Column/BitColumnSchema.php @@ -27,6 +27,7 @@ public function dbTypecast(mixed $value): int|string|ExpressionInterface|null }; } + /** @psalm-mutation-free */ public function getPhpType(): string { return PhpType::INT; diff --git a/src/Schema/Column/BooleanColumnSchema.php b/src/Schema/Column/BooleanColumnSchema.php index 4d60ba2dd..c20e4484f 100644 --- a/src/Schema/Column/BooleanColumnSchema.php +++ b/src/Schema/Column/BooleanColumnSchema.php @@ -25,6 +25,7 @@ public function dbTypecast(mixed $value): bool|ExpressionInterface|null }; } + /** @psalm-mutation-free */ public function getPhpType(): string { return PhpType::BOOL; diff --git a/src/Schema/Column/ColumnFactoryInterface.php b/src/Schema/Column/ColumnFactoryInterface.php index 57d9251ec..4a30ee5a3 100644 --- a/src/Schema/Column/ColumnFactoryInterface.php +++ b/src/Schema/Column/ColumnFactoryInterface.php @@ -21,6 +21,7 @@ * computed?: bool, * dbType?: string|null, * defaultValue?: mixed, + * defaultValueRaw?: string|null, * dimension?: positive-int, * enumValues?: array|null, * extra?: string|null, diff --git a/src/Schema/Column/ColumnSchemaInterface.php b/src/Schema/Column/ColumnSchemaInterface.php index ffaf24330..22843e824 100644 --- a/src/Schema/Column/ColumnSchemaInterface.php +++ b/src/Schema/Column/ColumnSchemaInterface.php @@ -148,13 +148,15 @@ public function extra(string|null $extra): static; * Returns the check constraint for the column. * * @see check() - */ + * @psalm-mutation-free + */ public function getCheck(): string|null; /** * @return string|null The comment of the column. * * @see comment() + * @psalm-mutation-free */ public function getComment(): string|null; @@ -166,6 +168,7 @@ public function getComment(): string|null; * separately via {@see getSize()}. * * @see dbType() + * @psalm-mutation-free */ public function getDbType(): string|null; @@ -173,6 +176,7 @@ public function getDbType(): string|null; * @return mixed The default value of the column. * * @see defaultValue() + * @psalm-mutation-free */ public function getDefaultValue(): mixed; @@ -180,6 +184,7 @@ public function getDefaultValue(): mixed; * @return array|null The enum values of the column. * * @see enumValues() + * @psalm-mutation-free */ public function getEnumValues(): array|null; @@ -187,6 +192,7 @@ public function getEnumValues(): array|null; * @return string|null The extra SQL for the column. * * @see extra() + * @psalm-mutation-free */ public function getExtra(): string|null; @@ -194,6 +200,7 @@ public function getExtra(): string|null; * @return string|null The name of the column. * * @deprecated Will be removed in version 2.0. + * @psalm-mutation-free */ public function getName(): string|null; @@ -203,6 +210,7 @@ public function getName(): string|null; * @see precision() * * @deprecated Use {@see getSize()} instead. Will be removed in version 2.0. + * @psalm-mutation-free */ public function getPrecision(): int|null; @@ -211,6 +219,7 @@ public function getPrecision(): int|null; * * @return string The PHP type of the column. * @psalm-return PhpType::* + * @psalm-mutation-free */ public function getPhpType(): string; @@ -218,6 +227,7 @@ public function getPhpType(): string; * Returns the reference to the foreign key constraint. * * @see reference() + * @psalm-mutation-free */ public function getReference(): ForeignKeyConstraint|null; @@ -225,6 +235,7 @@ public function getReference(): ForeignKeyConstraint|null; * @return int|null The scale of the column. * * @see scale() + * @psalm-mutation-free */ public function getScale(): int|null; @@ -232,6 +243,7 @@ public function getScale(): int|null; * @return int|null The size of the column. * * @see size() + * @psalm-mutation-free */ public function getSize(): int|null; @@ -240,15 +252,20 @@ public function getSize(): int|null; * @psalm-return ColumnType::* * * @see type() + * @psalm-mutation-free */ public function getType(): string; + /** @psalm-mutation-free */ + public function hasDefaultValue(): bool; + /** * Whether this column is nullable. * * @see allowNull() * * @deprecated Use {@see isNotNull()} instead. Will be removed in version 2.0. + * @psalm-mutation-free */ public function isAllowNull(): bool; @@ -258,6 +275,7 @@ public function isAllowNull(): bool; * This is only meaningful when {@see type} is `smallint`, `integer` or `bigint`. * * @see autoIncrement() + * @psalm-mutation-free */ public function isAutoIncrement(): bool; @@ -265,6 +283,7 @@ public function isAutoIncrement(): bool; * Whether this column is computed. * * @see computed() + * @psalm-mutation-free */ public function isComputed(): bool; @@ -272,13 +291,15 @@ public function isComputed(): bool; * Whether this column is not nullable. * * @see notNull() + * @psalm-mutation-free */ - public function isNotNull(): bool; + public function isNotNull(): bool|null; /** * Whether this column is a part of primary key. * * @see primaryKey() + * @psalm-mutation-free */ public function isPrimaryKey(): bool; @@ -286,6 +307,7 @@ public function isPrimaryKey(): bool; * Whether this column has a unique index. * * @see unique() + * @psalm-mutation-free */ public function isUnique(): bool; @@ -294,6 +316,7 @@ public function isUnique(): bool; * or `bigint`. * * @see unsigned() + * @psalm-mutation-free */ public function isUnsigned(): bool; @@ -321,6 +344,11 @@ public function name(string|null $name): static; */ public function notNull(bool $notNull = true): static; + /** + * Whether the column is nullable. Alias of {@see notNull(false)}. + */ + public function null(): static; + /** * Converts the input value according to {@see phpType} after retrieval from the database. * diff --git a/src/Schema/Column/DoubleColumnSchema.php b/src/Schema/Column/DoubleColumnSchema.php index 674e2abb0..932b5294a 100644 --- a/src/Schema/Column/DoubleColumnSchema.php +++ b/src/Schema/Column/DoubleColumnSchema.php @@ -29,6 +29,7 @@ public function dbTypecast(mixed $value): float|ExpressionInterface|null }; } + /** @psalm-mutation-free */ public function getPhpType(): string { return PhpType::FLOAT; diff --git a/src/Schema/Column/IntegerColumnSchema.php b/src/Schema/Column/IntegerColumnSchema.php index 30513ed80..e4710452a 100644 --- a/src/Schema/Column/IntegerColumnSchema.php +++ b/src/Schema/Column/IntegerColumnSchema.php @@ -29,6 +29,7 @@ public function dbTypecast(mixed $value): int|ExpressionInterface|null }; } + /** @psalm-mutation-free */ public function getPhpType(): string { return PhpType::INT; diff --git a/src/Schema/Column/StringColumnSchema.php b/src/Schema/Column/StringColumnSchema.php index 992abe7de..855bc3bd0 100644 --- a/src/Schema/Column/StringColumnSchema.php +++ b/src/Schema/Column/StringColumnSchema.php @@ -29,6 +29,7 @@ public function dbTypecast(mixed $value): mixed }; } + /** @psalm-mutation-free */ public function getPhpType(): string { return PhpType::STRING; diff --git a/src/Schema/Column/StructuredColumnSchema.php b/src/Schema/Column/StructuredColumnSchema.php index d71315d41..be08a0b27 100644 --- a/src/Schema/Column/StructuredColumnSchema.php +++ b/src/Schema/Column/StructuredColumnSchema.php @@ -52,12 +52,14 @@ public function columns(array $columns): static * Get the metadata of the structured type columns. * * @return ColumnSchemaInterface[] + * @psalm-mutation-free */ public function getColumns(): array { return $this->columns; } + /** @psalm-mutation-free */ public function getPhpType(): string { return PhpType::ARRAY; diff --git a/src/Syntax/ColumnDefinitionParser.php b/src/Syntax/ColumnDefinitionParser.php index cc7329b87..ebc0b40f4 100644 --- a/src/Syntax/ColumnDefinitionParser.php +++ b/src/Syntax/ColumnDefinitionParser.php @@ -7,8 +7,7 @@ use function explode; use function preg_match; use function preg_match_all; -use function str_ireplace; -use function stripos; +use function str_replace; use function strlen; use function strtolower; use function substr; @@ -27,11 +26,15 @@ final class ColumnDefinitionParser * @return array The column information. * * @psalm-return array{ + * check?: string, + * defaultValueRaw?: string, * enumValues?: list, * extra?: string, + * notNull?: bool, * scale?: int, * size?: int, * type: lowercase-string, + * unique?: bool, * unsigned?: bool, * } */ @@ -66,7 +69,14 @@ private function enumInfo(string $values): array } /** - * @psalm-return array{unsigned?: bool, extra?: string} + * @psalm-return array{ + * check?: string, + * defaultValueRaw?: string, + * extra?: string, + * notNull?: bool, + * unique?: bool, + * unsigned?: bool + * } */ private function extraInfo(string $extra): array { @@ -75,12 +85,41 @@ private function extraInfo(string $extra): array } $info = []; + $bracketsPattern = '(\(((?>[^()]+)|(?-2))*\))'; + $defaultPattern = "/\\s*\\bDEFAULT\\s+('(?:[^']|'')*'|\"(?:[^\"]|\"\")*\"|[^(\\s]*$bracketsPattern?\\S*)/i"; + + if (preg_match($defaultPattern, $extra, $matches) === 1) { + $info['defaultValueRaw'] = $matches[1]; + $extra = str_replace($matches[0], '', $extra); + } + + if (preg_match("/\\s*\\bCHECK\\s+$bracketsPattern/i", $extra, $matches) === 1) { + $info['check'] = substr($matches[1], 1, -1); + $extra = str_replace($matches[0], '', $extra); + } - if (stripos($extra, 'unsigned') !== false) { + $extra = preg_replace('/\s*\bUNSIGNED\b/i', '', $extra, 1, $count); + if ($count > 0) { $info['unsigned'] = true; - $extra = trim(str_ireplace('unsigned', '', $extra)); } + $extra = preg_replace('/\s*\bUNIQUE\b/i', '', $extra, 1, $count); + if ($count > 0) { + $info['unique'] = true; + } + + $extra = preg_replace('/\s*\bNOT\s+NULL\b/i', '', $extra, 1, $count); + if ($count > 0) { + $info['notNull'] = true; + } else { + $extra = preg_replace('/\s*\bNULL\b/i', '', $extra, 1, $count); + if ($count > 0) { + $info['notNull'] = false; + } + } + + $extra = trim($extra); + if (!empty($extra)) { $info['extra'] = $extra; } diff --git a/tests/AbstractColumnFactoryTest.php b/tests/AbstractColumnFactoryTest.php index 908f4b88e..53489c0ca 100644 --- a/tests/AbstractColumnFactoryTest.php +++ b/tests/AbstractColumnFactoryTest.php @@ -4,16 +4,19 @@ namespace Yiisoft\Db\Tests; +use PHPUnit\Framework\Attributes\DataProviderExternal; use PHPUnit\Framework\TestCase; +use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Schema\Column\StringColumnSchema; use Yiisoft\Db\Tests\Provider\ColumnBuilderProvider; +use Yiisoft\Db\Tests\Provider\ColumnFactoryProvider; use Yiisoft\Db\Tests\Support\TestTrait; abstract class AbstractColumnFactoryTest extends TestCase { use TestTrait; - /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnFactoryProvider::types */ + #[DataProviderExternal(ColumnFactoryProvider::class, 'types')] public function testFromDbType(string $dbType, string $expectedType, string $expectedInstanceOf): void { $db = $this->getConnection(); @@ -28,9 +31,7 @@ public function testFromDbType(string $dbType, string $expectedType, string $exp $db->close(); } - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\ColumnFactoryProvider::definitions - */ + #[DataProviderExternal(ColumnFactoryProvider::class, 'definitions')] public function testFromDefinition( string $definition, string $expectedType, @@ -57,7 +58,7 @@ public function testFromDefinition( $db->close(); } - /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnFactoryProvider::pseudoTypes */ + #[DataProviderExternal(ColumnFactoryProvider::class, 'pseudoTypes')] public function testFromPseudoType( string $pseudoType, string $expectedType, @@ -84,7 +85,7 @@ public function testFromPseudoType( $db->close(); } - /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnFactoryProvider::types */ + #[DataProviderExternal(ColumnFactoryProvider::class, 'types')] public function testFromType(string $type, string $expectedType, string $expectedInstanceOf): void { $db = $this->getConnection(); @@ -103,12 +104,45 @@ public function testFromDefinitionWithExtra(): void $db = $this->getConnection(); $columnFactory = $db->getSchema()->getColumnFactory(); - $column = $columnFactory->fromDefinition('char(1) NOT NULL', ['extra' => 'UNIQUE']); + $column = $columnFactory->fromDefinition('char(1) INVISIBLE', ['extra' => 'COLLATE utf8mb4']); $this->assertInstanceOf(StringColumnSchema::class, $column); $this->assertSame('char', $column->getType()); $this->assertSame(1, $column->getSize()); - $this->assertSame('NOT NULL UNIQUE', $column->getExtra()); + $this->assertSame('INVISIBLE COLLATE utf8mb4', $column->getExtra()); + + $db->close(); + } + + #[DataProviderExternal(ColumnFactoryProvider::class, 'defaultValueRaw')] + public function testFromTypeDefaultValueRaw(string $type, string|null $defaultValueRaw, mixed $expected): void + { + $db = $this->getConnection(); + $columnFactory = $db->getSchema()->getColumnFactory(); + + $column = $columnFactory->fromType($type, ['defaultValueRaw' => $defaultValueRaw]); + + if (is_scalar($expected)) { + $this->assertSame($expected, $column->getDefaultValue()); + } else { + $this->assertEquals($expected, $column->getDefaultValue()); + } + + $db->close(); + } + + public function testNullDefaultValueRaw(): void + { + $db = $this->getConnection(); + $columnFactory = $db->getSchema()->getColumnFactory(); + + $column = $columnFactory->fromType(ColumnType::INTEGER, ['defaultValueRaw' => '1', 'primaryKey' => true]); + + $this->assertNull($column->getDefaultValue()); + + $column = $columnFactory->fromType(ColumnType::INTEGER, ['defaultValueRaw' => '1', 'computed' => true]); + + $this->assertNull($column->getDefaultValue()); $db->close(); } diff --git a/tests/Common/CommonSchemaTest.php b/tests/Common/CommonSchemaTest.php index 3f2ffd6fe..bbf5447da 100644 --- a/tests/Common/CommonSchemaTest.php +++ b/tests/Common/CommonSchemaTest.php @@ -359,39 +359,6 @@ public function testGetSchemaUniques(): void $db->close(); } - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::columnsTypeChar - */ - public function testGetStringFieldsSize( - string $columnName, - string $columnType, - int|null $columnSize, - string $columnDbType - ): void { - $db = $this->getConnection(true); - - $schema = $db->getSchema(); - $tableSchema = $schema->getTableSchema('type'); - - $this->assertInstanceOf(TableSchemaInterface::class, $tableSchema); - - $columns = $tableSchema->getColumns(); - - foreach ($columns as $name => $column) { - $type = $column->getType(); - $size = $column->getSize(); - $dbType = $column->getDbType(); - - if ($name === $columnName) { - $this->assertSame($columnType, $type); - $this->assertSame($columnSize, $size); - $this->assertSame($columnDbType, $dbType); - } - } - - $db->close(); - } - public function testGetTableChecks(): void { $db = $this->getConnection(true); diff --git a/tests/Db/Schema/ColumnSchemaTest.php b/tests/Db/Schema/ColumnSchemaTest.php index 3f1539653..591f1bc8c 100644 --- a/tests/Db/Schema/ColumnSchemaTest.php +++ b/tests/Db/Schema/ColumnSchemaTest.php @@ -120,12 +120,15 @@ public function testDefaultValue(): void { $column = new ColumnSchema(); + $this->assertFalse($column->hasDefaultValue()); $this->assertNull($column->getDefaultValue()); $this->assertSame($column, $column->defaultValue('test')); + $this->assertTrue($column->hasDefaultValue()); $this->assertSame('test', $column->getDefaultValue()); $column->defaultValue(null); + $this->assertTrue($column->hasDefaultValue()); $this->assertNull($column->getDefaultValue()); } @@ -180,7 +183,7 @@ public function testNotNull(): void { $column = new ColumnSchema(); - $this->assertFalse($column->isNotNull()); + $this->assertNull($column->isNotNull()); $this->assertSame($column, $column->notNull()); $this->assertTrue($column->isNotNull()); @@ -188,9 +191,13 @@ public function testNotNull(): void $this->assertFalse($column->isNotNull()); - $column->notNull(true); + $column->notNull(null); - $this->assertTrue($column->isNotNull()); + $this->assertNull($column->isNotNull()); + + $column->null(); + + $this->assertFalse($column->isNotNull()); } public function testPrecision(): void diff --git a/tests/Provider/ColumnBuilderProvider.php b/tests/Provider/ColumnBuilderProvider.php index b147df30d..ea783d70c 100644 --- a/tests/Provider/ColumnBuilderProvider.php +++ b/tests/Provider/ColumnBuilderProvider.php @@ -28,7 +28,7 @@ class ColumnBuilderProvider 'getSize' => null, 'isAutoIncrement' => false, 'isComputed' => false, - 'isNotNull' => false, + 'isNotNull' => null, 'isPrimaryKey' => false, 'isUnsigned' => false, ]; diff --git a/tests/Provider/ColumnDefinitionParserProvider.php b/tests/Provider/ColumnDefinitionParserProvider.php index fe7b2eede..91816f253 100644 --- a/tests/Provider/ColumnDefinitionParserProvider.php +++ b/tests/Provider/ColumnDefinitionParserProvider.php @@ -13,12 +13,21 @@ public static function parse(): array ['int', ['type' => 'int']], ['int(10)', ['type' => 'int', 'size' => 10]], ['int UNSIGNED', ['type' => 'int', 'unsigned' => true]], + ['int UNIQUE', ['type' => 'int', 'unique' => true]], ['int(10) UNSIGNED', ['type' => 'int', 'size' => 10, 'unsigned' => true]], - ['int(10) UNSIGNED NOT NULL', ['type' => 'int', 'size' => 10, 'unsigned' => true, 'extra' => 'NOT NULL']], - ['int(10) NOT NULL', ['type' => 'int', 'size' => 10, 'extra' => 'NOT NULL']], - ['text NOT NULL', ['type' => 'text', 'extra' => 'NOT NULL']], + ['int(10) UNSIGNED NOT NULL', ['type' => 'int', 'size' => 10, 'unsigned' => true, 'notNull' => true]], + ['int(10) NOT NULL', ['type' => 'int', 'size' => 10, 'notNull' => true]], + ['text NOT NULL', ['type' => 'text', 'notNull' => true]], + ['text NULL', ['type' => 'text', 'notNull' => false]], + ['text COLLATE utf8mb4', ['type' => 'text', 'extra' => 'COLLATE utf8mb4']], + ['text DEFAULT NULL', ['type' => 'text', 'defaultValueRaw' => 'NULL']], + ["text DEFAULT 'value'", ['type' => 'text', 'defaultValueRaw' => "'value'"]], + ['varchar(36) DEFAULT uuid()', ['type' => 'varchar', 'size' => 36, 'defaultValueRaw' => 'uuid()']], + ['varchar(36) DEFAULT uuid()::varchar(36)', ['type' => 'varchar', 'size' => 36, 'defaultValueRaw' => 'uuid()::varchar(36)']], + ['int DEFAULT (1 + 2)', ['type' => 'int', 'defaultValueRaw' => '(1 + 2)']], + ['int CHECK (value > (1 + 5))', ['type' => 'int', 'check' => 'value > (1 + 5)']], ["enum('a','b','c')", ['type' => 'enum', 'enumValues' => ['a', 'b', 'c']]], - ["enum('a','b','c') NOT NULL", ['type' => 'enum', 'enumValues' => ['a', 'b', 'c'], 'extra' => 'NOT NULL']], + ["enum('a','b','c') NOT NULL", ['type' => 'enum', 'enumValues' => ['a', 'b', 'c'], 'notNull' => true]], ]; } } diff --git a/tests/Provider/ColumnFactoryProvider.php b/tests/Provider/ColumnFactoryProvider.php index 77ff08a8a..71bc2f18d 100644 --- a/tests/Provider/ColumnFactoryProvider.php +++ b/tests/Provider/ColumnFactoryProvider.php @@ -6,6 +6,7 @@ use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Constant\PseudoType; +use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Schema\Column\BigIntColumnSchema; use Yiisoft\Db\Schema\Column\BinaryColumnSchema; use Yiisoft\Db\Schema\Column\BooleanColumnSchema; @@ -23,7 +24,7 @@ public static function definitions(): array // definition, expected type, expected instance of, expected column method results '' => ['', ColumnType::STRING, StringColumnSchema::class, ['getDbType' => '']], 'text' => ['text', ColumnType::TEXT, StringColumnSchema::class, ['getDbType' => 'text']], - 'text NOT NULL' => ['text NOT NULL', ColumnType::TEXT, StringColumnSchema::class, ['getDbType' => 'text', 'getExtra' => 'NOT NULL']], + 'text NOT NULL' => ['text NOT NULL', ColumnType::TEXT, StringColumnSchema::class, ['getDbType' => 'text', 'isNotNull' => true]], 'char(1)' => ['char(1)', ColumnType::CHAR, StringColumnSchema::class, ['getDbType' => 'char', 'getSize' => 1]], 'decimal(10,2)' => ['decimal(10,2)', ColumnType::DECIMAL, DoubleColumnSchema::class, ['getDbType' => 'decimal', 'getSize' => 10, 'getScale' => 2]], 'bigint UNSIGNED' => ['bigint UNSIGNED', ColumnType::BIGINT, BigIntColumnSchema::class, ['getDbType' => 'bigint', 'isUnsigned' => true]], @@ -69,4 +70,33 @@ public static function types(): array 'json' => [ColumnType::JSON, ColumnType::JSON, JsonColumnSchema::class], ]; } + + public static function defaultValueRaw(): array + { + return [ + // type, default value, expected value + 'null' => [ColumnType::STRING, null, null], + '(null)' => [ColumnType::STRING, '(null)', null], + 'NULL' => [ColumnType::STRING, 'NULL', null], + '(NULL)' => [ColumnType::STRING, '(NULL)', null], + '' => [ColumnType::STRING, '', null], + '(0)' => [ColumnType::INTEGER, '(0)', 0], + '-1' => [ColumnType::INTEGER, '-1', -1], + '(-1)' => [ColumnType::INTEGER, '(-1)', -1], + '0.0' => [ColumnType::DOUBLE, '0.0', 0.0], + '(0.0)' => [ColumnType::DOUBLE, '(0.0)', 0.0], + '-1.1' => [ColumnType::DOUBLE, '-1.1', -1.1], + '(-1.1)' => [ColumnType::DOUBLE, '(-1.1)', -1.1], + 'true' => [ColumnType::BOOLEAN, 'true', true], + 'false' => [ColumnType::BOOLEAN, 'false', false], + '1' => [ColumnType::BOOLEAN, '1', true], + '0' => [ColumnType::BOOLEAN, '0', false], + "''" => [ColumnType::STRING, "''", ''], + "('')" => [ColumnType::STRING, "('')", ''], + "'str''ing'" => [ColumnType::STRING, "'str''ing'", "str'ing"], + "('str''ing')" => [ColumnType::STRING, "('str''ing')", "str'ing"], + 'CURRENT_TIMESTAMP' => [ColumnType::TIMESTAMP, 'CURRENT_TIMESTAMP', new Expression('CURRENT_TIMESTAMP')], + '(now())' => [ColumnType::TIMESTAMP, '(now())', new Expression('(now())')], + ]; + } } diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index 63b22d580..4bb133c9a 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -1578,20 +1578,20 @@ public static function buildColumnDefinition(): array $referenceWithSchema->foreignSchemaName('ref_schema'); return [ - PseudoType::PK => ['integer PRIMARY KEY AUTO_INCREMENT', PseudoType::PK], - PseudoType::UPK => ['integer UNSIGNED PRIMARY KEY AUTO_INCREMENT', PseudoType::UPK], - PseudoType::BIGPK => ['bigint PRIMARY KEY AUTO_INCREMENT', PseudoType::BIGPK], - PseudoType::UBIGPK => ['bigint UNSIGNED PRIMARY KEY AUTO_INCREMENT', PseudoType::UBIGPK], + PseudoType::PK => ['integer PRIMARY KEY AUTOINCREMENT', PseudoType::PK], + PseudoType::UPK => ['integer UNSIGNED PRIMARY KEY AUTOINCREMENT', PseudoType::UPK], + PseudoType::BIGPK => ['bigint PRIMARY KEY AUTOINCREMENT', PseudoType::BIGPK], + PseudoType::UBIGPK => ['bigint UNSIGNED PRIMARY KEY AUTOINCREMENT', PseudoType::UBIGPK], PseudoType::UUID_PK => ['uuid PRIMARY KEY DEFAULT uuid()', PseudoType::UUID_PK], PseudoType::UUID_PK_SEQ => ['uuid PRIMARY KEY DEFAULT uuid()', PseudoType::UUID_PK_SEQ], - 'STRING' => ['varchar', ColumnType::STRING], + 'STRING' => ['varchar(255)', ColumnType::STRING], 'STRING(100)' => ['varchar(100)', ColumnType::STRING . '(100)'], - 'primaryKey()' => ['integer PRIMARY KEY AUTO_INCREMENT', ColumnBuilder::primaryKey()], + 'primaryKey()' => ['integer PRIMARY KEY AUTOINCREMENT', ColumnBuilder::primaryKey()], 'primaryKey(false)' => ['integer PRIMARY KEY', ColumnBuilder::primaryKey(false)], - 'smallPrimaryKey()' => ['smallint PRIMARY KEY AUTO_INCREMENT', ColumnBuilder::smallPrimaryKey()], + 'smallPrimaryKey()' => ['smallint PRIMARY KEY AUTOINCREMENT', ColumnBuilder::smallPrimaryKey()], 'smallPrimaryKey(false)' => ['smallint PRIMARY KEY', ColumnBuilder::smallPrimaryKey(false)], - 'bigPrimaryKey()' => ['bigint PRIMARY KEY AUTO_INCREMENT', ColumnBuilder::bigPrimaryKey()], + 'bigPrimaryKey()' => ['bigint PRIMARY KEY AUTOINCREMENT', ColumnBuilder::bigPrimaryKey()], 'bigPrimaryKey(false)' => ['bigint PRIMARY KEY', ColumnBuilder::bigPrimaryKey(false)], 'uuidPrimaryKey()' => ['uuid PRIMARY KEY DEFAULT uuid()', ColumnBuilder::uuidPrimaryKey()], 'uuidPrimaryKey(false)' => ['uuid PRIMARY KEY', ColumnBuilder::uuidPrimaryKey(false)], @@ -1601,7 +1601,7 @@ public static function buildColumnDefinition(): array 'bit()' => ['bit', ColumnBuilder::bit()], 'bit(1)' => ['bit(1)', ColumnBuilder::bit(1)], 'bit(8)' => ['bit(8)', ColumnBuilder::bit(8)], - 'bit(1000)' => ['bit(1000)', ColumnBuilder::bit(1000)], + 'bit(64)' => ['bit(64)', ColumnBuilder::bit(64)], 'tinyint()' => ['tinyint', ColumnBuilder::tinyint()], 'tinyint(2)' => ['tinyint(2)', ColumnBuilder::tinyint(2)], 'smallint()' => ['smallint', ColumnBuilder::smallint()], @@ -1629,7 +1629,7 @@ public static function buildColumnDefinition(): array 'char(null)' => ['char', ColumnBuilder::char(null)], 'string()' => ['varchar(255)', ColumnBuilder::string()], 'string(100)' => ['varchar(100)', ColumnBuilder::string(100)], - 'string(null)' => ['varchar', ColumnBuilder::string(null)], + 'string(null)' => ['varchar(255)', ColumnBuilder::string(null)], 'text()' => ['text', ColumnBuilder::text()], 'text(1000)' => ['text(1000)', ColumnBuilder::text(1000)], 'binary()' => ['binary', ColumnBuilder::binary()], @@ -1648,25 +1648,26 @@ public static function buildColumnDefinition(): array 'time(null)' => ['time', ColumnBuilder::time(null)], 'array()' => ['json', ColumnBuilder::array()], 'structured()' => ['json', ColumnBuilder::structured()], - "structured('structured_type')" => ['structured_type', ColumnBuilder::structured('structured_type')], + "structured('json')" => ['json', ColumnBuilder::structured('json')], 'json()' => ['json', ColumnBuilder::json()], 'json(100)' => ['json', ColumnBuilder::json()->size(100)], - "enum('a','b','c')" => ["enum('a','b','c')", ColumnBuilder::string()->dbType("enum('a','b','c')")], - "extra('bar')" => ['varchar(255) bar', ColumnBuilder::string()->extra('bar')], + "extra('NOT NULL')" => ['varchar(255) NOT NULL', ColumnBuilder::string()->extra('NOT NULL')], "extra('')" => ['varchar(255)', ColumnBuilder::string()->extra('')], - "check('value > 5')" => ['varchar(255) CHECK (value > 5)', ColumnBuilder::string()->check('value > 5')], - "check('')" => ['varchar(255)', ColumnBuilder::string()->check('')], - 'check(null)' => ['varchar(255)', ColumnBuilder::string()->check(null)], + "check('value > 5')" => ['integer CHECK ([col_59] > 5)', ColumnBuilder::integer()->check(DbHelper::replaceQuotes('[[col_59]] > 5', static::$driverName))], + "check('')" => ['integer', ColumnBuilder::integer()->check('')], + 'check(null)' => ['integer', ColumnBuilder::integer()->check(null)], "comment('comment')" => ['varchar(255)', ColumnBuilder::string()->comment('comment')], "comment('')" => ['varchar(255)', ColumnBuilder::string()->comment('')], 'comment(null)' => ['varchar(255)', ColumnBuilder::string()->comment(null)], "defaultValue('value')" => ["varchar(255) DEFAULT 'value'", ColumnBuilder::string()->defaultValue('value')], "defaultValue('')" => ["varchar(255) DEFAULT ''", ColumnBuilder::string()->defaultValue('')], - 'defaultValue(null)' => ['varchar(255)', ColumnBuilder::string()->defaultValue(null)], - 'defaultValue($expression)' => ['varchar(255) DEFAULT expression', ColumnBuilder::string()->defaultValue(new Expression('expression'))], - "integer()->defaultValue('')" => ['integer', ColumnBuilder::integer()->defaultValue('')], + 'defaultValue(null)' => ['varchar(255) DEFAULT NULL', ColumnBuilder::string()->defaultValue(null)], + 'defaultValue($expression)' => ['integer DEFAULT (1 + 2)', ColumnBuilder::integer()->defaultValue(new Expression('(1 + 2)'))], + 'notNull()->defaultValue(null)' => ['varchar(255) NOT NULL', ColumnBuilder::string()->notNull()->defaultValue(null)], + "integer()->defaultValue('')" => ['integer DEFAULT NULL', ColumnBuilder::integer()->defaultValue('')], 'notNull()' => ['varchar(255) NOT NULL', ColumnBuilder::string()->notNull()], + 'null()' => ['varchar(255) NULL', ColumnBuilder::string()->null()], 'integer()->primaryKey()' => ['integer PRIMARY KEY', ColumnBuilder::integer()->primaryKey()], 'size(10)' => ['varchar(10)', ColumnBuilder::string()->size(10)], 'unique()' => ['varchar(255) UNIQUE', ColumnBuilder::string()->unique()], diff --git a/tests/Provider/SchemaProvider.php b/tests/Provider/SchemaProvider.php index 1cce996fc..bd451a4d1 100644 --- a/tests/Provider/SchemaProvider.php +++ b/tests/Provider/SchemaProvider.php @@ -19,15 +19,6 @@ public static function columns(): array return []; } - public static function columnsTypeChar(): array - { - return [ - ['char_col', 'char', 100, 'char(100)'], - ['char_col2', 'string', 100, 'varchar(100)'], - ['char_col3', 'text', null, 'text'], - ]; - } - public static function constraints(): array { return [ diff --git a/tests/Support/Stub/ColumnDefinitionBuilder.php b/tests/Support/Stub/ColumnDefinitionBuilder.php index 199943ef6..07d137477 100644 --- a/tests/Support/Stub/ColumnDefinitionBuilder.php +++ b/tests/Support/Stub/ColumnDefinitionBuilder.php @@ -10,7 +10,7 @@ final class ColumnDefinitionBuilder extends AbstractColumnDefinitionBuilder { - protected const AUTO_INCREMENT_KEYWORD = 'AUTO_INCREMENT'; + protected const AUTO_INCREMENT_KEYWORD = 'AUTOINCREMENT'; protected const GENERATE_UUID_EXPRESSION = 'uuid()'; @@ -40,7 +40,7 @@ final class ColumnDefinitionBuilder extends AbstractColumnDefinitionBuilder protected function getDbType(ColumnSchemaInterface $column): string { - return match ($column->getType()) { + return $column->getDbType() ?? match ($column->getType()) { ColumnType::BOOLEAN => 'boolean', ColumnType::BIT => 'bit', ColumnType::TINYINT => 'tinyint', @@ -52,7 +52,7 @@ protected function getDbType(ColumnSchemaInterface $column): string ColumnType::DECIMAL => 'decimal', ColumnType::MONEY => 'money', ColumnType::CHAR => 'char', - ColumnType::STRING => 'varchar', + ColumnType::STRING => 'varchar(' . ($column->getSize() ?? 255) . ')', ColumnType::TEXT => 'text', ColumnType::BINARY => 'binary', ColumnType::UUID => 'uuid',