Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix seconds being returned as seconds.milliseconds in timestamps #36

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
## v0.15.0 - Unreleased

- Ensure `ssl` and `pgo` are running before using `gleam_pgo`.
- Fix a bug where timestamps are returned as `#(#(Int, Int, Int), #(Int, Int, Float))`
leading `decode_timestamp` to fail. `decode_timestamp` now properly returns
`#(#(Int, Int, Int), #(Int, Int, Float))`.

## v0.14.0 - 2024-08-15

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ By default, `pgo` will return every selected value from your query as a tuple.
In case you want a different output, you can activate `rows_as_maps` in `Config`.
Once activated, every returned rows will take the form of a `Dict`.

## About timestamps

If you're used to manage timestamps in Postgres, you know timestamp are stored
as bytes, and clients transforms them in their desired formats. `pgo` uses the
Erlang datetime convention, i.e. managing a timestamp as a tuple
`#(#(year, month, day), #(hour, minutes, seconds))`. When querying a timestamp,
unless you're casting it (in ISO-8601 for example) voluntarily in your SQL query,
`pgo` will give you such a tuple. You're expected to decode it using
`pgo.decode_timestamp` that will do the work of decoding it for you.

## Atom generation

Creating a connection pool with the `pgo.connect` function dynamically generates
Expand Down
17 changes: 13 additions & 4 deletions src/gleam/pgo.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//// Gleam wrapper around pgo library

import gleam/dynamic.{type DecodeErrors, type Decoder, type Dynamic}
import gleam/int
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/result
Expand Down Expand Up @@ -170,7 +171,7 @@ pub fn array(a: List(a)) -> Value

/// Coerce a timestamp represented as `#(#(year, month, day), #(hour, minute, second))` into a `Value`.
@external(erlang, "gleam_pgo_ffi", "coerce")
pub fn timestamp(a: #(#(Int, Int, Int), #(Int, Int, Int))) -> Value
pub fn timestamp(a: #(#(Int, Int, Int), #(Int, Int, Float))) -> Value

/// Coerce a date represented as `#(year, month, day)` into a `Value`.
@external(erlang, "gleam_pgo_ffi", "coerce")
Expand Down Expand Up @@ -525,16 +526,24 @@ pub fn error_code_name(error_code: String) -> Result(String, Nil) {
}
}

/// Checks to see if the value is formatted as `#(#(Int, Int, Int), #(Int, Int, Int))`
/// to represent `#(#(year, month, day), #(hour, minute, second))`, and returns the
/// Checks to see if the value is formatted as `#(#(Int, Int, Int), #(Int, Int, Float))`
/// to represent `#(#(year, month, day), #(hour, minute, second.milliseconds))`, and returns the
/// value if it is.
pub fn decode_timestamp(value: dynamic.Dynamic) {
dynamic.tuple2(
dynamic.tuple3(dynamic.int, dynamic.int, dynamic.int),
dynamic.tuple3(dynamic.int, dynamic.int, dynamic.int),
dynamic.tuple3(dynamic.int, dynamic.int, decode_seconds),
)(value)
}

fn decode_int_to_float(value: dynamic.Dynamic) {
dynamic.int(value) |> result.map(int.to_float)
}

fn decode_seconds(value: dynamic.Dynamic) {
dynamic.any([dynamic.float, decode_int_to_float])(value)
}

/// Checks to see if the value is formatted as `#(Int, Int, Int)` to represent a date
/// as `#(year, month, day)`, and returns the value if it is.
pub fn decode_date(value: dynamic.Dynamic) {
Expand Down
6 changes: 3 additions & 3 deletions test/gleam/pgo_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ pub fn selecting_rows_test() {
|> should.equal(1)
returned.rows
|> should.equal([
#(id, "neo", True, ["black"], #(#(2022, 10, 10), #(11, 30, 30)), #(
#(id, "neo", True, ["black"], #(#(2022, 10, 10), #(11, 30, 30.0)), #(
2020,
3,
4,
Expand Down Expand Up @@ -362,7 +362,7 @@ pub fn array_test() {
pub fn datetime_test() {
start_default()
|> assert_roundtrip(
#(#(2022, 10, 12), #(11, 30, 33)),
#(#(2022, 10, 12), #(11, 30, 33.0)),
"timestamp",
pgo.timestamp,
pgo.decode_timestamp,
Expand Down Expand Up @@ -464,7 +464,7 @@ pub fn expected_maps_test() {
|> should.equal(1)
returned.rows
|> should.equal([
#(id, "neo", True, ["black"], #(#(2022, 10, 10), #(11, 30, 30)), #(
#(id, "neo", True, ["black"], #(#(2022, 10, 10), #(11, 30, 30.0)), #(
2020,
3,
4,
Expand Down
Loading