Skip to content

Commit c76d52f

Browse files
authored
Fix ColumnDefinitionParser (#299)
1 parent 3f867e3 commit c76d52f

13 files changed

+214
-70
lines changed

CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
- Enh #279: Separate column type constants (@Tigrov)
1616
- New #280, #291: Realize `ColumnBuilder` class (@Tigrov)
1717
- Enh #281: Update according changes in `ColumnSchemaInterface` (@Tigrov)
18-
- New #282, #291: Add `ColumnDefinitionBuilder` class (@Tigrov)
18+
- New #282, #291, #299: Add `ColumnDefinitionBuilder` class (@Tigrov)
1919
- Bug #285: Fix `DMLQueryBuilder::insertBatch()` method (@Tigrov)
2020
- Enh #283: Refactor `Dsn` class (@Tigrov)
2121
- Enh #286: Use constructor to create columns and initialize properties (@Tigrov)
@@ -27,6 +27,8 @@
2727
- Enh #296: Remove `ColumnInterface` (@Tigrov)
2828
- Enh #298: Rename `ColumnSchemaInterface` to `ColumnInterface` (@Tigrov)
2929
- Enh #298: Refactor `DMLQueryBuilder::prepareInsertValues()` method (@Tigrov)
30+
- Enh #299: Add `ColumnDefinitionParser` class (@Tigrov)
31+
- Enh #299: Convert database types to lower case (@Tigrov)
3032

3133
## 1.3.0 March 21, 2024
3234

src/Column/BinaryColumn.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class BinaryColumn extends BaseBinaryColumn
1414
{
1515
public function dbTypecast(mixed $value): mixed
1616
{
17-
if ($this->getDbType() === 'BLOB') {
17+
if ($this->getDbType() === 'blob') {
1818
if ($value instanceof ParamInterface && is_string($value->getValue())) {
1919
/** @var string */
2020
$value = $value->getValue();

src/Column/ColumnDefinitionBuilder.php

+36-27
Original file line numberDiff line numberDiff line change
@@ -64,37 +64,46 @@ protected function buildOnUpdate(string $onUpdate): string
6464

6565
protected function getDbType(ColumnInterface $column): string
6666
{
67+
$dbType = $column->getDbType();
6768
$size = $column->getSize();
69+
$scale = $column->getScale();
6870

6971
/** @psalm-suppress DocblockTypeContradiction */
70-
return $column->getDbType() ?? match ($column->getType()) {
71-
ColumnType::BOOLEAN => 'number(1)',
72-
ColumnType::BIT => match (true) {
73-
$size === null => 'number(38)',
74-
$size <= 126 => 'number(' . ceil(log10(2 ** $size)) . ')',
75-
default => 'raw(' . ceil($size / 8) . ')',
72+
return match ($dbType) {
73+
default => $dbType,
74+
null => match ($column->getType()) {
75+
ColumnType::BOOLEAN => 'number(1)',
76+
ColumnType::BIT => match (true) {
77+
$size === null => 'number(38)',
78+
$size <= 126 => 'number(' . ceil(log10(2 ** $size)) . ')',
79+
default => 'raw(' . ceil($size / 8) . ')',
80+
},
81+
ColumnType::TINYINT => 'number(' . ($size ?? 3) . ')',
82+
ColumnType::SMALLINT => 'number(' . ($size ?? 5) . ')',
83+
ColumnType::INTEGER => 'number(' . ($size ?? 10) . ')',
84+
ColumnType::BIGINT => 'number(' . ($size ?? 20) . ')',
85+
ColumnType::FLOAT => 'binary_float',
86+
ColumnType::DOUBLE => 'binary_double',
87+
ColumnType::DECIMAL => 'number(' . ($size ?? 10) . ',' . ($scale ?? 0) . ')',
88+
ColumnType::MONEY => 'number(' . ($size ?? 19) . ',' . ($scale ?? 4) . ')',
89+
ColumnType::CHAR => 'char',
90+
ColumnType::STRING => 'varchar2(' . ($size ?? 255) . ')',
91+
ColumnType::TEXT => 'clob',
92+
ColumnType::BINARY => 'blob',
93+
ColumnType::UUID => 'raw(16)',
94+
ColumnType::DATETIME => 'timestamp',
95+
ColumnType::TIMESTAMP => 'timestamp',
96+
ColumnType::DATE => 'date',
97+
ColumnType::TIME => 'interval day(0) to second',
98+
ColumnType::ARRAY => 'clob',
99+
ColumnType::STRUCTURED => 'clob',
100+
ColumnType::JSON => 'clob',
101+
default => 'varchar2',
76102
},
77-
ColumnType::TINYINT => 'number(' . ($size ?? 3) . ')',
78-
ColumnType::SMALLINT => 'number(' . ($size ?? 5) . ')',
79-
ColumnType::INTEGER => 'number(' . ($size ?? 10) . ')',
80-
ColumnType::BIGINT => 'number(' . ($size ?? 20) . ')',
81-
ColumnType::FLOAT => 'binary_float',
82-
ColumnType::DOUBLE => 'binary_double',
83-
ColumnType::DECIMAL => 'number(' . ($size ?? 10) . ',' . ($column->getScale() ?? 0) . ')',
84-
ColumnType::MONEY => 'number(' . ($size ?? 19) . ',' . ($column->getScale() ?? 4) . ')',
85-
ColumnType::CHAR => 'char',
86-
ColumnType::STRING => 'varchar2(' . ($size ?? 255) . ')',
87-
ColumnType::TEXT => 'clob',
88-
ColumnType::BINARY => 'blob',
89-
ColumnType::UUID => 'raw(16)',
90-
ColumnType::DATETIME => 'timestamp',
91-
ColumnType::TIMESTAMP => 'timestamp',
92-
ColumnType::DATE => 'date',
93-
ColumnType::TIME => 'interval day(0) to second',
94-
ColumnType::ARRAY => 'clob',
95-
ColumnType::STRUCTURED => 'clob',
96-
ColumnType::JSON => 'clob',
97-
default => 'varchar2',
103+
'timestamp with time zone' => 'timestamp' . ($size !== null ? "($size)" : '') . ' with time zone',
104+
'timestamp with local time zone' => 'timestamp' . ($size !== null ? "($size)" : '') . ' with local time zone',
105+
'interval day to second' => 'interval day' . ($scale !== null ? "($scale)" : '') . ' to second' . ($size !== null ? "($size)" : ''),
106+
'interval year to month' => 'interval year' . ($scale !== null ? "($scale)" : '') . ' to month',
98107
};
99108
}
100109

src/Column/ColumnDefinitionParser.php

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Oracle\Column;
6+
7+
use function preg_match;
8+
use function preg_replace;
9+
use function strlen;
10+
use function strtolower;
11+
use function substr;
12+
13+
/**
14+
* Parses column definition string. For example, `string(255)` or `int unsigned`.
15+
*/
16+
final class ColumnDefinitionParser extends \Yiisoft\Db\Syntax\ColumnDefinitionParser
17+
{
18+
private const TYPE_PATTERN = '/^('
19+
. 'timestamp\s*(?:\((\d+)\))? with(?: local)? time zone'
20+
. '|interval year\s*(?:\((\d+)\))? to month'
21+
. ')|('
22+
. 'interval day\s*(?:\((\d+)\))? to second'
23+
. '|long raw'
24+
. '|\w*'
25+
. ')\s*(?:\(([^)]+)\))?\s*'
26+
. '/i';
27+
28+
public function parse(string $definition): array
29+
{
30+
preg_match(self::TYPE_PATTERN, $definition, $matches);
31+
32+
$type = strtolower(preg_replace('/\s*\(\d+\)/', '', $matches[4] ?? $matches[1]));
33+
$info = ['type' => $type];
34+
35+
$typeDetails = $matches[6] ?? $matches[2] ?? '';
36+
37+
if ($typeDetails !== '') {
38+
if ($type === 'enum') {
39+
$info += $this->enumInfo($typeDetails);
40+
} else {
41+
$info += $this->sizeInfo($typeDetails);
42+
}
43+
}
44+
45+
$scale = $matches[5] ?? $matches[3] ?? '';
46+
47+
if ($scale !== '') {
48+
$info += ['scale' => (int) $scale];
49+
}
50+
51+
$extra = substr($definition, strlen($matches[0]));
52+
53+
return $info + $this->extraInfo($extra);
54+
}
55+
}

src/Column/ColumnFactory.php

+9-9
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
use Yiisoft\Db\Schema\Column\AbstractColumnFactory;
99
use Yiisoft\Db\Schema\Column\ColumnInterface;
1010

11-
use function preg_replace;
1211
use function rtrim;
13-
use function strtolower;
1412

1513
final class ColumnFactory extends AbstractColumnFactory
1614
{
@@ -40,19 +38,23 @@ final class ColumnFactory extends AbstractColumnFactory
4038
'binary_double' => ColumnType::DOUBLE, // 64 bit
4139
'float' => ColumnType::DOUBLE, // 126 bit
4240
'date' => ColumnType::DATE,
43-
'interval day to second' => ColumnType::TIME,
4441
'timestamp' => ColumnType::TIMESTAMP,
4542
'timestamp with time zone' => ColumnType::TIMESTAMP,
4643
'timestamp with local time zone' => ColumnType::TIMESTAMP,
44+
'interval day to second' => ColumnType::STRING,
45+
'interval year to month' => ColumnType::STRING,
4746

4847
/** Deprecated */
4948
'long' => ColumnType::TEXT,
5049
];
5150

52-
protected function getType(string $dbType, array $info = []): string
51+
protected function columnDefinitionParser(): ColumnDefinitionParser
5352
{
54-
$dbType = strtolower($dbType);
53+
return new ColumnDefinitionParser();
54+
}
5555

56+
protected function getType(string $dbType, array $info = []): string
57+
{
5658
if ($dbType === 'number') {
5759
return match ($info['scale'] ?? null) {
5860
null => ColumnType::DOUBLE,
@@ -61,10 +63,8 @@ protected function getType(string $dbType, array $info = []): string
6163
};
6264
}
6365

64-
$dbType = preg_replace('/\([^)]+\)/', '', $dbType);
65-
66-
if ($dbType === 'interval day to second' && isset($info['size']) && $info['size'] > 0) {
67-
return ColumnType::STRING;
66+
if ($dbType === 'interval day to second' && isset($info['scale']) && $info['scale'] === 0) {
67+
return ColumnType::TIME;
6868
}
6969

7070
return parent::getType($dbType, $info);

src/Command.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Yiisoft\Db\Oracle;
66

77
use PDO;
8-
use Yiisoft\Db\Command\DataType;
8+
use Yiisoft\Db\Constant\DataType;
99
use Yiisoft\Db\Constant\PhpType;
1010
use Yiisoft\Db\Driver\Pdo\AbstractPdoCommand;
1111
use Yiisoft\Db\QueryBuilder\AbstractQueryBuilder;

src/Schema.php

+14-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
use function implode;
2828
use function is_array;
2929
use function md5;
30+
use function preg_replace;
3031
use function serialize;
32+
use function strtolower;
3133

3234
/**
3335
* Implements the Oracle Server specific schema, supporting Oracle Server 11C and above.
@@ -437,7 +439,18 @@ protected function getTableSequenceName(string $tableName): string|null
437439
*/
438440
private function loadColumn(array $info): ColumnInterface
439441
{
440-
return $this->getColumnFactory()->fromDbType($info['data_type'], [
442+
$dbType = strtolower(preg_replace('/\([^)]+\)/', '', $info['data_type']));
443+
444+
match ($dbType) {
445+
'timestamp',
446+
'timestamp with time zone',
447+
'timestamp with local time zone',
448+
'interval day to second',
449+
'interval year to month' => [$info['size'], $info['data_scale']] = [$info['data_scale'], $info['size']],
450+
default => null,
451+
};
452+
453+
return $this->getColumnFactory()->fromDbType($dbType, [
441454
'autoIncrement' => $info['identity_column'] === 'YES',
442455
'comment' => $info['column_comment'],
443456
'defaultValueRaw' => $info['data_default'],

tests/ColumnDefinitionParserTest.php

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Oracle\Tests;
6+
7+
use Yiisoft\Db\Oracle\Column\ColumnDefinitionParser;
8+
use Yiisoft\Db\Tests\AbstractColumnDefinitionParserTest;
9+
10+
/**
11+
* @group oracle
12+
*/
13+
final class ColumnDefinitionParserTest extends AbstractColumnDefinitionParserTest
14+
{
15+
protected function createColumnDefinitionParser(): ColumnDefinitionParser
16+
{
17+
return new ColumnDefinitionParser();
18+
}
19+
20+
/**
21+
* @dataProvider \Yiisoft\Db\Oracle\Tests\Provider\ColumnDefinitionParserProvider::parse
22+
*/
23+
public function testParse(string $definition, array $expected): void
24+
{
25+
parent::testParse($definition, $expected);
26+
}
27+
}

tests/ColumnTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public function testDbTypecastColumns(string $className, array $values): void
9696
public function testBinaryColumn(): void
9797
{
9898
$binaryCol = new BinaryColumn();
99-
$binaryCol->dbType('BLOB');
99+
$binaryCol->dbType('blob');
100100

101101
$this->assertInstanceOf(Expression::class, $binaryCol->dbTypecast("\x10\x11\x12"));
102102
$this->assertInstanceOf(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Oracle\Tests\Provider;
6+
7+
class ColumnDefinitionParserProvider extends \Yiisoft\Db\Tests\Provider\ColumnDefinitionParserProvider
8+
{
9+
public static function parse(): array
10+
{
11+
return [
12+
...parent::parse(),
13+
['long raw', ['type' => 'long raw']],
14+
['interval day to second', ['type' => 'interval day to second']],
15+
['interval day to second (2)', ['type' => 'interval day to second', 'size' => 2]],
16+
['interval day(0) to second(2)', ['type' => 'interval day to second', 'size' => 2, 'scale' => 0]],
17+
['timestamp with time zone', ['type' => 'timestamp with time zone']],
18+
['timestamp (3) with time zone', ['type' => 'timestamp with time zone', 'size' => 3]],
19+
['timestamp(3) with local time zone', ['type' => 'timestamp with local time zone', 'size' => 3]],
20+
['interval year to month', ['type' => 'interval year to month']],
21+
['interval year (3) to month', ['type' => 'interval year to month', 'scale' => 3]],
22+
];
23+
}
24+
}

tests/Provider/ColumnFactoryProvider.php

+12-2
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ public static function dbTypes(): array
3232
['binary_double', ColumnType::DOUBLE, DoubleColumn::class],
3333
['float', ColumnType::DOUBLE, DoubleColumn::class],
3434
['date', ColumnType::DATE, StringColumn::class],
35-
['interval day(0) to second', ColumnType::TIME, StringColumn::class],
3635
['timestamp', ColumnType::TIMESTAMP, StringColumn::class],
3736
['timestamp with time zone', ColumnType::TIMESTAMP, StringColumn::class],
3837
['timestamp with local time zone', ColumnType::TIMESTAMP, StringColumn::class],
38+
['timestamp with local time zone', ColumnType::TIMESTAMP, StringColumn::class],
39+
['interval day to second', ColumnType::STRING, StringColumn::class],
40+
['interval year to month', ColumnType::STRING, StringColumn::class],
3941
];
4042
}
4143

@@ -52,7 +54,15 @@ public static function definitions(): array
5254

5355
unset($definitions['bigint UNSIGNED']);
5456

55-
return $definitions;
57+
return [
58+
...$definitions,
59+
['interval day to second', ColumnType::STRING, StringColumn::class, ['getDbType' => 'interval day to second']],
60+
['interval day(0) to second', ColumnType::TIME, StringColumn::class, ['getDbType' => 'interval day to second', 'getScale' => 0]],
61+
['interval day (0) to second(6)', ColumnType::TIME, StringColumn::class, ['getDbType' => 'interval day to second', 'getScale' => 0, 'getSize' => 6]],
62+
['interval day to second (0)', ColumnType::STRING, StringColumn::class, ['getDbType' => 'interval day to second', 'getSize' => 0]],
63+
['interval year to month', ColumnType::STRING, StringColumn::class, ['getDbType' => 'interval year to month']],
64+
['interval year (2) to month', ColumnType::STRING, StringColumn::class, ['getDbType' => 'interval year to month', 'getScale' => 2]],
65+
];
5666
}
5767

5868
public static function defaultValueRaw(): array

tests/Provider/QueryBuilderProvider.php

+4
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,10 @@ public static function buildColumnDefinition(): array
344344

345345
['number(10) REFERENCES "ref_table" ("id")', ColumnBuilder::integer()->reference($referenceRestrict)],
346346
['number(10) REFERENCES "ref_table" ("id") ON DELETE SET NULL', ColumnBuilder::integer()->reference($referenceSetNull)],
347+
['timestamp(3) with time zone', 'timestamp (3) with time zone'],
348+
['timestamp with local time zone', 'timestamp with local time zone'],
349+
['interval day(5) to second(6)', 'interval day(5) to second (6)'],
350+
['interval year(8) to month', 'interval year(8) to month'],
347351
];
348352
}
349353

0 commit comments

Comments
 (0)