diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d36d10..774c661 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,18 @@ # Changelog +## Unreleased + +- The `timestamp` functions now use the `timestamp.Timestamp` type from the + `gleam_time` package. + ## v3.3.0 - 2025-07-03 - Updated `result.then` to `result.try` to resolve deprecation warnings. ## v3.2.0 - 2025-01-16 -- The `url_config` function now defaults to port (5432) when port is not specified. +- The `url_config` function now defaults to port (5432) when port is not + specified. ## v3.1.1 - 2025-01-02 @@ -76,8 +82,8 @@ - Provided functions for handling timestamp values. The `timestamp` function coerces a `#(#(Int, Int, Int), #(Int, Int, Int))` value representing `#(#(year, month, day), #(hour, minute, second))` into a `Value`. The - `decode_timestamp` function can be used to decode a dynamic value returned from - the database as a timestamp in the same nested tuple format. + `decode_timestamp` function can be used to decode a dynamic value returned + from the database as a timestamp in the same nested tuple format. ## v0.8.0 - 2024-05-20 diff --git a/gleam.toml b/gleam.toml index 2ab8acd..deb3647 100644 --- a/gleam.toml +++ b/gleam.toml @@ -18,11 +18,12 @@ pages = [ [dependencies] gleam_stdlib = ">= 0.51.0 and < 2.0.0" pgo = ">= 0.12.0 and < 2.0.0" +gleam_time = ">= 1.2.0 and < 2.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" exception = ">= 2.0.0 and < 3.0.0" -gleam_erlang = ">= 0.30.0 and < 1.0.0" +gleam_erlang = ">= 1.0.0 and < 2.0.0" [erlang] # Starting an SSL connection relies on ssl application to be started. diff --git a/manifest.toml b/manifest.toml index e04d732..4eea178 100644 --- a/manifest.toml +++ b/manifest.toml @@ -3,10 +3,11 @@ packages = [ { name = "backoff", version = "1.1.6", build_tools = ["rebar3"], requirements = [], otp_app = "backoff", source = "hex", outer_checksum = "CF0CFFF8995FB20562F822E5CC47D8CCF664C5ECDC26A684CBE85C225F9D7C39" }, - { name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" }, - { name = "gleam_erlang", version = "0.33.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "A1D26B80F01901B59AABEE3475DD4C18D27D58FA5C897D922FCB9B099749C064" }, - { name = "gleam_stdlib", version = "0.51.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "14AFA8D3DDD7045203D422715DBB822D1725992A31DF35A08D97389014B74B68" }, - { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, + { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, + { name = "gleam_erlang", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "F91CE62A2D011FA13341F3723DB7DB118541AAA5FE7311BD2716D018F01EF9E3" }, + { name = "gleam_stdlib", version = "0.61.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "3DC407D6EDA98FCE089150C11F3AD892B6F4C3CA77C87A97BAE8D5AB5E41F331" }, + { name = "gleam_time", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "D71F1AFF7FEB534FF55E5DC58E534E9201BA75A444619788A2E4DEA4EBD87D16" }, + { name = "gleeunit", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "63022D81C12C17B7F1A60E029964E830A4CBD846BBC6740004FC1F1031AE0326" }, { name = "opentelemetry_api", version = "1.4.0", build_tools = ["rebar3", "mix"], requirements = [], otp_app = "opentelemetry_api", source = "hex", outer_checksum = "3DFBBFAA2C2ED3121C5C483162836C4F9027DEF469C41578AF5EF32589FCFC58" }, { name = "pg_types", version = "0.4.0", build_tools = ["rebar3"], requirements = [], otp_app = "pg_types", source = "hex", outer_checksum = "B02EFA785CAECECF9702C681C80A9CA12A39F9161A846CE17B01FB20AEEED7EB" }, { name = "pgo", version = "0.14.0", build_tools = ["rebar3"], requirements = ["backoff", "opentelemetry_api", "pg_types"], otp_app = "pgo", source = "hex", outer_checksum = "71016C22599936E042DC0012EE4589D24C71427D266292F775EBF201D97DF9C9" }, @@ -14,7 +15,8 @@ packages = [ [requirements] exception = { version = ">= 2.0.0 and < 3.0.0" } -gleam_erlang = { version = ">= 0.30.0 and < 1.0.0" } +gleam_erlang = { version = ">= 1.0.0 and < 2.0.0" } gleam_stdlib = { version = ">= 0.51.0 and < 2.0.0" } +gleam_time = { version = ">= 1.2.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } pgo = { version = ">= 0.12.0 and < 2.0.0" } diff --git a/src/pog.gleam b/src/pog.gleam index abe011d..d592066 100644 --- a/src/pog.gleam +++ b/src/pog.gleam @@ -12,6 +12,7 @@ import gleam/list import gleam/option.{type Option, None, Some} import gleam/result import gleam/string +import gleam/time/timestamp.{type Timestamp} import gleam/uri.{Uri} /// The port that will be used when none is specified. @@ -347,7 +348,10 @@ pub fn array(converter: fn(a) -> Value, values: List(a)) -> Value { } pub fn timestamp(timestamp: Timestamp) -> Value { - coerce_value(#(date(timestamp.date), time(timestamp.time))) + let #(seconds, nanoseconds) = + timestamp.to_unix_seconds_and_nanoseconds(timestamp) + + coerce_value(seconds * 1_000_000 + nanoseconds / 1000) } pub fn date(date: Date) -> Value { @@ -761,9 +765,10 @@ pub fn error_code_name(error_code: String) -> Result(String, Nil) { } pub fn timestamp_decoder() -> decode.Decoder(Timestamp) { - use date <- decode.field(0, date_decoder()) - use time <- decode.field(1, time_decoder()) - decode.success(Timestamp(date, time)) + use microseconds <- decode.map(decode.int) + let seconds = microseconds / 1_000_000 + let nanoseconds = { microseconds % 1_000_000 } * 1000 + timestamp.from_unix_seconds_and_nanoseconds(seconds, nanoseconds) } pub fn date_decoder() -> decode.Decoder(Date) { @@ -804,7 +809,3 @@ pub type Date { pub type Time { Time(hours: Int, minutes: Int, seconds: Int, microseconds: Int) } - -pub type Timestamp { - Timestamp(date: Date, time: Time) -} diff --git a/src/pog_ffi.erl b/src/pog_ffi.erl index f012650..e43edd6 100644 --- a/src/pog_ffi.erl +++ b/src/pog_ffi.erl @@ -39,6 +39,7 @@ default_ssl_options(Host, Ssl) -> end. connect(Config) -> + application:set_env(pg_types, timestamp_config, integer_system_time_microseconds), Id = integer_to_list(erlang:unique_integer([positive])), PoolName = list_to_atom("pog_pool_" ++ Id), #config{ diff --git a/test/pog_test.gleam b/test/pog_test.gleam index 4370848..20b119b 100644 --- a/test/pog_test.gleam +++ b/test/pog_test.gleam @@ -2,6 +2,8 @@ import exception import gleam/dynamic/decode.{type Decoder} import gleam/erlang/atom import gleam/option.{None, Some} +import gleam/time/calendar +import gleam/time/timestamp import gleeunit import gleeunit/should import pog @@ -11,7 +13,7 @@ pub fn main() { } pub fn run_with_timeout(time: Int, next: fn() -> a) { - let assert Ok(timeout) = atom.from_string("timeout") + let timeout = atom.create("timeout") #(timeout, time, next) } @@ -75,13 +77,7 @@ pub fn url_config_path_slash_test() { } fn start_default() { - pog.Config( - ..pog.default_config(), - database: "gleam_pog_test", - password: Some("postgres"), - pool_size: 1, - ) - |> pog.connect + pog.connect(default_config()) } fn default_config() { @@ -138,12 +134,14 @@ pub fn inserting_new_rows_and_returning_test() { pub fn selecting_rows_test() { let db = start_default() - let sql = - " + let timestamp_string = "2022-10-10T11:30:30.1Z" + let assert Ok(timestamp) = timestamp.parse_rfc3339("2022-10-10T11:30:30.1Z") + + let sql = " INSERT INTO cats VALUES - (DEFAULT, 'neo', true, ARRAY ['black'], '2022-10-10 11:30:30.1', '2020-03-04') + (DEFAULT, 'neo', true, ARRAY ['black'], '" <> timestamp_string <> "', '2020-03-04') RETURNING id" @@ -170,14 +168,7 @@ pub fn selecting_rows_test() { |> should.equal(1) returned.rows |> should.equal([ - #( - id, - "neo", - True, - ["black"], - pog.Timestamp(pog.Date(2022, 10, 10), pog.Time(11, 30, 30, 100_000)), - pog.Date(2020, 3, 4), - ), + #(id, "neo", True, ["black"], timestamp, pog.Date(2020, 3, 4)), ]) pog.disconnect(db) @@ -388,7 +379,11 @@ pub fn array_test() { pub fn datetime_test() { start_default() |> assert_roundtrip( - pog.Timestamp(pog.Date(2022, 10, 12), pog.Time(11, 30, 33, 101)), + timestamp.from_calendar( + date: calendar.Date(day: 10, month: calendar.October, year: 2022), + time: calendar.TimeOfDay(11, 30, 30, 00), + offset: calendar.utc_offset, + ), "timestamp", pog.timestamp, pog.timestamp_decoder(), @@ -508,12 +503,14 @@ pub fn expected_ten_millis_no_default_timeout_test() { pub fn expected_maps_test() { let db = pog.Config(..default_config(), rows_as_map: True) |> pog.connect - let sql = - " + let timestamp_string = "2022-10-10T11:30:30Z" + let assert Ok(timestamp) = timestamp.parse_rfc3339(timestamp_string) + + let sql = " INSERT INTO cats VALUES - (DEFAULT, 'neo', true, ARRAY ['black'], '2022-10-10 11:30:30', '2020-03-04') + (DEFAULT, 'neo', true, ARRAY ['black'], '" <> timestamp_string <> "', '2020-03-04') RETURNING id" @@ -543,14 +540,7 @@ pub fn expected_maps_test() { |> should.equal(1) returned.rows |> should.equal([ - #( - id, - "neo", - True, - ["black"], - pog.Timestamp(pog.Date(2022, 10, 10), pog.Time(11, 30, 30, 0)), - pog.Date(2020, 3, 4), - ), + #(id, "neo", True, ["black"], timestamp, pog.Date(2020, 3, 4)), ]) pog.disconnect(db)