From ad2fee0197d7e4a9207660fe25223eb5bea394ab Mon Sep 17 00:00:00 2001 From: Javier Spagnoletti Date: Thu, 25 Apr 2024 02:51:39 -0300 Subject: [PATCH 1/2] [Docs] Update docs regarding "datetimetz" type (#6014) --- docs/en/reference/known-vendor-issues.rst | 28 +++++++++++++++-------- docs/en/reference/types.rst | 9 ++++++++ src/Types/DateTimeTzType.php | 19 +++++++-------- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/docs/en/reference/known-vendor-issues.rst b/docs/en/reference/known-vendor-issues.rst index 19b87fd2414..f0162eabfec 100644 --- a/docs/en/reference/known-vendor-issues.rst +++ b/docs/en/reference/known-vendor-issues.rst @@ -70,8 +70,20 @@ MySQL DateTimeTz ~~~~~~~~~~ -MySQL does not support saving timezones or offsets. The DateTimeTz -type therefore behaves like the DateTime type. +Prior to version 8.0.19, MySQL does not support saving timezones or offsets. The DateTimeTz type therefore +behaves like the DateTime type on previous versions. +Starting from version `8.0.19 and later `_, +timezone offsets are supported. MySQL converts the time zone offset to UTC for storage, and back from UTC to the current +(SYSTEM, SESSION, etc) time zone for retrieval. + +MariaDB +------- + +DateTimeTz +~~~~~~~~~~ + +MariaDB does not support saving timezone offsets. The DateTimeTz type therefore behaves like the DateTime +type. Sqlite ------ @@ -86,7 +98,7 @@ breaks the SERIALIZABLE transaction isolation property that SQLite supposedly has. DateTime -~~~~~~~~~~ +~~~~~~~~ Unlike most database management systems, Sqlite does not convert supplied datetime strings to an internal storage format before storage. Instead, Sqlite @@ -104,8 +116,8 @@ when trying to convert database values to ``\DateTime`` objects using DateTimeTz ~~~~~~~~~~ -Sqlite does not support saving timezones or offsets. The DateTimeTz -type therefore behaves like the DateTime type. +Sqlite supports saving timezone offsets, but this feature is not yet implemented in DBAL. +The DateTimeTz type therefore behaves like the DateTime type. Reverse engineering primary key order ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -121,10 +133,8 @@ IBM DB2 DateTimeTz ~~~~~~~~~~ -DB2 does not save the actual Timezone Name but UTC-Offsets. The -difference is subtle but can be potentially very nasty. Derick -Rethans explains it very well -`in a blog post of his `_. +DB2 does not support saving timezone offsets. The DateTimeTz type therefore behaves like the DateTime +type. Oracle ------ diff --git a/docs/en/reference/types.rst b/docs/en/reference/types.rst index 64818adc228..8578b42ecc6 100644 --- a/docs/en/reference/types.rst +++ b/docs/en/reference/types.rst @@ -307,6 +307,15 @@ information, you should consider using this type. Values retrieved from the database are always converted to PHP's ``\DateTime`` object or ``null`` if no data is present. +.. note:: + + This type is not supported by all the vendor platforms or by all of their versions. Depending on + these variants, the databases that support this type may return the persisted date and time in a + different timezone than the one used during the ``INSERT`` or the ``UPDATE`` operation. This means + that if you persist a value like `1986-22-03 19:45:30-03:00`, you could have `1986-22-03 22:45:30-00:00` + as the result of a ``SELECT`` operation for that record. In these cases, the timezone offset present + in the result is usually UTC or the one configured as default in the database server. + .. warning:: Passing instances of ``DateTimeImmutable`` to this type is deprecated since 3.7. Use diff --git a/src/Types/DateTimeTzType.php b/src/Types/DateTimeTzType.php index 1980fd334bf..b3b5db813bc 100644 --- a/src/Types/DateTimeTzType.php +++ b/src/Types/DateTimeTzType.php @@ -11,18 +11,19 @@ use function get_class; /** - * DateTime type saving additional timezone information. + * DateTime type accepting additional information about timezone offsets. * * Caution: Databases are not necessarily experts at storing timezone related - * data of dates. First, of all the supported vendors only PostgreSQL and Oracle - * support storing Timezone data. But those two don't save the actual timezone - * attached to a DateTime instance (for example "Europe/Berlin" or "America/Montreal") - * but the current offset of them related to UTC. That means depending on daylight saving times - * or not you may get different offsets. + * data of dates. First, of not all the supported vendors support storing Timezone data, and some of + * them only use the offset to calculate the timestamp in its default timezone (usually UTC) and persist + * the value without the offset information. They even don't save the actual timezone names attached + * to a DateTime instance (for example "Europe/Berlin" or "America/Montreal") but the current offset + * of them related to UTC. That means, depending on daylight saving times or not, you may get different + * offsets. * - * This datatype makes only sense to use, if your application works with an offset, not - * with an actual timezone that uses transitions. Otherwise your DateTime instance - * attached with a timezone such as Europe/Berlin gets saved into the database with + * This datatype makes only sense to use, if your application only needs to accept the timezone offset, + * not the actual timezone that uses transitions. Otherwise your DateTime instance + * attached with a timezone such as "Europe/Berlin" gets saved into the database with * the offset and re-created from persistence with only the offset, not the original timezone * attached. */ From b05e48a745f722801f55408d0dbd8003b403dbbd Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 25 Apr 2024 09:04:44 +0200 Subject: [PATCH 2/2] Connection::setNestTransactionsWithSavepoints() should not break lazy connection (#6362) | Q | A |------------- | ----------- | Type | bug | Fixed issues | N/A #### Summary When calling `Connection::setNestTransactionsWithSavepoints()`, the platform is fetched to check if savepoints are actually supported. This is a problem because it might cause the connection to be opened. I've removed the check which will postpone the exception to the first time we attempt to create a savepoint. --- src/Connection.php | 4 --- tests/ConnectionTest.php | 42 +++++++++++++++++++++++++++++ tests/Functional/ConnectionTest.php | 12 --------- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/Connection.php b/src/Connection.php index 76b427b5b38..b9756706061 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -1317,10 +1317,6 @@ public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoint throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction(); } - if (! $this->getDatabasePlatform()->supportsSavepoints()) { - throw ConnectionException::savepointsNotSupported(); - } - $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints; } diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index d8bfd8f722d..0a94a129b45 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -2,12 +2,14 @@ namespace Doctrine\DBAL\Tests; +use BadMethodCallException; use Doctrine\Common\EventManager; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\ConnectionException; use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Driver\Connection as DriverConnection; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\DriverManager; @@ -81,6 +83,46 @@ public function testNoTransactionActiveByDefault(): void self::assertFalse($this->connection->isTransactionActive()); } + public function testSetNestTransactionsWithSavepointsDoesNotConnect(): void + { + $this->expectNotToPerformAssertions(); + + $connection = new Connection( + [], + new class implements VersionAwarePlatformDriver { + /** {@inheritDoc} */ + public function connect(array $params): DriverConnection + { + throw new BadMethodCallException('The connection must not be opened'); + } + + public function getDatabasePlatform(): AbstractPlatform + { + throw new BadMethodCallException('The connection must not be opened'); + } + + public function getSchemaManager(Connection $conn, AbstractPlatform $platform): AbstractSchemaManager + { + throw new BadMethodCallException('The connection must not be opened'); + } + + public function getExceptionConverter(): ExceptionConverter + { + throw new BadMethodCallException('The connection must not be opened'); + } + + /** {@inheritDoc} */ + public function createDatabasePlatformForVersion($version): AbstractPlatform + { + throw new BadMethodCallException('The connection must not be opened'); + } + }, + new Configuration(), + ); + + $connection->setNestTransactionsWithSavepoints(true); + } + public function testCommitWithNoActiveTransactionThrowsException(): void { $this->expectException(ConnectionException::class); diff --git a/tests/Functional/ConnectionTest.php b/tests/Functional/ConnectionTest.php index 567f7bcddb4..33f77385c95 100644 --- a/tests/Functional/ConnectionTest.php +++ b/tests/Functional/ConnectionTest.php @@ -195,18 +195,6 @@ public function testTransactionIsInactiveAfterConnectionClose(): void self::assertFalse($this->connection->isTransactionActive()); } - public function testSetNestedTransactionsThroughSavepointsNotSupportedThrowsException(): void - { - if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { - self::markTestSkipped('This test requires the platform not to support savepoints.'); - } - - $this->expectException(ConnectionException::class); - $this->expectExceptionMessage('Savepoints are not supported by this driver.'); - - $this->connection->setNestTransactionsWithSavepoints(true); - } - public function testCreateSavepointsNotSupportedThrowsException(): void { if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {