Skip to content

Commit 3e9ea98

Browse files
Add support for Elixir Durations (#696)
1 parent 85bac2d commit 3e9ea98

File tree

3 files changed

+186
-16
lines changed

3 files changed

+186
-16
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
- pg:
5151
version: 14
5252
pair:
53-
elixir: 1.16.2
53+
elixir: 1.17.1
5454
otp: 25.3
5555
lint: lint
5656
env:

lib/postgrex/extensions/interval.ex

Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,93 @@ defmodule Postgrex.Extensions.Interval do
33
import Postgrex.BinaryUtils, warn: false
44
use Postgrex.BinaryExtension, send: "interval_send"
55

6-
def encode(_) do
7-
quote location: :keep do
8-
%Postgrex.Interval{months: months, days: days, secs: secs, microsecs: microsecs} ->
9-
microsecs = secs * 1_000_000 + microsecs
10-
<<16::int32(), microsecs::int64(), days::int32(), months::int32()>>
11-
12-
other ->
13-
raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Postgrex.Interval)
6+
def init(opts), do: Keyword.get(opts, :interval_decode_type, Postgrex.Interval)
7+
8+
if Code.ensure_loaded?(Duration) do
9+
def encode(_) do
10+
quote location: :keep do
11+
%Postgrex.Interval{months: months, days: days, secs: seconds, microsecs: microseconds} ->
12+
microseconds = 1_000_000 * seconds + microseconds
13+
<<16::int32(), microseconds::int64(), days::int32(), months::int32()>>
14+
15+
%Duration{
16+
year: years,
17+
month: months,
18+
week: weeks,
19+
day: days,
20+
hour: hours,
21+
minute: minutes,
22+
second: seconds,
23+
microsecond: {microseconds, _precision}
24+
} ->
25+
months = 12 * years + months
26+
days = 7 * weeks + days
27+
microseconds = 1_000_000 * (3600 * hours + 60 * minutes + seconds) + microseconds
28+
<<16::int32(), microseconds::int64(), days::int32(), months::int32()>>
29+
30+
other ->
31+
raise DBConnection.EncodeError,
32+
Postgrex.Utils.encode_msg(other, {Postgrex.Interval, Duration})
33+
end
34+
end
35+
36+
def decode(type) do
37+
quote location: :keep do
38+
<<16::int32(), microseconds::int64(), days::int32(), months::int32()>> ->
39+
seconds = div(microseconds, 1_000_000)
40+
microseconds = rem(microseconds, 1_000_000)
41+
42+
case unquote(type) do
43+
Postgrex.Interval ->
44+
%Postgrex.Interval{
45+
months: months,
46+
days: days,
47+
secs: seconds,
48+
microsecs: microseconds
49+
}
50+
51+
Duration ->
52+
years = div(months, 12)
53+
months = rem(months, 12)
54+
weeks = div(days, 7)
55+
days = rem(days, 7)
56+
minutes = div(seconds, 60)
57+
seconds = rem(seconds, 60)
58+
hours = div(minutes, 60)
59+
minutes = rem(minutes, 60)
60+
61+
Duration.new!(
62+
year: years,
63+
month: months,
64+
week: weeks,
65+
day: days,
66+
hour: hours,
67+
minute: minutes,
68+
second: seconds,
69+
microsecond: {microseconds, 6}
70+
)
71+
end
72+
end
73+
end
74+
else
75+
def encode(_) do
76+
quote location: :keep do
77+
%Postgrex.Interval{months: months, days: days, secs: seconds, microsecs: microseconds} ->
78+
microseconds = 1_000_000 * seconds + microseconds
79+
<<16::int32(), microseconds::int64(), days::int32(), months::int32()>>
80+
81+
other ->
82+
raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Postgrex.Interval)
83+
end
1484
end
15-
end
1685

17-
def decode(_) do
18-
quote location: :keep do
19-
<<16::int32(), microsecs::int64(), days::int32(), months::int32()>> ->
20-
secs = div(microsecs, 1_000_000)
21-
microsecs = rem(microsecs, 1_000_000)
22-
%Postgrex.Interval{months: months, days: days, secs: secs, microsecs: microsecs}
86+
def decode(_) do
87+
quote location: :keep do
88+
<<16::int32(), microseconds::int64(), days::int32(), months::int32()>> ->
89+
seconds = div(microseconds, 1_000_000)
90+
microseconds = rem(microseconds, 1_000_000)
91+
%Postgrex.Interval{months: months, days: days, secs: seconds, microsecs: microseconds}
92+
end
2393
end
2494
end
2595
end

test/query_test.exs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ defmodule QueryTest do
44
import ExUnit.CaptureLog
55
alias Postgrex, as: P
66

