diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f3dcf5e..1f488b79a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ - New #902: Add `QueryBuilderInterface::prepareParam()` and `QueryBuilderInterface::prepareValue()` methods (@Tigrov) - Enh #902: Refactor `Quoter::quoteValue()` method (@Tigrov) - New #906: Add `ServerInfoInterface` and its implementation (@Tigrov) +- Enh #905: Use `AbstractColumnDefinitionBuilder` to generate table column SQL representation (@Tigrov) ## 1.3.0 March 21, 2024 diff --git a/docs/guide/en/command/ddl.md b/docs/guide/en/command/ddl.md index 62c1bd047..96519aa4c 100644 --- a/docs/guide/en/command/ddl.md +++ b/docs/guide/en/command/ddl.md @@ -13,16 +13,17 @@ To create a table, you can use the `Yiisoft\Db\Command\CommandInterface::createT ```php use Yiisoft\Db\Connection\ConnectionInterface; +use Yiisoft\Db\Schema\Column\ColumnBuilder; /** @var ConnectionInterface $db */ $db->createCommand()->createTable( '{{%customer}}', [ - 'id' => 'pk', - 'name' => 'string(255) NOT NULL', - 'email' => 'string(255) NOT NULL', - 'status' => 'integer NOT NULL', - 'created_at' => 'datetime NOT NULL', + 'id' => ColumnBuilder::primaryKey(), + 'name' => ColumnBuilder::string()->notNull(), + 'email' => ColumnBuilder::string()->notNull(), + 'status' => ColumnBuilder::integer()->notNull(), + 'created_at' => ColumnBuilder::datetime()->notNull(), ], )->execute(); ``` @@ -44,10 +45,10 @@ And the following SQL will be executed in MySQL/MariaDB: ```sql CREATE TABLE `customer` ( - `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `id` int PRIMARY KEY AUTO_INCREMENT, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, - `status` int(11) NOT NULL, + `status` int NOT NULL, `created_at` datetime(0) NOT NULL ) ``` diff --git a/src/Command/AbstractCommand.php b/src/Command/AbstractCommand.php index 27f973813..64100aa09 100644 --- a/src/Command/AbstractCommand.php +++ b/src/Command/AbstractCommand.php @@ -12,6 +12,7 @@ use Yiisoft\Db\QueryBuilder\DMLQueryBuilderInterface; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; use function explode; use function get_resource_type; @@ -131,7 +132,7 @@ public function addCheck(string $table, string $name, string $expression): stati return $this->setSql($sql)->requireTableSchemaRefresh($table); } - public function addColumn(string $table, string $column, ColumnInterface|string $type): static + public function addColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): static { $sql = $this->getQueryBuilder()->addColumn($table, $column, $type); return $this->setSql($sql)->requireTableSchemaRefresh($table); @@ -188,7 +189,7 @@ public function addUnique(string $table, string $name, array|string $columns): s return $this->setSql($sql)->requireTableSchemaRefresh($table); } - public function alterColumn(string $table, string $column, ColumnInterface|string $type): static + public function alterColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): static { $sql = $this->getQueryBuilder()->alterColumn($table, $column, $type); return $this->setSql($sql)->requireTableSchemaRefresh($table); diff --git a/src/Command/CommandInterface.php b/src/Command/CommandInterface.php index 521ffeed5..739388114 100644 --- a/src/Command/CommandInterface.php +++ b/src/Command/CommandInterface.php @@ -8,6 +8,8 @@ use JsonException; use Throwable; use Yiisoft\Db\Connection\ConnectionInterface; +use Yiisoft\Db\Constant\ColumnType; +use Yiisoft\Db\Constant\PseudoType; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidArgumentException; use Yiisoft\Db\Exception\InvalidCallException; @@ -17,6 +19,7 @@ use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\DMLQueryBuilderInterface; use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; /** * This interface represents a database command, such as a `SELECT`, `INSERT`, `UPDATE`, or `DELETE` statement. @@ -44,13 +47,13 @@ public function addCheck(string $table, string $name, string $expression): stati * * @param string $table The name of the table to add new column to. * @param string $column The name of the new column. - * @param ColumnInterface|string $type The column type. {@see QueryBuilder::getColumnType()} will be called - * to convert the given column type to the database one. + * @param ColumnInterface|ColumnSchemaInterface|string $type The column type. + * {@see QueryBuilder::buildColumnDefinition()} will be called to convert the given column type to the database one. * For example, `string` will be converted to `varchar(255)`, and `string not null` becomes `varchar(255) not null`. * * Note: The method will quote the `table` and `column` parameters before using them in the generated SQL. */ - public function addColumn(string $table, string $column, ColumnInterface|string $type): static; + public function addColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): static; /** * Builds an SQL command for adding a comment to a column. @@ -144,13 +147,13 @@ public function addPrimaryKey(string $table, string $name, array|string $columns * * @param string $table The table whose column is to change. * @param string $column The name of the column to change. - * @param ColumnInterface|string $type The column type. {@see QueryBuilder::getColumnType()} will be called to - * convert the give column type to the physical one. For example, `string` will be converted as `varchar(255)`, and - * `string not null` becomes `varchar(255) not null`. + * @param ColumnInterface|ColumnSchemaInterface|string $type The column type. + * {@see QueryBuilder::buildColumnDefinition()} will be called to convert the give column type to the physical one. + * For example, `string` will be converted as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. * * Note: The method will quote the `table` and `column` parameters before using them in the generated SQL. */ - public function alterColumn(string $table, string $column, ColumnInterface|string $type): static; + public function alterColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): static; /** * Creates a batch INSERT command. @@ -314,21 +317,40 @@ public function createIndex( /** * Creates an SQL command for creating a new DB table. * - * Specify the columns in the new table as name-definition pairs ('name' => 'string'), where name - * stands for a column name which will be quoted by the method, and definition stands for the column type - * which can contain an abstract DB type. + * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'), where name + * is the name of the column which will be properly quoted by the method, and definition is the type of the column + * which can contain a native database column type, {@see ColumnType abstract} or {@see PseudoType pseudo} type, + * or can be represented as instance of {@see ColumnSchemaInterface}. * - * The method {@see QueryBuilder::getColumnType()} will be called to convert the abstract column types to physical - * ones. - * For example, it will convert `string` to `varchar(255)`, and `string not null` to - * `varchar(255) not null`. + * The {@see QueryBuilderInterface::buildColumnDefinition()} method will be invoked to convert column definitions + * into SQL representation. For example, it will convert `string not null` to `varchar(255) not null` + * and `pk` to `int PRIMARY KEY AUTO_INCREMENT` (for MySQL). * - * If you specify a column with definition only (`PRIMARY KEY (name, type)`), it will be directly inserted - * into the generated SQL. + * The preferred method is to use {@see ColumnBuilder} to generate column definitions as instances of + * {@see ColumnSchemaInterface}. + * + * ```php + * $this->createTable( + * 'example_table', + * [ + * 'id' => ColumnBuilder::primaryKey(), + * 'name' => ColumnBuilder::string(64)->notNull(), + * 'type' => ColumnBuilder::integer()->notNull()->defaultValue(10), + * 'description' => ColumnBuilder::text(), + * 'rule_name' => ColumnBuilder::string(64), + * 'data' => ColumnBuilder::text(), + * 'created_at' => ColumnBuilder::datetime()->notNull(), + * 'updated_at' => ColumnBuilder::datetime(), + * ], + * ); + * ``` + * + * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly put into the + * generated SQL. * * @param string $table The name of the table to create. - * @param array $columns The columns (name => definition) in the new table. - * The definition can be `string` or {@see ColumnInterface} instance. + * @param (ColumnSchemaInterface|string)[] $columns The columns (name => definition) in the new table. + * The definition can be `string` or {@see ColumnSchemaInterface} instance. * @param string|null $options More SQL fragments to append to the generated SQL. * * @throws Exception @@ -337,7 +359,7 @@ public function createIndex( * * Note: The method will quote the `table` and `columns` parameter before using it in the generated SQL. * - * @psalm-param array|string[] $columns + * @psalm-param array|string[] $columns */ public function createTable(string $table, array $columns, string $options = null): static; diff --git a/src/Debug/CommandInterfaceProxy.php b/src/Debug/CommandInterfaceProxy.php index ddd0fc36f..338d49b2c 100644 --- a/src/Debug/CommandInterfaceProxy.php +++ b/src/Debug/CommandInterfaceProxy.php @@ -10,6 +10,7 @@ use Yiisoft\Db\Query\Data\DataReaderInterface; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; final class CommandInterfaceProxy implements CommandInterface { @@ -30,7 +31,7 @@ public function addCheck(string $table, string $name, string $expression): stati /** * @psalm-suppress MixedArgument */ - public function addColumn(string $table, string $column, ColumnInterface|string $type): static + public function addColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } @@ -93,7 +94,7 @@ public function addUnique(string $table, string $name, array|string $columns): s /** * @psalm-suppress MixedArgument */ - public function alterColumn(string $table, string $column, ColumnInterface|string $type): static + public function alterColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): static { return new self($this->decorated->{__FUNCTION__}(...func_get_args()), $this->collector); } diff --git a/src/QueryBuilder/AbstractColumnDefinitionBuilder.php b/src/QueryBuilder/AbstractColumnDefinitionBuilder.php index 23e7c49bf..b614a2e6c 100644 --- a/src/QueryBuilder/AbstractColumnDefinitionBuilder.php +++ b/src/QueryBuilder/AbstractColumnDefinitionBuilder.php @@ -21,11 +21,6 @@ abstract class AbstractColumnDefinitionBuilder implements ColumnDefinitionBuilde */ protected const AUTO_INCREMENT_KEYWORD = ''; - /** - * @var string The expression used to generate a UUID value. - */ - protected const GENERATE_UUID_EXPRESSION = ''; - /** * @var string[] The list of database column types (in lower case) that allow size specification. */ @@ -65,6 +60,11 @@ public function build(ColumnSchemaInterface $column): string . $this->buildExtra($column); } + public function buildAlter(ColumnSchemaInterface $column): string + { + return $this->build($column); + } + /** * Builds the auto increment clause for the column. * @@ -114,12 +114,14 @@ protected function buildComment(ColumnSchemaInterface $column): string */ protected function buildDefault(ColumnSchemaInterface $column): string { - if (!empty(static::GENERATE_UUID_EXPRESSION) + $uuidExpression = $this->getDefaultUuidExpression(); + + if (!empty($uuidExpression) && $column->getType() === ColumnType::UUID && $column->isAutoIncrement() && $column->getDefaultValue() === null ) { - return ' DEFAULT ' . static::GENERATE_UUID_EXPRESSION; + return " DEFAULT $uuidExpression"; } if ($column->isAutoIncrement() && $column->getType() !== ColumnType::UUID @@ -298,4 +300,12 @@ protected function buildUnsigned(ColumnSchemaInterface $column): string { return $column->isUnsigned() ? ' UNSIGNED' : ''; } + + /** + * Get the expression used to generate a UUID as a default value. + */ + protected function getDefaultUuidExpression(): string + { + return ''; + } } diff --git a/src/QueryBuilder/AbstractDDLQueryBuilder.php b/src/QueryBuilder/AbstractDDLQueryBuilder.php index 98a23ee7a..de8c8e1b6 100644 --- a/src/QueryBuilder/AbstractDDLQueryBuilder.php +++ b/src/QueryBuilder/AbstractDDLQueryBuilder.php @@ -7,6 +7,7 @@ use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; use Yiisoft\Db\Schema\QuoterInterface; use Yiisoft\Db\Schema\SchemaInterface; @@ -39,15 +40,14 @@ public function addCheck(string $table, string $name, string $expression): strin . ' CHECK (' . $this->quoter->quoteSql($expression) . ')'; } - public function addColumn(string $table, string $column, ColumnInterface|string $type): string + public function addColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): string { - /** @psalm-suppress DeprecatedMethod */ return 'ALTER TABLE ' . $this->quoter->quoteTableName($table) . ' ADD ' . $this->quoter->quoteColumnName($column) . ' ' - . $this->queryBuilder->getColumnType($type); + . $this->queryBuilder->buildColumnDefinition($type); } public function addCommentOnColumn(string $table, string $column, string $comment): string @@ -137,16 +137,15 @@ public function addUnique(string $table, string $name, array|string $columns): s public function alterColumn( string $table, string $column, - ColumnInterface|string $type + ColumnInterface|ColumnSchemaInterface|string $type ): string { - /** @psalm-suppress DeprecatedMethod */ return 'ALTER TABLE ' . $this->quoter->quoteTableName($table) . ' CHANGE ' . $this->quoter->quoteColumnName($column) . ' ' . $this->quoter->quoteColumnName($column) . ' ' - . $this->queryBuilder->getColumnType($type); + . $this->queryBuilder->buildColumnDefinition($type); } public function checkIntegrity(string $schema = '', string $table = '', bool $check = true): string @@ -173,11 +172,10 @@ public function createTable(string $table, array $columns, string $options = nul foreach ($columns as $name => $type) { if (is_string($name)) { - /** @psalm-suppress DeprecatedMethod */ $cols[] = "\t" . $this->quoter->quoteColumnName($name) . ' ' - . $this->queryBuilder->getColumnType($type); + . $this->queryBuilder->buildColumnDefinition($type); } else { /** @psalm-var string $type */ $cols[] = "\t" . $type; diff --git a/src/QueryBuilder/AbstractQueryBuilder.php b/src/QueryBuilder/AbstractQueryBuilder.php index 603611611..0b8a1d275 100644 --- a/src/QueryBuilder/AbstractQueryBuilder.php +++ b/src/QueryBuilder/AbstractQueryBuilder.php @@ -81,7 +81,7 @@ public function addCheck(string $table, string $name, string $expression): strin return $this->ddlBuilder->addCheck($table, $name, $expression); } - public function addColumn(string $table, string $column, ColumnInterface|string $type): string + public function addColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): string { return $this->ddlBuilder->addColumn($table, $column, $type); } @@ -131,7 +131,7 @@ public function addUnique(string $table, string $name, array|string $columns): s return $this->ddlBuilder->addUnique($table, $name, $columns); } - public function alterColumn(string $table, string $column, ColumnInterface|string $type): string + public function alterColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): string { return $this->ddlBuilder->alterColumn($table, $column, $type); } @@ -175,8 +175,12 @@ public function build(QueryInterface $query, array $params = []): array return $this->dqlBuilder->build($query, $params); } - public function buildColumnDefinition(ColumnSchemaInterface|string $column): string + public function buildColumnDefinition(ColumnInterface|ColumnSchemaInterface|string $column): string { + if ($column instanceof ColumnInterface) { + $column = $column->asString(); + } + if (is_string($column)) { $column = $this->schema->getColumnFactory()->fromDefinition($column); } @@ -355,6 +359,11 @@ public function dropView(string $viewName): string return $this->ddlBuilder->dropView($viewName); } + public function getColumnDefinitionBuilder(): ColumnDefinitionBuilderInterface + { + return $this->columnDefinitionBuilder; + } + /** @deprecated Use {@see buildColumnDefinition()}. Will be removed in version 2.0. */ public function getColumnType(ColumnInterface|string $type): string { diff --git a/src/QueryBuilder/ColumnDefinitionBuilderInterface.php b/src/QueryBuilder/ColumnDefinitionBuilderInterface.php index 332613149..b548051ba 100644 --- a/src/QueryBuilder/ColumnDefinitionBuilderInterface.php +++ b/src/QueryBuilder/ColumnDefinitionBuilderInterface.php @@ -16,4 +16,9 @@ interface ColumnDefinitionBuilderInterface * @return string the column SQL definition. */ public function build(ColumnSchemaInterface $column): string; + + /** + * Builds column definition for `ALTER` operation based on given column instance. + */ + public function buildAlter(ColumnSchemaInterface $column): string; } diff --git a/src/QueryBuilder/DDLQueryBuilderInterface.php b/src/QueryBuilder/DDLQueryBuilderInterface.php index c6e97fb7d..28319048e 100644 --- a/src/QueryBuilder/DDLQueryBuilderInterface.php +++ b/src/QueryBuilder/DDLQueryBuilderInterface.php @@ -10,6 +10,7 @@ use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; /** * Defines methods for building SQL statements for DDL (data definition language). @@ -36,7 +37,7 @@ public function addCheck(string $table, string $name, string $expression): strin * * @param string $table The table to add the new column will to. * @param string $column The name of the new column. - * @param ColumnInterface|string $type The column type. + * @param ColumnInterface|ColumnSchemaInterface|string $type The column type. * {@see getColumnType()} Method will be invoked to convert an abstract column type (if any) into the physical one. * Anything that isn't recognized as an abstract type will be kept in the generated SQL. * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become @@ -46,7 +47,7 @@ public function addCheck(string $table, string $name, string $expression): strin * * Note: The method will quote the `table` and `column` parameters before using them in the generated SQL. */ - public function addColumn(string $table, string $column, ColumnInterface|string $type): string; + public function addColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): string; /** * Builds an SQL command for adding comment to column. @@ -159,7 +160,7 @@ public function addUnique(string $table, string $name, array|string $columns): s * * @param string $table The table whose column is to change. * @param string $column The name of the column to change. - * @param ColumnInterface|string $type The new column type. + * @param ColumnInterface|ColumnSchemaInterface|string $type The new column type. * {@see getColumnType()} Method will be invoked to convert an abstract column type (if any) into the physical one. * Anything that isn't recognized as an abstract type will be kept in the generated SQL. * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become @@ -169,7 +170,7 @@ public function addUnique(string $table, string $name, array|string $columns): s * * Note: The method will quote the `table` and `column` parameters before using them in the generated SQL. */ - public function alterColumn(string $table, string $column, ColumnInterface|string $type): string; + public function alterColumn(string $table, string $column, ColumnInterface|ColumnSchemaInterface|string $type): string; /** * Builds an SQL statement for enabling or disabling integrity check. @@ -216,31 +217,47 @@ public function createIndex( /** * Builds an SQL statement for creating a new DB table. * - * The columns in the new table should be specified as name-definition pairs ('name' => 'string'), where name - * stands for a column name which will be quoted by the method, and definition stands for the column type which can - * contain an abstract DB type. + * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'), where name + * is the name of the column which will be properly quoted by the method, and definition is the type of the column + * which can contain a native database column type, {@see ColumnType abstract} or {@see PseudoType pseudo} type, + * or can be represented as instance of {@see ColumnSchemaInterface}. * - * The {@see getColumnType()} method will be invoked to convert any abstract type into a physical one. + * The {@see QueryBuilderInterface::buildColumnDefinition()} method will be invoked to convert column definitions + * into SQL representation. For example, it will convert `string not null` to `varchar(255) not null` + * and `pk` to `int PRIMARY KEY AUTO_INCREMENT` (for MySQL). * - * If a column is specified with definition only ('PRIMARY KEY (name, type)'), it will be directly inserted - * into the generated SQL. - * - * For example, + * The preferred method is to use {@see ColumnBuilder} to generate column definitions as instances of + * {@see ColumnSchemaInterface}. * * ```php - * $sql = $queryBuilder->createTable('user', ['id' => 'pk', 'name' => 'string', 'age' => 'integer']); + * $this->createTable( + * 'example_table', + * [ + * 'id' => ColumnBuilder::primaryKey(), + * 'name' => ColumnBuilder::string(64)->notNull(), + * 'type' => ColumnBuilder::integer()->notNull()->defaultValue(10), + * 'description' => ColumnBuilder::text(), + * 'rule_name' => ColumnBuilder::string(64), + * 'data' => ColumnBuilder::text(), + * 'created_at' => ColumnBuilder::datetime()->notNull(), + * 'updated_at' => ColumnBuilder::datetime(), + * ], + * ); * ``` * + * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly put into the + * generated SQL. + * * @param string $table The name of the table to create. * @param array $columns The columns (name => definition) in the new table. - * The definition can be `string` or {@see ColumnInterface} instance. + * The definition can be `string` or {@see ColumnInterface} or {@see ColumnSchemaInterface} instance. * @param string|null $options More SQL fragments to append to the generated SQL. * * @return string The SQL statement for creating a new DB table. * * Note: The method will quote the `table` and `columns` parameter before using it in the generated SQL. * - * @psalm-param array|string[] $columns + * @psalm-param array|string[] $columns */ public function createTable(string $table, array $columns, string $options = null): string; diff --git a/src/QueryBuilder/QueryBuilderInterface.php b/src/QueryBuilder/QueryBuilderInterface.php index d85609e9a..5f4cc834b 100644 --- a/src/QueryBuilder/QueryBuilderInterface.php +++ b/src/QueryBuilder/QueryBuilderInterface.php @@ -42,12 +42,12 @@ public function bindParam(mixed $value, array &$params = []): string; /** * Builds column definition based on given column instance. * - * @param ColumnSchemaInterface|string $column the column instance or string column definition which should be - * converted into a database string representation. + * @param ColumnInterface|ColumnSchemaInterface|string $column the column instance or string column definition which + * should be converted into a database string representation. * * @return string the SQL column definition. */ - public function buildColumnDefinition(ColumnSchemaInterface|string $column): string; + public function buildColumnDefinition(ColumnInterface|ColumnSchemaInterface|string $column): string; /** * Converts an abstract column type into a physical column type. @@ -99,6 +99,11 @@ public function buildColumnDefinition(ColumnSchemaInterface|string $column): str */ public function getColumnType(ColumnInterface|string $type): string; + /** + * Returns the column definition builder for the current DBMS. + */ + public function getColumnDefinitionBuilder(): ColumnDefinitionBuilderInterface; + /** * Gets an object of {@see ExpressionBuilderInterface} that's suitable for $expression. * diff --git a/src/Schema/Builder/AbstractColumn.php b/src/Schema/Builder/AbstractColumn.php index 0a34acb9f..1ee38c722 100644 --- a/src/Schema/Builder/AbstractColumn.php +++ b/src/Schema/Builder/AbstractColumn.php @@ -8,6 +8,7 @@ use Yiisoft\Db\Constant\PseudoType; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Helper\DbStringHelper; +use Yiisoft\Db\Schema\Column\AbstractColumnSchema; use function gettype; use function implode; @@ -27,6 +28,8 @@ * * 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. + * + * @deprecated Use {@see AbstractColumnSchema} instead. Will be removed in 2.0.0. */ abstract class AbstractColumn implements ColumnInterface { diff --git a/tests/AbstractQueryBuilderTest.php b/tests/AbstractQueryBuilderTest.php index 1c42cf3b3..6bf0e4160 100644 --- a/tests/AbstractQueryBuilderTest.php +++ b/tests/AbstractQueryBuilderTest.php @@ -71,7 +71,7 @@ public function testAddColumn(ColumnInterface|string $type): void DbHelper::replaceQuotes( <<getColumnType($type), + SQL . ' ' . $qb->buildColumnDefinition($type), $db->getDriverName(), ), $sql, @@ -192,22 +192,12 @@ public function testAddUnique(string $name, string $table, array|string $columns $this->assertSame($expected, $sql); } - public function testAlterColumn(): void + #[DataProviderExternal(QueryBuilderProvider::class, 'alterColumn')] + public function testAlterColumn(string|ColumnSchemaInterface $type, string $expected): void { - $db = $this->getConnection(); - - $qb = $db->getQueryBuilder(); - $sql = $qb->alterColumn('customer', 'email', ColumnType::STRING); + $qb = $this->getConnection()->getQueryBuilder(); - $this->assertSame( - DbHelper::replaceQuotes( - <<getColumnType(ColumnType::STRING), - $db->getDriverName(), - ), - $sql, - ); + $this->assertSame($expected, $qb->alterColumn('foo1', 'bar', $type)); } /** @@ -2413,6 +2403,8 @@ public function testBuildColumnDefinition(string $expected, ColumnSchemaInterfac $qb = $db->getQueryBuilder(); $this->assertSame($expected, $qb->buildColumnDefinition($column)); + + $db->close(); } #[DataProviderExternal(QueryBuilderProvider::class, 'prepareParam')] diff --git a/tests/Common/CommonColumnSchemaBuilderTest.php b/tests/Common/CommonColumnSchemaBuilderTest.php index a579d9d48..9374651ba 100644 --- a/tests/Common/CommonColumnSchemaBuilderTest.php +++ b/tests/Common/CommonColumnSchemaBuilderTest.php @@ -5,6 +5,8 @@ namespace Yiisoft\Db\Tests\Common; use PHPUnit\Framework\TestCase; +use Yiisoft\Db\Command\DataType; +use Yiisoft\Db\Command\Param; use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Constant\PseudoType; use Yiisoft\Db\Expression\Expression; @@ -26,14 +28,6 @@ public function testCustomTypes(string $expected, string $type, int|null $length $this->checkBuildString($expected, $type, $length, $calls); } - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\ColumnSchemaBuilderProvider::createColumnTypes - */ - public function testCreateColumnTypes(string $expected, string $type, int|null $length, array $calls): void - { - $this->checkCreateColumn($expected, $type, $length, $calls); - } - public function testUuid(): void { $db = $this->getConnection(); @@ -53,11 +47,13 @@ public function testUuid(): void $uuidValue = $uuidSource = '738146be-87b1-49f2-9913-36142fb6fcbe'; - if ($db->getDriverName() === 'oci') { - $uuidValue = new Expression('HEXTORAW(REGEXP_REPLACE(:uuid, \'-\', \'\'))', [':uuid' => $uuidValue]); - } elseif ($db->getDriverName() === 'mysql') { - $uuidValue = DbUuidHelper::uuidToBlob($uuidValue); - } + $uuidValue = match ($db->getDriverName()) { + 'oci' => new Expression("HEXTORAW(REPLACE(:uuid, '-', ''))", [':uuid' => $uuidValue]), + 'mysql' => new Expression("UNHEX(REPLACE(:uuid, '-', ''))", [':uuid' => $uuidValue]), + 'sqlite' => new Param(DbUuidHelper::uuidToBlob($uuidValue), DataType::LOB), + 'sqlsrv' => new Expression('CONVERT(uniqueidentifier, :uuid)', [':uuid' => $uuidValue]), + default => $uuidValue, + }; $db->createCommand()->insert($tableName, [ 'int_col' => 1, @@ -94,47 +90,4 @@ protected function checkBuildString(string $expected, string $type, int|null $le $db->close(); } - - protected function checkCreateColumn(string $expected, string $type, int|null $length, array $calls): void - { - $db = $this->getConnection(); - - if (str_contains($expected, 'UUID_TO_BIN')) { - $serverVersion = $db->getServerInfo()->getVersion(); - if (str_contains($serverVersion, 'MariaDB')) { - $db->close(); - $this->markTestSkipped('UUID_TO_BIN not supported MariaDB as defaultValue'); - } - if (version_compare($serverVersion, '8', '<')) { - $db->close(); - $this->markTestSkipped('UUID_TO_BIN not exists in MySQL 5.7'); - } - } - - $schema = $db->getSchema(); - $builder = $schema->createColumn($type, $length); - - foreach ($calls as $call) { - $method = array_shift($call); - ($builder->$method(...))(...$call); - } - - $tableName = '{{%column_schema_builder_types}}'; - if ($db->getTableSchema($tableName, true)) { - $db->createCommand()->dropTable($tableName)->execute(); - } - - $command = $db->createCommand()->createTable($tableName, [ - 'column' => $builder, - ]); - - $this->assertStringContainsString("\t" . $expected . "\n", $command->getRawSql()); - - $command->execute(); - - $tableSchema = $db->getTableSchema($tableName, true); - $this->assertNotNull($tableSchema); - - $db->close(); - } } diff --git a/tests/Common/CommonCommandTest.php b/tests/Common/CommonCommandTest.php index 62f0b1dc5..bb5947fed 100644 --- a/tests/Common/CommonCommandTest.php +++ b/tests/Common/CommonCommandTest.php @@ -23,9 +23,9 @@ use Yiisoft\Db\Query\Data\DataReaderInterface; use Yiisoft\Db\Query\Query; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; +use Yiisoft\Db\Schema\Column\ColumnBuilder; use Yiisoft\Db\Tests\AbstractCommandTest; use Yiisoft\Db\Tests\Support\Assert; -use Yiisoft\Db\Tests\Support\Stub\Column; use Yiisoft\Db\Transaction\TransactionInterface; use function is_string; @@ -548,7 +548,7 @@ public function testCreateTable(): void [ '[[id]]' => PseudoType::PK, '[[bar]]' => ColumnType::INTEGER, - '[[name]]' => (new Column('string(100)'))->notNull(), + '[[name]]' => ColumnBuilder::string(100)->notNull(), ], )->execute(); $command->insert('{{testCreateTable}}', ['[[bar]]' => 1, '[[name]]' => 'Lilo'])->execute(); diff --git a/tests/Common/CommonQueryBuilderTest.php b/tests/Common/CommonQueryBuilderTest.php index 238c8a36d..68f87042d 100644 --- a/tests/Common/CommonQueryBuilderTest.php +++ b/tests/Common/CommonQueryBuilderTest.php @@ -4,62 +4,82 @@ namespace Yiisoft\Db\Tests\Common; -use Yiisoft\Db\Constant\PseudoType; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use Yiisoft\Db\Command\CommandInterface; +use Yiisoft\Db\Exception\Exception; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; use Yiisoft\Db\Tests\AbstractQueryBuilderTest; use Yiisoft\Db\Tests\Provider\ColumnTypes; - -use function str_replace; -use function str_starts_with; -use function strncmp; -use function substr; +use Yiisoft\Db\Tests\Provider\QueryBuilderProvider; abstract class CommonQueryBuilderTest extends AbstractQueryBuilderTest { - public function testCreateTableWithGetColumnTypes(): void + public function getBuildColumnDefinitionProvider(): array { - $db = $this->getConnection(true); + return QueryBuilderProvider::buildColumnDefinition(); + } + public function testGetColumnType(): void + { + $db = $this->getConnection(); $qb = $db->getQueryBuilder(); + $columnTypes = (new ColumnTypes($db))->getColumnTypes(); - if ($db->getTableSchema('column_type_table', true) !== null) { - $db->createCommand($qb->dropTable('column_type_table'))->execute(); + foreach ($columnTypes as [$column, $expected]) { + $this->assertEquals($expected, $qb->getColumnType($column)); } - $columnTypes = (new ColumnTypes($db))->getColumnTypes(); - $columns = []; + $db->close(); + } + + #[DoesNotPerformAssertions] + public function testCreateTableWithBuildColumnDefinition(): void + { + $db = $this->getConnection(); + $schema = $db->getSchema(); + $columnFactory = $schema->getColumnFactory(); + $command = $db->createCommand(); + + $provider = $this->getBuildColumnDefinitionProvider(); + $i = 0; + $columns = []; - foreach ($columnTypes as [$column, $expected]) { - if ( - !( - strncmp($column, PseudoType::PK, 2) === 0 || - strncmp($column, PseudoType::UPK, 3) === 0 || - strncmp($column, PseudoType::BIGPK, 5) === 0 || - strncmp($column, PseudoType::UBIGPK, 6) === 0 || - str_starts_with(substr($column, -5), 'FIRST') - ) - ) { - $columns['col' . ++$i] = str_replace('CHECK (value', 'CHECK ([[col' . $i . ']]', $column); + foreach ($provider as $data) { + $column = $data[1]; + + if ($column instanceof ColumnSchemaInterface) { + if ($column->isPrimaryKey()) { + $this->createTebleWithColumn($command, $column); + continue; + } + + if ($column->getReference() !== null) { + continue; + } + } elseif ($columnFactory->fromDefinition($column)->isPrimaryKey()) { + $this->createTebleWithColumn($command, $column); + continue; } - } - $db->createCommand($qb->createTable('column_type_table', $columns))->execute(); + $columns['col_' . $i++] = $column; + } - $this->assertNotEmpty($db->getTableSchema('column_type_table', true)); + try { + $command->dropTable('build_column_definition')->execute(); + } catch (Exception) { + } - $db->close(); + $command->createTable('build_column_definition', $columns)->execute(); } - public function testGetColumnType(): void + private function createTebleWithColumn(CommandInterface $command, string|ColumnSchemaInterface $column) { - $db = $this->getConnection(); - $qb = $db->getQueryBuilder(); - $columnTypes = (new ColumnTypes($db))->getColumnTypes(); - - foreach ($columnTypes as [$column, $expected]) { - $this->assertEquals($expected, $qb->getColumnType($column)); + try { + $command->dropTable('build_column_definition_primary_key')->execute(); + } catch (Exception) { } - $db->close(); + $command->createTable('build_column_definition_primary_key', ['id' => $column])->execute(); } } diff --git a/tests/Db/Command/CommandTest.php b/tests/Db/Command/CommandTest.php index 0ff505839..8ad8370ed 100644 --- a/tests/Db/Command/CommandTest.php +++ b/tests/Db/Command/CommandTest.php @@ -7,7 +7,7 @@ use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Constant\PseudoType; use Yiisoft\Db\Exception\NotSupportedException; -use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; use Yiisoft\Db\Tests\AbstractCommandTest; use Yiisoft\Db\Tests\Support\Assert; use Yiisoft\Db\Tests\Support\DbHelper; @@ -42,14 +42,14 @@ public function testAddCheck(): void } /** @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::columnTypes */ - public function testAddColumn(ColumnInterface|string $type): void + public function testAddColumn(ColumnSchemaInterface|string $type): void { $db = $this->getConnection(); $command = $db->createCommand(); $sql = $command->addColumn('table', 'column', $type)->getSql(); - $columnType = $db->getQueryBuilder()->getColumnType($type); + $columnType = $db->getQueryBuilder()->buildColumnDefinition($type); $this->assertSame( DbHelper::replaceQuotes( @@ -233,10 +233,10 @@ public function testCreateTable(): void $expected = <<getConnection(); + $qb = $db->getQueryBuilder(); + + $cdb = new ColumnDefinitionBuilder($qb); + + $column = ColumnBuilder::integer(); + + $this->assertEquals($cdb->build($column), $cdb->buildAlter($column)); + } + + public function testBuildEmptyDefaultForUuid(): void + { + $db = $this->getConnection(); + $qb = $db->getQueryBuilder(); + + $cdb = new class ($qb) extends AbstractColumnDefinitionBuilder { + protected function getDbType(ColumnSchemaInterface $column): string + { + return 'uuid'; + } + }; + + $column = ColumnBuilder::uuidPrimaryKey(); + + $this->assertSame('uuid PRIMARY KEY', $cdb->build($column)); + } +} diff --git a/tests/Db/QueryBuilder/QueryBuilderTest.php b/tests/Db/QueryBuilder/QueryBuilderTest.php index d3b2043d6..47f7ce680 100644 --- a/tests/Db/QueryBuilder/QueryBuilderTest.php +++ b/tests/Db/QueryBuilder/QueryBuilderTest.php @@ -13,9 +13,9 @@ use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Query\Query; use Yiisoft\Db\Query\QueryInterface; +use Yiisoft\Db\Schema\Column\ColumnBuilder; use Yiisoft\Db\Tests\AbstractQueryBuilderTest; use Yiisoft\Db\Tests\Support\DbHelper; -use Yiisoft\Db\Tests\Support\Stub\Column; use Yiisoft\Db\Tests\Support\Stub\QueryBuilder; use Yiisoft\Db\Tests\Support\Stub\Schema; use Yiisoft\Db\Tests\Support\TestTrait; @@ -113,9 +113,9 @@ public function testCreateTable(): void DbHelper::replaceQuotes( << 'pk', 'name' => 'string(255) NOT NULL', - 'email' => (new Column('string(255)'))->notNull(), + 'email' => ColumnBuilder::string()->notNull(), 'status' => 'integer NOT NULL', 'created_at' => 'datetime NOT NULL', 'UNIQUE test_email_unique (email)', diff --git a/tests/Provider/ColumnSchemaBuilderProvider.php b/tests/Provider/ColumnSchemaBuilderProvider.php index 1ffc3d907..86303b61f 100644 --- a/tests/Provider/ColumnSchemaBuilderProvider.php +++ b/tests/Provider/ColumnSchemaBuilderProvider.php @@ -5,9 +5,7 @@ namespace Yiisoft\Db\Tests\Provider; use Yiisoft\Db\Constant\ColumnType; -use Yiisoft\Db\Constant\PseudoType; use Yiisoft\Db\Expression\Expression; -use Yiisoft\Db\Tests\Support\DbHelper; class ColumnSchemaBuilderProvider { @@ -28,52 +26,4 @@ public static function types(): array ], ]; } - - public static function createColumnTypes(): array - { - return [ - 'integer' => [ - Dbhelper::replaceQuotes('[[column]] integer', static::$driverName), - ColumnType::INTEGER, - null, - [], - ], - 'uuid' => [ - '', - ColumnType::UUID, - null, - [], - ], - 'uuid not null' => [ - '', - ColumnType::UUID, - null, - [['notNull']], - ], - 'uuid with default' => [ - '', - ColumnType::UUID, - null, - [['defaultValue', '875343b3-6bd0-4bec-81bb-aa68bb52d945']], - ], - 'uuid pk' => [ - '', - PseudoType::UUID_PK, - null, - [], - ], - 'uuid pk not null' => [ - '', - PseudoType::UUID_PK, - null, - [['notNull']], - ], - 'uuid pk not null with default' => [ - '', - PseudoType::UUID_PK, - null, - [], - ], - ]; - } } diff --git a/tests/Provider/CommandProvider.php b/tests/Provider/CommandProvider.php index 6c6dfc84a..ef542e013 100644 --- a/tests/Provider/CommandProvider.php +++ b/tests/Provider/CommandProvider.php @@ -12,10 +12,10 @@ use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Query\Query; +use Yiisoft\Db\Schema\Column\ColumnBuilder; use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Tests\Support\DbHelper; use Yiisoft\Db\Tests\Support\Stringable; -use Yiisoft\Db\Tests\Support\Stub\Column; use Yiisoft\Db\Tests\Support\TestTrait; class CommandProvider @@ -923,7 +923,7 @@ public static function columnTypes(): array { return [ [ColumnType::INTEGER], - [new Column('string(100)')], + [ColumnBuilder::string(100)], ]; } } diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index 53021b9db..0b1c55e05 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -144,6 +144,13 @@ public static function addUnique(): array ]; } + public static function alterColumn(): array + { + return [ + [ColumnType::STRING, 'ALTER TABLE [foo1] CHANGE [bar] [bar] varchar(255)'], + ]; + } + public static function batchInsert(): array { return [ diff --git a/tests/Support/Stub/ColumnDefinitionBuilder.php b/tests/Support/Stub/ColumnDefinitionBuilder.php index 07d137477..bb79f985c 100644 --- a/tests/Support/Stub/ColumnDefinitionBuilder.php +++ b/tests/Support/Stub/ColumnDefinitionBuilder.php @@ -12,8 +12,6 @@ final class ColumnDefinitionBuilder extends AbstractColumnDefinitionBuilder { protected const AUTO_INCREMENT_KEYWORD = 'AUTOINCREMENT'; - protected const GENERATE_UUID_EXPRESSION = 'uuid()'; - protected const TYPES_WITH_SIZE = [ 'bit', 'tinyint', @@ -66,4 +64,9 @@ protected function getDbType(ColumnSchemaInterface $column): string default => 'varchar', }; } + + protected function getDefaultUuidExpression(): string + { + return 'uuid()'; + } }