Skip to content

Commit

Permalink
Support JSON type
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov committed Feb 18, 2025
1 parent c74a84b commit 7acf312
Show file tree
Hide file tree
Showing 13 changed files with 146 additions and 69 deletions.
6 changes: 3 additions & 3 deletions src/Column/ColumnDefinitionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ protected function getDbType(ColumnInterface $column): string
ColumnType::TIMESTAMP => 'timestamp',
ColumnType::DATE => 'date',
ColumnType::TIME => 'interval day(0) to second',
ColumnType::ARRAY => 'clob',
ColumnType::STRUCTURED => 'clob',
ColumnType::JSON => 'clob',
ColumnType::ARRAY => 'json',
ColumnType::STRUCTURED => 'json',
ColumnType::JSON => 'json',
default => 'varchar2',
},
'timestamp with time zone' => 'timestamp' . ($size !== null ? "($size)" : '') . ' with time zone',
Expand Down
7 changes: 6 additions & 1 deletion src/Column/ColumnDefinitionParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class ColumnDefinitionParser extends \Yiisoft\Db\Syntax\ColumnDefinitionPa
. 'interval day\s*(?:\((\d+)\))? to second'
. '|long raw'
. '|\w*'
. ')\s*(?:\(([^)]+)\))?\s*'
. ')\s*(?:\(([^)]+)\))?(\[[\d\[\]]*\])?\s*'
. '/i';

public function parse(string $definition): array
Expand All @@ -48,6 +48,11 @@ public function parse(string $definition): array
$info += ['scale' => (int) $scale];
}

if (isset($matches[7])) {
/** @psalm-var positive-int */
$info['dimension'] = substr_count($matches[7], '[');
}

$extra = substr($definition, strlen($matches[0]));

return $info + $this->extraInfo($extra);
Expand Down
1 change: 1 addition & 0 deletions src/Column/ColumnFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ final class ColumnFactory extends AbstractColumnFactory
'timestamp with local time zone' => ColumnType::TIMESTAMP,
'interval day to second' => ColumnType::STRING,
'interval year to month' => ColumnType::STRING,
'json' => ColumnType::JSON,

/** Deprecated */
'long' => ColumnType::TEXT,
Expand Down
21 changes: 7 additions & 14 deletions tests/ColumnFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPUnit\Framework\Attributes\DataProviderExternal;
use Yiisoft\Db\Oracle\Tests\Provider\ColumnFactoryProvider;
use Yiisoft\Db\Oracle\Tests\Support\TestTrait;
use Yiisoft\Db\Schema\Column\ColumnInterface;
use Yiisoft\Db\Tests\AbstractColumnFactoryTest;

