diff --git a/CHANGELOG.md b/CHANGELOG.md index 30b03a0..4cf4b2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ - Enh #279: Separate column type constants (@Tigrov) - New #280, #291: Realize `ColumnBuilder` class (@Tigrov) - Enh #281: Update according changes in `ColumnSchemaInterface` (@Tigrov) -- New #282, #291: Add `ColumnDefinitionBuilder` class (@Tigrov) +- New #282, #291, #299: Add `ColumnDefinitionBuilder` class (@Tigrov) - Bug #285: Fix `DMLQueryBuilder::insertBatch()` method (@Tigrov) - Enh #283: Refactor `Dsn` class (@Tigrov) - Enh #286: Use constructor to create columns and initialize properties (@Tigrov) @@ -27,6 +27,8 @@ - Enh #296: Remove `ColumnInterface` (@Tigrov) - Enh #298: Rename `ColumnSchemaInterface` to `ColumnInterface` (@Tigrov) - Enh #298: Refactor `DMLQueryBuilder::prepareInsertValues()` method (@Tigrov) +- Enh #299: Add `ColumnDefinitionParser` class (@Tigrov) +- Enh #299: Convert database types to lower case (@Tigrov) ## 1.3.0 March 21, 2024 diff --git a/src/Column/BinaryColumn.php b/src/Column/BinaryColumn.php index 9adc51f..3b95854 100644 --- a/src/Column/BinaryColumn.php +++ b/src/Column/BinaryColumn.php @@ -14,7 +14,7 @@ final class BinaryColumn extends BaseBinaryColumn { public function dbTypecast(mixed $value): mixed { - if ($this->getDbType() === 'BLOB') { + if ($this->getDbType() === 'blob') { if ($value instanceof ParamInterface && is_string($value->getValue())) { /** @var string */ $value = $value->getValue(); diff --git a/src/Column/ColumnDefinitionBuilder.php b/src/Column/ColumnDefinitionBuilder.php index 43acc39..93384bb 100644 --- a/src/Column/ColumnDefinitionBuilder.php +++ b/src/Column/ColumnDefinitionBuilder.php @@ -64,37 +64,46 @@ protected function buildOnUpdate(string $onUpdate): string protected function getDbType(ColumnInterface $column): string { + $dbType = $column->getDbType(); $size = $column->getSize(); + $scale = $column->getScale(); /** @psalm-suppress DocblockTypeContradiction */ - return $column->getDbType() ?? match ($column->getType()) { - ColumnType::BOOLEAN => 'number(1)', - ColumnType::BIT => match (true) { - $size === null => 'number(38)', - $size <= 126 => 'number(' . ceil(log10(2 ** $size)) . ')', - default => 'raw(' . ceil($size / 8) . ')', + return match ($dbType) { + default => $dbType, + null => match ($column->getType()) { + ColumnType::BOOLEAN => 'number(1)', + ColumnType::BIT => match (true) { + $size === null => 'number(38)', + $size <= 126 => 'number(' . ceil(log10(2 ** $size)) . ')', + default => 'raw(' . ceil($size / 8) . ')', + }, + ColumnType::TINYINT => 'number(' . ($size ?? 3) . ')', + ColumnType::SMALLINT => 'number(' . ($size ?? 5) . ')', + ColumnType::INTEGER => 'number(' . ($size ?? 10) . ')', + ColumnType::BIGINT => 'number(' . ($size ?? 20) . ')', + ColumnType::FLOAT => 'binary_float', + ColumnType::DOUBLE => 'binary_double', + ColumnType::DECIMAL => 'number(' . ($size ?? 10) . ',' . ($scale ?? 0) . ')', + ColumnType::MONEY => 'number(' . ($size ?? 19) . ',' . ($scale ?? 4) . ')', + ColumnType::CHAR => 'char', + ColumnType::STRING => 'varchar2(' . ($size ?? 255) . ')', + ColumnType::TEXT => 'clob', + ColumnType::BINARY => 'blob', + ColumnType::UUID => 'raw(16)', + ColumnType::DATETIME => 'timestamp', + ColumnType::TIMESTAMP => 'timestamp', + ColumnType::DATE => 'date', + ColumnType::TIME => 'interval day(0) to second', + ColumnType::ARRAY => 'clob', + ColumnType::STRUCTURED => 'clob', + ColumnType::JSON => 'clob', + default => 'varchar2', }, - ColumnType::TINYINT => 'number(' . ($size ?? 3) . ')', - ColumnType::SMALLINT => 'number(' . ($size ?? 5) . ')', - ColumnType::INTEGER => 'number(' . ($size ?? 10) . ')', - ColumnType::BIGINT => 'number(' . ($size ?? 20) . ')', - ColumnType::FLOAT => 'binary_float', - ColumnType::DOUBLE => 'binary_double', - ColumnType::DECIMAL => 'number(' . ($size ?? 10) . ',' . ($column->getScale() ?? 0) . ')', - ColumnType::MONEY => 'number(' . ($size ?? 19) . ',' . ($column->getScale() ?? 4) . ')', - ColumnType::CHAR => 'char', - ColumnType::STRING => 'varchar2(' . ($size ?? 255) . ')', - ColumnType::TEXT => 'clob', - ColumnType::BINARY => 'blob', - ColumnType::UUID => 'raw(16)', - ColumnType::DATETIME => 'timestamp', - ColumnType::TIMESTAMP => 'timestamp', - ColumnType::DATE => 'date', - ColumnType::TIME => 'interval day(0) to second', - ColumnType::ARRAY => 'clob', - ColumnType::STRUCTURED => 'clob', - ColumnType::JSON => 'clob', - default => 'varchar2', + 'timestamp with time zone' => 'timestamp' . ($size !== null ? "($size)" : '') . ' with time zone', + 'timestamp with local time zone' => 'timestamp' . ($size !== null ? "($size)" : '') . ' with local time zone', + 'interval day to second' => 'interval day' . ($scale !== null ? "($scale)" : '') . ' to second' . ($size !== null ? "($size)" : ''), + 'interval year to month' => 'interval year' . ($scale !== null ? "($scale)" : '') . ' to month', }; } diff --git a/src/Column/ColumnDefinitionParser.php b/src/Column/ColumnDefinitionParser.php new file mode 100644 index 0000000..120d444 --- /dev/null +++ b/src/Column/ColumnDefinitionParser.php @@ -0,0 +1,55 @@ + $type]; + + $typeDetails = $matches[6] ?? $matches[2] ?? ''; + + if ($typeDetails !== '') { + if ($type === 'enum') { + $info += $this->enumInfo($typeDetails); + } else { + $info += $this->sizeInfo($typeDetails); + } + } + + $scale = $matches[5] ?? $matches[3] ?? ''; + + if ($scale !== '') { + $info += ['scale' => (int) $scale]; + } + + $extra = substr($definition, strlen($matches[0])); + + return $info + $this->extraInfo($extra); + } +} diff --git a/src/Column/ColumnFactory.php b/src/Column/ColumnFactory.php index b2fa346..29f301f 100644 --- a/src/Column/ColumnFactory.php +++ b/src/Column/ColumnFactory.php @@ -8,9 +8,7 @@ use Yiisoft\Db\Schema\Column\AbstractColumnFactory; use Yiisoft\Db\Schema\Column\ColumnInterface; -use function preg_replace; use function rtrim; -use function strtolower; final class ColumnFactory extends AbstractColumnFactory { @@ -40,19 +38,23 @@ final class ColumnFactory extends AbstractColumnFactory 'binary_double' => ColumnType::DOUBLE, // 64 bit 'float' => ColumnType::DOUBLE, // 126 bit 'date' => ColumnType::DATE, - 'interval day to second' => ColumnType::TIME, 'timestamp' => ColumnType::TIMESTAMP, 'timestamp with time zone' => ColumnType::TIMESTAMP, 'timestamp with local time zone' => ColumnType::TIMESTAMP, + 'interval day to second' => ColumnType::STRING, + 'interval year to month' => ColumnType::STRING, /** Deprecated */ 'long' => ColumnType::TEXT, ]; - protected function getType(string $dbType, array $info = []): string + protected function columnDefinitionParser(): ColumnDefinitionParser { - $dbType = strtolower($dbType); + return new ColumnDefinitionParser(); + } + protected function getType(string $dbType, array $info = []): string + { if ($dbType === 'number') { return match ($info['scale'] ?? null) { null => ColumnType::DOUBLE, @@ -61,10 +63,8 @@ protected function getType(string $dbType, array $info = []): string }; } - $dbType = preg_replace('/\([^)]+\)/', '', $dbType); - - if ($dbType === 'interval day to second' && isset($info['size']) && $info['size'] > 0) { - return ColumnType::STRING; + if ($dbType === 'interval day to second' && isset($info['scale']) && $info['scale'] === 0) { + return ColumnType::TIME; } return parent::getType($dbType, $info); diff --git a/src/Command.php b/src/Command.php index 8a28cca..196a689 100644 --- a/src/Command.php +++ b/src/Command.php @@ -5,7 +5,7 @@ namespace Yiisoft\Db\Oracle; use PDO; -use Yiisoft\Db\Command\DataType; +use Yiisoft\Db\Constant\DataType; use Yiisoft\Db\Constant\PhpType; use Yiisoft\Db\Driver\Pdo\AbstractPdoCommand; use Yiisoft\Db\QueryBuilder\AbstractQueryBuilder; diff --git a/src/Schema.php b/src/Schema.php index 490a62b..a07caae 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -27,7 +27,9 @@ use function implode; use function is_array; use function md5; +use function preg_replace; use function serialize; +use function strtolower; /** * Implements the Oracle Server specific schema, supporting Oracle Server 11C and above. @@ -437,7 +439,18 @@ protected function getTableSequenceName(string $tableName): string|null */ private function loadColumn(array $info): ColumnInterface { - return $this->getColumnFactory()->fromDbType($info['data_type'], [ + $dbType = strtolower(preg_replace('/\([^)]+\)/', '', $info['data_type'])); + + match ($dbType) { + 'timestamp', + 'timestamp with time zone', + 'timestamp with local time zone', + 'interval day to second', + 'interval year to month' => [$info['size'], $info['data_scale']] = [$info['data_scale'], $info['size']], + default => null, + }; + + return $this->getColumnFactory()->fromDbType($dbType, [ 'autoIncrement' => $info['identity_column'] === 'YES', 'comment' => $info['column_comment'], 'defaultValueRaw' => $info['data_default'], diff --git a/tests/ColumnDefinitionParserTest.php b/tests/ColumnDefinitionParserTest.php new file mode 100644 index 0000000..de74477 --- /dev/null +++ b/tests/ColumnDefinitionParserTest.php @@ -0,0 +1,27 @@ +dbType('BLOB'); + $binaryCol->dbType('blob'); $this->assertInstanceOf(Expression::class, $binaryCol->dbTypecast("\x10\x11\x12")); $this->assertInstanceOf( diff --git a/tests/Provider/ColumnDefinitionParserProvider.php b/tests/Provider/ColumnDefinitionParserProvider.php new file mode 100644 index 0000000..f697de7 --- /dev/null +++ b/tests/Provider/ColumnDefinitionParserProvider.php @@ -0,0 +1,24 @@ + 'long raw']], + ['interval day to second', ['type' => 'interval day to second']], + ['interval day to second (2)', ['type' => 'interval day to second', 'size' => 2]], + ['interval day(0) to second(2)', ['type' => 'interval day to second', 'size' => 2, 'scale' => 0]], + ['timestamp with time zone', ['type' => 'timestamp with time zone']], + ['timestamp (3) with time zone', ['type' => 'timestamp with time zone', 'size' => 3]], + ['timestamp(3) with local time zone', ['type' => 'timestamp with local time zone', 'size' => 3]], + ['interval year to month', ['type' => 'interval year to month']], + ['interval year (3) to month', ['type' => 'interval year to month', 'scale' => 3]], + ]; + } +} diff --git a/tests/Provider/ColumnFactoryProvider.php b/tests/Provider/ColumnFactoryProvider.php index 913dc0a..8004f3a 100644 --- a/tests/Provider/ColumnFactoryProvider.php +++ b/tests/Provider/ColumnFactoryProvider.php @@ -32,10 +32,12 @@ public static function dbTypes(): array ['binary_double', ColumnType::DOUBLE, DoubleColumn::class], ['float', ColumnType::DOUBLE, DoubleColumn::class], ['date', ColumnType::DATE, StringColumn::class], - ['interval day(0) to second', ColumnType::TIME, StringColumn::class], ['timestamp', ColumnType::TIMESTAMP, StringColumn::class], ['timestamp with time zone', ColumnType::TIMESTAMP, StringColumn::class], ['timestamp with local time zone', ColumnType::TIMESTAMP, StringColumn::class], + ['timestamp with local time zone', ColumnType::TIMESTAMP, StringColumn::class], + ['interval day to second', ColumnType::STRING, StringColumn::class], + ['interval year to month', ColumnType::STRING, StringColumn::class], ]; } @@ -52,7 +54,15 @@ public static function definitions(): array unset($definitions['bigint UNSIGNED']); - return $definitions; + return [ + ...$definitions, + ['interval day to second', ColumnType::STRING, StringColumn::class, ['getDbType' => 'interval day to second']], + ['interval day(0) to second', ColumnType::TIME, StringColumn::class, ['getDbType' => 'interval day to second', 'getScale' => 0]], + ['interval day (0) to second(6)', ColumnType::TIME, StringColumn::class, ['getDbType' => 'interval day to second', 'getScale' => 0, 'getSize' => 6]], + ['interval day to second (0)', ColumnType::STRING, StringColumn::class, ['getDbType' => 'interval day to second', 'getSize' => 0]], + ['interval year to month', ColumnType::STRING, StringColumn::class, ['getDbType' => 'interval year to month']], + ['interval year (2) to month', ColumnType::STRING, StringColumn::class, ['getDbType' => 'interval year to month', 'getScale' => 2]], + ]; } public static function defaultValueRaw(): array diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index ce28d35..9f4e229 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -344,6 +344,10 @@ public static function buildColumnDefinition(): array ['number(10) REFERENCES "ref_table" ("id")', ColumnBuilder::integer()->reference($referenceRestrict)], ['number(10) REFERENCES "ref_table" ("id") ON DELETE SET NULL', ColumnBuilder::integer()->reference($referenceSetNull)], + ['timestamp(3) with time zone', 'timestamp (3) with time zone'], + ['timestamp with local time zone', 'timestamp with local time zone'], + ['interval day(5) to second(6)', 'interval day(5) to second (6)'], + ['interval year(8) to month', 'interval year(8) to month'], ]; } diff --git a/tests/Provider/SchemaProvider.php b/tests/Provider/SchemaProvider.php index 8a406d8..dcd4a48 100644 --- a/tests/Provider/SchemaProvider.php +++ b/tests/Provider/SchemaProvider.php @@ -17,7 +17,7 @@ public static function columns(): array [ 'int_col' => [ 'type' => 'integer', - 'dbType' => 'NUMBER', + 'dbType' => 'number', 'phpType' => 'int', 'primaryKey' => false, 'notNull' => true, @@ -29,7 +29,7 @@ public static function columns(): array ], 'int_col2' => [ 'type' => 'integer', - 'dbType' => 'NUMBER', + 'dbType' => 'number', 'phpType' => 'int', 'primaryKey' => false, 'notNull' => false, @@ -41,7 +41,7 @@ public static function columns(): array ], 'tinyint_col' => [ 'type' => 'integer', - 'dbType' => 'NUMBER', + 'dbType' => 'number', 'phpType' => 'int', 'primaryKey' => false, 'notNull' => false, @@ -53,7 +53,7 @@ public static function columns(): array ], 'smallint_col' => [ 'type' => 'integer', - 'dbType' => 'NUMBER', + 'dbType' => 'number', 'phpType' => 'int', 'primaryKey' => false, 'notNull' => false, @@ -65,7 +65,7 @@ public static function columns(): array ], 'char_col' => [ 'type' => 'char', - 'dbType' => 'CHAR', + 'dbType' => 'char', 'phpType' => 'string', 'primaryKey' => false, 'notNull' => true, @@ -77,7 +77,7 @@ public static function columns(): array ], 'char_col2' => [ 'type' => 'string', - 'dbType' => 'VARCHAR2', + 'dbType' => 'varchar2', 'phpType' => 'string', 'primaryKey' => false, 'notNull' => false, @@ -89,7 +89,7 @@ public static function columns(): array ], 'char_col3' => [ 'type' => 'string', - 'dbType' => 'VARCHAR2', + 'dbType' => 'varchar2', 'phpType' => 'string', 'primaryKey' => false, 'notNull' => false, @@ -101,7 +101,7 @@ public static function columns(): array ], 'nvarchar_col' => [ 'type' => 'string', - 'dbType' => 'NVARCHAR2', + 'dbType' => 'nvarchar2', 'phpType' => 'string', 'primaryKey' => false, 'notNull' => false, @@ -113,7 +113,7 @@ public static function columns(): array ], 'float_col' => [ 'type' => 'double', - 'dbType' => 'FLOAT', + 'dbType' => 'float', 'phpType' => 'float', 'primaryKey' => false, 'notNull' => true, @@ -125,7 +125,7 @@ public static function columns(): array ], 'float_col2' => [ 'type' => 'double', - 'dbType' => 'FLOAT', + 'dbType' => 'float', 'phpType' => 'float', 'primaryKey' => false, 'notNull' => false, @@ -137,7 +137,7 @@ public static function columns(): array ], 'blob_col' => [ 'type' => 'binary', - 'dbType' => 'BLOB', + 'dbType' => 'blob', 'phpType' => 'mixed', 'primaryKey' => false, 'notNull' => false, @@ -149,7 +149,7 @@ public static function columns(): array ], 'numeric_col' => [ 'type' => 'decimal', - 'dbType' => 'NUMBER', + 'dbType' => 'number', 'phpType' => 'float', 'primaryKey' => false, 'notNull' => false, @@ -161,19 +161,19 @@ public static function columns(): array ], 'timestamp_col' => [ 'type' => 'timestamp', - 'dbType' => 'TIMESTAMP(6)', + 'dbType' => 'timestamp', 'phpType' => 'string', 'primaryKey' => false, 'notNull' => true, 'autoIncrement' => false, 'enumValues' => null, - 'size' => null, - 'scale' => 6, + 'size' => 6, + 'scale' => null, 'defaultValue' => new Expression("to_timestamp('2002-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')"), ], 'time_col' => [ 'type' => 'time', - 'dbType' => 'INTERVAL DAY(0) TO SECOND(0)', + 'dbType' => 'interval day to second', 'phpType' => 'string', 'primaryKey' => false, 'notNull' => false, @@ -185,19 +185,19 @@ public static function columns(): array ], 'interval_day_col' => [ 'type' => 'string', - 'dbType' => 'INTERVAL DAY(1) TO SECOND(0)', + 'dbType' => 'interval day to second', 'phpType' => 'string', 'primaryKey' => false, 'notNull' => false, 'autoIncrement' => false, 'enumValues' => null, - 'size' => 1, - 'scale' => 0, + 'size' => 0, + 'scale' => 1, 'defaultValue' => new Expression("INTERVAL '2 04:56:12' DAY(1) TO SECOND(0)"), ], 'bool_col' => [ 'type' => 'char', - 'dbType' => 'CHAR', + 'dbType' => 'char', 'phpType' => 'string', 'primaryKey' => false, 'notNull' => true, @@ -209,7 +209,7 @@ public static function columns(): array ], 'bool_col2' => [ 'type' => 'char', - 'dbType' => 'CHAR', + 'dbType' => 'char', 'phpType' => 'string', 'primaryKey' => false, 'notNull' => false, @@ -221,19 +221,19 @@ public static function columns(): array ], 'ts_default' => [ 'type' => 'timestamp', - 'dbType' => 'TIMESTAMP(6)', + 'dbType' => 'timestamp', 'phpType' => 'string', 'primaryKey' => false, 'notNull' => true, 'autoIncrement' => false, 'enumValues' => null, - 'size' => null, - 'scale' => 6, + 'size' => 6, + 'scale' => null, 'defaultValue' => new Expression('CURRENT_TIMESTAMP'), ], 'bit_col' => [ 'type' => 'integer', - 'dbType' => 'NUMBER', + 'dbType' => 'number', 'phpType' => 'int', 'primaryKey' => false, 'notNull' => true, @@ -250,7 +250,7 @@ public static function columns(): array [ 'id' => [ 'type' => 'integer', - 'dbType' => 'NUMBER', + 'dbType' => 'number', 'phpType' => 'int', 'primaryKey' => true, 'notNull' => true, @@ -262,7 +262,7 @@ public static function columns(): array ], 'type' => [ 'type' => 'string', - 'dbType' => 'VARCHAR2', + 'dbType' => 'varchar2', 'phpType' => 'string', 'primaryKey' => false, 'notNull' => true,