Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support DateTime instances #231

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/ColumnSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Yiisoft\Db\Schema\AbstractColumnSchema;
use Yiisoft\Db\Schema\SchemaInterface;

use function explode;
use function is_string;
use function preg_replace;
use function uniqid;
Expand Down Expand Up @@ -54,4 +55,18 @@ public function dbTypecast(mixed $value): mixed

return parent::dbTypecast($value);
}

public function phpTypecast(mixed $value): mixed
{
if (is_string($value) && $this->getType() === SchemaInterface::TYPE_TIME) {
$value = explode(' ', $value)[1] ?? $value;
}

return parent::phpTypecast($value);
}

public function hasTimezone(): bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need have a tests for public functions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ColumnSchema::hasTimezone() can be replaced with two new abstract types SchemaInterface::TYPE_TIMESTAMPTZ and SchemaInterface::TYPE_TIMETZ

Tests will be added after review this option in yiisoft/db #736

{
return str_ends_with((string) $this->getDbType(), ' WITH TIME ZONE');
}
}
23 changes: 23 additions & 0 deletions src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,27 @@ public function getSchema(): SchemaInterface

return $this->schema;
}

/**
* Initializes the DB connection and sets default date and time format.
*
* This method is invoked right after the DB connection is established.
*
* The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`, if {@see getEmulatePrepare()} is `true`.
*/
protected function initConnection(): void
{
parent::initConnection();

$this->pdo->exec(
<<<SQL
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What benefits in changes settings of db? What about cases with default settings?
This changes can be in documentation only. It's change user experience.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When getting datetime values from Oracle they are returned in a specific format that is not suitable for proper parsing by DateTime.
Yes, sure it will need to be added to the documentation

ALTER SESSION SET
NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SSXFF'
NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SSXFFTZH:TZM'
NLS_DATE_FORMAT = 'YYYY-MM-DD'
NLS_TIME_FORMAT = 'HH24:MI:SSXFF'
NLS_TIME_TZ_FORMAT = 'HH24:MI:SSXFFTZH:TZM'
SQL
);
}
}
32 changes: 30 additions & 2 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ protected function createColumnSchema(array $info): ColumnSchemaInterface
$column->dbType($info['data_type']);
$column->type($this->extractColumnType($column));
$column->phpType($this->getColumnPhpType($column));
$column->dateTimeFormat($this->getDateTimeFormat($column));
$column->defaultValue($this->normalizeDefaultValue($info['data_default'], $column));

return $column;
Expand All @@ -453,8 +454,11 @@ private function normalizeDefaultValue(string|null $defaultValue, ColumnSchemaIn
return null;
}

if ($column->getType() === self::TYPE_TIMESTAMP && $defaultValue === 'CURRENT_TIMESTAMP') {
return new Expression($defaultValue);
if ($column->getDateTimeFormat() !== null) {
$dateTimeRegex = "/^(?:TIMESTAMP|DATE|INTERVAL|to_timestamp(?:_tz)?\(|to_date\(|to_dsinterval\()?\s*'(?:\d )?([^']+)'.*/";
$value = preg_replace($dateTimeRegex, '$1', $defaultValue, 1);

return date_create_immutable($value) ?: new Expression($defaultValue);
}

if (preg_match("/^'(.*)'$/s", $defaultValue, $matches) === 1) {
Expand Down Expand Up @@ -624,6 +628,7 @@ private function extractColumnType(ColumnSchemaInterface $column): string
'BLOB' => self::TYPE_BINARY,
'CLOB' => self::TYPE_TEXT,
'TIMESTAMP' => self::TYPE_TIMESTAMP,
'DATE' => self::TYPE_DATE,
default => self::TYPE_STRING,
};
}
Expand Down Expand Up @@ -782,4 +787,27 @@ protected function getCacheTag(): string
{
return md5(serialize(array_merge([self::class], $this->generateCacheKey())));
}

protected function getDateTimeFormat(ColumnSchemaInterface $column): string|null
{
return match ($column->getType()) {
self::TYPE_TIMESTAMP => 'Y-m-d H:i:s'
. $this->getMillisecondsFormat($column)
. ($column->hasTimezone() ? 'P' : ''),
self::TYPE_DATE => 'Y-m-d',
self::TYPE_TIME => '0 H:i:s' . $this->getMillisecondsFormat($column),
default => null,
};
}

protected function getMillisecondsFormat(ColumnSchemaInterface $column): string
{
$precision = $column->getScale();

return match (true) {
$precision > 3 => '.u',
$precision > 0 => '.v',
default => '',
};
}
}
17 changes: 15 additions & 2 deletions tests/ColumnSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace Yiisoft\Db\Oracle\Tests;

use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Oracle\Tests\Support\TestTrait;
use Yiisoft\Db\Query\Query;

