From add3c577a9823fe115b5d7427d200d3807885f28 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Fri, 20 Nov 2020 23:52:19 -0500 Subject: [PATCH 1/2] feat(postgres to_timestamp): Implementing Postgres to_timestamp function. #215 --- jdbc/src/main/scala/zio/sql/jdbc.scala | 12 +++++-- .../zio/sql/postgresql/PostgresModule.scala | 4 +-- postgres/src/test/resources/shop_schema.sql | 16 +++++++++- .../zio/sql/postgresql/FunctionDefSpec.scala | 31 +++++++++++++++++-- .../scala/zio/sql/postgresql/ShopSchema.scala | 13 ++++++-- 5 files changed, 66 insertions(+), 10 deletions(-) diff --git a/jdbc/src/main/scala/zio/sql/jdbc.scala b/jdbc/src/main/scala/zio/sql/jdbc.scala index 73be39c16..71f902015 100644 --- a/jdbc/src/main/scala/zio/sql/jdbc.scala +++ b/jdbc/src/main/scala/zio/sql/jdbc.scala @@ -1,9 +1,8 @@ package zio.sql import java.sql._ - import java.io.IOException - +import java.time.{ ZoneId, ZoneOffset } import zio.{ Chunk, Has, IO, Managed, ZIO, ZLayer, ZManaged } import zio.blocking.Blocking import zio.stream.{ Stream, ZStream } @@ -193,7 +192,14 @@ trait Jdbc extends zio.sql.Sql { tryDecode[java.util.UUID]( java.util.UUID.fromString(column.fold(resultSet.getString(_), resultSet.getString(_))) ) - case TZonedDateTime => ??? + case TZonedDateTime => + tryDecode[java.time.ZonedDateTime]( + java.time.ZonedDateTime + .ofInstant( + column.fold(resultSet.getTimestamp(_), resultSet.getTimestamp(_)).toInstant, + ZoneId.of(ZoneOffset.UTC.getId) + ) + ) case TDialectSpecific(_) => ??? case t @ Nullable() => extractColumn(column, resultSet, t.typeTag, false).map(Option(_)) } diff --git a/postgres/src/main/scala/zio/sql/postgresql/PostgresModule.scala b/postgres/src/main/scala/zio/sql/postgresql/PostgresModule.scala index 83386774a..b016047b5 100644 --- a/postgres/src/main/scala/zio/sql/postgresql/PostgresModule.scala +++ b/postgres/src/main/scala/zio/sql/postgresql/PostgresModule.scala @@ -1,7 +1,6 @@ package zio.sql.postgresql -import java.time.LocalDate - +import java.time.{ LocalDate, ZonedDateTime } import zio.sql.Jdbc /** @@ -30,6 +29,7 @@ trait PostgresModule extends Jdbc { self => val Degrees = FunctionDef[Double, Double](FunctionName("degrees")) val Div = FunctionDef[(Double, Double), Double](FunctionName("div")) val Factorial = FunctionDef[Int, Int](FunctionName("factorial")) + val ToTimestamp = FunctionDef[Long, ZonedDateTime](FunctionName("to_timestamp")) } override def renderRead(read: self.Read[_]): String = { diff --git a/postgres/src/test/resources/shop_schema.sql b/postgres/src/test/resources/shop_schema.sql index 9dfdfbdb1..05ebd7bbf 100644 --- a/postgres/src/test/resources/shop_schema.sql +++ b/postgres/src/test/resources/shop_schema.sql @@ -37,6 +37,12 @@ create table order_details unit_price money not null ); +create table timestamp_test +( + timestamp_id uuid not null, + created_timestamp_string varchar not null, + created_timestamp timestamp with time zone default now() +); insert into customers (id, first_name, last_name, verified, dob) @@ -189,4 +195,12 @@ values ('852E2DC9-4EC3-4225-A6F7-4F42F8FF728E', 'D5137D3A-894A-4109-9986-E982541B434F', 1, 45.45), ('D6D8DDDC-4B0B-4D74-8EDC-A54E9B7F35F7', 'D5137D3A-894A-4109-9986-E982541B434F', 2, 50.00), ('2C3FC180-D0DF-4D7B-A271-E6CCD2440393', 'D5137D3A-894A-4109-9986-E982541B434F', 2, 50.00), - ('5883CB62-D792-4EE3-ACBC-FE85B6BAA998', 'D5137D3A-894A-4109-9986-E982541B434F', 1, 55.00); \ No newline at end of file + ('5883CB62-D792-4EE3-ACBC-FE85B6BAA998', 'D5137D3A-894A-4109-9986-E982541B434F', 1, 55.00); + +insert into timestamp_test + (timestamp_id, created_timestamp_string, created_timestamp) +values + ('354ec738-71b6-4166-9c62-aa092ede73c4', '2020-11-21 19:10:25+00', '2020-11-21 19:10:25+00'), + ('2f97e2c5-62de-478e-bb30-742f2614f3cd', '2020-11-21 15:10:25-04', '2020-11-21 15:10:25-04'), + ('261a4290-2da4-4e3f-bbab-3f0af31d1914', '2020-11-22 02:10:25+07', '2020-11-22 02:10:25+07'), + ('2e9d0d70-b947-4126-9149-7a8e6d492171', '2020-11-21 12:10:25-07', '2020-11-21 12:10:25-07') \ No newline at end of file diff --git a/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala b/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala index faac0aa6a..d56eb9769 100644 --- a/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala +++ b/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala @@ -1,8 +1,7 @@ package zio.sql.postgresql -import java.time.LocalDate +import java.time.{ LocalDate, ZoneId, ZoneOffset, ZonedDateTime } import java.util.UUID - import zio.Cause import zio.test._ import zio.test.Assertion._ @@ -523,6 +522,34 @@ object FunctionDefSpec extends PostgresRunnableSpec with ShopSchema { r <- result.runCollect } yield assert(r)(hasSameElements(expected)) + assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) + }, + testM("to_timestamp") { + import this.TimestampTests._ + + val query = select(ToTimestamp(1284352323L)) from customers + val expected = ZonedDateTime.of(2010, 9, 13, 4, 32, 3, 0, ZoneId.of(ZoneOffset.UTC.getId)) + val testResult = execute(query).to[ZonedDateTime, ZonedDateTime](identity) + + val expectedRoundTripTimestamp = ZonedDateTime.of(2020, 11, 21, 19, 10, 25, 0, ZoneId.of(ZoneOffset.UTC.getId)) + val roundTripQuery = + select(createdString ++ createdTimestamp) from timestampTests + val roundTripResults = execute(roundTripQuery).to[String, ZonedDateTime, (String, ZonedDateTime)] { case row => + row + } + val roundTripExpected = List( + ("2020-11-21 19:10:25+00", expectedRoundTripTimestamp), + ("2020-11-21 15:10:25-04", expectedRoundTripTimestamp), + ("2020-11-22 02:10:25+07", expectedRoundTripTimestamp), + ("2020-11-21 12:10:25-07", expectedRoundTripTimestamp) + ) + + val assertion = for { + single <- testResult.runCollect + roundTrip <- roundTripResults.runCollect + } yield assert(single.head)(equalTo(expected)) && + assert(roundTrip)(hasSameElementsDistinct(roundTripExpected)) + assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) } ) diff --git a/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala b/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala index c37cf090a..650fa9e22 100644 --- a/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala +++ b/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala @@ -7,10 +7,12 @@ trait ShopSchema extends Jdbc { self => object Customers { val customers = - (uuid("id") ++ localDate("dob") ++ string("first_name") ++ string("last_name") ++ boolean("verified")) + (uuid("id") ++ localDate("dob") ++ string("first_name") ++ string("last_name") ++ boolean( + "verified" + ) ++ zonedDateTime("created_timestamp")) .table("customers") - val customerId :*: dob :*: fName :*: lName :*: verified :*: _ = customers.columns + val customerId :*: dob :*: fName :*: lName :*: verified :*: createdTimestamp :*: _ = customers.columns } object Orders { val orders = (uuid("id") ++ uuid("customer_id") ++ localDate("order_date")).table("orders") @@ -39,4 +41,11 @@ trait ShopSchema extends Jdbc { self => val fkOrderId :*: fkProductId :*: quantity :*: unitPrice :*: _ = orderDetails.columns } + + object TimestampTests { + val timestampTests = + (uuid("timestamp_id") ++ string("created_timestamp_string") ++ zonedDateTime("created_timestamp")) + .table("timestamp_test") + val tId :*: createdString :*: createdTimestamp :*: _ = timestampTests.columns + } } From d8fa9b92ce14f0e4e2a00f59c8807b9192b83ef8 Mon Sep 17 00:00:00 2001 From: Brandon Brown Date: Mon, 23 Nov 2020 22:06:18 -0500 Subject: [PATCH 2/2] pr feedback --- postgres/src/test/resources/shop_schema.sql | 31 ++++++------------- .../zio/sql/postgresql/FunctionDefSpec.scala | 17 +++++----- .../scala/zio/sql/postgresql/ShopSchema.scala | 13 +++----- 3 files changed, 21 insertions(+), 40 deletions(-) diff --git a/postgres/src/test/resources/shop_schema.sql b/postgres/src/test/resources/shop_schema.sql index 05ebd7bbf..6c1064e3e 100644 --- a/postgres/src/test/resources/shop_schema.sql +++ b/postgres/src/test/resources/shop_schema.sql @@ -4,7 +4,9 @@ create table customers first_name varchar not null, last_name varchar not null, verified boolean not null, - dob date not null + dob date not null, + created_timestamp_string varchar not null, + created_timestamp timestamp with time zone default now() ); create table orders @@ -37,21 +39,14 @@ create table order_details unit_price money not null ); -create table timestamp_test -( - timestamp_id uuid not null, - created_timestamp_string varchar not null, - created_timestamp timestamp with time zone default now() -); - insert into customers - (id, first_name, last_name, verified, dob) + (id, first_name, last_name, verified, dob, created_timestamp_string, created_timestamp) values - ('60b01fc9-c902-4468-8d49-3c0f989def37', 'Ronald', 'Russell', true, '1983-01-05'), - ('f76c9ace-be07-4bf3-bd4c-4a9c62882e64', 'Terrence', 'Noel', true, '1999-11-02'), - ('784426a5-b90a-4759-afbb-571b7a0ba35e', 'Mila', 'Paterso', true, '1990-11-16'), - ('df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', 'Alana', 'Murray', true, '1995-11-12'), - ('636ae137-5b1a-4c8c-b11f-c47c624d9cdc', 'Jose', 'Wiggins', false, '1987-03-23'); + ('60b01fc9-c902-4468-8d49-3c0f989def37', 'Ronald', 'Russell', true, '1983-01-05', '2020-11-21T19:10:25+00:00', '2020-11-21 19:10:25+00'), + ('f76c9ace-be07-4bf3-bd4c-4a9c62882e64', 'Terrence', 'Noel', true, '1999-11-02', '2020-11-21T15:10:25-04:00', '2020-11-21 15:10:25-04'), + ('784426a5-b90a-4759-afbb-571b7a0ba35e', 'Mila', 'Paterso', true, '1990-11-16', '2020-11-22T02:10:25+07:00', '2020-11-22 02:10:25+07'), + ('df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', 'Alana', 'Murray', true, '1995-11-12', '2020-11-21T12:10:25-07:00', '2020-11-21 12:10:25-07'), + ('636ae137-5b1a-4c8c-b11f-c47c624d9cdc', 'Jose', 'Wiggins', false, '1987-03-23', '2020-11-21T19:10:25+00:00', '2020-11-21 19:10:25+00'); insert into products (id, name, description, image_url) @@ -196,11 +191,3 @@ values ('D6D8DDDC-4B0B-4D74-8EDC-A54E9B7F35F7', 'D5137D3A-894A-4109-9986-E982541B434F', 2, 50.00), ('2C3FC180-D0DF-4D7B-A271-E6CCD2440393', 'D5137D3A-894A-4109-9986-E982541B434F', 2, 50.00), ('5883CB62-D792-4EE3-ACBC-FE85B6BAA998', 'D5137D3A-894A-4109-9986-E982541B434F', 1, 55.00); - -insert into timestamp_test - (timestamp_id, created_timestamp_string, created_timestamp) -values - ('354ec738-71b6-4166-9c62-aa092ede73c4', '2020-11-21 19:10:25+00', '2020-11-21 19:10:25+00'), - ('2f97e2c5-62de-478e-bb30-742f2614f3cd', '2020-11-21 15:10:25-04', '2020-11-21 15:10:25-04'), - ('261a4290-2da4-4e3f-bbab-3f0af31d1914', '2020-11-22 02:10:25+07', '2020-11-22 02:10:25+07'), - ('2e9d0d70-b947-4126-9149-7a8e6d492171', '2020-11-21 12:10:25-07', '2020-11-21 12:10:25-07') \ No newline at end of file diff --git a/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala b/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala index d56eb9769..606b2945b 100644 --- a/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala +++ b/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala @@ -525,23 +525,22 @@ object FunctionDefSpec extends PostgresRunnableSpec with ShopSchema { assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) }, testM("to_timestamp") { - import this.TimestampTests._ - val query = select(ToTimestamp(1284352323L)) from customers val expected = ZonedDateTime.of(2010, 9, 13, 4, 32, 3, 0, ZoneId.of(ZoneOffset.UTC.getId)) val testResult = execute(query).to[ZonedDateTime, ZonedDateTime](identity) val expectedRoundTripTimestamp = ZonedDateTime.of(2020, 11, 21, 19, 10, 25, 0, ZoneId.of(ZoneOffset.UTC.getId)) val roundTripQuery = - select(createdString ++ createdTimestamp) from timestampTests - val roundTripResults = execute(roundTripQuery).to[String, ZonedDateTime, (String, ZonedDateTime)] { case row => - row + select(createdString ++ createdTimestamp) from customers + val roundTripResults = execute(roundTripQuery).to[String, ZonedDateTime, (String, ZonedDateTime, ZonedDateTime)] { + case row => + (row._1, ZonedDateTime.parse(row._1), row._2) } val roundTripExpected = List( - ("2020-11-21 19:10:25+00", expectedRoundTripTimestamp), - ("2020-11-21 15:10:25-04", expectedRoundTripTimestamp), - ("2020-11-22 02:10:25+07", expectedRoundTripTimestamp), - ("2020-11-21 12:10:25-07", expectedRoundTripTimestamp) + ("2020-11-21T19:10:25+00:00", ZonedDateTime.parse("2020-11-21T19:10:25+00:00"), expectedRoundTripTimestamp), + ("2020-11-21T15:10:25-04:00", ZonedDateTime.parse("2020-11-21T15:10:25-04:00"), expectedRoundTripTimestamp), + ("2020-11-22T02:10:25+07:00", ZonedDateTime.parse("2020-11-22T02:10:25+07:00"), expectedRoundTripTimestamp), + ("2020-11-21T12:10:25-07:00", ZonedDateTime.parse("2020-11-21T12:10:25-07:00"), expectedRoundTripTimestamp) ) val assertion = for { diff --git a/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala b/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala index 650fa9e22..b2722d9bf 100644 --- a/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala +++ b/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala @@ -6,13 +6,15 @@ trait ShopSchema extends Jdbc { self => import self.ColumnSet._ object Customers { + //https://github.com/zio/zio-sql/issues/320 Once Insert is supported, we can remove created_timestamp_string val customers = (uuid("id") ++ localDate("dob") ++ string("first_name") ++ string("last_name") ++ boolean( "verified" - ) ++ zonedDateTime("created_timestamp")) + ) ++ string("created_timestamp_string") ++ zonedDateTime("created_timestamp")) .table("customers") - val customerId :*: dob :*: fName :*: lName :*: verified :*: createdTimestamp :*: _ = customers.columns + val customerId :*: dob :*: fName :*: lName :*: verified :*: createdString :*: createdTimestamp :*: _ = + customers.columns } object Orders { val orders = (uuid("id") ++ uuid("customer_id") ++ localDate("order_date")).table("orders") @@ -41,11 +43,4 @@ trait ShopSchema extends Jdbc { self => val fkOrderId :*: fkProductId :*: quantity :*: unitPrice :*: _ = orderDetails.columns } - - object TimestampTests { - val timestampTests = - (uuid("timestamp_id") ++ string("created_timestamp_string") ++ zonedDateTime("created_timestamp")) - .table("timestamp_test") - val tId :*: createdString :*: createdTimestamp :*: _ = timestampTests.columns - } }