diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ac0433df..44ce8fb13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ - Enh #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) ## 1.3.0 March 21, 2024 diff --git a/UPGRADE.md b/UPGRADE.md index aac6197ae..6fe3a7c57 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -94,7 +94,7 @@ 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; -- `SchemaInterface::getColumnFactory()` - returns the column factory. +- `ConnectionInterface::getColumnFactory()` - returns the column factory object for concrete DBMS. ### Remove methods diff --git a/src/Connection/ConnectionInterface.php b/src/Connection/ConnectionInterface.php index a8aad3d1c..55e24a83c 100644 --- a/src/Connection/ConnectionInterface.php +++ b/src/Connection/ConnectionInterface.php @@ -14,6 +14,7 @@ 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; @@ -85,6 +86,11 @@ 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 3944c3f6f..802392be8 100644 --- a/src/Debug/ConnectionInterfaceProxy.php +++ b/src/Debug/ConnectionInterfaceProxy.php @@ -10,6 +10,7 @@ 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; @@ -62,6 +63,11 @@ 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/Schema/Column/AbstractColumnFactory.php b/src/Schema/Column/AbstractColumnFactory.php index d4990d050..6cb1329f0 100644 --- a/src/Schema/Column/AbstractColumnFactory.php +++ b/src/Schema/Column/AbstractColumnFactory.php @@ -5,15 +5,8 @@ namespace Yiisoft\Db\Schema\Column; use Yiisoft\Db\Constant\ColumnType; - -use function explode; -use function preg_match; -use function str_ireplace; -use function stripos; -use function strlen; -use function strtolower; -use function substr; -use function trim; +use Yiisoft\Db\Constant\PseudoType; +use Yiisoft\Db\Syntax\ColumnDefinitionParser; use const PHP_INT_SIZE; @@ -38,6 +31,11 @@ abstract class AbstractColumnFactory implements ColumnFactoryInterface */ abstract protected function getType(string $dbType, array $info = []): string; + /** + * Checks if the column type is a database type. + */ + abstract protected function isDbType(string $dbType): bool; + public function fromDbType(string $dbType, array $info = []): ColumnSchemaInterface { $info['db_type'] = $dbType; @@ -48,41 +46,45 @@ public function fromDbType(string $dbType, array $info = []): ColumnSchemaInterf public function fromDefinition(string $definition, array $info = []): ColumnSchemaInterface { - preg_match('/^(\w*)(?:\(([^)]+)\))?\s*/', $definition, $matches); + $definitionInfo = $this->columnDefinitionParser()->parse($definition); - $dbType = strtolower($matches[1]); + if (isset($info['extra'], $definitionInfo['extra'])) { + $info['extra'] = $definitionInfo['extra'] . ' ' . $info['extra']; + } + + /** @var string $dbType */ + $dbType = $definitionInfo['db_type'] ?? ''; + unset($definitionInfo['db_type']); - if (isset($matches[2])) { - $values = explode(',', $matches[2]); - $info['size'] = (int) $values[0]; - $info['precision'] = (int) $values[0]; + $info += $definitionInfo; + + if ($this->isDbType($dbType)) { + return $this->fromDbType($dbType, $info); + } - if (isset($values[1])) { - $info['scale'] = (int) $values[1]; - } + if ($this->isType($dbType)) { + return $this->fromType($dbType, $info); } - $extra = substr($definition, strlen($matches[0])); - - if (!empty($extra)) { - if (stripos($extra, 'unsigned') !== false) { - $info['unsigned'] = true; - $extra = trim(str_ireplace('unsigned', '', $extra)); - } - - if (!empty($extra)) { - if (empty($info['extra'])) { - $info['extra'] = $extra; - } else { - /** @psalm-suppress MixedOperand */ - $info['extra'] = $extra . ' ' . $info['extra']; - } - } + if ($this->isPseudoType($dbType)) { + return $this->fromPseudoType($dbType, $info); } return $this->fromDbType($dbType, $info); } + public function fromPseudoType(string $pseudoType, array $info = []): ColumnSchemaInterface + { + return match ($pseudoType) { + PseudoType::PK => ColumnBuilder::primaryKey()->load($info), + PseudoType::UPK => ColumnBuilder::primaryKey()->unsigned()->load($info), + PseudoType::BIGPK => 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), + }; + } + public function fromType(string $type, array $info = []): ColumnSchemaInterface { $column = match ($type) { @@ -106,4 +108,62 @@ public function fromType(string $type, array $info = []): ColumnSchemaInterface return $column->load($info); } + + /** + * Returns the column definition parser. + */ + protected function columnDefinitionParser(): ColumnDefinitionParser + { + return new ColumnDefinitionParser(); + } + + /** + * Checks if the column type is a pseudo-type. + * + * @psalm-assert-if-true PseudoType::* $pseudoType + */ + protected function isPseudoType(string $pseudoType): bool + { + return match ($pseudoType) { + PseudoType::PK, + PseudoType::UPK, + PseudoType::BIGPK, + PseudoType::UBIGPK, + PseudoType::UUID_PK, + PseudoType::UUID_PK_SEQ => true, + default => false, + }; + } + + /** + * Checks if the column type is an abstract type. + * + * @psalm-assert-if-true ColumnType::* $type + */ + protected function isType(string $type): bool + { + return match ($type) { + ColumnType::BOOLEAN, + ColumnType::BIT, + ColumnType::TINYINT, + ColumnType::SMALLINT, + ColumnType::INTEGER, + ColumnType::BIGINT, + ColumnType::FLOAT, + ColumnType::DOUBLE, + ColumnType::DECIMAL, + ColumnType::MONEY, + ColumnType::CHAR, + ColumnType::STRING, + ColumnType::TEXT, + ColumnType::BINARY, + ColumnType::UUID, + ColumnType::DATETIME, + ColumnType::TIMESTAMP, + ColumnType::DATE, + ColumnType::TIME, + ColumnType::JSON => true, + default => false, + }; + } } diff --git a/src/Schema/Column/ColumnBuilder.php b/src/Schema/Column/ColumnBuilder.php new file mode 100644 index 000000000..0f89b3f6e --- /dev/null +++ b/src/Schema/Column/ColumnBuilder.php @@ -0,0 +1,241 @@ +primaryKey() + ->autoIncrement($autoIncrement) + ->allowNull(false); + } + + /** + * Builds a column as a `smallint` primary key. + */ + public static function smallPrimaryKey(bool $autoIncrement = true): ColumnSchemaInterface + { + return static::smallint() + ->primaryKey() + ->autoIncrement($autoIncrement) + ->allowNull(false); + } + + /** + * Builds a column as a `bigint` primary key. + */ + public static function bigPrimaryKey(bool $autoIncrement = true): ColumnSchemaInterface + { + return static::bigint() + ->primaryKey() + ->autoIncrement($autoIncrement) + ->allowNull(false); + } + + /** + * Builds a column as an `uuid` primary key. + */ + public static function uuidPrimaryKey(bool $autoIncrement = false): ColumnSchemaInterface + { + return static::uuid() + ->primaryKey() + ->autoIncrement($autoIncrement) + ->allowNull(false); + } + + // Abstract type column builders + /** + * Builds a column with the abstract type `boolean`. + */ + public static function boolean(): ColumnSchemaInterface + { + return new BooleanColumnSchema(ColumnType::BOOLEAN); + } + + /** + * Builds a column with the abstract type `bit`. + */ + public static function bit(int|null $size = null): ColumnSchemaInterface + { + return (new BitColumnSchema(ColumnType::BIT)) + ->size($size); + } + + /** + * Builds a column with the abstract type `tinyint`. + */ + public static function tinyint(int|null $size = null): ColumnSchemaInterface + { + return (new IntegerColumnSchema(ColumnType::TINYINT)) + ->size($size); + } + + /** + * Builds a column with the abstract type `smallint`. + */ + public static function smallint(int|null $size = null): ColumnSchemaInterface + { + return (new IntegerColumnSchema(ColumnType::SMALLINT)) + ->size($size); + } + + /** + * Builds a column with the abstract type `integer`. + */ + public static function integer(int|null $size = null): ColumnSchemaInterface + { + return (new IntegerColumnSchema(ColumnType::INTEGER)) + ->size($size); + } + + /** + * Builds a column with the abstract type `bigint`. + */ + public static function bigint(int|null $size = null): ColumnSchemaInterface + { + return (new IntegerColumnSchema(ColumnType::BIGINT)) + ->size($size); + } + + /** + * Builds a column with the abstract type `float`. + */ + public static function float(int|null $size = null, int|null $scale = null): ColumnSchemaInterface + { + return (new DoubleColumnSchema(ColumnType::FLOAT)) + ->size($size) + ->scale($scale); + } + + /** + * Builds a column with the abstract type `double`. + */ + public static function double(int|null $size = null, int|null $scale = null): ColumnSchemaInterface + { + return (new DoubleColumnSchema(ColumnType::DOUBLE)) + ->size($size) + ->scale($scale); + } + + /** + * Builds a column with the abstract type `decimal`. + */ + public static function decimal(int|null $size = 10, int|null $scale = 0): ColumnSchemaInterface + { + return (new DoubleColumnSchema(ColumnType::DECIMAL)) + ->size($size) + ->scale($scale); + } + + /** + * Builds a column with the abstract type `money`. + */ + public static function money(int|null $size = 19, int|null $scale = 4): ColumnSchemaInterface + { + return (new DoubleColumnSchema(ColumnType::MONEY)) + ->size($size) + ->scale($scale); + } + + /** + * Builds a column with the abstract type `char`. + */ + public static function char(int|null $size = 1): ColumnSchemaInterface + { + return (new StringColumnSchema(ColumnType::CHAR)) + ->size($size); + } + + /** + * Builds a column with the abstract type `string`. + */ + public static function string(int|null $size = 255): ColumnSchemaInterface + { + return (new StringColumnSchema(ColumnType::STRING)) + ->size($size); + } + + /** + * Builds a column with the abstract type `text`. + */ + public static function text(int|null $size = null): ColumnSchemaInterface + { + return (new StringColumnSchema(ColumnType::TEXT)) + ->size($size); + } + + /** + * Builds a column with the abstract type `binary`. + */ + public static function binary(int|null $size = null): ColumnSchemaInterface + { + return (new BinaryColumnSchema(ColumnType::BINARY)) + ->size($size); + } + + /** + * Builds a column with the abstract type `uuid`. + */ + public static function uuid(): ColumnSchemaInterface + { + return new StringColumnSchema(ColumnType::UUID); + } + + /** + * Builds a column with the abstract type `datetime`. + */ + public static function datetime(int|null $size = 0): ColumnSchemaInterface + { + return (new StringColumnSchema(ColumnType::DATETIME)) + ->size($size); + } + + /** + * Builds a column with the abstract type `timestamp`. + */ + public static function timestamp(int|null $size = 0): ColumnSchemaInterface + { + return (new StringColumnSchema(ColumnType::TIMESTAMP)) + ->size($size); + } + + /** + * Builds a column with the abstract type `date`. + */ + public static function date(): ColumnSchemaInterface + { + return new StringColumnSchema(ColumnType::DATE); + } + + /** + * Builds a column with the abstract type `time`. + */ + public static function time(int|null $size = 0): ColumnSchemaInterface + { + return (new StringColumnSchema(ColumnType::TIME)) + ->size($size); + } + + /** + * Builds a column with the abstract type `json`. + */ + public static function json(): ColumnSchemaInterface + { + return new JsonColumnSchema(ColumnType::JSON); + } +} diff --git a/src/Schema/Column/ColumnFactoryInterface.php b/src/Schema/Column/ColumnFactoryInterface.php index d1e35ec5e..61398e35d 100644 --- a/src/Schema/Column/ColumnFactoryInterface.php +++ b/src/Schema/Column/ColumnFactoryInterface.php @@ -5,6 +5,7 @@ namespace Yiisoft\Db\Schema\Column; use Yiisoft\Db\Constant\ColumnType; +use Yiisoft\Db\Constant\PseudoType; /** * The interface must be implemented by a column factory class. It should create a column schema for a database column @@ -26,6 +27,9 @@ public function fromDbType(string $dbType, array $info = []): ColumnSchemaInterf /** * Creates a column schema for a database column definition and initializes column information. + * The definition string can contain the database type of the column, its size, default value, etc. + * + * For example, `varchar(255) NOT NULL` is a database type with a size and a NOT NULL constraint. * * @param string $definition The database column definition. * @param array $info The column information. @@ -34,6 +38,19 @@ public function fromDbType(string $dbType, array $info = []): ColumnSchemaInterf */ public function fromDefinition(string $definition, array $info = []): ColumnSchemaInterface; + /** + * Creates a column schema for a pseudo-type and initializes column information. + * + * @param string $pseudoType The pseudo-type. + * @param array $info The column information. + * + * @return ColumnSchemaInterface The column schema. + * + * @psalm-param PseudoType::* $pseudoType + * @psalm-param ColumnInfo $info + */ + public function fromPseudoType(string $pseudoType, array $info = []): ColumnSchemaInterface; + /** * Creates a column schema for an abstract database type and initializes column information. * diff --git a/src/Schema/SchemaInterface.php b/src/Schema/SchemaInterface.php index 2cb0e11c5..72a40aa47 100644 --- a/src/Schema/SchemaInterface.php +++ b/src/Schema/SchemaInterface.php @@ -13,7 +13,6 @@ 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. @@ -283,11 +282,6 @@ interface SchemaInterface extends ConstraintSchemaInterface */ 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/src/Syntax/ColumnDefinitionParser.php b/src/Syntax/ColumnDefinitionParser.php new file mode 100644 index 000000000..a81f79e15 --- /dev/null +++ b/src/Syntax/ColumnDefinitionParser.php @@ -0,0 +1,98 @@ + $dbType]; + + if (isset($matches[2])) { + if ($dbType === 'enum') { + $info += $this->enumInfo($matches[2]); + } else { + $info += $this->sizeInfo($matches[2]); + } + } + + $extra = substr($definition, strlen($matches[0])); + + /** @var ColumnInfo */ + return $info + $this->extraInfo($extra); + } + + private function enumInfo(string $values): array + { + preg_match_all("/'([^']*)'/", $values, $matches); + + return ['enum_values' => $matches[1]]; + } + + private function extraInfo(string $extra): array + { + if (empty($extra)) { + return []; + } + + $info = []; + + if (stripos($extra, 'unsigned') !== false) { + $info['unsigned'] = true; + $extra = trim(str_ireplace('unsigned', '', $extra)); + } + + if (!empty($extra)) { + $info['extra'] = $extra; + } + + return $info; + } + + private function sizeInfo(string $size): array + { + $values = explode(',', $size); + + $info = [ + 'size' => (int) $values[0], + 'precision' => (int) $values[0], + ]; + + if (isset($values[1])) { + $info['scale'] = (int) $values[1]; + } + + return $info; + } +} diff --git a/tests/AbstractColumnBuilderTest.php b/tests/AbstractColumnBuilderTest.php new file mode 100644 index 000000000..9707be712 --- /dev/null +++ b/tests/AbstractColumnBuilderTest.php @@ -0,0 +1,49 @@ +getColumnBuilderClass(); + + $column = $columnBuilderClass::$buildingMethod(...$args); + + $this->assertInstanceOf($expectedInstanceOf, $column); + $this->assertSame($expectedType, $column->getType()); + + $columnMethodResults = array_merge( + ColumnBuilderProvider::DEFAULT_COLUMN_METHOD_RESULTS, + $expectedMethodResults, + ); + + foreach ($columnMethodResults as $method => $result) { + $this->assertSame($result, $column->$method()); + } + } +} diff --git a/tests/AbstractColumnFactoryTest.php b/tests/AbstractColumnFactoryTest.php index 8ade7dbe6..1f457242a 100644 --- a/tests/AbstractColumnFactoryTest.php +++ b/tests/AbstractColumnFactoryTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Yiisoft\Db\Schema\Column\StringColumnSchema; +use Yiisoft\Db\Tests\Provider\ColumnBuilderProvider; use Yiisoft\Db\Tests\Support\TestTrait; abstract class AbstractColumnFactoryTest extends TestCase @@ -16,12 +17,13 @@ abstract class AbstractColumnFactoryTest extends TestCase public function testFromDbType(string $dbType, string $expectedType, string $expectedInstanceOf): void { $db = $this->getConnection(); - $factory = $db->getSchema()->getColumnFactory(); + $columnFactory = $db->getColumnFactory(); - $column = $factory->fromDbType($dbType); + $column = $columnFactory->fromDbType($dbType); $this->assertInstanceOf($expectedInstanceOf, $column); $this->assertSame($expectedType, $column->getType()); + $this->assertSame($dbType, $column->getDbType()); } /** @@ -31,18 +33,48 @@ public function testFromDefinition( string $definition, string $expectedType, string $expectedInstanceOf, - array $expectedInfo = [] + array $expectedMethodResults = [] ): void { $db = $this->getConnection(); - $factory = $db->getSchema()->getColumnFactory(); + $columnFactory = $db->getColumnFactory(); - $column = $factory->fromDefinition($definition); + $column = $columnFactory->fromDefinition($definition); $this->assertInstanceOf($expectedInstanceOf, $column); $this->assertSame($expectedType, $column->getType()); - foreach ($expectedInfo as $method => $value) { - $this->assertSame($value, $column->$method()); + $columnMethodResults = array_merge( + ColumnBuilderProvider::DEFAULT_COLUMN_METHOD_RESULTS, + $expectedMethodResults, + ); + + foreach ($columnMethodResults as $method => $result) { + $this->assertSame($result, $column->$method()); + } + } + + /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnFactoryProvider::pseudoTypes */ + public function testFromPseudoType( + string $pseudoType, + string $expectedType, + string $expectedInstanceOf, + array $expectedMethodResults = [] + ): void { + $db = $this->getConnection(); + $columnFactory = $db->getColumnFactory(); + + $column = $columnFactory->fromPseudoType($pseudoType); + + $this->assertInstanceOf($expectedInstanceOf, $column); + $this->assertSame($expectedType, $column->getType()); + + $columnMethodResults = array_merge( + ColumnBuilderProvider::DEFAULT_COLUMN_METHOD_RESULTS, + $expectedMethodResults, + ); + + foreach ($columnMethodResults as $method => $result) { + $this->assertSame($result, $column->$method()); } } @@ -50,9 +82,9 @@ public function testFromDefinition( public function testFromType(string $type, string $expectedType, string $expectedInstanceOf): void { $db = $this->getConnection(); - $factory = $db->getSchema()->getColumnFactory(); + $columnFactory = $db->getColumnFactory(); - $column = $factory->fromType($type); + $column = $columnFactory->fromType($type); $this->assertInstanceOf($expectedInstanceOf, $column); $this->assertSame($expectedType, $column->getType()); @@ -61,9 +93,9 @@ public function testFromType(string $type, string $expectedType, string $expecte public function testFromDefinitionWithExtra(): void { $db = $this->getConnection(); - $factory = $db->getSchema()->getColumnFactory(); + $columnFactory = $db->getColumnFactory(); - $column = $factory->fromDefinition('char(1) NOT NULL', ['extra' => 'UNIQUE']); + $column = $columnFactory->fromDefinition('char(1) NOT NULL', ['extra' => 'UNIQUE']); $this->assertInstanceOf(StringColumnSchema::class, $column); $this->assertSame('char', $column->getType()); diff --git a/tests/Common/CommonPdoConnectionTest.php b/tests/Common/CommonPdoConnectionTest.php index 88b101987..756616e9c 100644 --- a/tests/Common/CommonPdoConnectionTest.php +++ b/tests/Common/CommonPdoConnectionTest.php @@ -311,6 +311,7 @@ public function testTransactionRollbackTransactionOnLevel(): void 'createTransaction', 'createCommand', 'close', + 'getColumnFactory', 'getDriverName', 'getLastInsertID', 'getQueryBuilder', @@ -345,6 +346,7 @@ public function testGetActivePdo(): void $db = $this->getMockBuilder(AbstractPdoConnection::class)->onlyMethods([ 'createCommand', 'createTransaction', + 'getColumnFactory', 'getPdo', 'getQueryBuilder', 'getQuoter', diff --git a/tests/Db/Schema/Column/ColumnBuilderTest.php b/tests/Db/Schema/Column/ColumnBuilderTest.php new file mode 100644 index 000000000..f101332e3 --- /dev/null +++ b/tests/Db/Schema/Column/ColumnBuilderTest.php @@ -0,0 +1,14 @@ +autoIncrement(); - $columnCid->dbType('int'); - $columnCid->primaryKey(); - - // defined column C_not_null - $columnCNotNull = new IntegerColumnSchema(); - $columnCNotNull->dbType('int'); - - // defined column C_check - $columnCCheck = new StringColumnSchema(); - $columnCCheck->dbType('varchar(255)'); - - // defined column C_default - $columnCDefault = new IntegerColumnSchema(); - $columnCDefault->dbType('int'); - - // defined column C_unique - $columnCUnique = new IntegerColumnSchema(); - $columnCUnique->dbType('int'); - // defined table T_constraints_1 $tableSchema = new TableSchema(); - $tableSchema->column('C_id', $columnCid); - $tableSchema->column('C_not_null', $columnCNotNull); - $tableSchema->column('C_check', $columnCCheck); - $tableSchema->column('C_default', $columnCDefault); - $tableSchema->column('C_unique', $columnCUnique); + $tableSchema->column('C_id', ColumnBuilder::primaryKey()->dbType('int')); + $tableSchema->column('C_not_null', ColumnBuilder::integer()->dbType('int')); + $tableSchema->column('C_check', ColumnBuilder::string()->dbType('varchar(255)')); + $tableSchema->column('C_default', ColumnBuilder::integer()->dbType('int')); + $tableSchema->column('C_unique', ColumnBuilder::integer()->dbType('int')); $tableSchema->fullName('T_constraints_1'); $tableSchema->name('T_constraints_1'); $tableSchema->primaryKey('C_id'); diff --git a/tests/Db/Syntax/ColumnDefinitionParserTest.php b/tests/Db/Syntax/ColumnDefinitionParserTest.php new file mode 100644 index 000000000..543e0391a --- /dev/null +++ b/tests/Db/Syntax/ColumnDefinitionParserTest.php @@ -0,0 +1,27 @@ +assertSame($expected, $parser->parse($definition)); + } +} diff --git a/tests/Provider/ColumnBuilderProvider.php b/tests/Provider/ColumnBuilderProvider.php new file mode 100644 index 000000000..7deff7338 --- /dev/null +++ b/tests/Provider/ColumnBuilderProvider.php @@ -0,0 +1,87 @@ + null, + 'getDbType' => null, + 'getDefaultValue' => null, + 'getEnumValues' => null, + 'getExtra' => null, + 'getScale' => null, + 'getSize' => null, + 'isAllowNull' => false, + 'isAutoIncrement' => false, + 'isComputed' => false, + 'isPrimaryKey' => false, + 'isUnsigned' => false, + ]; + + public static function buildingMethods(): array + { + return [ + // building method, args, expected instance of, expected type, expected column method results + ['primaryKey', [], IntegerColumnSchema::class, ColumnType::INTEGER, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], + ['primaryKey', [false], IntegerColumnSchema::class, ColumnType::INTEGER, ['isPrimaryKey' => true, 'isAutoIncrement' => false]], + ['smallPrimaryKey', [], IntegerColumnSchema::class, ColumnType::SMALLINT, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], + ['smallPrimaryKey', [false], IntegerColumnSchema::class, ColumnType::SMALLINT, ['isPrimaryKey' => true, 'isAutoIncrement' => false]], + ['bigPrimaryKey', [], IntegerColumnSchema::class, ColumnType::BIGINT, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], + ['bigPrimaryKey', [false], IntegerColumnSchema::class, ColumnType::BIGINT, ['isPrimaryKey' => true, 'isAutoIncrement' => false]], + ['uuidPrimaryKey', [], StringColumnSchema::class, ColumnType::UUID, ['isPrimaryKey' => true, 'isAutoIncrement' => false]], + ['uuidPrimaryKey', [true], StringColumnSchema::class, ColumnType::UUID, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], + ['boolean', [], BooleanColumnSchema::class, ColumnType::BOOLEAN], + ['bit', [], BitColumnSchema::class, ColumnType::BIT], + ['bit', [1], BitColumnSchema::class, ColumnType::BIT, ['getSize' => 1]], + ['tinyint', [], IntegerColumnSchema::class, ColumnType::TINYINT], + ['tinyint', [1], IntegerColumnSchema::class, ColumnType::TINYINT, ['getSize' => 1]], + ['smallint', [], IntegerColumnSchema::class, ColumnType::SMALLINT], + ['smallint', [1], IntegerColumnSchema::class, ColumnType::SMALLINT, ['getSize' => 1]], + ['integer', [], IntegerColumnSchema::class, ColumnType::INTEGER], + ['integer', [1], IntegerColumnSchema::class, ColumnType::INTEGER, ['getSize' => 1]], + ['bigint', [], IntegerColumnSchema::class, ColumnType::BIGINT], + ['bigint', [1], IntegerColumnSchema::class, ColumnType::BIGINT, ['getSize' => 1]], + ['float', [], DoubleColumnSchema::class, ColumnType::FLOAT], + ['float', [8], DoubleColumnSchema::class, ColumnType::FLOAT, ['getSize' => 8]], + ['float', [8, 2], DoubleColumnSchema::class, ColumnType::FLOAT, ['getSize' => 8, 'getScale' => 2]], + ['double', [], DoubleColumnSchema::class, ColumnType::DOUBLE], + ['double', [8], DoubleColumnSchema::class, ColumnType::DOUBLE, ['getSize' => 8]], + ['double', [8, 2], DoubleColumnSchema::class, ColumnType::DOUBLE, ['getSize' => 8, 'getScale' => 2]], + ['decimal', [], DoubleColumnSchema::class, ColumnType::DECIMAL, ['getSize' => 10, 'getScale' => 0]], + ['decimal', [8], DoubleColumnSchema::class, ColumnType::DECIMAL, ['getSize' => 8, 'getScale' => 0]], + ['decimal', [8, 2], DoubleColumnSchema::class, ColumnType::DECIMAL, ['getSize' => 8, 'getScale' => 2]], + ['money', [], DoubleColumnSchema::class, ColumnType::MONEY, ['getSize' => 19, 'getScale' => 4]], + ['money', [8], DoubleColumnSchema::class, ColumnType::MONEY, ['getSize' => 8, 'getScale' => 4]], + ['money', [8, 2], DoubleColumnSchema::class, ColumnType::MONEY, ['getSize' => 8, 'getScale' => 2]], + ['char', [], StringColumnSchema::class, ColumnType::CHAR, ['getSize' => 1]], + ['char', [100], StringColumnSchema::class, ColumnType::CHAR, ['getSize' => 100]], + ['string', [], StringColumnSchema::class, ColumnType::STRING, ['getSize' => 255]], + ['string', [100], StringColumnSchema::class, ColumnType::STRING, ['getSize' => 100]], + ['text', [], StringColumnSchema::class, ColumnType::TEXT], + ['text', [5000], StringColumnSchema::class, ColumnType::TEXT, ['getSize' => 5000]], + ['binary', [], BinaryColumnSchema::class, ColumnType::BINARY], + ['binary', [8], BinaryColumnSchema::class, ColumnType::BINARY, ['getSize' => 8]], + ['uuid', [], StringColumnSchema::class, ColumnType::UUID], + ['datetime', [], StringColumnSchema::class, ColumnType::DATETIME, ['getSize' => 0]], + ['datetime', [3], StringColumnSchema::class, ColumnType::DATETIME, ['getSize' => 3]], + ['timestamp', [], StringColumnSchema::class, ColumnType::TIMESTAMP, ['getSize' => 0]], + ['timestamp', [3], StringColumnSchema::class, ColumnType::TIMESTAMP, ['getSize' => 3]], + ['date', [], StringColumnSchema::class, ColumnType::DATE], + ['time', [], StringColumnSchema::class, ColumnType::TIME, ['getSize' => 0]], + ['time', [3], StringColumnSchema::class, ColumnType::TIME, ['getSize' => 3]], + ['json', [], JsonColumnSchema::class, ColumnType::JSON], + ]; + } +} diff --git a/tests/Provider/ColumnDefinitionParserProvider.php b/tests/Provider/ColumnDefinitionParserProvider.php new file mode 100644 index 000000000..7d2b4cabc --- /dev/null +++ b/tests/Provider/ColumnDefinitionParserProvider.php @@ -0,0 +1,24 @@ + '']], + ['int', ['db_type' => 'int']], + ['int(10)', ['db_type' => 'int', 'size' => 10, 'precision' => 10]], + ['int UNSIGNED', ['db_type' => 'int', 'unsigned' => true]], + ['int(10) UNSIGNED', ['db_type' => 'int', 'size' => 10, 'precision' => 10, 'unsigned' => true]], + ['int(10) UNSIGNED NOT NULL', ['db_type' => 'int', 'size' => 10, 'precision' => 10, 'unsigned' => true, 'extra' => 'NOT NULL']], + ['int(10) NOT NULL', ['db_type' => 'int', 'size' => 10, 'precision' => 10, 'extra' => 'NOT NULL']], + ['text NOT NULL', ['db_type' => 'text', 'extra' => 'NOT NULL']], + ["enum('a','b','c')", ['db_type' => 'enum', 'enum_values' => ['a', 'b', 'c']]], + ["enum('a','b','c') NOT NULL", ['db_type' => 'enum', 'enum_values' => ['a', 'b', 'c'], 'extra' => 'NOT NULL']], + ]; + } +} diff --git a/tests/Provider/ColumnFactoryProvider.php b/tests/Provider/ColumnFactoryProvider.php index 8d46a5e24..cdb647b19 100644 --- a/tests/Provider/ColumnFactoryProvider.php +++ b/tests/Provider/ColumnFactoryProvider.php @@ -5,6 +5,7 @@ namespace Yiisoft\Db\Tests\Provider; use Yiisoft\Db\Constant\ColumnType; +use Yiisoft\Db\Constant\PseudoType; use Yiisoft\Db\Schema\Column\BigIntColumnSchema; use Yiisoft\Db\Schema\Column\BinaryColumnSchema; use Yiisoft\Db\Schema\Column\BooleanColumnSchema; @@ -19,12 +20,25 @@ public static function definitions(): array { return [ // definition, expected type, expected instance of, expected column method results - '' => ['', ColumnType::STRING, StringColumnSchema::class], - 'text' => ['text', ColumnType::TEXT, StringColumnSchema::class], - 'text NOT NULL' => ['text NOT NULL', ColumnType::TEXT, StringColumnSchema::class], - 'char(1)' => ['char(1)', ColumnType::CHAR, StringColumnSchema::class], - 'decimal(10,2)' => ['decimal(10,2)', ColumnType::DECIMAL, DoubleColumnSchema::class], - 'bigint UNSIGNED' => ['bigint UNSIGNED', ColumnType::BIGINT, BigIntColumnSchema::class], + '' => ['', 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']], + '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]], + ]; + } + + public static function pseudoTypes(): array + { + return [ + // pseudo-type, expected type, expected instance of, expected column method results + 'pk' => [PseudoType::PK, ColumnType::INTEGER, IntegerColumnSchema::class, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], + '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_seq' => [PseudoType::UUID_PK_SEQ, ColumnType::UUID, StringColumnSchema::class, ['isPrimaryKey' => true, 'isAutoIncrement' => true]], ]; } diff --git a/tests/Support/Stub/ColumnFactory.php b/tests/Support/Stub/ColumnFactory.php index e1d4f58a5..bd6d4f2bc 100644 --- a/tests/Support/Stub/ColumnFactory.php +++ b/tests/Support/Stub/ColumnFactory.php @@ -4,39 +4,18 @@ namespace Yiisoft\Db\Tests\Support\Stub; +use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Schema\Column\AbstractColumnFactory; -use Yiisoft\Db\Schema\SchemaInterface; class ColumnFactory extends AbstractColumnFactory { protected function getType(string $dbType, array $info = []): string { - return $this->isType($dbType) ? $dbType : SchemaInterface::TYPE_STRING; + return $this->isType($dbType) ? $dbType : ColumnType::STRING; } - protected function isType(string $dbType): bool + protected function isDbType(string $dbType): bool { - return match ($dbType) { - SchemaInterface::TYPE_UUID, - SchemaInterface::TYPE_CHAR, - SchemaInterface::TYPE_STRING, - SchemaInterface::TYPE_TEXT, - SchemaInterface::TYPE_BINARY, - SchemaInterface::TYPE_BOOLEAN, - SchemaInterface::TYPE_TINYINT, - SchemaInterface::TYPE_SMALLINT, - SchemaInterface::TYPE_INTEGER, - SchemaInterface::TYPE_BIGINT, - SchemaInterface::TYPE_FLOAT, - SchemaInterface::TYPE_DOUBLE, - SchemaInterface::TYPE_DECIMAL, - SchemaInterface::TYPE_MONEY, - SchemaInterface::TYPE_DATETIME, - SchemaInterface::TYPE_TIMESTAMP, - SchemaInterface::TYPE_TIME, - SchemaInterface::TYPE_DATE, - SchemaInterface::TYPE_JSON => true, - default => false, - }; + return $this->isType($dbType); } } diff --git a/tests/Support/Stub/Connection.php b/tests/Support/Stub/Connection.php index 8b0cee145..ac616b0af 100644 --- a/tests/Support/Stub/Connection.php +++ b/tests/Support/Stub/Connection.php @@ -8,6 +8,7 @@ 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; @@ -42,6 +43,11 @@ 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/Schema.php b/tests/Support/Stub/Schema.php index 62098a38a..554299ccb 100644 --- a/tests/Support/Stub/Schema.php +++ b/tests/Support/Stub/Schema.php @@ -8,7 +8,6 @@ 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; /** @@ -23,11 +22,6 @@ 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.');