diff --git a/CHANGELOG.md b/CHANGELOG.md index c7e82cd77..cd645674b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - Enh #806: Build `Expression` instances inside `Expression::$params` when build a query using `QueryBuilder` (@Tigrov) - Enh #766: Allow `ColumnInterface` as column type. (@Tigrov) - Bug #828: Fix `float` type when use `AbstractCommand::getRawSql()` method (@Tigrov) -- Enh #752: Implement `ColumnSchemaInterface` classes according to the data type of database table columns +- New #752: Implement `ColumnSchemaInterface` classes according to the data type of database table columns for type casting performance (@Tigrov) - Enh #829: Rename `batchInsert()` to `insertBatch()` in `DMLQueryBuilderInterface` and `CommandInterface` and change parameters from `$table, $columns, $rows` to `$table, $rows, $columns = []` (@Tigrov) @@ -29,18 +29,19 @@ - Chg #846: Remove `SchemaInterface::isReadQuery()` and `AbstractSchema::isReadQuery()` methods (@Tigrov) - Chg #847: Remove `SchemaInterface::getRawTableName()` and `AbstractSchema::getRawTableName()` methods (@Tigrov) - Enh #852: Add method chaining for column classes (@Tigrov) -- Enh #855: Add array and JSON overlaps conditions (@Tigrov) -- Enh #860: Add `bit` abstract type (@Tigrov) +- New #855: Add array and JSON overlaps conditions (@Tigrov) +- New #860: Add `bit` abstract type (@Tigrov) - Enh #862: Refactor PHP type of `ColumnSchemaInterface` instances (@Tigrov) - 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) -- Enh #864: Realize column factory (@Tigrov) +- New #864: 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) - Enh #881: Refactor `ColumnSchemaInterface` and `AbstractColumnSchema` (@Tigrov) -- End #882: Move `ArrayColumnSchema` and `StructuredColumnSchema` classes from `db-pgsql` package (@Tigrov) +- New #882: Move `ArrayColumnSchema` and `StructuredColumnSchema` classes from `db-pgsql` package (@Tigrov) +- New #883: Add `ColumnDefinitionBuilder` class and `QueryBuilderInterface::buildColumnDefinition()` method (@Tigrov) - Enh #885: Refactor `AbstractDsn` class (@Tigrov) ## 1.3.0 March 21, 2024 diff --git a/UPGRADE.md b/UPGRADE.md index cb859fd69..42496e1f1 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -104,7 +104,8 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace ### New methods - `QuoterInterface::getRawTableName()` - returns the raw table name without quotes; -- `ConnectionInterface::getColumnFactory()` - returns the column factory object for concrete DBMS. +- `SchemaInterface::getColumnFactory()` - returns the column factory object for concrete DBMS; +- `QueryBuilderInterface::buildColumnDefinition()` - builds column definition for `CREATE TABLE` statement. ### Remove methods diff --git a/src/Connection/ConnectionInterface.php b/src/Connection/ConnectionInterface.php index 55e24a83c..a8aad3d1c 100644 --- a/src/Connection/ConnectionInterface.php +++ b/src/Connection/ConnectionInterface.php @@ -14,7 +14,6 @@ use Yiisoft\Db\Query\BatchQueryResultInterface; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; -use Yiisoft\Db\Schema\Column\ColumnFactoryInterface; use Yiisoft\Db\Schema\QuoterInterface; use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Schema\TableSchemaInterface; @@ -86,11 +85,6 @@ public function createTransaction(): TransactionInterface; */ public function close(): void; - /** - * Returns the column factory for creating column instances. - */ - public function getColumnFactory(): ColumnFactoryInterface; - /** * Returns the name of the DB driver for the current `dsn`. * diff --git a/src/Debug/ConnectionInterfaceProxy.php b/src/Debug/ConnectionInterfaceProxy.php index 802392be8..3944c3f6f 100644 --- a/src/Debug/ConnectionInterfaceProxy.php +++ b/src/Debug/ConnectionInterfaceProxy.php @@ -10,7 +10,6 @@ use Yiisoft\Db\Query\BatchQueryResultInterface; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; -use Yiisoft\Db\Schema\Column\ColumnFactoryInterface; use Yiisoft\Db\Schema\QuoterInterface; use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Schema\TableSchemaInterface; @@ -63,11 +62,6 @@ public function close(): void $this->connection->close(); } - public function getColumnFactory(): ColumnFactoryInterface - { - return $this->connection->getColumnFactory(); - } - public function getLastInsertID(string $sequenceName = null): string { return $this->connection->getLastInsertID($sequenceName); diff --git a/src/QueryBuilder/AbstractColumnDefinitionBuilder.php b/src/QueryBuilder/AbstractColumnDefinitionBuilder.php new file mode 100644 index 000000000..6682de87d --- /dev/null +++ b/src/QueryBuilder/AbstractColumnDefinitionBuilder.php @@ -0,0 +1,329 @@ +buildType($column) + . $this->buildUnsigned($column) + . $this->buildNotNull($column) + . $this->buildPrimaryKey($column) + . $this->buildAutoIncrement($column) + . $this->buildUnique($column) + . $this->buildDefault($column) + . $this->buildComment($column) + . $this->buildCheck($column) + . $this->buildReferences($column) + . $this->buildExtra($column); + } + + /** + * Builds the auto increment clause for the column. + * + * @return string A string containing the {@see AUTO_INCREMENT_KEYWORD} keyword. + */ + protected function buildAutoIncrement(ColumnSchemaInterface $column): string + { + if (empty(static::AUTO_INCREMENT_KEYWORD) || !$column->isAutoIncrement()) { + return ''; + } + + return match ($column->getType()) { + ColumnType::TINYINT, + ColumnType::SMALLINT, + ColumnType::INTEGER, + ColumnType::BIGINT => ' ' . static::AUTO_INCREMENT_KEYWORD, + default => '', + }; + } + + /** + * Builds the check constraint for the column. + * + * @return string A string containing the CHECK constraint. + */ + protected function buildCheck(ColumnSchemaInterface $column): string + { + $check = $column->getCheck(); + + return !empty($check) ? " CHECK ($check)" : ''; + } + + /** + * Builds the comment clause for the column. Default is empty string. + * + * @return string A string containing the COMMENT keyword and the comment itself. + */ + protected function buildComment(ColumnSchemaInterface $column): string + { + return ''; + } + + /** + * Builds the default value specification for the column. + * + * @return string A string containing the DEFAULT keyword and the default value. + */ + protected function buildDefault(ColumnSchemaInterface $column): string + { + if (!empty(static::GENERATE_UUID_EXPRESSION) + && $column->getType() === ColumnType::UUID + && $column->isAutoIncrement() + && $column->getDefaultValue() === null + ) { + return ' DEFAULT ' . static::GENERATE_UUID_EXPRESSION; + } + + if ($column->isAutoIncrement() && $column->getType() !== ColumnType::UUID + || $column->getDefaultValue() === null + ) { + return ''; + } + + $defaultValue = $this->buildDefaultValue($column); + + if ($defaultValue === null) { + return ''; + } + + return " DEFAULT $defaultValue"; + } + + /** + * Return the default value for the column. + * + * @return string|null string with default value of column. + */ + protected function buildDefaultValue(ColumnSchemaInterface $column): string|null + { + $value = $column->dbTypecast($column->getDefaultValue()); + + if ($value === null) { + return null; + } + + if ($value instanceof ExpressionInterface) { + return $this->queryBuilder->buildExpression($value); + } + + /** @var string */ + return match (gettype($value)) { + GettypeResult::INTEGER => (string) $value, + GettypeResult::DOUBLE => (string) $value, + GettypeResult::BOOLEAN => $value ? 'TRUE' : 'FALSE', + default => $this->queryBuilder->quoter()->quoteValue((string) $value), + }; + } + + /** + * Builds the custom string that's appended to column definition. + * + * @return string A string containing the custom SQL fragment appended to column definition. + */ + protected function buildExtra(ColumnSchemaInterface $column): string + { + $extra = $column->getExtra(); + + return !empty($extra) ? " $extra" : ''; + } + + /** + * Builds the not null constraint for the column. + * + * @return string A string 'NOT NULL' if {@see ColumnSchemaInterface::isNotNull()} is `true` + * or an empty string otherwise. + */ + protected function buildNotNull(ColumnSchemaInterface $column): string + { + return $column->isNotNull() ? ' NOT NULL' : ''; + } + + /** + * Builds the primary key clause for column. + * + * @return string A string containing the PRIMARY KEY keyword. + */ + protected function buildPrimaryKey(ColumnSchemaInterface $column): string + { + return $column->isPrimaryKey() ? ' PRIMARY KEY' : ''; + } + + /** + * Builds the references clause for the column. + */ + protected function buildReferences(ColumnSchemaInterface $column): string + { + $reference = $this->buildReferenceDefinition($column); + + if ($reference === null) { + return ''; + } + + return " REFERENCES $reference"; + } + + /** + * Builds the reference definition for the column. + */ + protected function buildReferenceDefinition(ColumnSchemaInterface $column): string|null + { + $reference = $column->getReference(); + $table = $reference?->getForeignTableName(); + + if ($table === null) { + return null; + } + + $quoter = $this->queryBuilder->quoter(); + $schema = $reference?->getForeignSchemaName(); + + $sql = $quoter->quoteTableName($table); + + if ($schema !== null) { + $sql = $quoter->quoteTableName($schema) . '.' . $sql; + } + + $columns = $reference?->getForeignColumnNames(); + + if (!empty($columns)) { + $sql .= ' (' . $this->queryBuilder->buildColumns($columns) . ')'; + } + + if (null !== $onDelete = $reference?->getOnDelete()) { + $sql .= $this->buildOnDelete($onDelete); + } + + if (null !== $onUpdate = $reference?->getOnUpdate()) { + $sql .= $this->buildOnUpdate($onUpdate); + } + + return $sql; + } + + /** + * Builds the ON DELETE clause for the column reference. + */ + protected function buildOnDelete(string $onDelete): string + { + return " ON DELETE $onDelete"; + } + + /** + * Builds the ON UPDATE clause for the column reference. + */ + protected function buildOnUpdate(string $onUpdate): string + { + return " ON UPDATE $onUpdate"; + } + + /** + * Builds the type definition for the column. For example: `varchar(128)` or `decimal(10,2)`. + * + * @return string A string containing the column type definition. + */ + protected function buildType(ColumnSchemaInterface $column): string + { + $dbType = $column->getDbType(); + + if ($dbType === null) { + $dbType = $this->getDbType($column); + } + + if (empty($dbType) + || $dbType[-1] === ')' + || !in_array(strtolower($dbType), static::TYPES_WITH_SIZE, true) + ) { + return $dbType; + } + + $size = $column->getSize(); + + if ($size === null) { + return $dbType; + } + + $scale = $column->getScale(); + + if ($scale === null || !in_array(strtolower($dbType), static::TYPES_WITH_SCALE, true)) { + return "$dbType($size)"; + } + + return "$dbType($size,$scale)"; + } + + /** + * Builds the unique constraint for the column. + * + * @return string A string 'UNIQUE' if {@see ColumnSchemaInterface::isUnique()} is true + * or an empty string otherwise. + */ + protected function buildUnique(ColumnSchemaInterface $column): string + { + if ($column->isPrimaryKey()) { + return ''; + } + + return $column->isUnique() ? ' UNIQUE' : ''; + } + + /** + * Builds the unsigned string for the column. + * + * @return string A string containing the UNSIGNED keyword. + */ + protected function buildUnsigned(ColumnSchemaInterface $column): string + { + return $column->isUnsigned() ? ' UNSIGNED' : ''; + } +} diff --git a/src/QueryBuilder/AbstractDDLQueryBuilder.php b/src/QueryBuilder/AbstractDDLQueryBuilder.php index ce387a8ae..7365d5967 100644 --- a/src/QueryBuilder/AbstractDDLQueryBuilder.php +++ b/src/QueryBuilder/AbstractDDLQueryBuilder.php @@ -41,6 +41,7 @@ public function addCheck(string $table, string $name, string $expression): strin public function addColumn(string $table, string $column, ColumnInterface|string $type): string { + /** @psalm-suppress DeprecatedMethod */ return 'ALTER TABLE ' . $this->quoter->quoteTableName($table) . ' ADD ' @@ -138,6 +139,7 @@ public function alterColumn( string $column, ColumnInterface|string $type ): string { + /** @psalm-suppress DeprecatedMethod */ return 'ALTER TABLE ' . $this->quoter->quoteTableName($table) . ' CHANGE ' @@ -171,6 +173,7 @@ 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) . ' ' diff --git a/src/QueryBuilder/AbstractQueryBuilder.php b/src/QueryBuilder/AbstractQueryBuilder.php index 4123e7cc0..a8a4a9703 100644 --- a/src/QueryBuilder/AbstractQueryBuilder.php +++ b/src/QueryBuilder/AbstractQueryBuilder.php @@ -10,10 +10,12 @@ use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\Condition\Interface\ConditionInterface; use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; use Yiisoft\Db\Schema\QuoterInterface; use Yiisoft\Db\Schema\SchemaInterface; use function count; +use function is_string; use function preg_match; use function preg_replace; @@ -48,7 +50,8 @@ public function __construct( private SchemaInterface $schema, private AbstractDDLQueryBuilder $ddlBuilder, private AbstractDMLQueryBuilder $dmlBuilder, - private AbstractDQLQueryBuilder $dqlBuilder + private AbstractDQLQueryBuilder $dqlBuilder, + private AbstractColumnDefinitionBuilder $columnDefinitionBuilder, ) { } @@ -151,6 +154,15 @@ public function build(QueryInterface $query, array $params = []): array return $this->dqlBuilder->build($query, $params); } + public function buildColumnDefinition(ColumnSchemaInterface|string $column): string + { + if (is_string($column)) { + $column = $this->schema->getColumnFactory()->fromDefinition($column); + } + + return $this->columnDefinitionBuilder->build($column); + } + public function buildColumns(array|string $columns): string { return $this->dqlBuilder->buildColumns($columns); @@ -322,6 +334,7 @@ public function dropView(string $viewName): string return $this->ddlBuilder->dropView($viewName); } + /** @deprecated Use {@see buildColumnDefinition()}. Will be removed in version 2.0. */ public function getColumnType(ColumnInterface|string $type): string { if ($type instanceof ColumnInterface) { diff --git a/src/QueryBuilder/ColumnDefinitionBuilderInterface.php b/src/QueryBuilder/ColumnDefinitionBuilderInterface.php new file mode 100644 index 000000000..332613149 --- /dev/null +++ b/src/QueryBuilder/ColumnDefinitionBuilderInterface.php @@ -0,0 +1,19 @@ + ColumnBuilder::bigPrimaryKey()->load($info), PseudoType::UBIGPK => ColumnBuilder::bigPrimaryKey()->unsigned()->load($info), PseudoType::UUID_PK => ColumnBuilder::uuidPrimaryKey()->load($info), - PseudoType::UUID_PK_SEQ => ColumnBuilder::uuidPrimaryKey(true)->load($info), + PseudoType::UUID_PK_SEQ => ColumnBuilder::uuidPrimaryKey()->load($info), }; } diff --git a/src/Schema/Column/ColumnBuilder.php b/src/Schema/Column/ColumnBuilder.php index 6dfb22722..9160fffd3 100644 --- a/src/Schema/Column/ColumnBuilder.php +++ b/src/Schema/Column/ColumnBuilder.php @@ -47,7 +47,7 @@ public static function bigPrimaryKey(bool $autoIncrement = true): ColumnSchemaIn /** * Builds a column as an `uuid` primary key. */ - public static function uuidPrimaryKey(bool $autoIncrement = false): ColumnSchemaInterface + public static function uuidPrimaryKey(bool $autoIncrement = true): ColumnSchemaInterface { return static::uuid() ->primaryKey() @@ -65,6 +65,8 @@ public static function boolean(): ColumnSchemaInterface /** * Builds a column with the abstract type `bit`. + * + * @param int|null $size The number of bits that the column can store. */ public static function bit(int|null $size = null): ColumnSchemaInterface { diff --git a/src/Schema/SchemaInterface.php b/src/Schema/SchemaInterface.php index 72a40aa47..3e774b8a8 100644 --- a/src/Schema/SchemaInterface.php +++ b/src/Schema/SchemaInterface.php @@ -13,6 +13,7 @@ use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnFactoryInterface; /** * Represents the schema for a database table. @@ -279,9 +280,16 @@ interface SchemaInterface extends ConstraintSchemaInterface /** * @psalm-param string[]|int[]|int|string|null $length + * + * @deprecated Use {@see ColumnBuilder} instead. Will be removed in 2.0. */ public function createColumn(string $type, array|int|string $length = null): ColumnInterface; + /** + * Returns the column factory for creating column instances. + */ + public function getColumnFactory(): ColumnFactoryInterface; + /** * @return string|null The default schema name. */ diff --git a/tests/AbstractColumnFactoryTest.php b/tests/AbstractColumnFactoryTest.php index cce700a6a..908f4b88e 100644 --- a/tests/AbstractColumnFactoryTest.php +++ b/tests/AbstractColumnFactoryTest.php @@ -17,7 +17,7 @@ abstract class AbstractColumnFactoryTest extends TestCase public function testFromDbType(string $dbType, string $expectedType, string $expectedInstanceOf): void { $db = $this->getConnection(); - $columnFactory = $db->getColumnFactory(); + $columnFactory = $db->getSchema()->getColumnFactory(); $column = $columnFactory->fromDbType($dbType); @@ -38,7 +38,7 @@ public function testFromDefinition( array $expectedMethodResults = [] ): void { $db = $this->getConnection(); - $columnFactory = $db->getColumnFactory(); + $columnFactory = $db->getSchema()->getColumnFactory(); $column = $columnFactory->fromDefinition($definition); @@ -65,7 +65,7 @@ public function testFromPseudoType( array $expectedMethodResults = [] ): void { $db = $this->getConnection(); - $columnFactory = $db->getColumnFactory(); + $columnFactory = $db->getSchema()->getColumnFactory(); $column = $columnFactory->fromPseudoType($pseudoType); @@ -88,7 +88,7 @@ public function testFromPseudoType( public function testFromType(string $type, string $expectedType, string $expectedInstanceOf): void { $db = $this->getConnection(); - $columnFactory = $db->getColumnFactory(); + $columnFactory = $db->getSchema()->getColumnFactory(); $column = $columnFactory->fromType($type); @@ -101,7 +101,7 @@ public function testFromType(string $type, string $expectedType, string $expecte public function testFromDefinitionWithExtra(): void { $db = $this->getConnection(); - $columnFactory = $db->getColumnFactory(); + $columnFactory = $db->getSchema()->getColumnFactory(); $column = $columnFactory->fromDefinition('char(1) NOT NULL', ['extra' => 'UNIQUE']); diff --git a/tests/AbstractQueryBuilderTest.php b/tests/AbstractQueryBuilderTest.php index c1fc66be9..91c73468c 100644 --- a/tests/AbstractQueryBuilderTest.php +++ b/tests/AbstractQueryBuilderTest.php @@ -26,6 +26,7 @@ use Yiisoft\Db\QueryBuilder\Condition\JsonOverlapsCondition; use Yiisoft\Db\QueryBuilder\Condition\SimpleCondition; use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; use Yiisoft\Db\Schema\QuoterInterface; use Yiisoft\Db\Tests\Support\Assert; use Yiisoft\Db\Tests\Support\DbHelper; @@ -2402,4 +2403,13 @@ public function testOverrideParameters2(): void $command->getRawSql() ); } + + /** @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::buildColumnDefinition() */ + public function testBuildColumnDefinition(string $expected, ColumnSchemaInterface|string $column): void + { + $db = $this->getConnection(); + $qb = $db->getQueryBuilder(); + + $this->assertSame($expected, $qb->buildColumnDefinition($column)); + } } diff --git a/tests/Common/CommonColumnSchemaTest.php b/tests/Common/CommonColumnSchemaTest.php index c9ffc454c..274f1b66b 100644 --- a/tests/Common/CommonColumnSchemaTest.php +++ b/tests/Common/CommonColumnSchemaTest.php @@ -36,7 +36,7 @@ public function testDbTypecastColumns(string $className, array $values) /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnSchemaProvider::phpTypecastColumns */ public function testPhpTypecastColumns(string $className, array $values) { - $column = new $className('column_name'); + $column = new $className(); foreach ($values as [$expected, $value]) { $this->assertSame($expected, $column->phpTypecast($value)); diff --git a/tests/Common/CommonPdoConnectionTest.php b/tests/Common/CommonPdoConnectionTest.php index 756616e9c..88b101987 100644 --- a/tests/Common/CommonPdoConnectionTest.php +++ b/tests/Common/CommonPdoConnectionTest.php @@ -311,7 +311,6 @@ public function testTransactionRollbackTransactionOnLevel(): void 'createTransaction', 'createCommand', 'close', - 'getColumnFactory', 'getDriverName', 'getLastInsertID', 'getQueryBuilder', @@ -346,7 +345,6 @@ public function testGetActivePdo(): void $db = $this->getMockBuilder(AbstractPdoConnection::class)->onlyMethods([ 'createCommand', 'createTransaction', - 'getColumnFactory', 'getPdo', 'getQueryBuilder', 'getQuoter', diff --git a/tests/Db/Schema/ColumnSchemaTest.php b/tests/Db/Schema/ColumnSchemaTest.php index e76a1fef7..449646517 100644 --- a/tests/Db/Schema/ColumnSchemaTest.php +++ b/tests/Db/Schema/ColumnSchemaTest.php @@ -307,7 +307,7 @@ public function testUnsigned(): void $this->assertFalse($column->isUnsigned()); - $column->unsigned(true); + $column->unsigned(); $this->assertTrue($column->isUnsigned()); } diff --git a/tests/Provider/ColumnBuilderProvider.php b/tests/Provider/ColumnBuilderProvider.php index 0572bc219..b147df30d 100644 --- a/tests/Provider/ColumnBuilderProvider.php +++ b/tests/Provider/ColumnBuilderProvider.php @@ -46,8 +46,8 @@ public static function buildingMethods(): array 'smallPrimaryKey(false)' => ['smallPrimaryKey', [false], IntegerColumnSchema::class, ColumnType::SMALLINT, ['isPrimaryKey' => true, 'isAutoIncrement' => false]], 'bigPrimaryKey()' => ['bigPrimaryKey', [], IntegerColumnSchema::class, ColumnType::BIGINT, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], 'bigPrimaryKey(false)' => ['bigPrimaryKey', [false], IntegerColumnSchema::class, ColumnType::BIGINT, ['isPrimaryKey' => true, 'isAutoIncrement' => false]], - 'uuidPrimaryKey()' => ['uuidPrimaryKey', [], StringColumnSchema::class, ColumnType::UUID, ['isPrimaryKey' => true, 'isAutoIncrement' => false]], - 'uuidPrimaryKey(true)' => ['uuidPrimaryKey', [true], StringColumnSchema::class, ColumnType::UUID, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], + 'uuidPrimaryKey()' => ['uuidPrimaryKey', [], StringColumnSchema::class, ColumnType::UUID, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], + 'uuidPrimaryKey(false)' => ['uuidPrimaryKey', [false], StringColumnSchema::class, ColumnType::UUID, ['isPrimaryKey' => true, 'isAutoIncrement' => false]], 'boolean()' => ['boolean', [], BooleanColumnSchema::class, ColumnType::BOOLEAN], 'bit()' => ['bit', [], BitColumnSchema::class, ColumnType::BIT], 'bit(1)' => ['bit', [1], BitColumnSchema::class, ColumnType::BIT, ['getSize' => 1]], diff --git a/tests/Provider/ColumnFactoryProvider.php b/tests/Provider/ColumnFactoryProvider.php index efd7106b7..7b7f7f778 100644 --- a/tests/Provider/ColumnFactoryProvider.php +++ b/tests/Provider/ColumnFactoryProvider.php @@ -38,7 +38,7 @@ public static function pseudoTypes(): array 'upk' => [PseudoType::UPK, ColumnType::INTEGER, IntegerColumnSchema::class, ['isPrimaryKey' => true, 'isAutoIncrement' => true, 'isUnsigned' => true]], 'bigpk' => [PseudoType::BIGPK, ColumnType::BIGINT, IntegerColumnSchema::class, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], 'ubigpk' => [PseudoType::UBIGPK, ColumnType::BIGINT, IntegerColumnSchema::class, ['isPrimaryKey' => true, 'isAutoIncrement' => true, 'isUnsigned' => true]], - 'uuid_pk' => [PseudoType::UUID_PK, ColumnType::UUID, StringColumnSchema::class, ['isPrimaryKey' => true]], + 'uuid_pk' => [PseudoType::UUID_PK, ColumnType::UUID, StringColumnSchema::class, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], 'uuid_pk_seq' => [PseudoType::UUID_PK_SEQ, ColumnType::UUID, StringColumnSchema::class, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], ]; } diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index de73e3110..85df631c5 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -8,6 +8,8 @@ use Yiisoft\Db\Command\DataType; use Yiisoft\Db\Command\Param; use Yiisoft\Db\Constant\ColumnType; +use Yiisoft\Db\Constant\PseudoType; +use Yiisoft\Db\Constraint\ForeignKeyConstraint; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Expression\JsonExpression; use Yiisoft\Db\Query\Query; @@ -15,6 +17,7 @@ use Yiisoft\Db\QueryBuilder\Condition\InCondition; use Yiisoft\Db\QueryBuilder\Condition\LikeCondition; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; +use Yiisoft\Db\Schema\Column\ColumnBuilder; use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Tests\Support\DbHelper; use Yiisoft\Db\Tests\Support\Stub\Column; @@ -1562,4 +1565,132 @@ public static function overlapsCondition(): array 'query expression' => [(new Query(static::getDb()))->select(new JsonExpression([0,1,2,7])), 1], ]; } + + public static function buildColumnDefinition(): array + { + $reference = new ForeignKeyConstraint(); + $reference->foreignColumnNames(['id']); + $reference->foreignTableName('ref_table'); + $reference->onDelete('CASCADE'); + $reference->onUpdate('CASCADE'); + + $referenceWithSchema = clone $reference; + $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::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(100)' => ['varchar(100)', ColumnType::STRING . '(100)'], + + 'primaryKey()' => ['integer PRIMARY KEY AUTO_INCREMENT', ColumnBuilder::primaryKey()], + 'primaryKey(false)' => ['integer PRIMARY KEY', ColumnBuilder::primaryKey(false)], + 'smallPrimaryKey()' => ['smallint PRIMARY KEY AUTO_INCREMENT', ColumnBuilder::smallPrimaryKey()], + 'smallPrimaryKey(false)' => ['smallint PRIMARY KEY', ColumnBuilder::smallPrimaryKey(false)], + 'bigPrimaryKey()' => ['bigint PRIMARY KEY AUTO_INCREMENT', 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)], + + 'boolean()' => ['boolean', ColumnBuilder::boolean()], + 'boolean(100)' => ['boolean', ColumnBuilder::boolean()->size(100)], + '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)], + 'tinyint()' => ['tinyint', ColumnBuilder::tinyint()], + 'tinyint(2)' => ['tinyint(2)', ColumnBuilder::tinyint(2)], + 'smallint()' => ['smallint', ColumnBuilder::smallint()], + 'smallint(4)' => ['smallint(4)', ColumnBuilder::smallint(4)], + 'integer()' => ['integer', ColumnBuilder::integer()], + 'integer(8)' => ['integer(8)', ColumnBuilder::integer(8)], + 'bigint()' => ['bigint', ColumnBuilder::bigint()], + 'bigint(15)' => ['bigint(15)', ColumnBuilder::bigint(15)], + 'float()' => ['float', ColumnBuilder::float()], + 'float(10)' => ['float(10)', ColumnBuilder::float(10)], + 'float(10,2)' => ['float(10,2)', ColumnBuilder::float(10, 2)], + 'double()' => ['double', ColumnBuilder::double()], + 'double(10)' => ['double(10)', ColumnBuilder::double(10)], + 'double(10,2)' => ['double(10,2)', ColumnBuilder::double(10, 2)], + 'decimal()' => ['decimal(10,0)', ColumnBuilder::decimal()], + 'decimal(5)' => ['decimal(5,0)', ColumnBuilder::decimal(5)], + 'decimal(5,2)' => ['decimal(5,2)', ColumnBuilder::decimal(5, 2)], + 'decimal(null)' => ['decimal', ColumnBuilder::decimal(null)], + 'money()' => ['money', ColumnBuilder::money()], + 'money(10)' => ['money', ColumnBuilder::money(10)], + 'money(10,2)' => ['money', ColumnBuilder::money(10, 2)], + 'money(null)' => ['money', ColumnBuilder::money(null)], + 'char()' => ['char(1)', ColumnBuilder::char()], + 'char(10)' => ['char(10)', ColumnBuilder::char(10)], + 'char(null)' => ['char', ColumnBuilder::char(null)], + 'string()' => ['varchar(255)', ColumnBuilder::string()], + 'string(100)' => ['varchar(100)', ColumnBuilder::string(100)], + 'string(null)' => ['varchar', ColumnBuilder::string(null)], + 'text()' => ['text', ColumnBuilder::text()], + 'text(1000)' => ['text(1000)', ColumnBuilder::text(1000)], + 'binary()' => ['binary', ColumnBuilder::binary()], + 'binary(1000)' => ['binary(1000)', ColumnBuilder::binary(1000)], + 'uuid()' => ['uuid', ColumnBuilder::uuid()], + 'datetime()' => ['datetime(0)', ColumnBuilder::datetime()], + 'datetime(6)' => ['datetime(6)', ColumnBuilder::datetime(6)], + 'datetime(null)' => ['datetime', ColumnBuilder::datetime(null)], + 'timestamp()' => ['timestamp(0)', ColumnBuilder::timestamp()], + 'timestamp(6)' => ['timestamp(6)', ColumnBuilder::timestamp(6)], + 'timestamp(null)' => ['timestamp', ColumnBuilder::timestamp(null)], + 'date()' => ['date', ColumnBuilder::date()], + 'date(100)' => ['date', ColumnBuilder::date()->size(100)], + 'time()' => ['time(0)', ColumnBuilder::time()], + 'time(6)' => ['time(6)', ColumnBuilder::time(6)], + 'time(null)' => ['time', ColumnBuilder::time(null)], + 'array()' => ['json', ColumnBuilder::array()], + 'structured()' => ['json', ColumnBuilder::structured()], + "structured('structured_type')" => ['structured_type', ColumnBuilder::structured('structured_type')], + '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('')" => ['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)], + "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('')], + 'notNull()' => ['varchar(255) NOT NULL', ColumnBuilder::string()->notNull()], + 'integer()->primaryKey()' => ['integer PRIMARY KEY', ColumnBuilder::integer()->primaryKey()], + 'size(10)' => ['varchar(10)', ColumnBuilder::string()->size(10)], + 'unique()' => ['varchar(255) UNIQUE', ColumnBuilder::string()->unique()], + 'unsigned()' => ['integer UNSIGNED', ColumnBuilder::integer()->unsigned()], + 'scale(2)' => ['decimal(10,2)', ColumnBuilder::decimal()->scale(2)], + 'integer(8)->scale(2)' => ['integer(8)', ColumnBuilder::integer(8)->scale(2)], + 'reference($reference)' => [ + DbHelper::replaceQuotes( + <<reference($reference), + ], + 'reference($referenceWithSchema)' => [ + DbHelper::replaceQuotes( + <<reference($referenceWithSchema), + ], + ]; + } } diff --git a/tests/Support/Stub/ColumnDefinitionBuilder.php b/tests/Support/Stub/ColumnDefinitionBuilder.php new file mode 100644 index 000000000..199943ef6 --- /dev/null +++ b/tests/Support/Stub/ColumnDefinitionBuilder.php @@ -0,0 +1,69 @@ +getType()) { + ColumnType::BOOLEAN => 'boolean', + ColumnType::BIT => 'bit', + ColumnType::TINYINT => 'tinyint', + ColumnType::SMALLINT => 'smallint', + ColumnType::INTEGER => 'integer', + ColumnType::BIGINT => 'bigint', + ColumnType::FLOAT => 'float', + ColumnType::DOUBLE => 'double', + ColumnType::DECIMAL => 'decimal', + ColumnType::MONEY => 'money', + ColumnType::CHAR => 'char', + ColumnType::STRING => 'varchar', + ColumnType::TEXT => 'text', + ColumnType::BINARY => 'binary', + ColumnType::UUID => 'uuid', + ColumnType::DATETIME => 'datetime', + ColumnType::TIMESTAMP => 'timestamp', + ColumnType::DATE => 'date', + ColumnType::TIME => 'time', + ColumnType::ARRAY => 'json', + ColumnType::STRUCTURED => 'json', + ColumnType::JSON => 'json', + default => 'varchar', + }; + } +} diff --git a/tests/Support/Stub/ColumnFactory.php b/tests/Support/Stub/ColumnFactory.php index bd6d4f2bc..2adf21f23 100644 --- a/tests/Support/Stub/ColumnFactory.php +++ b/tests/Support/Stub/ColumnFactory.php @@ -16,6 +16,7 @@ protected function getType(string $dbType, array $info = []): string protected function isDbType(string $dbType): bool { - return $this->isType($dbType); + return $dbType === 'varchar' + || $dbType !== 'string' && $this->isType($dbType); } } diff --git a/tests/Support/Stub/Connection.php b/tests/Support/Stub/Connection.php index ac616b0af..8b0cee145 100644 --- a/tests/Support/Stub/Connection.php +++ b/tests/Support/Stub/Connection.php @@ -8,7 +8,6 @@ use Yiisoft\Db\Command\CommandInterface; use Yiisoft\Db\Driver\Pdo\AbstractPdoConnection; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; -use Yiisoft\Db\Schema\Column\ColumnFactoryInterface; use Yiisoft\Db\Schema\Quoter; use Yiisoft\Db\Schema\QuoterInterface; use Yiisoft\Db\Schema\SchemaInterface; @@ -43,11 +42,6 @@ public function createTransaction(): TransactionInterface return new Transaction($this); } - public function getColumnFactory(): ColumnFactoryInterface - { - return new ColumnFactory(); - } - public function getQueryBuilder(): QueryBuilderInterface { if ($this->queryBuilder === null) { diff --git a/tests/Support/Stub/QueryBuilder.php b/tests/Support/Stub/QueryBuilder.php index e78c90cdc..ad93dd8ce 100644 --- a/tests/Support/Stub/QueryBuilder.php +++ b/tests/Support/Stub/QueryBuilder.php @@ -14,8 +14,9 @@ public function __construct(QuoterInterface $quoter, SchemaInterface $schema) { $ddlBuilder = new DDLQueryBuilder($this, $quoter, $schema); $dmlBuilder = new DMLQueryBuilder($this, $quoter, $schema); - $dqlBuilder = new DQLQueryBuilder($this, $quoter, $schema); + $dqlBuilder = new DQLQueryBuilder($this, $quoter); + $columnDefinitionBuilder = new ColumnDefinitionBuilder($this); - parent::__construct($quoter, $schema, $ddlBuilder, $dmlBuilder, $dqlBuilder); + parent::__construct($quoter, $schema, $ddlBuilder, $dmlBuilder, $dqlBuilder, $columnDefinitionBuilder); } } diff --git a/tests/Support/Stub/Schema.php b/tests/Support/Stub/Schema.php index 554299ccb..62098a38a 100644 --- a/tests/Support/Stub/Schema.php +++ b/tests/Support/Stub/Schema.php @@ -8,6 +8,7 @@ use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Schema\AbstractSchema; use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnFactoryInterface; use Yiisoft\Db\Schema\TableSchemaInterface; /** @@ -22,6 +23,11 @@ public function createColumn(string $type, array|int|string $length = null): Col return new Column($type, $length); } + public function getColumnFactory(): ColumnFactoryInterface + { + return new ColumnFactory(); + } + public function findUniqueIndexes(TableSchemaInterface $table): array { throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.');