diff --git a/CHANGELOG.md b/CHANGELOG.md index aed49e5d..98ca6eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Enh #355: Implement `ColumnFactory` class (@Tigrov) - Enh #359: Separate column type constants (@Tigrov) - Enh #359: Remove `Schema::TYPE_ARRAY` and `Schema::TYPE_STRUCTURED` constants (@Tigrov) +- Enh #360: Realize `ColumnBuilder` class (@Tigrov) ## 1.3.0 March 21, 2024 diff --git a/src/Column/ArrayColumnSchema.php b/src/Column/ArrayColumnSchema.php index 3db77fe3..2c00bb44 100644 --- a/src/Column/ArrayColumnSchema.php +++ b/src/Column/ArrayColumnSchema.php @@ -56,8 +56,7 @@ public function column(ColumnSchemaInterface|null $column): static public function getColumn(): ColumnSchemaInterface { if ($this->column === null) { - $this->column = (new ColumnFactory())->fromType($this->getType()); - $this->column->dbType($this->getDbType()); + $this->column = (new ColumnFactory())->fromDbType($this->getDbType() ?? ''); $this->column->enumValues($this->getEnumValues()); $this->column->precision($this->getPrecision()); $this->column->scale($this->getScale()); @@ -101,7 +100,7 @@ public function dbTypecast(mixed $value): ExpressionInterface|null $value = $this->dbTypecastArray($value, $this->dimension); } - return new ArrayExpression($value, $this->getDbType(), $this->dimension); + return new ArrayExpression($value, $this->getDbType() ?? $this->getColumn()->getDbType(), $this->dimension); } public function phpTypecast(mixed $value): array|null diff --git a/src/Column/ColumnBuilder.php b/src/Column/ColumnBuilder.php new file mode 100644 index 00000000..4c66a3b4 --- /dev/null +++ b/src/Column/ColumnBuilder.php @@ -0,0 +1,71 @@ +size($size); + } + + public static function tinyint(int|null $size = null): ColumnSchemaInterface + { + return (new IntegerColumnSchema(ColumnType::TINYINT)) + ->size($size); + } + + public static function smallint(int|null $size = null): ColumnSchemaInterface + { + return (new IntegerColumnSchema(ColumnType::SMALLINT)) + ->size($size); + } + + public static function integer(int|null $size = null): ColumnSchemaInterface + { + return (new IntegerColumnSchema(ColumnType::INTEGER)) + ->size($size); + } + + public static function bigint(int|null $size = null): ColumnSchemaInterface + { + return (new IntegerColumnSchema(ColumnType::BIGINT)) + ->size($size); + } + + public static function binary(int|null $size = null): ColumnSchemaInterface + { + return (new BinaryColumnSchema(ColumnType::BINARY)) + ->size($size); + } + + public static function array(ColumnSchemaInterface|null $column = null): ColumnSchemaInterface + { + return (new ArrayColumnSchema(ColumnType::ARRAY)) + ->column($column); + } + + /** + * @param string|null $dbType The DB type of the column. + * @param ColumnSchemaInterface[] $columns The columns (name -> instance) that the structured column should contain. + * + * @psalm-param array $columns + */ + public static function structured(string|null $dbType = null, array $columns = []): ColumnSchemaInterface + { + return (new StructuredColumnSchema(ColumnType::STRUCTURED)) + ->dbType($dbType) + ->columns($columns); + } +} diff --git a/src/Column/ColumnFactory.php b/src/Column/ColumnFactory.php index 34d0a16a..c2004f9e 100644 --- a/src/Column/ColumnFactory.php +++ b/src/Column/ColumnFactory.php @@ -114,6 +114,7 @@ final class ColumnFactory extends AbstractColumnFactory * @psalm-param ColumnType::* $type * @psalm-param ColumnInfo $info * @psalm-suppress MoreSpecificImplementedParamType + * @psalm-suppress ArgumentTypeCoercion */ public function fromType(string $type, array $info = []): ColumnSchemaInterface { @@ -125,7 +126,6 @@ public function fromType(string $type, array $info = []): ColumnSchemaInterface ->dimension($dimension) ->column($this->fromType($type, $info)); } else { - /** @psalm-suppress ArgumentTypeCoercion */ $column = match ($type) { ColumnType::BOOLEAN => new BooleanColumnSchema($type), ColumnType::BIT => new BitColumnSchema($type), @@ -141,11 +141,16 @@ public function fromType(string $type, array $info = []): ColumnSchemaInterface }; } - return $column; + return $column->load($info); } protected function getType(string $dbType, array $info = []): string { return self::TYPE_MAP[$dbType] ?? ColumnType::STRING; } + + protected function isDbType(string $dbType): bool + { + return isset(self::TYPE_MAP[$dbType]); + } } diff --git a/src/Connection.php b/src/Connection.php index 46b57fc4..99929eda 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -7,7 +7,9 @@ use Yiisoft\Db\Driver\Pdo\AbstractPdoConnection; use Yiisoft\Db\Driver\Pdo\PdoCommandInterface; use Yiisoft\Db\Exception\InvalidArgumentException; +use Yiisoft\Db\Pgsql\Column\ColumnFactory; 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; @@ -44,6 +46,11 @@ public function createTransaction(): TransactionInterface return new Transaction($this); } + public function getColumnFactory(): ColumnFactoryInterface + { + return new ColumnFactory(); + } + public function getLastInsertID(string $sequenceName = null): string { if ($sequenceName === null) { diff --git a/src/Schema.php b/src/Schema.php index 6dad644e..bc2544d8 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -19,11 +19,9 @@ use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Helper\DbArrayHelper; use Yiisoft\Db\Pgsql\Column\ArrayColumnSchema; -use Yiisoft\Db\Pgsql\Column\ColumnFactory; use Yiisoft\Db\Pgsql\Column\SequenceColumnSchemaInterface; use Yiisoft\Db\Pgsql\Column\StructuredColumnSchemaInterface; use Yiisoft\Db\Schema\Builder\ColumnInterface; -use Yiisoft\Db\Schema\Column\ColumnFactoryInterface; use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; use Yiisoft\Db\Schema\TableSchemaInterface; @@ -111,11 +109,6 @@ public function createColumn(string $type, array|int|string $length = null): Col return new Column($type, $length); } - public function getColumnFactory(): ColumnFactoryInterface - { - return new ColumnFactory(); - } - /** * Resolves the table name and schema name (if any). * @@ -722,6 +715,7 @@ protected function findColumns(TableSchemaInterface $table): bool */ private function loadColumnSchema(array $info): ColumnSchemaInterface { + $columnFactory = $this->db->getColumnFactory(); $dbType = $info['data_type']; if (!in_array($info['type_scheme'], [$this->defaultSchema, 'pg_catalog'], true)) { @@ -737,10 +731,10 @@ private function loadColumnSchema(array $info): ColumnSchemaInterface $columns = $structured->getColumns(); } - $column = $this->getColumnFactory() + $column = $columnFactory ->fromType(ColumnType::STRUCTURED, ['dimension' => $info['dimension'], 'columns' => $columns]); } else { - $column = $this->getColumnFactory() + $column = $columnFactory ->fromDbType($dbType, ['dimension' => $info['dimension']]); } diff --git a/tests/ColumnBuilderTest.php b/tests/ColumnBuilderTest.php new file mode 100644 index 00000000..7b964767 --- /dev/null +++ b/tests/ColumnBuilderTest.php @@ -0,0 +1,35 @@ +dbType($dbType); + $db = $this->getConnection(); + $columnFactory = $db->getColumnFactory(); + + $arrayCol = (new ArrayColumnSchema())->column($columnFactory->fromType($type)->dbType($dbType)); foreach ($values as [$dimension, $expected, $value]) { $arrayCol->dimension($dimension); @@ -331,10 +333,12 @@ public function testDbTypecastArrayColumnSchema(string $dbType, string $type, st } /** @dataProvider \Yiisoft\Db\Pgsql\Tests\Provider\ColumnSchemaProvider::phpTypecastArrayColumns */ - public function testPhpTypecastArrayColumnSchema(string $dbType, string $type, string $phpType, array $values): void + public function testPhpTypecastArrayColumnSchema(string $dbType, string $type, array $values): void { - $arrayCol = new ArrayColumnSchema($type, $phpType); - $arrayCol->dbType($dbType); + $db = $this->getConnection(); + $columnFactory = $db->getColumnFactory(); + + $arrayCol = (new ArrayColumnSchema())->column($columnFactory->fromType($type)->dbType($dbType)); foreach ($values as [$dimension, $expected, $value]) { $arrayCol->dimension($dimension); diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index cb8290a3..1f181e5b 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -10,6 +10,7 @@ use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; +use Yiisoft\Db\Pgsql\Column\ColumnFactory; use Yiisoft\Db\Pgsql\Tests\Support\TestTrait; use Yiisoft\Db\Tests\Common\CommonConnectionTest; use Yiisoft\Db\Transaction\TransactionInterface; @@ -133,4 +134,11 @@ static function (ConnectionInterface $db) { $db->close(); } + + public function testGetColumnFactory(): void + { + $db = $this->getConnection(); + + $this->assertInstanceOf(ColumnFactory::class, $db->getColumnFactory()); + } } diff --git a/tests/Provider/ColumnBuilderProvider.php b/tests/Provider/ColumnBuilderProvider.php new file mode 100644 index 00000000..078af75e --- /dev/null +++ b/tests/Provider/ColumnBuilderProvider.php @@ -0,0 +1,72 @@ + 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]], + ['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]], + ['binary', [], BinaryColumnSchema::class, ColumnType::BINARY], + ['binary', [8], BinaryColumnSchema::class, ColumnType::BINARY, ['getSize' => 8]], + ['array', [], ArrayColumnSchema::class, ColumnType::ARRAY], + ['array', [$column = new StringColumnSchema()], ArrayColumnSchema::class, ColumnType::ARRAY, ['getColumn' => $column]], + ['structured', [], StructuredColumnSchema::class, ColumnType::STRUCTURED], + ['structured', ['money_currency'], StructuredColumnSchema::class, ColumnType::STRUCTURED, ['getDbType' => 'money_currency']], + [ + 'structured', + [ + 'money_currency', + $columns = ['value' => ColumnBuilder::money(), 'currency' => ColumnBuilder::string(3)], + ], + StructuredColumnSchema::class, + ColumnType::STRUCTURED, + ['getDbType' => 'money_currency', 'getColumns' => $columns], + ], + ]; + } +} diff --git a/tests/Provider/ColumnSchemaProvider.php b/tests/Provider/ColumnSchemaProvider.php index 248839fc..063a5885 100644 --- a/tests/Provider/ColumnSchemaProvider.php +++ b/tests/Provider/ColumnSchemaProvider.php @@ -7,7 +7,6 @@ use PDO; use Yiisoft\Db\Command\Param; use Yiisoft\Db\Constant\ColumnType; -use Yiisoft\Db\Constant\PhpType; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Expression\JsonExpression; use Yiisoft\Db\Pgsql\Column\BigIntColumnSchema; @@ -93,11 +92,10 @@ public static function dbTypecastArrayColumns() $bigInt = PHP_INT_SIZE === 8 ? 9223372036854775807 : '9223372036854775807'; return [ - // [dbType, type, phpType, values] + // [dbType, type, values] [ 'int4', ColumnType::INTEGER, - PhpType::INT, [ // [dimension, expected, typecast value] [1, [1, 2, 3, null], [1, 2.0, '3', null]], @@ -108,7 +106,6 @@ public static function dbTypecastArrayColumns() [ 'int8', ColumnType::BIGINT, - PhpType::INT, [ [1, [1, 2, 3, $bigInt], [1, 2.0, '3', '9223372036854775807']], [2, [[1, 2], [3], [$bigInt]], [[1, 2.0], ['3'], ['9223372036854775807']]], @@ -117,7 +114,6 @@ public static function dbTypecastArrayColumns() [ 'float8', ColumnType::DOUBLE, - PhpType::FLOAT, [ [1, [1.0, 2.2, 3.3, null], [1, 2.2, '3.3', null]], [2, [[1.0, 2.2], [3.3, null]], [[1, 2.2], ['3.3', null]]], @@ -126,7 +122,6 @@ public static function dbTypecastArrayColumns() [ 'bool', ColumnType::BOOLEAN, - PhpType::BOOL, [ [1, [true, true, true, false, false, false, null], [true, 1, '1', false, 0, '0', null]], [2, [[true, true, true, false, false, false, null]], [[true, 1, '1', false, 0, '0', null]]], @@ -135,7 +130,6 @@ public static function dbTypecastArrayColumns() [ 'varchar', ColumnType::STRING, - PhpType::STRING, [ [1, ['1', '2', '1', '0', '', null], [1, '2', true, false, '', null]], [2, [['1', '2', '1', '0'], [''], [null]], [[1, '2', true, false], [''], [null]]], @@ -144,7 +138,6 @@ public static function dbTypecastArrayColumns() [ 'bytea', ColumnType::BINARY, - PhpType::MIXED, [ [1, [ '1', @@ -163,7 +156,6 @@ public static function dbTypecastArrayColumns() [ 'jsonb', ColumnType::JSON, - PhpType::MIXED, [ [1, [ new JsonExpression([1, 2, 3], 'jsonb'), @@ -185,7 +177,6 @@ public static function dbTypecastArrayColumns() [ 'varbit', ColumnType::BIT, - PhpType::INT, [ [1, ['1011', '1001', null], [0b1011, '1001', null]], [2, [['1011', '1001', null]], [[0b1011, '1001', null]]], @@ -194,7 +185,6 @@ public static function dbTypecastArrayColumns() [ 'price_composite', ColumnType::STRUCTURED, - PhpType::ARRAY, [ [ 1, @@ -228,11 +218,10 @@ public static function phpTypecastArrayColumns() $bigInt = PHP_INT_SIZE === 8 ? 9223372036854775807 : '9223372036854775807'; return [ - // [dbtype, type, phpType, values] + // [dbtype, type, values] [ 'int4', ColumnType::INTEGER, - PhpType::INT, [ // [dimension, expected, typecast value] [1, [1, 2, 3, null], '{1,2,3,}'], @@ -242,7 +231,6 @@ public static function phpTypecastArrayColumns() [ 'int8', ColumnType::BIGINT, - PhpType::INT, [ [1, [1, 2, $bigInt], '{1,2,9223372036854775807}'], [2, [[1, 2], [$bigInt]], '{{1,2},{9223372036854775807}}'], @@ -251,7 +239,6 @@ public static function phpTypecastArrayColumns() [ 'float8', ColumnType::DOUBLE, - PhpType::FLOAT, [ [1, [1.0, 2.2, null], '{1,2.2,}'], [2, [[1.0], [2.2, null]], '{{1},{2.2,}}'], @@ -260,7 +247,6 @@ public static function phpTypecastArrayColumns() [ 'bool', ColumnType::BOOLEAN, - PhpType::BOOL, [ [1, [true, false, null], '{t,f,}'], [2, [[true, false, null]], '{{t,f,}}'], @@ -269,7 +255,6 @@ public static function phpTypecastArrayColumns() [ 'varchar', ColumnType::STRING, - PhpType::STRING, [ [1, ['1', '2', '', null], '{1,2,"",}'], [2, [['1', '2'], [''], [null]], '{{1,2},{""},{NULL}}'], @@ -278,7 +263,6 @@ public static function phpTypecastArrayColumns() [ 'bytea', ColumnType::BINARY, - PhpType::MIXED, [ [1, ["\x10\x11", '', null], '{\x1011,"",}'], [2, [["\x10\x11"], ['', null]], '{{\x1011},{"",}}'], @@ -287,7 +271,6 @@ public static function phpTypecastArrayColumns() [ 'jsonb', ColumnType::JSON, - PhpType::MIXED, [ [1, [[1, 2, 3], null], '{"[1,2,3]",}'], [1, [[1, 2, 3]], '{{1,2,3}}'], @@ -297,7 +280,6 @@ public static function phpTypecastArrayColumns() [ 'varbit', ColumnType::BIT, - PhpType::INT, [ [1, [0b1011, 0b1001, null], '{1011,1001,}'], [2, [[0b1011, 0b1001, null]], '{{1011,1001,}}'], @@ -306,7 +288,6 @@ public static function phpTypecastArrayColumns() [ 'price_structured', ColumnType::STRUCTURED, - PhpType::ARRAY, [ [1, [['10', 'USD'], null], '{"(10,USD)",}'], [2, [[['10', 'USD'], null]], '{{"(10,USD)",}}'], diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index ed88aff3..921c16ae 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -15,7 +15,6 @@ use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Expression\Expression; -use Yiisoft\Db\Pgsql\Column\ColumnFactory; use Yiisoft\Db\Pgsql\Schema; use Yiisoft\Db\Pgsql\Tests\Support\TestTrait; use Yiisoft\Db\Schema\SchemaInterface; @@ -645,12 +644,4 @@ public function testTableIndexes(): void $this->assertFalse($tableIndexes[4]->isPrimary()); $this->assertFalse($tableIndexes[4]->isUnique()); } - - public function testGetColumnFactory(): void - { - $db = $this->getConnection(); - $factory = $db->getSchema()->getColumnFactory(); - - $this->assertInstanceOf(ColumnFactory::class, $factory); - } }