Fix Postgres not padding timestamps correctly (fix hasura/graphql-engine#8096)

## Description

As identified in hasura/graphql-engine#8096, the format string we used for timestamps was incorrect; we were using `%F`, which expands to `%Y-%m-%d`; but that meant that the year was not padded to four digits: `0001` would be represented simply as `1`. However, Postgres inteprets that `1` as `2001`, probably due to interpretation rules about two-digit years (in `25/12/01`, `01` is indeed `2001`).

```
# create table timestamp_test ( test timestamptz );
CREATE TABLE
# insert into timestamp_test values ('1-01-01T00:00:57Z');
INSERT 0 1
# select * from timestamp_test;
          test
------------------------
 2001-01-01 00:00:57+00
(1 row)
```

To fix this, this PR changes the format string to use `%0Y`, which always pads the year number with zeroes.

## Remaining work

- [x] write Changelog entry
- [ ] copy timestamp tests from the python suite into the hspec tests

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3536
GitOrigin-RevId: fa144111358339fd4a35b32d888c1d2c5b418ea6
This commit is contained in:
Antoine Leblanc 2022-05-09 13:17:12 +01:00 committed by hasura-bot
parent d1f8b9a87d
commit 697137dd77
4 changed files with 114 additions and 16 deletions

View File

@ -4,6 +4,7 @@
### Bug fixes and improvements
- server: fixed a bug where timestamp values sent to postgres would erroneously trim leading zeroes (#8096)
- server: fix bug when event triggers where defined on tables that contained non lower-case alphabet characters
- server: avoid encoding 'varchar' values to UTF8 in MSSQL backends
- server: add support for MSSQL event triggers (#7228)

View File

@ -1091,9 +1091,9 @@ test-suite tests-hspec
Harness.Backend.Sqlserver
-- Harness.Test
Harness.Test.BackendType
Harness.Test.Context
Harness.Test.Schema
Harness.Test.BackendType
-- Harness.Quoter
Harness.Quoter.Graphql
@ -1107,30 +1107,31 @@ test-suite tests-hspec
Test.CustomFieldNamesSpec
Test.DirectivesSpec
Test.HelloWorldSpec
Test.InsertCheckPermissionSpec
Test.InsertEnumColumnSpec
Test.LimitOffsetSpec
Test.NestedRelationshipsSpec
Test.ObjectRelationshipsLimitSpec
Test.ObjectRelationshipsSpec
Test.OrderingSpec
Test.PostgresTypesSpec
Test.PrimaryKeySpec
Test.RemoteRelationship.MetadataAPI.Common
Test.RemoteRelationship.FromRemoteSchemaSpec
Test.RemoteRelationship.MetadataAPI.ClearMetadataSpec
Test.RemoteRelationship.MetadataAPI.Common
Test.RemoteRelationship.MetadataAPI.DropSource.DBtoDBRelationshipSpec
Test.RemoteRelationship.MetadataAPI.DropSource.RSToDBRelationshipSpec
Test.RemoteRelationship.FromRemoteSchemaSpec
Test.RemoteRelationship.XToDBArrayRelationshipSpec
Test.RemoteRelationship.XToDBObjectRelationshipSpec
Test.RemoteRelationship.XToRemoteSchemaRelationshipSpec
Test.RequestHeadersSpec
Test.SerializationSpec
Test.RunSQLSpec
Test.SQLServer.InsertVarcharColumnSpec
Test.SelectSpec
Test.SerializationSpec
Test.ServiceLivenessSpec
Test.ViewsSpec
Test.WhereSpec
Test.RunSQLSpec
Test.InsertCheckPermissionSpec
Test.InsertEnumColumnSpec
Test.SQLServer.InsertVarcharColumnSpec
test-suite tests-dc-api
import: common-all, common-exe

View File

@ -120,10 +120,8 @@ pgScalarValueToJson = \case
PGValText t -> toJSON t
PGValCitext t -> toJSON t
PGValDate d -> toJSON d
PGValTimeStamp u ->
toJSON $ formatTime defaultTimeLocale "%FT%T%QZ" u
PGValTimeStampTZ u ->
toJSON $ formatTime defaultTimeLocale "%FT%T%QZ" u
PGValTimeStamp u -> String $ formatTimestamp u
PGValTimeStampTZ u -> String $ formatTimestamp u
PGValTimeTZ (ZonedTimeOfDay tod tz) ->
toJSON (show tod ++ timeZoneOffsetString tz)
PGNull _ -> Null
@ -243,10 +241,8 @@ txtEncodedVal = \case
PGValText t -> TELit t
PGValCitext t -> TELit t
PGValDate d -> TELit $ T.pack $ showGregorian d
PGValTimeStamp u ->
TELit $ T.pack $ formatTime defaultTimeLocale "%FT%T%QZ" u
PGValTimeStampTZ u ->
TELit $ T.pack $ formatTime defaultTimeLocale "%FT%T%QZ" u
PGValTimeStamp u -> TELit $ formatTimestamp u
PGValTimeStampTZ u -> TELit $ formatTimestamp u
PGValTimeTZ (ZonedTimeOfDay tod tz) ->
TELit $ T.pack (show tod ++ timeZoneOffsetString tz)
PGNull _ ->
@ -332,6 +328,9 @@ binEncoder = \case
PGValLtxtquery t -> Q.toPrepVal t
PGValUnknown t -> (PTI.auto, Just (TE.encodeUtf8 t, PQ.Text))
formatTimestamp :: FormatTime t => t -> Text
formatTimestamp = T.pack . formatTime defaultTimeLocale "%0Y-%m-%dT%T%QZ"
txtEncoder :: PGScalarValue -> S.SQLExp
txtEncoder colVal = case txtEncodedVal colVal of
TENull -> S.SENull

View File

@ -0,0 +1,97 @@
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
-- | Tests around Postgres-specific database types
module Test.PostgresTypesSpec (spec) where
import Harness.Backend.Postgres as Postgres
import Harness.GraphqlEngine qualified as GraphqlEngine
import Harness.Quoter.Graphql
import Harness.Quoter.Sql
import Harness.Quoter.Yaml
import Harness.Test.Context qualified as Context
import Harness.TestEnvironment (TestEnvironment)
import Test.Hspec
import Prelude
--------------------------------------------------------------------------------
-- Preamble
spec :: SpecWith TestEnvironment
spec =
Context.run
[ Context.Context
{ name = Context.Backend Context.Postgres,
mkLocalTestEnvironment = Context.noLocalTestEnvironment,
setup = postgresSetup,
teardown = postgresTeardown,
customOptions = Nothing
}
]
tests
--------------------------------------------------------------------------------
-- Postgres
postgresSetup :: (TestEnvironment, ()) -> IO ()
postgresSetup (testEnvironment, _) = do
-- Clear and reconfigure the metadata
GraphqlEngine.setSource testEnvironment Postgres.defaultSourceMetadata
-- Setup tables
Postgres.run_
[sql|
create table timestamp_test (
id serial primary key,
time timestamptz
);
|]
-- Track the tables
GraphqlEngine.post_
testEnvironment
"/v1/metadata"
[yaml|
type: postgres_track_table
args:
source: postgres
table:
schema: hasura
name: timestamp_test
|]
postgresTeardown :: (TestEnvironment, ()) -> IO ()
postgresTeardown _ = do
Postgres.run_
[sql|
DROP TABLE hasura.timestamp_test;
|]
--------------------------------------------------------------------------------
-- Tests
tests :: Context.Options -> SpecWith TestEnvironment
tests opts = do
it "doesn't mess up timestamps on insertion" $ \testEnvironment ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphql
testEnvironment
[graphql|
mutation {
insert_hasura_timestamp_test(objects:[{
time: "0001-01-01T00:00:57Z"
}]) {
returning {
time
}
}
}
|]
)
[yaml|
data:
insert_hasura_timestamp_test:
returning:
- time: "0001-01-01T00:00:57+00:00"
|]