/**
Expand All @@ -23,23 +24,15 @@ public function testFromDbType(string $dbType, string $expectedType, string $exp
}

#[DataProviderExternal(ColumnFactoryProvider::class, 'definitions')]
public function testFromDefinition(
string $definition,
string $expectedType,
string $expectedInstanceOf,
array $expectedMethodResults = []
): void {
parent::testFromDefinition($definition, $expectedType, $expectedInstanceOf, $expectedMethodResults);
public function testFromDefinition(string $definition, ColumnInterface $expected): void
{
parent::testFromDefinition($definition, $expected);
}

#[DataProviderExternal(ColumnFactoryProvider::class, 'pseudoTypes')]
public function testFromPseudoType(
string $pseudoType,
string $expectedType,
string $expectedInstanceOf,
array $expectedMethodResults = []
): void {
parent::testFromPseudoType($pseudoType, $expectedType, $expectedInstanceOf, $expectedMethodResults);
public function testFromPseudoType(string $pseudoType, ColumnInterface $expected): void
{
parent::testFromPseudoType($pseudoType, $expected);
}

#[DataProviderExternal(ColumnFactoryProvider::class, 'types')]
Expand Down
61 changes: 44 additions & 17 deletions tests/ColumnTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,53 @@
use Yiisoft\Db\Oracle\Column\BinaryColumn;
use Yiisoft\Db\Oracle\Tests\Support\TestTrait;
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\Schema\Column\ColumnInterface;
use Yiisoft\Db\Schema\Column\DoubleColumn;
use Yiisoft\Db\Schema\Column\IntegerColumn;
use Yiisoft\Db\Schema\Column\JsonColumn;
use Yiisoft\Db\Schema\Column\StringColumn;
use Yiisoft\Db\Tests\Common\CommonColumnTest;
use Yiisoft\Db\Tests\AbstractColumnTest;

use function str_repeat;
use function version_compare;

/**
* @group oracle
*/
final class ColumnTest extends CommonColumnTest
final class ColumnTest extends AbstractColumnTest
{
use TestTrait;

public function testPhpTypeCast(): void
{
$version21 = version_compare($this->getConnection()->getServerInfo()->getVersion(), '21', '>=');

if ($version21) {
$this->fixture = 'oci21.sql';
}

$db = $this->getConnection(true);

$command = $db->createCommand();
$schema = $db->getSchema();
$tableSchema = $schema->getTableSchema('type');

$command->insert(
'type',
[
'int_col' => 1,
'char_col' => str_repeat('x', 100),
'char_col3' => null,
'float_col' => 1.234,
'blob_col' => "\x10\x11\x12",
'timestamp_col' => new Expression("TIMESTAMP '2023-07-11 14:50:23'"),
'bool_col' => false,
'bit_col' => 0b0110_0110, // 102
]
);
$insert = [
'int_col' => 1,
'char_col' => str_repeat('x', 100),
'char_col3' => null,
'float_col' => 1.234,
'blob_col' => "\x10\x11\x12",
'timestamp_col' => new Expression("TIMESTAMP '2023-07-11 14:50:23'"),
'bool_col' => false,
'bit_col' => 0b0110_0110, // 102
];

if ($version21) {
$insert['json_col'] = [['a' => 1, 'b' => null, 'c' => [1, 3, 5]]];
}

$command->insert('type', $insert);
$command->execute();
$query = (new Query($db))->from('type')->one();

Expand All @@ -66,11 +78,22 @@ public function testPhpTypeCast(): void
$this->assertEquals(false, $boolColPhpType);
$this->assertSame(0b0110_0110, $bitColPhpType);

if ($version21) {
$jsonColPhpType = $tableSchema->getColumn('json_col')?->phpTypecast($query['json_col']);
$this->assertSame([['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], $jsonColPhpType);
}

$db->close();
}

public function testColumnInstance(): void
{
$version21 = version_compare($this->getConnection()->getServerInfo()->getVersion(), '21', '>=');

if ($version21) {
$this->fixture = 'oci21.sql';
}

$db = $this->getConnection(true);
$schema = $db->getSchema();
$tableSchema = $schema->getTableSchema('type');
Expand All @@ -79,6 +102,10 @@ public function testColumnInstance(): void
$this->assertInstanceOf(StringColumn::class, $tableSchema->getColumn('char_col'));
$this->assertInstanceOf(DoubleColumn::class, $tableSchema->getColumn('float_col'));
$this->assertInstanceOf(BinaryColumn::class, $tableSchema->getColumn('blob_col'));

if ($version21) {
$this->assertInstanceOf(JsonColumn::class, $tableSchema->getColumn('json_col'));
}
}

/** @dataProvider \Yiisoft\Db\Oracle\Tests\Provider\ColumnProvider::predefinedTypes */
Expand All @@ -88,9 +115,9 @@ public function testPredefinedType(string $className, string $type, string $phpT
}

/** @dataProvider \Yiisoft\Db\Oracle\Tests\Provider\ColumnProvider::dbTypecastColumns */
public function testDbTypecastColumns(string $className, array $values): void
public function testDbTypecastColumns(ColumnInterface $column, array $values): void
{
parent::testDbTypecastColumns($className, $values);
parent::testDbTypecastColumns($column, $values);
}

public function testBinaryColumn(): void
Expand Down
6 changes: 6 additions & 0 deletions tests/CommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ public function testBatchInsert(
array $expectedParams = [],
int $insertedRow = 1
): void {
$version21 = version_compare($this->getConnection()->getServerInfo()->getVersion(), '21', '>=');

if ($version21) {
$this->fixture = 'oci21.sql';
}

parent::testBatchInsert($table, $values, $columns, $expected, $expectedParams, $insertedRow);
}

Expand Down
25 changes: 14 additions & 11 deletions tests/Provider/ColumnFactoryProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Oracle\Column\BinaryColumn;
use Yiisoft\Db\Schema\Column\ArrayColumn;
use Yiisoft\Db\Schema\Column\BigIntColumn;
use Yiisoft\Db\Schema\Column\DoubleColumn;
use Yiisoft\Db\Schema\Column\IntegerColumn;
use Yiisoft\Db\Schema\Column\StringColumn;

final class ColumnFactoryProvider extends \Yiisoft\Db\Tests\Provider\ColumnFactoryProvider
Expand Down Expand Up @@ -46,22 +49,22 @@ public static function definitions(): array
$definitions = parent::definitions();

$definitions['text'][0] = 'clob';
$definitions['text'][3]['getDbType'] = 'clob';
$definitions['text'][1]->dbType('clob');
$definitions['text NOT NULL'][0] = 'clob NOT NULL';
$definitions['text NOT NULL'][3]['getDbType'] = 'clob';
$definitions['text NOT NULL'][1]->dbType('clob');
$definitions['decimal(10,2)'][0] = 'number(10,2)';
$definitions['decimal(10,2)'][3]['getDbType'] = 'number';

unset($definitions['bigint UNSIGNED']);
$definitions['decimal(10,2)'][1]->dbType('number');
$definitions['bigint UNSIGNED'][1] = new BigIntColumn(unsigned: true);
$definitions['integer[]'] = ['number(10,0)[]', new ArrayColumn(dbType: 'number', size: 10, column: new IntegerColumn(dbType: 'number', size: 10))];

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]],
['interval day to second', new StringColumn(dbType: 'interval day to second')],
['interval day(0) to second', new StringColumn(ColumnType::TIME, dbType: 'interval day to second', scale: 0)],
['interval day (0) to second(6)', new StringColumn(ColumnType::TIME, dbType: 'interval day to second', scale: 0, size: 6)],
['interval day to second (0)', new StringColumn(dbType: 'interval day to second', size: 0)],
['interval year to month', new StringColumn(dbType: 'interval year to month')],
['interval year (2) to month', new StringColumn(dbType: 'interval year to month', scale: 2)],
];
}

