From b896da6b32b284c2a376006242f999f4f7357f98 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Mon, 31 Mar 2025 12:24:01 +0700 Subject: [PATCH 1/3] Use `DateTimeColumn` --- src/Column/ColumnDefinitionBuilder.php | 12 +++-- src/Column/ColumnFactory.php | 10 +++- tests/ColumnTest.php | 60 ++++++++++++------------ tests/Provider/ColumnFactoryProvider.php | 13 +++-- tests/Provider/SchemaProvider.php | 38 ++++++++++----- tests/Support/Fixture/sqlite.sql | 6 ++- 6 files changed, 85 insertions(+), 54 deletions(-) diff --git a/src/Column/ColumnDefinitionBuilder.php b/src/Column/ColumnDefinitionBuilder.php index c141aa33..9239359d 100644 --- a/src/Column/ColumnDefinitionBuilder.php +++ b/src/Column/ColumnDefinitionBuilder.php @@ -29,9 +29,11 @@ final class ColumnDefinitionBuilder extends AbstractColumnDefinitionBuilder 'varchar', 'text', 'blob', - 'time', - 'datetime', 'timestamp', + 'datetime', + 'datetimetz', + 'time', + 'timetz', ]; protected const TYPES_WITH_SCALE = [ @@ -87,10 +89,12 @@ protected function getDbType(ColumnInterface $column): string ColumnType::TEXT => 'text', ColumnType::BINARY => 'blob', ColumnType::UUID => 'blob(16)', - ColumnType::DATETIME => 'datetime', ColumnType::TIMESTAMP => 'timestamp', - ColumnType::DATE => 'date', + ColumnType::DATETIME => 'datetime', + ColumnType::DATETIMETZ => 'datetimetz', ColumnType::TIME => 'time', + ColumnType::TIMETZ => 'timetz', + ColumnType::DATE => 'date', ColumnType::ARRAY => 'json', ColumnType::STRUCTURED => 'json', ColumnType::JSON => 'json', diff --git a/src/Column/ColumnFactory.php b/src/Column/ColumnFactory.php index fc2675cd..d524c8c3 100644 --- a/src/Column/ColumnFactory.php +++ b/src/Column/ColumnFactory.php @@ -42,10 +42,12 @@ final class ColumnFactory extends AbstractColumnFactory 'longtext' => ColumnType::TEXT, 'text' => ColumnType::TEXT, 'blob' => ColumnType::BINARY, - 'year' => ColumnType::DATE, + 'year' => ColumnType::SMALLINT, 'date' => ColumnType::DATE, 'time' => ColumnType::TIME, + 'timetz' => ColumnType::TIMETZ, 'datetime' => ColumnType::DATETIME, + 'datetimetz' => ColumnType::DATETIMETZ, 'timestamp' => ColumnType::TIMESTAMP, 'json' => ColumnType::JSON, ]; @@ -56,6 +58,12 @@ protected function getType(string $dbType, array $info = []): string 'bit', 'tinyint' => isset($info['size']) && $info['size'] === 1 ? ColumnType::BOOLEAN : parent::getType($dbType, $info), + 'text' => match($info['defaultValueRaw'] ?? null) { + 'CURRENT_TIMESTAMP' => ColumnType::DATETIMETZ, + 'CURRENT_DATE' => ColumnType::DATE, + 'CURRENT_TIME' => ColumnType::TIMETZ, + default => parent::getType($dbType, $info), + }, default => parent::getType($dbType, $info), }; } diff --git a/tests/ColumnTest.php b/tests/ColumnTest.php index e4d9e8c9..af7bbfcf 100644 --- a/tests/ColumnTest.php +++ b/tests/ColumnTest.php @@ -4,6 +4,8 @@ namespace Yiisoft\Db\Sqlite\Tests; +use DateTimeImmutable; +use DateTimeZone; use PDO; use Yiisoft\Db\Command\Param; use Yiisoft\Db\Schema\Column\BinaryColumn; @@ -12,20 +14,23 @@ use Yiisoft\Db\Schema\Column\IntegerColumn; use Yiisoft\Db\Schema\Column\JsonColumn; use Yiisoft\Db\Schema\Column\StringColumn; +use Yiisoft\Db\Sqlite\Column\ColumnBuilder; use Yiisoft\Db\Sqlite\Connection; use Yiisoft\Db\Sqlite\Tests\Support\TestTrait; use Yiisoft\Db\Query\Query; -use Yiisoft\Db\Tests\AbstractColumnTest; +use Yiisoft\Db\Tests\Common\CommonColumnTest; use function str_repeat; /** * @group sqlite */ -final class ColumnTest extends AbstractColumnTest +final class ColumnTest extends CommonColumnTest { use TestTrait; + protected const COLUMN_BUILDER = ColumnBuilder::class; + private function insertTypeValues(Connection $db): void { $command = $db->createCommand(); @@ -39,6 +44,7 @@ private function insertTypeValues(Connection $db): void 'float_col' => 1.234, 'blob_col' => "\x10\x11\x12", 'timestamp_col' => '2023-07-11 14:50:23', + 'timestamp_default' => new DateTimeImmutable('2023-07-11 14:50:23'), 'bool_col' => false, 'bit_col' => 0b0110_0110, // 102 'json_col' => [['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], @@ -48,18 +54,24 @@ private function insertTypeValues(Connection $db): void $command->execute(); } - private function assertResultValues(array $result): void + private function assertTypecastedValues(array $result, bool $allTypecasted = false): void { $this->assertSame(1, $result['int_col']); $this->assertSame(str_repeat('x', 100), $result['char_col']); $this->assertNull($result['char_col3']); $this->assertSame(1.234, $result['float_col']); $this->assertSame("\x10\x11\x12", $result['blob_col']); - $this->assertSame('2023-07-11 14:50:23', $result['timestamp_col']); + $this->assertEquals(new DateTimeImmutable('2023-07-11 14:50:23', new DateTimeZone('UTC')), $result['timestamp_col']); + $this->assertEquals(new DateTimeImmutable('2023-07-11 14:50:23'), $result['timestamp_default']); $this->assertFalse($result['bool_col']); $this->assertSame(0b0110_0110, $result['bit_col']); $this->assertSame([['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], $result['json_col']); - $this->assertSame('[1,2,3,"string",null]', $result['json_text_col']); + + if ($allTypecasted) { + $this->assertSame([1, 2, 3, 'string', null], $result['json_text_col']); + } else { + $this->assertSame('[1,2,3,"string",null]', $result['json_text_col']); + } } public function testQueryWithTypecasting(): void @@ -72,11 +84,11 @@ public function testQueryWithTypecasting(): void $result = $query->one(); - $this->assertResultValues($result); + $this->assertTypecastedValues($result); $result = $query->all(); - $this->assertResultValues($result[0]); + $this->assertTypecastedValues($result[0]); $db->close(); } @@ -91,11 +103,11 @@ public function testCommandWithPhpTypecasting(): void $result = $command->queryOne(); - $this->assertResultValues($result); + $this->assertTypecastedValues($result); $result = $command->queryAll(); - $this->assertResultValues($result[0]); + $this->assertTypecastedValues($result[0]); $db->close(); } @@ -130,33 +142,19 @@ public function testPhpTypeCast(): void { $db = $this->getConnection(true); $schema = $db->getSchema(); - $tableSchema = $schema->getTableSchema('type'); + $columns = $schema->getTableSchema('type')->getColumns(); $this->insertTypeValues($db); $query = (new Query($db))->from('type')->one(); - $intColPhpType = $tableSchema->getColumn('int_col')?->phpTypecast($query['int_col']); - $charColPhpType = $tableSchema->getColumn('char_col')?->phpTypecast($query['char_col']); - $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']); - $boolColPhpType = $tableSchema->getColumn('bool_col')?->phpTypecast($query['bool_col']); - $bitColPhpType = $tableSchema->getColumn('bit_col')?->phpTypecast($query['bit_col']); - $jsonColPhpType = $tableSchema->getColumn('json_col')?->phpTypecast($query['json_col']); - $jsonTextColPhpType = $tableSchema->getColumn('json_text_col')?->phpTypecast($query['json_text_col']); - - $this->assertSame(1, $intColPhpType); - $this->assertSame(str_repeat('x', 100), $charColPhpType); - $this->assertNull($charCol3PhpType); - $this->assertSame(1.234, $floatColPhpType); - $this->assertSame("\x10\x11\x12", $blobColPhpType); - $this->assertSame('2023-07-11 14:50:23', $timestampColPhpType); - $this->assertFalse($boolColPhpType); - $this->assertSame(0b0110_0110, $bitColPhpType); - $this->assertSame([['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], $jsonColPhpType); - $this->assertSame([1, 2, 3, 'string', null], $jsonTextColPhpType); + $result = []; + + foreach ($columns as $columnName => $column) { + $result[$columnName] = $column->phpTypecast($query[$columnName]); + } + + $this->assertTypecastedValues($result, true); $db->close(); } diff --git a/tests/Provider/ColumnFactoryProvider.php b/tests/Provider/ColumnFactoryProvider.php index b7055e9e..941a8b06 100644 --- a/tests/Provider/ColumnFactoryProvider.php +++ b/tests/Provider/ColumnFactoryProvider.php @@ -8,6 +8,7 @@ use Yiisoft\Db\Schema\Column\BinaryColumn; use Yiisoft\Db\Schema\Column\BitColumn; use Yiisoft\Db\Schema\Column\BooleanColumn; +use Yiisoft\Db\Schema\Column\DatetimeColumn; use Yiisoft\Db\Schema\Column\DoubleColumn; use Yiisoft\Db\Schema\Column\IntegerColumn; use Yiisoft\Db\Schema\Column\JsonColumn; @@ -42,11 +43,13 @@ public static function dbTypes(): array ['longtext', ColumnType::TEXT, StringColumn::class], ['text', ColumnType::TEXT, StringColumn::class], ['blob', ColumnType::BINARY, BinaryColumn::class], - ['year', ColumnType::DATE, StringColumn::class], - ['date', ColumnType::DATE, StringColumn::class], - ['time', ColumnType::TIME, StringColumn::class], - ['datetime', ColumnType::DATETIME, StringColumn::class], - ['timestamp', ColumnType::TIMESTAMP, StringColumn::class], + ['year', ColumnType::SMALLINT, IntegerColumn::class], + ['date', ColumnType::DATE, DatetimeColumn::class], + ['time', ColumnType::TIME, DatetimeColumn::class], + ['timetz', ColumnType::TIMETZ, DatetimeColumn::class], + ['datetime', ColumnType::DATETIME, DatetimeColumn::class], + ['datetimetz', ColumnType::DATETIMETZ, DatetimeColumn::class], + ['timestamp', ColumnType::TIMESTAMP, DatetimeColumn::class], ['json', ColumnType::JSON, JsonColumn::class], ]; } diff --git a/tests/Provider/SchemaProvider.php b/tests/Provider/SchemaProvider.php index f72fcd54..d7670218 100644 --- a/tests/Provider/SchemaProvider.php +++ b/tests/Provider/SchemaProvider.php @@ -4,12 +4,14 @@ namespace Yiisoft\Db\Sqlite\Tests\Provider; +use DateTimeImmutable; use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Schema\Column\ArrayColumn; use Yiisoft\Db\Schema\Column\BinaryColumn; use Yiisoft\Db\Schema\Column\BitColumn; use Yiisoft\Db\Schema\Column\BooleanColumn; +use Yiisoft\Db\Schema\Column\DatetimeColumn; use Yiisoft\Db\Schema\Column\DoubleColumn; use Yiisoft\Db\Schema\Column\IntegerColumn; use Yiisoft\Db\Schema\Column\JsonColumn; @@ -78,11 +80,19 @@ public static function columns(): array scale: 2, defaultValue: 33.22, ), - 'timestamp_col' => new StringColumn( + 'timestamp_col' => new DatetimeColumn( ColumnType::TIMESTAMP, dbType: 'timestamp', notNull: true, - defaultValue: '2002-01-01 00:00:00', + defaultValue: new DateTimeImmutable('2002-01-01 00:00:00'), + hasTimezone: false, + shouldConvertTimezone: true, + ), + 'timestamp_default' => new DatetimeColumn( + ColumnType::TIMESTAMP, + dbType: 'timestamp', + notNull: true, + defaultValue: new Expression('CURRENT_TIMESTAMP'), ), 'bool_col' => new BooleanColumn( dbType: 'tinyint', @@ -94,12 +104,6 @@ public static function columns(): array size: 1, defaultValue: true, ), - 'ts_default' => new StringColumn( - ColumnType::TIMESTAMP, - dbType: 'timestamp', - notNull: true, - defaultValue: new Expression('CURRENT_TIMESTAMP'), - ), 'bit_col' => new BitColumn( dbType: 'bit', notNull: true, @@ -146,12 +150,24 @@ public static function columns(): array notNull: true, defaultValue: 'CURRENT_TIMESTAMP', ), - 'timestamp_text' => new StringColumn( - ColumnType::TEXT, + 'timestamp_text' => new DatetimeColumn( + ColumnType::DATETIMETZ, dbType: 'text', notNull: true, defaultValue: new Expression('CURRENT_TIMESTAMP'), ), + 'time_text' => new DatetimeColumn( + ColumnType::TIMETZ, + dbType: 'text', + notNull: true, + defaultValue: new Expression('CURRENT_TIME'), + ), + 'date_text' => new DatetimeColumn( + ColumnType::DATE, + dbType: 'text', + notNull: true, + defaultValue: new Expression('CURRENT_DATE'), + ), ], 'timestamp_default', ], @@ -266,7 +282,7 @@ public static function resultColumns(): array 'len' => -1, 'precision' => 0, ]], - [new StringColumn(ColumnType::TIMESTAMP, dbType: 'timestamp', name: 'timestamp_col'), [ + [new DateTimeColumn(ColumnType::TIMESTAMP, dbType: 'timestamp', name: 'timestamp_col'), [ 'native_type' => 'null', 'pdo_type' => 0, 'sqlite:decl_type' => 'timestamp', diff --git a/tests/Support/Fixture/sqlite.sql b/tests/Support/Fixture/sqlite.sql index c083acbb..de47c723 100644 --- a/tests/Support/Fixture/sqlite.sql +++ b/tests/Support/Fixture/sqlite.sql @@ -134,9 +134,9 @@ CREATE TABLE "type" ( blob_col blob, numeric_col decimal(5,2) DEFAULT '33.22', timestamp_col timestamp NOT NULL DEFAULT '2002-01-01 00:00:00', + timestamp_default TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, bool_col tinyint(1) NOT NULL, bool_col2 tinyint(1) DEFAULT '1', - ts_default TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, bit_col BIT(8) NOT NULL DEFAULT 130, -- 0b1000_0010 json_col json NOT NULL DEFAULT '{"number":10}', json_text_col text CHECK(json_text_col IS NULL OR json_valid(json_text_col)) -- for STRICT table @@ -173,7 +173,9 @@ CREATE TABLE "notauto_pk" ( CREATE TABLE "timestamp_default" ( id INTEGER PRIMARY KEY, text_col TEXT NOT NULL DEFAULT 'CURRENT_TIMESTAMP', - timestamp_text TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + timestamp_text TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + time_text TEXT NOT NULL DEFAULT CURRENT_TIME, + date_text TEXT NOT NULL DEFAULT CURRENT_DATE ); -- STRICT CREATE TABLE "json_type" ( From e5506be557ecc12dbdede9a096fa5334ea7806bc Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 4 May 2025 08:07:11 +0000 Subject: [PATCH 2/3] Apply fixes from StyleCI --- src/Column/ColumnFactory.php | 2 +- tests/Provider/SchemaProvider.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Column/ColumnFactory.php b/src/Column/ColumnFactory.php index d524c8c3..b52b45bd 100644 --- a/src/Column/ColumnFactory.php +++ b/src/Column/ColumnFactory.php @@ -58,7 +58,7 @@ protected function getType(string $dbType, array $info = []): string 'bit', 'tinyint' => isset($info['size']) && $info['size'] === 1 ? ColumnType::BOOLEAN : parent::getType($dbType, $info), - 'text' => match($info['defaultValueRaw'] ?? null) { + 'text' => match ($info['defaultValueRaw'] ?? null) { 'CURRENT_TIMESTAMP' => ColumnType::DATETIMETZ, 'CURRENT_DATE' => ColumnType::DATE, 'CURRENT_TIME' => ColumnType::TIMETZ, diff --git a/tests/Provider/SchemaProvider.php b/tests/Provider/SchemaProvider.php index d7670218..4e94de32 100644 --- a/tests/Provider/SchemaProvider.php +++ b/tests/Provider/SchemaProvider.php @@ -282,7 +282,7 @@ public static function resultColumns(): array 'len' => -1, 'precision' => 0, ]], - [new DateTimeColumn(ColumnType::TIMESTAMP, dbType: 'timestamp', name: 'timestamp_col'), [ + [new DatetimeColumn(ColumnType::TIMESTAMP, dbType: 'timestamp', name: 'timestamp_col'), [ 'native_type' => 'null', 'pdo_type' => 0, 'sqlite:decl_type' => 'timestamp', From a1a203022363cc16db7d50bdd6b46daa64e4a682 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Tue, 13 May 2025 17:10:34 +0700 Subject: [PATCH 3/3] Add line to CHANGELOG.md [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b10967f8..d14767c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - Enh #350, #351: Use `DbArrayHelper::arrange()` instead of `DbArrayHelper::index()` method (@Tigrov) - New #348: Realize `Schema::loadResultColumn()` method (@Tigrov) - New #354: Add `FOR` clause to query (@vjik) +- New #355: Use `DateTimeColumn` class for datetime column types (@Tigrov) ## 1.2.0 March 21, 2024