diff --git a/CHANGELOG.md b/CHANGELOG.md index 44680c41e..7eac161d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,19 +3,20 @@ ## 2.0.0 under development - Enh #336: Implement `SqlParser` and `ExpressionBuilder` driver classes (@Tigrov) -- Enh #315: Implement `ColumnSchemaInterface` classes according to the data type of database table columns +- New #315: Implement `ColumnSchemaInterface` classes according to the data type of database table columns for type casting performance. Related with yiisoft/db#752 (@Tigrov) - Chg #348: Replace call of `SchemaInterface::getRawTableName()` to `QuoterInterface::getRawTableName()` (@Tigrov) - Enh #349: Add method chaining for column classes (@Tigrov) -- Enh #350: Add array overlaps and JSON overlaps condition builders (@Tigrov) +- New #350: Add array overlaps and JSON overlaps condition builders (@Tigrov) - Enh #353: Update `bit` type according to main PR yiisoft/db#860 (@Tigrov) - Enh #354: Refactor PHP type of `ColumnSchemaInterface` instances (@Tigrov) - Enh #356: Raise minimum PHP version to `^8.1` with minor refactoring (@Tigrov) -- Enh #355: Implement `ColumnFactory` class (@Tigrov) +- New #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) +- New #360: Realize `ColumnBuilder` class (@Tigrov) - Enh #362: Update according changes in `ColumnSchemaInterface` (@Tigrov) +- New #364: Add `ColumnDefinitionBuilder` class (@Tigrov) ## 1.3.0 March 21, 2024 diff --git a/src/Column/ColumnDefinitionBuilder.php b/src/Column/ColumnDefinitionBuilder.php new file mode 100644 index 000000000..0bf498b6f --- /dev/null +++ b/src/Column/ColumnDefinitionBuilder.php @@ -0,0 +1,87 @@ +buildType($column) + . $this->buildNotNull($column) + . $this->buildPrimaryKey($column) + . $this->buildUnique($column) + . $this->buildDefault($column) + . $this->buildCheck($column) + . $this->buildReferences($column) + . $this->buildExtra($column); + } + + protected function buildType(ColumnSchemaInterface $column): string + { + if ($column instanceof \Yiisoft\Db\Schema\Column\ArrayColumnSchema) { + return $this->buildType($column->getColumn()) . str_repeat('[]', $column->getDimension()); + } + + return parent::buildType($column); + } + + protected function getDbType(ColumnSchemaInterface $column): string + { + /** @psalm-suppress DocblockTypeContradiction */ + return match ($column->getType()) { + ColumnType::BOOLEAN => 'boolean', + ColumnType::BIT => 'varbit', + ColumnType::TINYINT => $column->isAutoIncrement() ? 'smallserial' : 'smallint', + ColumnType::SMALLINT => $column->isAutoIncrement() ? 'smallserial' : 'smallint', + ColumnType::INTEGER => $column->isAutoIncrement() ? 'serial' : 'integer', + ColumnType::BIGINT => $column->isAutoIncrement() ? 'bigserial' : 'bigint', + ColumnType::FLOAT => 'real', + ColumnType::DOUBLE => 'double precision', + ColumnType::DECIMAL => 'numeric', + ColumnType::MONEY => 'money', + ColumnType::CHAR => 'char', + ColumnType::STRING => 'varchar', + ColumnType::TEXT => 'text', + ColumnType::BINARY => 'bytea', + ColumnType::UUID => 'uuid', + ColumnType::DATETIME => 'timestamp', + ColumnType::TIMESTAMP => 'timestamp', + ColumnType::DATE => 'date', + ColumnType::TIME => 'time', + ColumnType::STRUCTURED => 'jsonb', + ColumnType::JSON => 'jsonb', + default => 'varchar', + }; + } +} diff --git a/src/Column/ColumnFactory.php b/src/Column/ColumnFactory.php index 17dcf5da6..5e50b7164 100644 --- a/src/Column/ColumnFactory.php +++ b/src/Column/ColumnFactory.php @@ -104,6 +104,7 @@ final class ColumnFactory extends AbstractColumnFactory 'cidr' => ColumnType::STRING, 'inet' => ColumnType::STRING, 'macaddr' => ColumnType::STRING, + 'macaddr8' => ColumnType::STRING, 'tsquery' => ColumnType::STRING, 'tsvector' => ColumnType::STRING, 'txid_snapshot' => ColumnType::STRING, diff --git a/src/Connection.php b/src/Connection.php index 99929eda7..46b57fc43 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -7,9 +7,7 @@ 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; @@ -46,11 +44,6 @@ 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/DDLQueryBuilder.php b/src/DDLQueryBuilder.php index ad96a5627..de4442fc9 100644 --- a/src/DDLQueryBuilder.php +++ b/src/DDLQueryBuilder.php @@ -44,6 +44,7 @@ public function alterColumn(string $table, string $column, ColumnInterface|strin return "ALTER TABLE $tableName ALTER COLUMN $columnName $type"; } + /** @psalm-suppress DeprecatedMethod */ $type = 'TYPE ' . $this->queryBuilder->getColumnType($type); $multiAlterStatement = []; $constraintPrefix = preg_replace('/[^a-z0-9_]/i', '', $table . '_' . $column); diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 7766181a8..e667c9527 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -6,6 +6,7 @@ use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Constant\PseudoType; +use Yiisoft\Db\Pgsql\Column\ColumnDefinitionBuilder; use Yiisoft\Db\QueryBuilder\AbstractQueryBuilder; use Yiisoft\Db\Schema\QuoterInterface; use Yiisoft\Db\Schema\SchemaInterface; @@ -52,6 +53,8 @@ public function __construct(QuoterInterface $quoter, SchemaInterface $schema) $ddlBuilder = new DDLQueryBuilder($this, $quoter, $schema); $dmlBuilder = new DMLQueryBuilder($this, $quoter, $schema); $dqlBuilder = new DQLQueryBuilder($this, $quoter); - parent::__construct($quoter, $schema, $ddlBuilder, $dmlBuilder, $dqlBuilder); + $columnDefinitionBuilder = new ColumnDefinitionBuilder($this); + + parent::__construct($quoter, $schema, $ddlBuilder, $dmlBuilder, $dqlBuilder, $columnDefinitionBuilder); } } diff --git a/src/Schema.php b/src/Schema.php index 71524e0af..7157a7e7c 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -19,8 +19,10 @@ 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\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnFactoryInterface; use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; use Yiisoft\Db\Schema\Column\StructuredColumnSchema; use Yiisoft\Db\Schema\TableSchemaInterface; @@ -102,11 +104,17 @@ final class Schema extends AbstractPdoSchema */ protected string|array $tableQuoteCharacter = '"'; + /** @deprecated Use {@see ColumnBuilder} instead. Will be removed in 2.0. */ public function createColumn(string $type, array|int|string $length = null): ColumnInterface { return new Column($type, $length); } + public function getColumnFactory(): ColumnFactoryInterface + { + return new ColumnFactory(); + } + /** * Resolves the table name and schema name (if any). * @@ -718,7 +726,7 @@ protected function findColumns(TableSchemaInterface $table): bool */ private function loadColumnSchema(array $info): ColumnSchemaInterface { - $columnFactory = $this->db->getColumnFactory(); + $columnFactory = $this->getColumnFactory(); $dbType = $info['data_type']; if (!in_array($info['type_scheme'], [$this->defaultSchema, 'pg_catalog'], true)) { diff --git a/tests/ColumnFactoryTest.php b/tests/ColumnFactoryTest.php index dc014ad56..8df2d5319 100644 --- a/tests/ColumnFactoryTest.php +++ b/tests/ColumnFactoryTest.php @@ -21,7 +21,7 @@ public function testFromDbType(string $dbType, string $expectedType, string $exp parent::testFromDbType($dbType, $expectedType, $expectedInstanceOf); $db = $this->getConnection(); - $columnFactory = $db->getColumnFactory(); + $columnFactory = $db->getSchema()->getColumnFactory(); // With dimension $column = $columnFactory->fromDbType($dbType, ['dimension' => 1]); @@ -60,7 +60,7 @@ public function testFromType(string $type, string $expectedType, string $expecte parent::testFromType($type, $expectedType, $expectedInstanceOf); $db = $this->getConnection(); - $columnFactory = $db->getColumnFactory(); + $columnFactory = $db->getSchema()->getColumnFactory(); // With dimension $column = $columnFactory->fromType($type, ['dimension' => 1]); diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 1f181e5b0..cb8290a3e 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -10,7 +10,6 @@ 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; @@ -134,11 +133,4 @@ static function (ConnectionInterface $db) { $db->close(); } - - public function testGetColumnFactory(): void - { - $db = $this->getConnection(); - - $this->assertInstanceOf(ColumnFactory::class, $db->getColumnFactory()); - } } diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index f02dc0687..3f7f26a1c 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -5,6 +5,7 @@ namespace Yiisoft\Db\Pgsql\Tests\Provider; use Yiisoft\Db\Constant\ColumnType; +use Yiisoft\Db\Constant\PseudoType; use Yiisoft\Db\Expression\ArrayExpression; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Expression\JsonExpression; @@ -536,4 +537,60 @@ public static function overlapsCondition(): array return $data; } + + public static function buildColumnDefinition(): array + { + $values = parent::buildColumnDefinition(); + + $values[PseudoType::PK][0] = 'serial PRIMARY KEY'; + $values[PseudoType::UPK][0] = 'serial PRIMARY KEY'; + $values[PseudoType::BIGPK][0] = 'bigserial PRIMARY KEY'; + $values[PseudoType::UBIGPK][0] = 'bigserial PRIMARY KEY'; + $values[PseudoType::UUID_PK][0] = 'uuid PRIMARY KEY DEFAULT gen_random_uuid()'; + $values[PseudoType::UUID_PK_SEQ][0] = 'uuid PRIMARY KEY DEFAULT gen_random_uuid()'; + $values['primaryKey()'][0] = 'serial PRIMARY KEY'; + $values['smallPrimaryKey()'][0] = 'smallserial PRIMARY KEY'; + $values['bigPrimaryKey()'][0] = 'bigserial PRIMARY KEY'; + $values['uuidPrimaryKey()'][0] = 'uuid PRIMARY KEY DEFAULT gen_random_uuid()'; + $values['bit()'][0] = 'varbit'; + $values['bit(1)'][0] = 'varbit(1)'; + $values['bit(8)'][0] = 'varbit(8)'; + $values['bit(1000)'][0] = 'varbit(1000)'; + $values['tinyint()'][0] = 'smallint'; + $values['tinyint(2)'][0] = 'smallint'; + $values['smallint(4)'][0] = 'smallint'; + $values['integer(8)'][0] = 'integer'; + $values['bigint(15)'][0] = 'bigint'; + $values['float()'][0] = 'real'; + $values['float(10)'][0] = 'real'; + $values['float(10,2)'][0] = 'real'; + $values['double()'][0] = 'double precision'; + $values['double(10)'][0] = 'double precision'; + $values['double(10,2)'][0] = 'double precision'; + $values['decimal()'][0] = 'numeric(10,0)'; + $values['decimal(5)'][0] = 'numeric(5,0)'; + $values['decimal(5,2)'][0] = 'numeric(5,2)'; + $values['decimal(null)'][0] = 'numeric'; + $values['money()'][0] = 'money'; + $values['money(10)'][0] = 'money'; + $values['money(10,2)'][0] = 'money'; + $values['money(null)'][0] = 'money'; + $values['text(1000)'][0] = 'text'; + $values['binary()'][0] = 'bytea'; + $values['binary(1000)'][0] = 'bytea'; + $values['uuid()'][0] = 'uuid'; + $values['datetime()'][0] = 'timestamp(0)'; + $values['datetime(6)'][0] = 'timestamp(6)'; + $values['datetime(null)'][0] = 'timestamp'; + $values['array()'][0] = 'varchar[]'; + $values['structured()'][0] = 'jsonb'; + $values["structured('structured_type')"][0] = 'structured_type'; + $values['json()'][0] = 'jsonb'; + $values['json(100)'][0] = 'jsonb'; + $values['unsigned()'][0] = 'integer'; + $values['scale(2)'][0] = 'numeric(10,2)'; + $values['integer(8)->scale(2)'][0] = 'integer'; + + return $values; + } } diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 316ea1f2e..4566c2069 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -19,6 +19,7 @@ use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\Condition\ArrayOverlapsCondition; use Yiisoft\Db\QueryBuilder\Condition\JsonOverlapsCondition; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; use Yiisoft\Db\Tests\Common\CommonQueryBuilderTest; use function version_compare; @@ -433,6 +434,8 @@ public function testDropDefaultValue(): void ); $qb->dropDefaultValue('T_constraints_1', 'CN_pk'); + + $db->close(); } /** @@ -783,4 +786,10 @@ public function testOverlapsConditionOperator(iterable|ExpressionInterface $valu $db->close(); } + + /** @dataProvider \Yiisoft\Db\Pgsql\Tests\Provider\QueryBuilderProvider::buildColumnDefinition() */ + public function testBuildColumnDefinition(string $expected, ColumnSchemaInterface|string $column): void + { + parent::testBuildColumnDefinition($expected, $column); + } } diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index bbf4403e6..3735187c9 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -15,6 +15,7 @@ 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; @@ -648,4 +649,13 @@ public function testTableIndexes(): void $db->close(); } + + public function testGetColumnFactory(): void + { + $db = $this->getConnection(); + + $this->assertInstanceOf(ColumnFactory::class, $db->getSchema()->getColumnFactory()); + + $db->close(); + } }