Expand Down
2 changes: 1 addition & 1 deletion tests/Provider/ColumnProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static function predefinedTypes(): array
public static function dbTypecastColumns(): array
{
$values = parent::dbTypecastColumns();
$values['binary'][0] = BinaryColumn::class;
$values['binary'][0] = new BinaryColumn();

return $values;
}
Expand Down
4 changes: 4 additions & 0 deletions tests/Provider/CommandProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public static function batchInsert(): array
'empty columns and Traversable values' => [
':qp3' => '1',
],
'binds json params' => [
':qp3' => '1',
':qp8' => '0',
],
];

foreach ($replaceParams as $key => $expectedParams) {
Expand Down
12 changes: 6 additions & 6 deletions tests/Provider/QueryBuilderProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,14 @@ public static function buildColumnDefinition(): array
$values['time()'][0] = 'interval day(0) to second(0)';
$values['time(6)'][0] = 'interval day(0) to second(6)';
$values['time(null)'][0] = 'interval day(0) to second';
$values['array()'][0] = 'clob';
$values['structured()'][0] = 'clob';
$values["structured('json')"] = ['blob', ColumnBuilder::structured('blob')];
$values['json()'][0] = 'clob';
$values['json(100)'][0] = 'clob';
$values['array()'][0] = 'json';
$values['structured()'][0] = 'json';
$values["structured('json')"] = ['clob', ColumnBuilder::structured('clob')];
$values['json()'][0] = 'json';
$values['json(100)'][0] = 'json';
$values["extra('NOT NULL')"][0] = 'varchar2(255) NOT NULL';
$values["extra('')"][0] = 'varchar2(255)';
$values["check('value > 5')"][0] = 'number(10) CHECK ("col_59" > 5)';
$values["check('value > 5')"][0] = 'number(10) CHECK ("check_col" > 5)';
$values["check('')"][0] = 'number(10)';
$values['check(null)'][0] = 'number(10)';
$values["comment('comment')"][0] = 'varchar2(255)';
Expand Down
36 changes: 21 additions & 15 deletions tests/SchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,27 @@ final class SchemaTest extends CommonSchemaTest
*/
public function testColumns(array $columns, string $tableName = 'type'): void
{
$version21 = version_compare($this->getConnection()->getServerInfo()->getVersion(), '21', '>=');

if ($version21) {
$this->fixture = 'oci21.sql';

if ($tableName === 'type') {
$columns['json_col'] = [
'type' => 'json',
'dbType' => 'json',
'phpType' => 'mixed',
'primaryKey' => false,
'notNull' => false,
'autoIncrement' => false,
'enumValues' => null,
'size' => null,
'scale' => null,
'defaultValue' => ['a' => 1],
];
}
}

parent::testColumns($columns, $tableName);
}

Expand Down Expand Up @@ -259,21 +280,6 @@ public function testWorkWithDefaultValueConstraint(): void
parent::testWorkWithDefaultValueConstraint();
}

