From 5b55a6d9762e45b31ebbd7499a448c435fd7be92 Mon Sep 17 00:00:00 2001 From: Giacomo Cavalieri Date: Thu, 3 Jul 2025 17:11:58 +0200 Subject: [PATCH 1/5] update dependencies --- gleam.toml | 1 - manifest.toml | 6 ++---- test/pog_test.gleam | 8 +++++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/gleam.toml b/gleam.toml index 2ab8acd..c6c2cc8 100644 --- a/gleam.toml +++ b/gleam.toml @@ -22,7 +22,6 @@ pgo = ">= 0.12.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" [erlang] # Starting an SSL connection relies on ssl application to be started. diff --git a/manifest.toml b/manifest.toml index e04d732..11a2f71 100644 --- a/manifest.toml +++ b/manifest.toml @@ -4,9 +4,8 @@ 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 = "gleam_stdlib", version = "0.61.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "3DC407D6EDA98FCE089150C11F3AD892B6F4C3CA77C87A97BAE8D5AB5E41F331" }, + { 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 +13,6 @@ packages = [ [requirements] exception = { version = ">= 2.0.0 and < 3.0.0" } -gleam_erlang = { version = ">= 0.30.0 and < 1.0.0" } gleam_stdlib = { version = ">= 0.51.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/test/pog_test.gleam b/test/pog_test.gleam index 4370848..f742cbf 100644 --- a/test/pog_test.gleam +++ b/test/pog_test.gleam @@ -1,6 +1,5 @@ import exception import gleam/dynamic/decode.{type Decoder} -import gleam/erlang/atom import gleam/option.{None, Some} import gleeunit import gleeunit/should @@ -10,9 +9,12 @@ pub fn main() { gleeunit.main() } +pub type Timeout(a) { + Timeout(time: Int, next: fn() -> a) +} + pub fn run_with_timeout(time: Int, next: fn() -> a) { - let assert Ok(timeout) = atom.from_string("timeout") - #(timeout, time, next) + Timeout(time, next) } pub fn url_config_everything_test() { From d07996055359fdc7561725a62224cc9859a71890 Mon Sep 17 00:00:00 2001 From: Giacomo Cavalieri Date: Thu, 3 Jul 2025 17:12:58 +0200 Subject: [PATCH 2/5] reuse default config --- test/pog_test.gleam | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/pog_test.gleam b/test/pog_test.gleam index f742cbf..de45575 100644 --- a/test/pog_test.gleam +++ b/test/pog_test.gleam @@ -77,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() { From 8eccbb2b424e9fe7ce1140bf9e61f2235e0937d5 Mon Sep 17 00:00:00 2001 From: Giacomo Cavalieri Date: Thu, 3 Jul 2025 17:17:11 +0200 Subject: [PATCH 3/5] use assert syntax in tests --- gleam.toml | 2 +- test/pog_test.gleam | 217 ++++++++++++++++++++------------------------ 2 files changed, 99 insertions(+), 120 deletions(-) diff --git a/gleam.toml b/gleam.toml index c6c2cc8..6fbe5a1 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,6 +1,6 @@ name = "pog" version = "3.3.0" -gleam = ">= 1.4.0" +gleam = ">= 1.11.0" licences = ["Apache-2.0"] description = "A PostgreSQL database client for Gleam, based on PGO" diff --git a/test/pog_test.gleam b/test/pog_test.gleam index de45575..b76e3d2 100644 --- a/test/pog_test.gleam +++ b/test/pog_test.gleam @@ -2,7 +2,6 @@ import exception import gleam/dynamic/decode.{type Decoder} import gleam/option.{None, Some} import gleeunit -import gleeunit/should import pog pub fn main() { @@ -26,8 +25,7 @@ pub fn url_config_everything_test() { |> pog.user("u") |> pog.password(Some("p")) - pog.url_config("postgres://u:p@db.test:1234/my_db") - |> should.equal(Ok(expected)) + assert pog.url_config("postgres://u:p@db.test:1234/my_db") == Ok(expected) } pub fn url_config_alternative_postgres_protocol_test() { @@ -38,13 +36,12 @@ pub fn url_config_alternative_postgres_protocol_test() { |> pog.database("my_db") |> pog.user("u") |> pog.password(Some("p")) - pog.url_config("postgresql://u:p@db.test:1234/my_db") - |> should.equal(Ok(expected)) + + assert pog.url_config("postgresql://u:p@db.test:1234/my_db") == Ok(expected) } pub fn url_config_not_postgres_protocol_test() { - pog.url_config("foo://u:p@db.test:1234/my_db") - |> should.equal(Error(Nil)) + assert pog.url_config("foo://u:p@db.test:1234/my_db") == Error(Nil) } pub fn url_config_no_password_test() { @@ -55,8 +52,8 @@ pub fn url_config_no_password_test() { |> pog.database("my_db") |> pog.user("u") |> pog.password(None) - pog.url_config("postgres://u@db.test:1234/my_db") - |> should.equal(Ok(expected)) + + assert pog.url_config("postgres://u@db.test:1234/my_db") == Ok(expected) } pub fn url_config_no_port_test() { @@ -67,13 +64,12 @@ pub fn url_config_no_port_test() { |> pog.database("my_db") |> pog.user("u") |> pog.password(None) - pog.url_config("postgres://u@db.test/my_db") - |> should.equal(Ok(expected)) + + assert pog.url_config("postgres://u@db.test/my_db") == Ok(expected) } pub fn url_config_path_slash_test() { - pog.url_config("postgres://u:p@db.test:1234/my_db/foo") - |> should.equal(Error(Nil)) + assert pog.url_config("postgres://u:p@db.test:1234/my_db/foo") == Error(Nil) } fn start_default() { @@ -100,10 +96,8 @@ pub fn inserting_new_rows_test() { (DEFAULT, 'felix', false, ARRAY ['grey'], now(), '2020-03-05')" let assert Ok(returned) = pog.query(sql) |> pog.execute(db) - returned.count - |> should.equal(2) - returned.rows - |> should.equal([]) + assert returned.count == 2 + assert returned.rows == [] pog.disconnect(db) } @@ -124,10 +118,8 @@ pub fn inserting_new_rows_and_returning_test() { |> pog.returning(decode.at([0], decode.string)) |> pog.execute(db) - returned.count - |> should.equal(2) - returned.rows - |> should.equal(["bill", "felix"]) + assert returned.count == 2 + assert returned.rows == ["bill", "felix"] pog.disconnect(db) } @@ -162,19 +154,18 @@ pub fn selecting_rows_test() { }) |> pog.execute(db) - returned.count - |> 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), - ), - ]) + assert returned.count == 1 + assert returned.rows + == [ + #( + id, + "neo", + True, + ["black"], + pog.Timestamp(pog.Date(2022, 10, 10), pog.Time(11, 30, 30, 100_000)), + pog.Date(2020, 3, 4), + ), + ] pog.disconnect(db) } @@ -186,12 +177,9 @@ pub fn invalid_sql_test() { let assert Error(pog.PostgresqlError(code, name, message)) = pog.query(sql) |> pog.execute(db) - code - |> should.equal("42601") - name - |> should.equal("syntax_error") - message - |> should.equal("syntax error at or near \"select\"") + assert code == "42601" + assert name == "syntax_error" + assert message == "syntax error at or near \"select\"" pog.disconnect(db) } @@ -209,16 +197,10 @@ pub fn insert_constraint_error_test() { let assert Error(pog.ConstraintViolated(message, constraint, detail)) = pog.query(sql) |> pog.execute(db) - constraint - |> should.equal("cats_pkey") - - detail - |> should.equal("Key (id)=(900) already exists.") - - message - |> should.equal( - "duplicate key value violates unique constraint \"cats_pkey\"", - ) + assert constraint == "cats_pkey" + assert detail == "Key (id)=(900) already exists." + assert message + == "duplicate key value violates unique constraint \"cats_pkey\"" pog.disconnect(db) } @@ -230,12 +212,9 @@ pub fn select_from_unknown_table_test() { let assert Error(pog.PostgresqlError(code, name, message)) = pog.query(sql) |> pog.execute(db) - code - |> should.equal("42P01") - name - |> should.equal("undefined_table") - message - |> should.equal("relation \"unknown\" does not exist") + assert code == "42P01" + assert name == "undefined_table" + assert message == "relation \"unknown\" does not exist" pog.disconnect(db) } @@ -251,14 +230,10 @@ pub fn insert_with_incorrect_type_test() { let assert Error(pog.PostgresqlError(code, name, message)) = pog.query(sql) |> pog.execute(db) - code - |> should.equal("42804") - name - |> should.equal("datatype_mismatch") - message - |> should.equal( - "column \"id\" is of type integer but expression is of type boolean", - ) + assert code == "42804" + assert name == "datatype_mismatch" + assert message + == "column \"id\" is of type integer but expression is of type boolean" pog.disconnect(db) } @@ -267,9 +242,8 @@ pub fn execute_with_wrong_number_of_arguments_test() { let db = start_default() let sql = "SELECT * FROM cats WHERE id = $1" - pog.query(sql) - |> pog.execute(db) - |> should.equal(Error(pog.UnexpectedArgumentCount(expected: 1, got: 0))) + assert pog.execute(pog.query(sql), db) + == Error(pog.UnexpectedArgumentCount(expected: 1, got: 0)) pog.disconnect(db) } @@ -281,21 +255,21 @@ fn assert_roundtrip( encoder: fn(a) -> pog.Value, decoder: Decoder(a), ) -> pog.Connection { - pog.query("select $1::" <> type_name) - |> pog.parameter(encoder(value)) - |> pog.returning(decode.at([0], decoder)) - |> pog.execute(db) - |> should.equal(Ok(pog.Returned(count: 1, rows: [value]))) + assert pog.query("select $1::" <> type_name) + |> pog.parameter(encoder(value)) + |> pog.returning(decode.at([0], decoder)) + |> pog.execute(db) + == Ok(pog.Returned(count: 1, rows: [value])) db } pub fn null_test() { let db = start_default() - pog.query("select $1") - |> pog.parameter(pog.null()) - |> pog.returning(decode.at([0], decode.optional(decode.int))) - |> pog.execute(db) - |> should.equal(Ok(pog.Returned(count: 1, rows: [None]))) + assert pog.query("select $1") + |> pog.parameter(pog.null()) + |> pog.returning(decode.at([0], decode.optional(decode.int))) + |> pog.execute(db) + == Ok(pog.Returned(count: 1, rows: [None])) pog.disconnect(db) } @@ -435,27 +409,27 @@ pub fn nullable_test() { pub fn expected_argument_type_test() { let db = start_default() - pog.query("select $1::int") - |> pog.returning(decode.at([0], decode.string)) - |> pog.parameter(pog.float(1.2)) - |> pog.execute(db) - |> should.equal(Error(pog.UnexpectedArgumentType("int4", "1.2"))) + assert pog.query("select $1::int") + |> pog.returning(decode.at([0], decode.string)) + |> pog.parameter(pog.float(1.2)) + |> pog.execute(db) + == Error(pog.UnexpectedArgumentType("int4", "1.2")) pog.disconnect(db) } pub fn expected_return_type_test() { let db = start_default() - pog.query("select 1") - |> pog.returning(decode.at([0], decode.string)) - |> pog.execute(db) - |> should.equal( - Error( - pog.UnexpectedResultType([ - decode.DecodeError(expected: "String", found: "Int", path: ["0"]), - ]), - ), - ) + + let expected = + pog.UnexpectedResultType([ + decode.DecodeError(expected: "String", found: "Int", path: ["0"]), + ]) + + assert pog.query("select 1") + |> pog.returning(decode.at([0], decode.string)) + |> pog.execute(db) + == Error(expected) pog.disconnect(db) } @@ -464,11 +438,13 @@ pub fn expected_five_millis_timeout_test() { use <- run_with_timeout(20) let db = start_default() - pog.query("select sub.ret from (select pg_sleep(0.05), 'OK' as ret) as sub") - |> pog.timeout(5) - |> pog.returning(decode.at([0], decode.string)) - |> pog.execute(db) - |> should.equal(Error(pog.QueryTimeout)) + assert pog.query( + "select sub.ret from (select pg_sleep(0.05), 'OK' as ret) as sub", + ) + |> pog.timeout(5) + |> pog.returning(decode.at([0], decode.string)) + |> pog.execute(db) + == Error(pog.QueryTimeout) pog.disconnect(db) } @@ -477,11 +453,13 @@ pub fn expected_ten_millis_no_timeout_test() { use <- run_with_timeout(20) let db = start_default() - pog.query("select sub.ret from (select pg_sleep(0.01), 'OK' as ret) as sub") - |> pog.timeout(30) - |> pog.returning(decode.at([0], decode.string)) - |> pog.execute(db) - |> should.equal(Ok(pog.Returned(1, ["Ok"]))) + assert pog.query( + "select sub.ret from (select pg_sleep(0.01), 'OK' as ret) as sub", + ) + |> pog.timeout(30) + |> pog.returning(decode.at([0], decode.string)) + |> pog.execute(db) + == Ok(pog.Returned(1, ["Ok"])) pog.disconnect(db) } @@ -493,10 +471,12 @@ pub fn expected_ten_millis_no_default_timeout_test() { |> pog.default_timeout(30) |> pog.connect - pog.query("select sub.ret from (select pg_sleep(0.01), 'OK' as ret) as sub") - |> pog.returning(decode.at([0], decode.string)) - |> pog.execute(db) - |> should.equal(Ok(pog.Returned(1, ["Ok"]))) + assert pog.query( + "select sub.ret from (select pg_sleep(0.01), 'OK' as ret) as sub", + ) + |> pog.returning(decode.at([0], decode.string)) + |> pog.execute(db) + == Ok(pog.Returned(1, ["Ok"])) pog.disconnect(db) } @@ -535,19 +515,18 @@ pub fn expected_maps_test() { }) |> pog.execute(db) - returned.count - |> 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), - ), - ]) + assert returned.count == 1 + assert returned.rows + == [ + #( + id, + "neo", + True, + ["black"], + pog.Timestamp(pog.Date(2022, 10, 10), pog.Time(11, 30, 30, 0)), + pog.Date(2020, 3, 4), + ), + ] pog.disconnect(db) } From e8264667e52650082cd2d89702c2ac4a24d86c56 Mon Sep 17 00:00:00 2001 From: Giacomo Cavalieri Date: Thu, 3 Jul 2025 18:57:16 +0200 Subject: [PATCH 4/5] use timestamp.Timestamp type --- CHANGELOG.md | 12 +++++++++--- gleam.toml | 1 + manifest.toml | 2 ++ src/pog.gleam | 17 +++++++++-------- src/pog_ffi.erl | 1 + test/pog_test.gleam | 42 +++++++++++++++++++----------------------- 6 files changed, 41 insertions(+), 34 deletions(-) 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 6fbe5a1..2927119 100644 --- a/gleam.toml +++ b/gleam.toml @@ -18,6 +18,7 @@ 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" diff --git a/manifest.toml b/manifest.toml index 11a2f71..4a6e23c 100644 --- a/manifest.toml +++ b/manifest.toml @@ -5,6 +5,7 @@ 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_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" }, @@ -14,5 +15,6 @@ packages = [ [requirements] exception = { version = ">= 2.0.0 and < 3.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 b76e3d2..3bf3037 100644 --- a/test/pog_test.gleam +++ b/test/pog_test.gleam @@ -1,6 +1,8 @@ import exception import gleam/dynamic/decode.{type Decoder} import gleam/option.{None, Some} +import gleam/time/calendar +import gleam/time/timestamp import gleeunit import pog @@ -126,12 +128,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" @@ -157,14 +161,7 @@ pub fn selecting_rows_test() { assert returned.count == 1 assert returned.rows == [ - #( - 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) @@ -358,7 +355,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(), @@ -484,12 +485,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" @@ -518,14 +521,7 @@ pub fn expected_maps_test() { assert returned.count == 1 assert returned.rows == [ - #( - 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) From 715de345b447822d63ac25e4dd64185380463a51 Mon Sep 17 00:00:00 2001 From: Giacomo Cavalieri Date: Sun, 6 Jul 2025 17:54:51 +0200 Subject: [PATCH 5/5] Add gleam_erlang dependency back, remove assert This reverts commit 8eccbb2b424e9fe7ce1140bf9e61f2235e0937d5. --- gleam.toml | 3 +- manifest.toml | 4 +- test/pog_test.gleam | 197 ++++++++++++++++++++++++-------------------- 3 files changed, 113 insertions(+), 91 deletions(-) diff --git a/gleam.toml b/gleam.toml index 2927119..deb3647 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,6 +1,6 @@ name = "pog" version = "3.3.0" -gleam = ">= 1.11.0" +gleam = ">= 1.4.0" licences = ["Apache-2.0"] description = "A PostgreSQL database client for Gleam, based on PGO" @@ -23,6 +23,7 @@ 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 = ">= 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 4a6e23c..4eea178 100644 --- a/manifest.toml +++ b/manifest.toml @@ -3,7 +3,8 @@ 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 = "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" }, @@ -14,6 +15,7 @@ packages = [ [requirements] exception = { version = ">= 2.0.0 and < 3.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" } diff --git a/test/pog_test.gleam b/test/pog_test.gleam index 3bf3037..20b119b 100644 --- a/test/pog_test.gleam +++ b/test/pog_test.gleam @@ -1,21 +1,20 @@ 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 pub fn main() { gleeunit.main() } -pub type Timeout(a) { - Timeout(time: Int, next: fn() -> a) -} - pub fn run_with_timeout(time: Int, next: fn() -> a) { - Timeout(time, next) + let timeout = atom.create("timeout") + #(timeout, time, next) } pub fn url_config_everything_test() { @@ -27,7 +26,8 @@ pub fn url_config_everything_test() { |> pog.user("u") |> pog.password(Some("p")) - assert pog.url_config("postgres://u:p@db.test:1234/my_db") == Ok(expected) + pog.url_config("postgres://u:p@db.test:1234/my_db") + |> should.equal(Ok(expected)) } pub fn url_config_alternative_postgres_protocol_test() { @@ -38,12 +38,13 @@ pub fn url_config_alternative_postgres_protocol_test() { |> pog.database("my_db") |> pog.user("u") |> pog.password(Some("p")) - - assert pog.url_config("postgresql://u:p@db.test:1234/my_db") == Ok(expected) + pog.url_config("postgresql://u:p@db.test:1234/my_db") + |> should.equal(Ok(expected)) } pub fn url_config_not_postgres_protocol_test() { - assert pog.url_config("foo://u:p@db.test:1234/my_db") == Error(Nil) + pog.url_config("foo://u:p@db.test:1234/my_db") + |> should.equal(Error(Nil)) } pub fn url_config_no_password_test() { @@ -54,8 +55,8 @@ pub fn url_config_no_password_test() { |> pog.database("my_db") |> pog.user("u") |> pog.password(None) - - assert pog.url_config("postgres://u@db.test:1234/my_db") == Ok(expected) + pog.url_config("postgres://u@db.test:1234/my_db") + |> should.equal(Ok(expected)) } pub fn url_config_no_port_test() { @@ -66,12 +67,13 @@ pub fn url_config_no_port_test() { |> pog.database("my_db") |> pog.user("u") |> pog.password(None) - - assert pog.url_config("postgres://u@db.test/my_db") == Ok(expected) + pog.url_config("postgres://u@db.test/my_db") + |> should.equal(Ok(expected)) } pub fn url_config_path_slash_test() { - assert pog.url_config("postgres://u:p@db.test:1234/my_db/foo") == Error(Nil) + pog.url_config("postgres://u:p@db.test:1234/my_db/foo") + |> should.equal(Error(Nil)) } fn start_default() { @@ -98,8 +100,10 @@ pub fn inserting_new_rows_test() { (DEFAULT, 'felix', false, ARRAY ['grey'], now(), '2020-03-05')" let assert Ok(returned) = pog.query(sql) |> pog.execute(db) - assert returned.count == 2 - assert returned.rows == [] + returned.count + |> should.equal(2) + returned.rows + |> should.equal([]) pog.disconnect(db) } @@ -120,8 +124,10 @@ pub fn inserting_new_rows_and_returning_test() { |> pog.returning(decode.at([0], decode.string)) |> pog.execute(db) - assert returned.count == 2 - assert returned.rows == ["bill", "felix"] + returned.count + |> should.equal(2) + returned.rows + |> should.equal(["bill", "felix"]) pog.disconnect(db) } @@ -158,11 +164,12 @@ pub fn selecting_rows_test() { }) |> pog.execute(db) - assert returned.count == 1 - assert returned.rows - == [ - #(id, "neo", True, ["black"], timestamp, pog.Date(2020, 3, 4)), - ] + returned.count + |> should.equal(1) + returned.rows + |> should.equal([ + #(id, "neo", True, ["black"], timestamp, pog.Date(2020, 3, 4)), + ]) pog.disconnect(db) } @@ -174,9 +181,12 @@ pub fn invalid_sql_test() { let assert Error(pog.PostgresqlError(code, name, message)) = pog.query(sql) |> pog.execute(db) - assert code == "42601" - assert name == "syntax_error" - assert message == "syntax error at or near \"select\"" + code + |> should.equal("42601") + name + |> should.equal("syntax_error") + message + |> should.equal("syntax error at or near \"select\"") pog.disconnect(db) } @@ -194,10 +204,16 @@ pub fn insert_constraint_error_test() { let assert Error(pog.ConstraintViolated(message, constraint, detail)) = pog.query(sql) |> pog.execute(db) - assert constraint == "cats_pkey" - assert detail == "Key (id)=(900) already exists." - assert message - == "duplicate key value violates unique constraint \"cats_pkey\"" + constraint + |> should.equal("cats_pkey") + + detail + |> should.equal("Key (id)=(900) already exists.") + + message + |> should.equal( + "duplicate key value violates unique constraint \"cats_pkey\"", + ) pog.disconnect(db) } @@ -209,9 +225,12 @@ pub fn select_from_unknown_table_test() { let assert Error(pog.PostgresqlError(code, name, message)) = pog.query(sql) |> pog.execute(db) - assert code == "42P01" - assert name == "undefined_table" - assert message == "relation \"unknown\" does not exist" + code + |> should.equal("42P01") + name + |> should.equal("undefined_table") + message + |> should.equal("relation \"unknown\" does not exist") pog.disconnect(db) } @@ -227,10 +246,14 @@ pub fn insert_with_incorrect_type_test() { let assert Error(pog.PostgresqlError(code, name, message)) = pog.query(sql) |> pog.execute(db) - assert code == "42804" - assert name == "datatype_mismatch" - assert message - == "column \"id\" is of type integer but expression is of type boolean" + code + |> should.equal("42804") + name + |> should.equal("datatype_mismatch") + message + |> should.equal( + "column \"id\" is of type integer but expression is of type boolean", + ) pog.disconnect(db) } @@ -239,8 +262,9 @@ pub fn execute_with_wrong_number_of_arguments_test() { let db = start_default() let sql = "SELECT * FROM cats WHERE id = $1" - assert pog.execute(pog.query(sql), db) - == Error(pog.UnexpectedArgumentCount(expected: 1, got: 0)) + pog.query(sql) + |> pog.execute(db) + |> should.equal(Error(pog.UnexpectedArgumentCount(expected: 1, got: 0))) pog.disconnect(db) } @@ -252,21 +276,21 @@ fn assert_roundtrip( encoder: fn(a) -> pog.Value, decoder: Decoder(a), ) -> pog.Connection { - assert pog.query("select $1::" <> type_name) - |> pog.parameter(encoder(value)) - |> pog.returning(decode.at([0], decoder)) - |> pog.execute(db) - == Ok(pog.Returned(count: 1, rows: [value])) + pog.query("select $1::" <> type_name) + |> pog.parameter(encoder(value)) + |> pog.returning(decode.at([0], decoder)) + |> pog.execute(db) + |> should.equal(Ok(pog.Returned(count: 1, rows: [value]))) db } pub fn null_test() { let db = start_default() - assert pog.query("select $1") - |> pog.parameter(pog.null()) - |> pog.returning(decode.at([0], decode.optional(decode.int))) - |> pog.execute(db) - == Ok(pog.Returned(count: 1, rows: [None])) + pog.query("select $1") + |> pog.parameter(pog.null()) + |> pog.returning(decode.at([0], decode.optional(decode.int))) + |> pog.execute(db) + |> should.equal(Ok(pog.Returned(count: 1, rows: [None]))) pog.disconnect(db) } @@ -410,27 +434,27 @@ pub fn nullable_test() { pub fn expected_argument_type_test() { let db = start_default() - assert pog.query("select $1::int") - |> pog.returning(decode.at([0], decode.string)) - |> pog.parameter(pog.float(1.2)) - |> pog.execute(db) - == Error(pog.UnexpectedArgumentType("int4", "1.2")) + pog.query("select $1::int") + |> pog.returning(decode.at([0], decode.string)) + |> pog.parameter(pog.float(1.2)) + |> pog.execute(db) + |> should.equal(Error(pog.UnexpectedArgumentType("int4", "1.2"))) pog.disconnect(db) } pub fn expected_return_type_test() { let db = start_default() - - let expected = - pog.UnexpectedResultType([ - decode.DecodeError(expected: "String", found: "Int", path: ["0"]), - ]) - - assert pog.query("select 1") - |> pog.returning(decode.at([0], decode.string)) - |> pog.execute(db) - == Error(expected) + pog.query("select 1") + |> pog.returning(decode.at([0], decode.string)) + |> pog.execute(db) + |> should.equal( + Error( + pog.UnexpectedResultType([ + decode.DecodeError(expected: "String", found: "Int", path: ["0"]), + ]), + ), + ) pog.disconnect(db) } @@ -439,13 +463,11 @@ pub fn expected_five_millis_timeout_test() { use <- run_with_timeout(20) let db = start_default() - assert pog.query( - "select sub.ret from (select pg_sleep(0.05), 'OK' as ret) as sub", - ) - |> pog.timeout(5) - |> pog.returning(decode.at([0], decode.string)) - |> pog.execute(db) - == Error(pog.QueryTimeout) + pog.query("select sub.ret from (select pg_sleep(0.05), 'OK' as ret) as sub") + |> pog.timeout(5) + |> pog.returning(decode.at([0], decode.string)) + |> pog.execute(db) + |> should.equal(Error(pog.QueryTimeout)) pog.disconnect(db) } @@ -454,13 +476,11 @@ pub fn expected_ten_millis_no_timeout_test() { use <- run_with_timeout(20) let db = start_default() - assert pog.query( - "select sub.ret from (select pg_sleep(0.01), 'OK' as ret) as sub", - ) - |> pog.timeout(30) - |> pog.returning(decode.at([0], decode.string)) - |> pog.execute(db) - == Ok(pog.Returned(1, ["Ok"])) + pog.query("select sub.ret from (select pg_sleep(0.01), 'OK' as ret) as sub") + |> pog.timeout(30) + |> pog.returning(decode.at([0], decode.string)) + |> pog.execute(db) + |> should.equal(Ok(pog.Returned(1, ["Ok"]))) pog.disconnect(db) } @@ -472,12 +492,10 @@ pub fn expected_ten_millis_no_default_timeout_test() { |> pog.default_timeout(30) |> pog.connect - assert pog.query( - "select sub.ret from (select pg_sleep(0.01), 'OK' as ret) as sub", - ) - |> pog.returning(decode.at([0], decode.string)) - |> pog.execute(db) - == Ok(pog.Returned(1, ["Ok"])) + pog.query("select sub.ret from (select pg_sleep(0.01), 'OK' as ret) as sub") + |> pog.returning(decode.at([0], decode.string)) + |> pog.execute(db) + |> should.equal(Ok(pog.Returned(1, ["Ok"]))) pog.disconnect(db) } @@ -518,11 +536,12 @@ pub fn expected_maps_test() { }) |> pog.execute(db) - assert returned.count == 1 - assert returned.rows - == [ - #(id, "neo", True, ["black"], timestamp, pog.Date(2020, 3, 4)), - ] + returned.count + |> should.equal(1) + returned.rows + |> should.equal([ + #(id, "neo", True, ["black"], timestamp, pog.Date(2020, 3, 4)), + ]) pog.disconnect(db) }