Expand Down Expand Up @@ -34,7 +34,10 @@ public function testPhpTypeCast(): void
'char_col3' => null,
'float_col' => 1.234,
'blob_col' => "\x10\x11\x12",
'timestamp_col' => new Expression("TIMESTAMP '2023-07-11 14:50:23'"),
'timestamp_col' => '2023-07-11 14:50:23',
'timestamp_col2' => new DateTimeImmutable('2023-07-11 14:50:23.123456 +02:00'),
'timestamptz_col' => new DateTimeImmutable('2023-07-11 14:50:23.12 -2:30'),
'date_col' => new DateTimeImmutable('2023-07-11'),
'bool_col' => false,
'bit_col' => 0b0110_0110, // 102
]
Expand All @@ -49,6 +52,11 @@ public function testPhpTypeCast(): void
$charCol3PhpType = $tableSchema->getColumn('char_col3')?->phpTypecast($query['char_col3']);
$floatColPhpType = $tableSchema->getColumn('float_col')?->phpTypecast($query['float_col']);
$blobColPhpType = $tableSchema->getColumn('blob_col')?->phpTypecast($query['blob_col']);
$timestampColPhpType = $tableSchema->getColumn('timestamp_col')?->phpTypecast($query['timestamp_col']);
$timestampCol2PhpType = $tableSchema->getColumn('timestamp_col2')?->phpTypecast($query['timestamp_col2']);
$timestamptzColPhpType = $tableSchema->getColumn('timestamptz_col')?->phpTypecast($query['timestamptz_col']);
$dateColPhpType = $tableSchema->getColumn('date_col')?->phpTypecast($query['date_col']);
$tsDefaultPhpType = $tableSchema->getColumn('ts_default')?->phpTypecast($query['ts_default']);
$boolColPhpType = $tableSchema->getColumn('bool_col')?->phpTypecast($query['bool_col']);
$bitColPhpType = $tableSchema->getColumn('bit_col')?->phpTypecast($query['bit_col']);

Expand All @@ -57,6 +65,11 @@ public function testPhpTypeCast(): void
$this->assertNull($charCol3PhpType);
$this->assertSame(1.234, $floatColPhpType);
$this->assertSame("\x10\x11\x12", stream_get_contents($blobColPhpType));
$this->assertEquals(new DateTimeImmutable('2023-07-11 14:50:23'), $timestampColPhpType);
$this->assertEquals(new DateTimeImmutable('2023-07-11 14:50:23.123456 +02:00'), $timestampCol2PhpType);
$this->assertEquals(new DateTimeImmutable('2023-07-11 14:50:23.12 -2:30'), $timestamptzColPhpType);
$this->assertEquals(new DateTimeImmutable('2023-07-11'), $dateColPhpType);
$this->assertInstanceOf(DateTimeImmutable::class, $tsDefaultPhpType);
$this->assertEquals(false, $boolColPhpType);
$this->assertEquals(0b0110_0110, $bitColPhpType);

Expand Down
53 changes: 49 additions & 4 deletions tests/Provider/SchemaProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Yiisoft\Db\Oracle\Tests\Provider;

use DateTimeImmutable;
use Yiisoft\Db\Constraint\CheckConstraint;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Tests\Support\AnyValue;
Expand Down Expand Up @@ -173,16 +174,59 @@ public static function columns(): array
],
'timestamp_col' => [
'type' => 'timestamp',
'dbType' => 'TIMESTAMP(6)',
'phpType' => 'string',
'dbType' => 'TIMESTAMP(0)',
'phpType' => 'DateTimeInterface',
'primaryKey' => false,
'allowNull' => false,
'autoIncrement' => false,
'enumValues' => null,
'size' => 7,
'precision' => null,
'scale' => 0,
'defaultValue' => new DateTimeImmutable('2002-01-01 00:00:00'),
'dateTimeFormat' => 'Y-m-d H:i:s',
],
'timestamp_col2' => [
'type' => 'timestamp',
'dbType' => 'TIMESTAMP(6)',
'phpType' => 'DateTimeInterface',
'primaryKey' => false,
'allowNull' => true,
'autoIncrement' => false,
'enumValues' => null,
'size' => 11,
'precision' => null,
'scale' => 6,
'defaultValue' => "to_timestamp('2002-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')",
'defaultValue' => null,
'dateTimeFormat' => 'Y-m-d H:i:s.u',
],
'timestamptz_col' => [
'type' => 'timestamp',
'dbType' => 'TIMESTAMP(2) WITH TIME ZONE',
'phpType' => 'DateTimeInterface',
'primaryKey' => false,
'allowNull' => false,
'autoIncrement' => false,
'enumValues' => null,
'size' => 13,
'precision' => null,
'scale' => 2,
'defaultValue' => new DateTimeImmutable('2023-06-11 15:24:11.12 +02:00'),
'dateTimeFormat' => 'Y-m-d H:i:s.vP',
],
'date_col' => [
'type' => 'date',
'dbType' => 'DATE',
'phpType' => 'DateTimeInterface',
'primaryKey' => false,
'allowNull' => false,
'autoIncrement' => false,
'enumValues' => null,
'size' => 7,
'precision' => null,
'scale' => null,
'defaultValue' => new DateTimeImmutable('2023-06-11'),
'dateTimeFormat' => 'Y-m-d',
],
'bool_col' => [
'type' => 'string',
Expand Down Expand Up @@ -213,7 +257,7 @@ public static function columns(): array
'ts_default' => [
'type' => 'timestamp',
'dbType' => 'TIMESTAMP(6)',
'phpType' => 'string',
'phpType' => 'DateTimeInterface',
'primaryKey' => false,
'allowNull' => false,
'autoIncrement' => false,
Expand All @@ -222,6 +266,7 @@ public static function columns(): array
'precision' => null,
'scale' => 6,
'defaultValue' => new Expression('CURRENT_TIMESTAMP'),
'dateTimeFormat' => 'Y-m-d H:i:s.u',
],
'bit_col' => [
'type' => 'string',
Expand Down
5 changes: 4 additions & 1 deletion tests/Support/Fixture/oci.sql
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,10 @@ CREATE TABLE "type" (
"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,
"timestamp_col" timestamp(0) DEFAULT to_timestamp('2002-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss') NOT NULL,
"timestamp_col2" timestamp,
"timestamptz_col" timestamp(2) with time zone DEFAULT TIMESTAMP '2023-06-11 15:24:11.12 +02:00' NOT NULL,
"date_col" date DEFAULT DATE '2023-06-11' NOT NULL,
"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,
Expand Down