Loading core/lib/Drupal/Core/Database/SchemaDefinition/Column.php +57 −0 Changes for core/lib/Drupal/Core/Database/SchemaDefinition/Column.php: 57 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -59,6 +59,10 @@ final class Column implements SchemaDefinitionInterface { * has no effect on other database types for which case sensitivity is * already the default behavior. If NULL, it is not specified. Defaults to * NULL. * @param ?GeneratedColumnStorage $generatedStorage * The storage strategy for a generated column (VIRTUAL or STORED). * @param ?GeneratedColumnExpression $generatedExpression * The expression for a generated column. * @param array<string,array<string,mixed>>|null $dbSpecificExtra * (Optional) If you need to use a column type not included in the * officially supported list of types above, you can specify a type for Loading @@ -69,6 +73,8 @@ final class Column implements SchemaDefinitionInterface { * * @see \Drupal\Core\Database\SchemaDefinition\ColumnType * @see \Drupal\Core\Database\SchemaDefinition\ColumnSize * @see \Drupal\Core\Database\SchemaDefinition\GeneratedColumnExpression * @see \Drupal\Core\Database\SchemaDefinition\GeneratedColumnStorage */ private function __construct( public readonly string $name, Loading @@ -83,6 +89,8 @@ private function __construct( public readonly ?int $precision = NULL, public readonly ?int $scale = NULL, public readonly ?bool $binary = NULL, public readonly ?GeneratedColumnStorage $generatedStorage = NULL, public readonly ?GeneratedColumnExpression $generatedExpression = NULL, public readonly ?array $dbSpecificExtra = NULL, ) { } Loading Loading @@ -141,6 +149,12 @@ private function validate(): void { throw new SchemaDefinitionException("Cannot set 'notNull' for {$this->type->value} column '{$this->name}'"); } // A serial column takes its value from a sequence, so it cannot also be // computed from an expression. if ($this->generatedExpression !== NULL && $this->type === ColumnType::Serial) { throw new SchemaDefinitionException("Cannot set 'generatedExpression' for {$this->type->value} column '{$this->name}'"); } // Can only set unsigned for some column types. if ($this->unsigned !== NULL && $this->type !== NULL && !in_array($this->type, [ ColumnType::Int, Loading Loading @@ -215,6 +229,9 @@ public function toArray(): array { if ($this->description !== NULL) { $spec['description'] = $this->description; } if ($this->generatedStorage) { $spec['generated'] = $this; } if ($this->serialize !== NULL) { $spec['serialize'] = $this->serialize; } Loading Loading @@ -458,4 +475,44 @@ public static function blob( ); } /** * Returns a Column object for a generated column. * * @see https://dev.mysql.com/doc/refman/9.7/en/create-table-generated-columns.html * @see https://mariadb.com/docs/server/reference/sql-statements/data-definition/create/generated-columns * @see https://www.postgresql.org/docs/current/ddl-generated-columns.html * @see https://sqlite.org/gencol.html */ public static function generated( string $name, ColumnType $type, GeneratedColumnExpression $expression, GeneratedColumnStorage $storage, ?string $description = NULL, ?ColumnSize $size = NULL, ?int $length = NULL, ?bool $unsigned = NULL, ?int $precision = NULL, ?int $scale = NULL, ?bool $binary = NULL, ?array $dbSpecificExtra = NULL, ): self { $column = new self( type: $type, name: $name, description: $description, size: $size, length: $length, unsigned: $unsigned, precision: $precision, scale: $scale, binary: $binary, generatedStorage: $storage, generatedExpression: $expression, dbSpecificExtra: $dbSpecificExtra, ); $column->validate(); return $column; } } core/lib/Drupal/Core/Database/SchemaDefinition/GeneratedColumnExpression.php 0 → 100644 +26 −0 Changes for core/lib/Drupal/Core/Database/SchemaDefinition/GeneratedColumnExpression.php: 26 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Core\Database\SchemaDefinition; use Drupal\Core\Database\Expression; /** * A value object representing an expression for a generated column. */ final class GeneratedColumnExpression extends Expression { /** * Constructor. * * @param string $expression * The expression text. */ public function __construct( string $expression, ) { parent::__construct($expression); } } core/lib/Drupal/Core/Database/SchemaDefinition/GeneratedColumnStorage.php 0 → 100644 +15 −0 Changes for core/lib/Drupal/Core/Database/SchemaDefinition/GeneratedColumnStorage.php: 15 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Core\Database\SchemaDefinition; /** * Enumeration of cases for generated column. */ enum GeneratedColumnStorage { case Virtual; case Stored; } core/lib/Drupal/Core/Database/SchemaDefinition/Table.php +14 −1 Changes for core/lib/Drupal/Core/Database/SchemaDefinition/Table.php: 14 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -62,7 +62,6 @@ public function __construct( * @throws \Drupal\Core\Database\Exception\SchemaDefinitionException */ private function validate(): void { if ($this->columns && !Inspector::assertAllObjects($this->columns, Column::class)) { throw new SchemaDefinitionException("All members of the 'columns' argument of the '{$this->name}' Table must be Column objects"); } Loading @@ -76,6 +75,20 @@ private function validate(): void { throw new SchemaDefinitionException("All members of the 'foreignKeys' argument of the '{$this->name}' Table must be ForeignKey objects"); } // MariaDB and SQLite do not allow generated columns in the primary key. if ($this->columns && $this->primaryKey) { $generatedColumnNames = array_map( static fn (Column $column): string => $column->name, array_filter($this->columns, static fn (Column $column): bool => $column->generatedExpression !== NULL), ); $primaryKeyColumnNames = array_map( static fn (KeyColumn $keyColumn): string => $keyColumn->name, $this->primaryKey->columns, ); if (array_intersect($primaryKeyColumnNames, $generatedColumnNames)) { throw new SchemaDefinitionException("The primary key of the '{$this->name}' Table cannot include generated columns"); } } } /** Loading core/lib/Drupal/Core/Database/database.api.php +7 −0 Changes for core/lib/Drupal/Core/Database/database.api.php: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -377,6 +377,13 @@ * 'varchar' or 'text' fields to use case-sensitive binary collation. * This has no effect on other database types for which case sensitivity * is already the default behavior. * - 'generated': A \Drupal\Core\Database\SchemaDefinition\Column object * returned by Column::generated(). It holds the expression the database * computes the column value from, and whether that value is stored on * write or computed on read. A generated column cannot have a 'default' * or a 'not null' constraint, and cannot be part of the primary key. * Prefer stored over virtual for portability: PostgreSQL supports virtual * columns only from version 18 and cannot index them. * All parameters apart from 'type' are optional except that type * 'numeric' columns must specify 'precision' and 'scale', and type * 'varchar' must specify the 'length' parameter. Loading Loading
core/lib/Drupal/Core/Database/SchemaDefinition/Column.php +57 −0 Changes for core/lib/Drupal/Core/Database/SchemaDefinition/Column.php: 57 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -59,6 +59,10 @@ final class Column implements SchemaDefinitionInterface { * has no effect on other database types for which case sensitivity is * already the default behavior. If NULL, it is not specified. Defaults to * NULL. * @param ?GeneratedColumnStorage $generatedStorage * The storage strategy for a generated column (VIRTUAL or STORED). * @param ?GeneratedColumnExpression $generatedExpression * The expression for a generated column. * @param array<string,array<string,mixed>>|null $dbSpecificExtra * (Optional) If you need to use a column type not included in the * officially supported list of types above, you can specify a type for Loading @@ -69,6 +73,8 @@ final class Column implements SchemaDefinitionInterface { * * @see \Drupal\Core\Database\SchemaDefinition\ColumnType * @see \Drupal\Core\Database\SchemaDefinition\ColumnSize * @see \Drupal\Core\Database\SchemaDefinition\GeneratedColumnExpression * @see \Drupal\Core\Database\SchemaDefinition\GeneratedColumnStorage */ private function __construct( public readonly string $name, Loading @@ -83,6 +89,8 @@ private function __construct( public readonly ?int $precision = NULL, public readonly ?int $scale = NULL, public readonly ?bool $binary = NULL, public readonly ?GeneratedColumnStorage $generatedStorage = NULL, public readonly ?GeneratedColumnExpression $generatedExpression = NULL, public readonly ?array $dbSpecificExtra = NULL, ) { } Loading Loading @@ -141,6 +149,12 @@ private function validate(): void { throw new SchemaDefinitionException("Cannot set 'notNull' for {$this->type->value} column '{$this->name}'"); } // A serial column takes its value from a sequence, so it cannot also be // computed from an expression. if ($this->generatedExpression !== NULL && $this->type === ColumnType::Serial) { throw new SchemaDefinitionException("Cannot set 'generatedExpression' for {$this->type->value} column '{$this->name}'"); } // Can only set unsigned for some column types. if ($this->unsigned !== NULL && $this->type !== NULL && !in_array($this->type, [ ColumnType::Int, Loading Loading @@ -215,6 +229,9 @@ public function toArray(): array { if ($this->description !== NULL) { $spec['description'] = $this->description; } if ($this->generatedStorage) { $spec['generated'] = $this; } if ($this->serialize !== NULL) { $spec['serialize'] = $this->serialize; } Loading Loading @@ -458,4 +475,44 @@ public static function blob( ); } /** * Returns a Column object for a generated column. * * @see https://dev.mysql.com/doc/refman/9.7/en/create-table-generated-columns.html * @see https://mariadb.com/docs/server/reference/sql-statements/data-definition/create/generated-columns * @see https://www.postgresql.org/docs/current/ddl-generated-columns.html * @see https://sqlite.org/gencol.html */ public static function generated( string $name, ColumnType $type, GeneratedColumnExpression $expression, GeneratedColumnStorage $storage, ?string $description = NULL, ?ColumnSize $size = NULL, ?int $length = NULL, ?bool $unsigned = NULL, ?int $precision = NULL, ?int $scale = NULL, ?bool $binary = NULL, ?array $dbSpecificExtra = NULL, ): self { $column = new self( type: $type, name: $name, description: $description, size: $size, length: $length, unsigned: $unsigned, precision: $precision, scale: $scale, binary: $binary, generatedStorage: $storage, generatedExpression: $expression, dbSpecificExtra: $dbSpecificExtra, ); $column->validate(); return $column; } }
core/lib/Drupal/Core/Database/SchemaDefinition/GeneratedColumnExpression.php 0 → 100644 +26 −0 Changes for core/lib/Drupal/Core/Database/SchemaDefinition/GeneratedColumnExpression.php: 26 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Core\Database\SchemaDefinition; use Drupal\Core\Database\Expression; /** * A value object representing an expression for a generated column. */ final class GeneratedColumnExpression extends Expression { /** * Constructor. * * @param string $expression * The expression text. */ public function __construct( string $expression, ) { parent::__construct($expression); } }
core/lib/Drupal/Core/Database/SchemaDefinition/GeneratedColumnStorage.php 0 → 100644 +15 −0 Changes for core/lib/Drupal/Core/Database/SchemaDefinition/GeneratedColumnStorage.php: 15 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Core\Database\SchemaDefinition; /** * Enumeration of cases for generated column. */ enum GeneratedColumnStorage { case Virtual; case Stored; }
core/lib/Drupal/Core/Database/SchemaDefinition/Table.php +14 −1 Changes for core/lib/Drupal/Core/Database/SchemaDefinition/Table.php: 14 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -62,7 +62,6 @@ public function __construct( * @throws \Drupal\Core\Database\Exception\SchemaDefinitionException */ private function validate(): void { if ($this->columns && !Inspector::assertAllObjects($this->columns, Column::class)) { throw new SchemaDefinitionException("All members of the 'columns' argument of the '{$this->name}' Table must be Column objects"); } Loading @@ -76,6 +75,20 @@ private function validate(): void { throw new SchemaDefinitionException("All members of the 'foreignKeys' argument of the '{$this->name}' Table must be ForeignKey objects"); } // MariaDB and SQLite do not allow generated columns in the primary key. if ($this->columns && $this->primaryKey) { $generatedColumnNames = array_map( static fn (Column $column): string => $column->name, array_filter($this->columns, static fn (Column $column): bool => $column->generatedExpression !== NULL), ); $primaryKeyColumnNames = array_map( static fn (KeyColumn $keyColumn): string => $keyColumn->name, $this->primaryKey->columns, ); if (array_intersect($primaryKeyColumnNames, $generatedColumnNames)) { throw new SchemaDefinitionException("The primary key of the '{$this->name}' Table cannot include generated columns"); } } } /** Loading
core/lib/Drupal/Core/Database/database.api.php +7 −0 Changes for core/lib/Drupal/Core/Database/database.api.php: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -377,6 +377,13 @@ * 'varchar' or 'text' fields to use case-sensitive binary collation. * This has no effect on other database types for which case sensitivity * is already the default behavior. * - 'generated': A \Drupal\Core\Database\SchemaDefinition\Column object * returned by Column::generated(). It holds the expression the database * computes the column value from, and whether that value is stored on * write or computed on read. A generated column cannot have a 'default' * or a 'not null' constraint, and cannot be part of the primary key. * Prefer stored over virtual for portability: PostgreSQL supports virtual * columns only from version 18 and cannot index them. * All parameters apart from 'type' are optional except that type * 'numeric' columns must specify 'precision' and 'scale', and type * 'varchar' must specify the 'length' parameter. Loading