public function withIndexDataProvider(): array
{
/*
* Bitmap indexes are not available for standard edition.
return array_merge(parent::withIndexDataProvider(), [
[
'indexType' => SchemaInterface::BITMAP,
'indexMethod' => null,
'columnType' => 'varchar(16)',
],
]);
*/
return parent::withIndexDataProvider();
}

public function testNotConnectionPDO(): void
{
$db = $this->createMock(ConnectionInterface::class);
Expand Down
30 changes: 30 additions & 0 deletions tests/Support/Fixture/oci21.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
BEGIN EXECUTE IMMEDIATE 'DROP TABLE "type"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;--

/* STATEMENTS */

CREATE TABLE "type" (
"int_col" integer NOT NULL,
"int_col2" integer DEFAULT 1,
"tinyint_col" number(3) DEFAULT 1,
"smallint_col" smallint DEFAULT 1,
"char_col" char(100) NOT NULL,
"char_col2" varchar2(100) DEFAULT 'some''thing',
"char_col3" varchar2(4000),
"nvarchar_col" nvarchar2(100) DEFAULT '',
"float_col" double precision NOT NULL,
"float_col2" double precision DEFAULT 1.23,
"blob_col" blob DEFAULT NULL,
"numeric_col" decimal(5,2) DEFAULT 33.22,
"timestamp_col" timestamp DEFAULT to_timestamp('2002-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss') NOT NULL,
"time_col" interval day (0) to second(0) DEFAULT INTERVAL '0 10:33:21' DAY(0) TO SECOND(0),
"interval_day_col" interval day (1) to second(0) DEFAULT INTERVAL '2 04:56:12' DAY(1) TO SECOND(0),
"bool_col" char NOT NULL check ("bool_col" in (0,1)),
"bool_col2" char DEFAULT 1 check("bool_col2" in (0,1)),
"ts_default" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
"bit_col" number(3) DEFAULT 130 NOT NULL,
"json_col" json DEFAULT '{"a":1}'
);

/* TRIGGERS */

/* TRIGGERS */
4 changes: 3 additions & 1 deletion tests/Support/TestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ trait TestTrait
{
private string $dsn = 'oci:dbname=localhost/XE;';

private string $fixture = 'oci.sql';

/**
* @throws InvalidConfigException
* @throws Exception
Expand All @@ -25,7 +27,7 @@ protected function getConnection(bool $fixture = false): PdoConnectionInterface
$db = new Connection(new Driver($this->getDsn(), 'system', 'root'), DbHelper::getSchemaCache());

if ($fixture) {
DbHelper::loadFixture($db, __DIR__ . '/Fixture/oci.sql');
DbHelper::loadFixture($db, __DIR__ . "/Fixture/$this->fixture");
}

return $db;
Expand Down

0 comments on commit 7acf312

Please sign in to comment.