7+
Postgrex.Types.define(Postgrex.ElixirDurationTypes, [], interval_decode_type: Duration)
8+
79
setup context do
810
opts = [
911
database: "postgrex_test",
@@ -133,6 +135,66 @@ defmodule QueryTest do
133135
query("SELECT interval '10240000 microseconds'", [])
134136
end
135137

138+
if Version.match?(System.version(), ">= 1.17.0") do
139+
test "decode interval with Elixir Duration" do
140+
opts = [database: "postgrex_test", backoff_type: :stop, types: Postgrex.ElixirDurationTypes]
141+
{:ok, pid} = P.start_link(opts)
142+
143+
assert [[%Duration{microsecond: {0, 6}}]] =
144+
P.query!(pid, "SELECT interval '0'", []).rows
145+
146+
assert [[%Duration{year: 100, microsecond: {0, 6}}]] =
147+
P.query!(pid, "SELECT interval '100 years'", []).rows
148+
149+
assert [[%Duration{month: 10, microsecond: {0, 6}}]] =
150+
P.query!(pid, "SELECT interval '10 months'", []).rows
151+
152+
assert [[%Duration{week: 100, microsecond: {0, 6}}]] =
153+
P.query!(pid, "SELECT interval '100 weeks'", []).rows
154+
155+
assert [[%Duration{day: 5, microsecond: {0, 6}}]] =
156+
P.query!(pid, "SELECT interval '5 days'", []).rows
157+
158+
assert [[%Duration{hour: 100, microsecond: {0, 6}}]] =
159+
P.query!(pid, "SELECT interval '100 hours'", []).rows
160+
161+
assert [[%Duration{minute: 10, microsecond: {0, 6}}]] =
162+
P.query!(pid, "SELECT interval '10 minutes'", []).rows
163+
164+
assert [[%Duration{second: 10, microsecond: {0, 6}}]] =
165+
P.query!(pid, "SELECT interval '10 secs'", []).rows
166+
167+
assert [
168+
[
169+
%Duration{
170+
year: 1,
171+
month: 2,
172+
week: 5,
173+
day: 5,
174+
hour: 3,
175+
minute: 2,
176+
second: 1,
177+
microsecond: {0, 6}
178+
}
179+
]
180+
] =
181+
P.query!(
182+
pid,
183+
"SELECT interval '1 year 2 months 40 days 3 hours 2 minutes 1 seconds'",
184+
[]
185+
).rows
186+
187+
assert [[%Duration{second: 53, microsecond: {204_800, 6}}]] =
188+
P.query!(pid, "SELECT interval '53 secs 204800 microseconds'", []).rows
189+
190+
assert [[%Duration{second: 10, microsecond: {240_000, 6}}]] =
191+
P.query!(pid, "SELECT interval '10240000 microseconds'", []).rows
192+
193+
assert [[[%Duration{second: 10, microsecond: {240_000, 6}}]]] =
194+
P.query!(pid, "SELECT ARRAY[interval '10240000 microseconds']", []).rows
195+
end
196+
end
197+
136198
test "decode point", context do
137199
assert [[%Postgrex.Point{x: -97.5, y: 100.1}]] ==
138200
query("SELECT point(-97.5, 100.1)::point", [])
@@ -896,6 +958,44 @@ defmodule QueryTest do
896958
])
897959
end
898960

961+
if Version.match?(System.version(), ">= 1.17.0") do
962+
test "encode interval with Elixir duration", context do
963+
assert [[%Postgrex.Interval{months: 0, days: 0, secs: 0, microsecs: 0}]] =
964+
query("SELECT $1::interval", [Duration.new!([])])
965+
966+
assert [[%Postgrex.Interval{months: 100, days: 0, secs: 0, microsecs: 0}]] =
967+
query("SELECT $1::interval", [Duration.new!(month: 100)])
968+
969+
assert [[%Postgrex.Interval{months: 0, days: 100, secs: 0, microsecs: 0}]] =
970+
query("SELECT $1::interval", [Duration.new!(day: 100)])
971+
972+
assert [[%Postgrex.Interval{months: 0, days: 0, secs: 100, microsecs: 0}]] =
973+
query("SELECT $1::interval", [Duration.new!(second: 100)])
974+
975+
assert [[%Postgrex.Interval{months: 14, days: 40, secs: 10920, microsecs: 0}]] =
976+
query("SELECT $1::interval", [Duration.new!(month: 14, day: 40, second: 10920)])
977+
978+
assert [[%Postgrex.Interval{months: 14, days: 40, secs: 10921, microsecs: 24000}]] =
979+
query("SELECT $1::interval", [
980+
Duration.new!(month: 14, day: 40, second: 10920, microsecond: {1_024_000, 0})
981+
])
982+
983+
assert [[%Postgrex.Interval{months: 74, days: 54, secs: 46921, microsecs: 24000}]] =
984+
query("SELECT $1::interval", [
985+
Duration.new!(
986+
year: 5,
987+
month: 14,
988+
week: 2,
989+
day: 40,
990+
hour: 9,
991+
minute: 60,
992+
second: 10920,
993+
microsecond: {1_024_000, 0}
994+
)
995+
])
996+
end
997+
end
998+
899999
test "encode arrays", context do
9001000
assert [[[]]] = query("SELECT $1::integer[]", [[]])
9011001
assert [[[1]]] = query("SELECT $1::integer[]", [[1]])

0 commit comments

Comments
 (0)