diff --git a/CHANGELOG.md b/CHANGELOG.md index 50646bda..a5f3531d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ - Bug #388: Set empty `comment` and `extra` properties to `null` when loading table columns (@Tigrov) - Enh #389, #390: Use `DbArrayHelper::arrange()` instead of `DbArrayHelper::index()` method (@Tigrov) - New #387: Realize `Schema::loadResultColumn()` method (@Tigrov) +- New #393: Use `DateTimeColumn` class for datetime column types (@Tigrov) ## 1.2.0 March 21, 2024 diff --git a/src/Column/ColumnBuilder.php b/src/Column/ColumnBuilder.php index f2937a55..1eb51979 100644 --- a/src/Column/ColumnBuilder.php +++ b/src/Column/ColumnBuilder.php @@ -4,6 +4,37 @@ namespace Yiisoft\Db\Mysql\Column; +use Yiisoft\Db\Constant\ColumnType; + final class ColumnBuilder extends \Yiisoft\Db\Schema\Column\ColumnBuilder { + public static function timestamp(int|null $size = 0): DateTimeColumn + { + return new DateTimeColumn(ColumnType::TIMESTAMP, size: $size); + } + + public static function datetime(int|null $size = 0): DateTimeColumn + { + return new DateTimeColumn(ColumnType::DATETIME, size: $size); + } + + public static function datetimeWithTimezone(int|null $size = 0): DateTimeColumn + { + return new DateTimeColumn(ColumnType::DATETIMETZ, size: $size); + } + + public static function time(int|null $size = 0): DateTimeColumn + { + return new DateTimeColumn(ColumnType::TIME, size: $size); + } + + public static function timeWithTimezone(int|null $size = 0): DateTimeColumn + { + return new DateTimeColumn(ColumnType::TIMETZ, size: $size); + } + + public static function date(): DateTimeColumn + { + return new DateTimeColumn(ColumnType::DATE); + } } diff --git a/src/Column/ColumnDefinitionBuilder.php b/src/Column/ColumnDefinitionBuilder.php index 9c9dfd9a..29017fa9 100644 --- a/src/Column/ColumnDefinitionBuilder.php +++ b/src/Column/ColumnDefinitionBuilder.php @@ -45,9 +45,9 @@ final class ColumnDefinitionBuilder extends AbstractColumnDefinitionBuilder 'varbinary', 'blob', 'year', - 'time', - 'datetime', 'timestamp', + 'datetime', + 'time', ]; protected const TYPES_WITH_SCALE = [ @@ -87,10 +87,12 @@ protected function getDbType(ColumnInterface $column): string ColumnType::TEXT => 'text', ColumnType::BINARY => 'blob', ColumnType::UUID => 'binary(16)', - ColumnType::DATETIME => 'datetime', ColumnType::TIMESTAMP => 'timestamp', - ColumnType::DATE => 'date', + ColumnType::DATETIME => 'datetime', + ColumnType::DATETIMETZ => 'datetime', ColumnType::TIME => 'time', + ColumnType::TIMETZ => 'time', + 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 03abb214..6c0a871f 100644 --- a/src/Column/ColumnFactory.php +++ b/src/Column/ColumnFactory.php @@ -49,14 +49,27 @@ final class ColumnFactory extends AbstractColumnFactory 'tinyblob' => ColumnType::BINARY, 'mediumblob' => ColumnType::BINARY, 'longblob' => ColumnType::BINARY, - 'year' => ColumnType::DATE, - 'date' => ColumnType::DATE, - 'time' => ColumnType::TIME, - 'datetime' => ColumnType::DATETIME, + 'year' => ColumnType::SMALLINT, 'timestamp' => ColumnType::TIMESTAMP, + 'datetime' => ColumnType::DATETIME, + 'time' => ColumnType::TIME, + 'date' => ColumnType::DATE, 'json' => ColumnType::JSON, ]; + protected function getColumnClass(string $type, array $info = []): string + { + return match ($type) { + ColumnType::TIMESTAMP => DateTimeColumn::class, + ColumnType::DATETIME => DateTimeColumn::class, + ColumnType::DATETIMETZ => DateTimeColumn::class, + ColumnType::TIME => DateTimeColumn::class, + ColumnType::TIMETZ => DateTimeColumn::class, + ColumnType::DATE => DateTimeColumn::class, + default => parent::getColumnClass($type, $info), + }; + } + protected function getType(string $dbType, array $info = []): string { if ($dbType === 'bit' && isset($info['size']) && $info['size'] === 1) { diff --git a/src/Column/DateTimeColumn.php b/src/Column/DateTimeColumn.php new file mode 100644 index 00000000..b77cf1d5 --- /dev/null +++ b/src/Column/DateTimeColumn.php @@ -0,0 +1,39 @@ + [!WARNING] + * > MySQL DBMS converts `TIMESTAMP` column type values from database session time zone to UTC for storage, and back + * > from UTC to the session time zone when retrieve the values. + * + * `TIMESTAMP` database type does not store time zone offset and require to convert datetime values to the database + * session time zone before insert and back to the PHP time zone after retrieve the values. This will be done in the + * {@see dbTypecast()} and {@see phpTypecast()} methods and guarantees that the values are stored in the database + * in the correct time zone. + * + * To avoid possible time zone issues with the datetime values conversion, it is recommended to set the PHP and database + * time zones to UTC. + */ +final class DateTimeColumn extends \Yiisoft\Db\Schema\Column\DateTimeColumn +{ + protected function getFormat(): string + { + return $this->format ??= match ($this->getType()) { + ColumnType::DATETIMETZ => 'Y-m-d H:i:s' . $this->getMillisecondsFormat(), + ColumnType::TIMETZ => 'H:i:s' . $this->getMillisecondsFormat(), + default => parent::getFormat(), + }; + } + + protected function shouldConvertTimezone(): bool + { + return $this->shouldConvertTimezone ??= !empty($this->dbTimezone) && $this->getType() !== ColumnType::DATE; + } +} diff --git a/src/Connection.php b/src/Connection.php index 03c42512..a5198d04 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -6,6 +6,7 @@ use Psr\Log\LogLevel; use Throwable; +use Yiisoft\Db\Connection\ServerInfoInterface; use Yiisoft\Db\Driver\Pdo\AbstractPdoConnection; use Yiisoft\Db\Driver\Pdo\PdoCommandInterface; use Yiisoft\Db\Mysql\Column\ColumnFactory; @@ -84,4 +85,9 @@ public function getSchema(): SchemaInterface { return $this->schema ??= new Schema($this, $this->schemaCache); } + + public function getServerInfo(): ServerInfoInterface + { + return $this->serverInfo ??= new ServerInfo($this); + } } diff --git a/src/Schema.php b/src/Schema.php index 48a6594e..a36596d5 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -31,6 +31,7 @@ use function str_starts_with; use function strtolower; use function substr; +use function substr_compare; use function trim; use const PHP_INT_SIZE; @@ -424,6 +425,7 @@ protected function loadResultColumn(array $metadata): ColumnInterface|null 'float', 'double', 'decimal' => $columnInfo['scale'] = $metadata['precision'], 'bigint' => $metadata['len'] === 20 ? $columnInfo['unsigned'] = true : null, 'int' => $metadata['len'] === 10 && PHP_INT_SIZE !== 8 ? $columnInfo['unsigned'] = true : null, + 'timestamp' => $columnInfo['dbTimezone'] = $this->db->getServerInfo()->getTimezone(), default => null, }; @@ -444,8 +446,7 @@ protected function loadResultColumn(array $metadata): ColumnInterface|null private function loadColumn(array $info): ColumnInterface { $extra = trim(str_ireplace('auto_increment', '', $info['extra'], $autoIncrement)); - - $column = $this->db->getColumnFactory()->fromDefinition($info['column_type'], [ + $columnInfo = [ 'autoIncrement' => $autoIncrement > 0, 'comment' => $info['column_comment'] === '' ? null : $info['column_comment'], 'defaultValueRaw' => $info['column_default'], @@ -456,7 +457,13 @@ private function loadColumn(array $info): ColumnInterface 'schema' => $info['schema'], 'table' => $info['table'], 'unique' => $info['column_key'] === 'UNI', - ]); + ]; + + if (substr_compare($info['column_type'], 'timestamp', 0, 9, true) === 0) { + $columnInfo['dbTimezone'] = $this->db->getServerInfo()->getTimezone(); + } + + $column = $this->db->getColumnFactory()->fromDefinition($info['column_type'], $columnInfo); if (str_starts_with($extra, 'DEFAULT_GENERATED')) { $extra = trim(substr($extra, 18)); diff --git a/src/ServerInfo.php b/src/ServerInfo.php new file mode 100644 index 00000000..e749ee52 --- /dev/null +++ b/src/ServerInfo.php @@ -0,0 +1,26 @@ +timezone) || $refresh) { + /** @var string */ + $this->timezone = $this->db->createCommand( + "SELECT LPAD(TIME_FORMAT(TIMEDIFF(NOW(), UTC_TIMESTAMP), '%H:%i'), 6, '+')" + )->queryScalar(); + } + + return $this->timezone; + } +} diff --git a/tests/ColumnBuilderTest.php b/tests/ColumnBuilderTest.php index c53c14b6..9185f60e 100644 --- a/tests/ColumnBuilderTest.php +++ b/tests/ColumnBuilderTest.php @@ -4,7 +4,9 @@ namespace Yiisoft\Db\Mysql\Tests; +use PHPUnit\Framework\Attributes\DataProviderExternal; use Yiisoft\Db\Mysql\Column\ColumnBuilder; +use Yiisoft\Db\Mysql\Tests\Provider\ColumnBuilderProvider; use Yiisoft\Db\Mysql\Tests\Support\TestTrait; use Yiisoft\Db\Tests\AbstractColumnBuilderTest; @@ -19,4 +21,15 @@ public function getColumnBuilderClass(): string { return ColumnBuilder::class; } + + #[DataProviderExternal(ColumnBuilderProvider::class, 'buildingMethods')] + public function testBuildingMethods( + string $buildingMethod, + array $args, + string $expectedInstanceOf, + string $expectedType, + array $expectedMethodResults = [], + ): void { + parent::testBuildingMethods($buildingMethod, $args, $expectedInstanceOf, $expectedType, $expectedMethodResults); + } } diff --git a/tests/ColumnTest.php b/tests/ColumnTest.php index 98f91088..681ecc76 100644 --- a/tests/ColumnTest.php +++ b/tests/ColumnTest.php @@ -4,19 +4,24 @@ namespace Yiisoft\Db\Mysql\Tests; +use DateTimeImmutable; +use DateTimeZone; +use PHPUnit\Framework\Attributes\DataProviderExternal; use Throwable; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Mysql\Column\ColumnBuilder; use Yiisoft\Db\Mysql\Connection; +use Yiisoft\Db\Mysql\Tests\Provider\ColumnProvider; use Yiisoft\Db\Mysql\Tests\Support\TestTrait; use Yiisoft\Db\Query\Query; use Yiisoft\Db\Schema\Column\BinaryColumn; use Yiisoft\Db\Schema\Column\BooleanColumn; +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\AbstractColumnTest; +use Yiisoft\Db\Tests\Common\CommonColumnTest; use function str_repeat; @@ -25,10 +30,12 @@ * * @psalm-suppress PropertyNotSetInConstructor */ -final class ColumnTest extends AbstractColumnTest +final class ColumnTest extends CommonColumnTest { use TestTrait; + protected const COLUMN_BUILDER = ColumnBuilder::class; + private function insertTypeValues(Connection $db): void { $db->createCommand()->insert( @@ -40,7 +47,8 @@ private function insertTypeValues(Connection $db): void 'char_col3' => null, 'float_col' => 1.234, 'blob_col' => "\x10\x11\x12", - 'time' => '2023-07-11 14:50:23', + 'timestamp_col' => '2023-07-11 14:50:23', + 'timestamp_default' => new DateTimeImmutable('2023-07-11 14:50:23'), 'bool_col' => false, 'bit_col' => 0b0110_0100, // 100 'json_col' => [['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], @@ -48,7 +56,7 @@ private function insertTypeValues(Connection $db): void )->execute(); } - private function assertResultValues(array $result): void + private function assertTypecastedValues(array $result, bool $allTypecasted = false): void { $this->assertSame(1, $result['int_col']); $this->assertSame('12345678901234567890', $result['bigunsigned_col']); @@ -56,10 +64,16 @@ private function assertResultValues(array $result): void $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['time']); + $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_0100, $result['bit_col']); - $this->assertJsonStringEqualsJsonString('[{"a":1,"b":null,"c":[1,3,5]}]', $result['json_col']); + + if ($allTypecasted) { + $this->assertSame([['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], $result['json_col']); + } else { + $this->assertJsonStringEqualsJsonString('[{"a":1,"b":null,"c":[1,3,5]}]', $result['json_col']); + } } public function testQueryTypecasting(): void @@ -72,11 +86,11 @@ public function testQueryTypecasting(): 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 +105,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(); } @@ -174,33 +188,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']); - $bigUnsignedColPhpType = $tableSchema->getColumn('bigunsigned_col')?->phpTypecast($query['bigunsigned_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']); - $timePhpType = $tableSchema->getColumn('time')?->phpTypecast($query['time']); - $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']); - - $this->assertSame(1, $intColPhpType); - $this->assertSame('12345678901234567890', $bigUnsignedColPhpType); - $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', $timePhpType); - $this->assertFalse($boolColPhpType); - $this->assertSame(0b0110_0100, $bitColPhpType); - $this->assertSame([['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], $jsonColPhpType); + $result = []; + + foreach ($columns as $columnName => $column) { + $result[$columnName] = $column->phpTypecast($query[$columnName]); + } + + $this->assertTypecastedValues($result, true); $db->close(); } @@ -250,4 +250,75 @@ public function testLongtextType(): void $db->close(); } + + public function testTimestampColumnOnDifferentTimezones(): void + { + $db = $this->getConnection(); + $schema = $db->getSchema(); + $command = $db->createCommand(); + $tableName = 'timestamp_column_test'; + + $command->setSql("SET @@session.time_zone = '+03:00'")->execute(); + + $this->assertSame('+03:00', $db->getServerInfo()->getTimezone()); + + $phpTimezone = date_default_timezone_get(); + date_default_timezone_set('America/New_York'); + + if ($schema->hasTable($tableName)) { + $command->dropTable($tableName)->execute(); + } + + $command->createTable( + $tableName, + [ + 'timestamp_col' => ColumnBuilder::timestamp(), + 'datetime_col' => ColumnBuilder::datetime(), + ] + )->execute(); + + $command->insert($tableName, [ + 'timestamp_col' => new DateTimeImmutable('2025-04-19 14:11:35'), + 'datetime_col' => new DateTimeImmutable('2025-04-19 14:11:35'), + ])->execute(); + + $command->setSql("SET @@session.time_zone = '+04:00'")->execute(); + + $this->assertSame('+04:00', $db->getServerInfo()->getTimezone(true)); + + $columns = $schema->getTableSchema($tableName, true)->getColumns(); + $query = (new Query($db))->from($tableName); + + $result = $query->one(); + + $this->assertEquals(new DateTimeImmutable('2025-04-19 14:11:35'), $columns['timestamp_col']->phpTypecast($result['timestamp_col'])); + $this->assertEquals(new DateTimeImmutable('2025-04-19 14:11:35'), $columns['datetime_col']->phpTypecast($result['datetime_col'])); + + $result = $query->withTypecasting()->one(); + + $this->assertEquals(new DateTimeImmutable('2025-04-19 14:11:35'), $result['timestamp_col']); + $this->assertEquals(new DateTimeImmutable('2025-04-19 14:11:35'), $result['datetime_col']); + + date_default_timezone_set($phpTimezone); + + $db->close(); + } + + #[DataProviderExternal(ColumnProvider::class, 'predefinedTypes')] + public function testPredefinedType(string $className, string $type, string $phpType) + { + parent::testPredefinedType($className, $type, $phpType); + } + + #[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumns')] + public function testDbTypecastColumns(ColumnInterface $column, array $values) + { + parent::testDbTypecastColumns($column, $values); + } + + #[DataProviderExternal(ColumnProvider::class, 'phpTypecastColumns')] + public function testPhpTypecastColumns(ColumnInterface $column, array $values) + { + parent::testPhpTypecastColumns($column, $values); + } } diff --git a/tests/PdoConnectionTest.php b/tests/PdoConnectionTest.php index 085abaad..572f4b04 100644 --- a/tests/PdoConnectionTest.php +++ b/tests/PdoConnectionTest.php @@ -9,6 +9,7 @@ use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidCallException; use Yiisoft\Db\Exception\InvalidConfigException; +use Yiisoft\Db\Mysql\ServerInfo; use Yiisoft\Db\Mysql\Tests\Support\TestTrait; use Yiisoft\Db\Tests\Common\CommonPdoConnectionTest; @@ -111,4 +112,24 @@ public function testTransactionAutocommit() $db->close(); } + + public function testGetServerInfo(): void + { + $db = $this->getConnection(); + $serverInfo = $db->getServerInfo(); + + $this->assertInstanceOf(ServerInfo::class, $serverInfo); + + $dbTimezone = $serverInfo->getTimezone(); + + $this->assertSame(6, strlen($dbTimezone)); + + $db->createCommand("SET @@session.time_zone = '+06:15'")->execute(); + + $this->assertSame($dbTimezone, $serverInfo->getTimezone()); + $this->assertNotSame($dbTimezone, $serverInfo->getTimezone(true)); + $this->assertSame('+06:15', $serverInfo->getTimezone()); + + $db->close(); + } } diff --git a/tests/Provider/ColumnBuilderProvider.php b/tests/Provider/ColumnBuilderProvider.php new file mode 100644 index 00000000..ed87e359 --- /dev/null +++ b/tests/Provider/ColumnBuilderProvider.php @@ -0,0 +1,29 @@ + 5')"][0] = 'int CHECK (`check_col` > 5)'; $values["check('')"][0] = 'int'; $values['check(null)'][0] = 'int'; diff --git a/tests/Provider/SchemaProvider.php b/tests/Provider/SchemaProvider.php index deb5b446..8f0943a9 100644 --- a/tests/Provider/SchemaProvider.php +++ b/tests/Provider/SchemaProvider.php @@ -4,8 +4,11 @@ namespace Yiisoft\Db\Mysql\Tests\Provider; +use DateTimeImmutable; +use DateTimeZone; use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Expression\Expression; +use Yiisoft\Db\Mysql\Column\DateTimeColumn; use Yiisoft\Db\Mysql\Tests\Support\TestTrait; use Yiisoft\Db\Schema\Column\BigIntColumn; use Yiisoft\Db\Schema\Column\BinaryColumn; @@ -22,6 +25,10 @@ final class SchemaProvider extends \Yiisoft\Db\Tests\Provider\SchemaProvider public static function columns(): array { + $db = self::getDb(); + $dbTimezone = self::getDb()->getServerInfo()->getTimezone(); + $db->close(); + return [ [ [ @@ -96,11 +103,20 @@ enumValues: ['a', 'B', 'c,D'], scale: 2, defaultValue: 33.22, ), - 'time' => new StringColumn( + 'timestamp_col' => new DateTimeColumn( + ColumnType::TIMESTAMP, + dbType: 'timestamp', + notNull: true, + defaultValue: new DateTimeImmutable('2002-01-01 00:00:00', new DateTimeZone('UTC')), + shouldConvertTimezone: true, + dbTimezone: $dbTimezone, + ), + 'timestamp_default' => new DateTimeColumn( ColumnType::TIMESTAMP, dbType: 'timestamp', notNull: true, - defaultValue: '2002-01-01 00:00:00', + defaultValue: new Expression('CURRENT_TIMESTAMP'), + dbTimezone: $dbTimezone, ), 'bool_col' => new BooleanColumn( dbType: 'bit', @@ -113,12 +129,6 @@ enumValues: ['a', 'B', 'c,D'], size: 1, defaultValue: 2, ), - 'ts_default' => new StringColumn( - ColumnType::TIMESTAMP, - dbType: 'timestamp', - notNull: true, - defaultValue: new Expression('CURRENT_TIMESTAMP'), - ), 'bit_col' => new BitColumn( dbType: 'bit', notNull: true, @@ -254,6 +264,10 @@ public static function constraints(): array public static function resultColumns(): array { + $db = self::getDb(); + $dbTimezone = self::getDb()->getServerInfo()->getTimezone(); + $db->close(); + return [ [null, []], [null, ['native_type' => 'NULL']], @@ -338,7 +352,7 @@ public static function resultColumns(): array 'len' => 7, 'precision' => 2, ]], - [new StringColumn(ColumnType::TIMESTAMP, dbType: 'timestamp', name: 'time', notNull: true, size: 0), [ + [new DateTimeColumn(ColumnType::TIMESTAMP, dbType: 'timestamp', name: 'time', notNull: true, size: 0, dbTimezone: $dbTimezone), [ 'native_type' => 'TIMESTAMP', 'pdo_type' => 2, 'flags' => ['not_null'], @@ -401,7 +415,7 @@ public static function resultColumns(): array 'len' => 24, 'precision' => 39, ]], - [new StringColumn(ColumnType::DATETIME, dbType: 'datetime', name: 'CURRENT_TIMESTAMP(3)', notNull: true, size: 3), [ + [new DateTimeColumn(dbType: 'datetime', name: 'CURRENT_TIMESTAMP(3)', notNull: true, size: 3), [ 'native_type' => 'DATETIME', 'pdo_type' => 2, 'flags' => ['not_null'], diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index 5465dfef..fe8a5614 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -4,6 +4,8 @@ namespace Yiisoft\Db\Mysql\Tests; +use DateTimeImmutable; +use DateTimeZone; use PHPUnit\Framework\Attributes\DataProviderExternal; use Throwable; use Yiisoft\Db\Command\CommandInterface; @@ -98,14 +100,17 @@ public function testDefaultValueDatetimeColumn(): void !str_contains($serverVersion, 'MariaDB') ); + $utcTimezone = new DateTimeZone('UTC'); + $dbTimezone = new DateTimeZone($db->getServerInfo()->getTimezone()); + $columnsData = [ 'id' => ['int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', ''], - 'd' => ['date DEFAULT \'2011-11-11\'', '2011-11-11'], + 'd' => ['date DEFAULT \'2011-11-11\'', new DateTimeImmutable('2011-11-11', $utcTimezone)], 'dt' => ['datetime NOT NULL DEFAULT CURRENT_TIMESTAMP', new Expression('CURRENT_TIMESTAMP')], - 'dt1' => ['datetime DEFAULT \'2011-11-11 00:00:00\'', '2011-11-11 00:00:00'], + 'dt1' => ['datetime DEFAULT \'2011-11-11 00:00:00\'', new DateTimeImmutable('2011-11-11 00:00:00', $utcTimezone)], 'dt2' => ['datetime DEFAULT CURRENT_TIMESTAMP', new Expression('CURRENT_TIMESTAMP')], 'ts' => ['timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', new Expression('CURRENT_TIMESTAMP')], - 'ts1' => ['timestamp DEFAULT \'2011-11-11 00:00:00\'', '2011-11-11 00:00:00'], + 'ts1' => ['timestamp DEFAULT \'2011-11-11 00:00:00\'', new DateTimeImmutable('2011-11-11 00:00:00', $dbTimezone)], 'ts2' => ['timestamp DEFAULT CURRENT_TIMESTAMP', new Expression('CURRENT_TIMESTAMP')], 'simple_col' => ['varchar(40) DEFAULT \'uuid()\'', 'uuid()'], ]; @@ -491,8 +496,8 @@ public function testInsertDefaultValues() } #[DataProviderExternal(SchemaProvider::class, 'resultColumns')] - public function testGetResultColumn(ColumnInterface|null $expected, array $info): void + public function testGetResultColumn(ColumnInterface|null $expected, array $metadata): void { - parent::testGetResultColumn($expected, $info); + parent::testGetResultColumn($expected, $metadata); } } diff --git a/tests/Support/Fixture/mysql.sql b/tests/Support/Fixture/mysql.sql index 5b14bd90..75c6d286 100644 --- a/tests/Support/Fixture/mysql.sql +++ b/tests/Support/Fixture/mysql.sql @@ -161,10 +161,10 @@ CREATE TABLE `type` ( `float_col2` double DEFAULT '1.23', `blob_col` blob, `numeric_col` decimal(5,2) DEFAULT '33.22', - `time` timestamp NOT NULL DEFAULT '2002-01-01 00:00:00', + `timestamp_col` timestamp NOT NULL DEFAULT '2002-01-01 00:00:00', + `timestamp_default` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `bool_col` bit(1) NOT NULL, `tiny_col` tinyint(1) DEFAULT '2', - `ts_default` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `bit_col` BIT(8) NOT NULL DEFAULT b'10000010', `tinyblob_col` tinyblob, `tinytext_col` tinytext,