Skip to content

Commit 2925cde

Browse files
committed
Adding date_format and binary_format to run() options. Also slightly changing the binary pseudo type to better match the other drivers.
1 parent 9fe98ec commit 2925cde

File tree

8 files changed

+169
-44
lines changed

8 files changed

+169
-44
lines changed

lib/rethinkdb/connection.ex

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ defmodule RethinkDB.Connection do
9696
* `durability` - possible values are 'hard' and 'soft'. In soft durability mode RethinkDB will acknowledge the write immediately after receiving it, but before the write has been committed to disk.
9797
* `noreply` - set to true to not receive the result object or cursor and return immediately.
9898
* `profile` - whether or not to return a profile of the query’s execution (default: false).
99+
* `time_format` - what format to return times in (default: :native). Set this to :raw if you want times returned as JSON objects for exporting.
100+
* `binary_format` - what format to return binary data in (default: :native). Set this to :raw if you want the raw pseudotype.
99101
"""
100102
def run(query, conn, opts \\ []) do
101103
timeout = Dict.get(opts, :timeout, 5000)
@@ -110,7 +112,7 @@ defmodule RethinkDB.Connection do
110112
false -> {:query, query}
111113
end
112114
case Connection.call(conn, msg, timeout) do
113-
{response, token} -> RethinkDB.Response.parse(response, token, conn)
115+
{response, token} -> RethinkDB.Response.parse(response, token, conn, opts)
114116
:noreply -> :ok
115117
result -> result
116118
end
@@ -122,9 +124,9 @@ defmodule RethinkDB.Connection do
122124
Since a feed is tied to a particular connection, no connection is needed when calling
123125
`next`.
124126
"""
125-
def next(%{token: token, pid: pid}) do
127+
def next(%{token: token, pid: pid, opts: opts}) do
126128
case Connection.call(pid, {:continue, token}, :infinity) do
127-
{response, token} -> RethinkDB.Response.parse(response, token, pid)
129+
{response, token} -> RethinkDB.Response.parse(response, token, pid, opts)
128130
x -> x
129131
end
130132
end
@@ -137,15 +139,15 @@ defmodule RethinkDB.Connection do
137139
"""
138140
def close(%{token: token, pid: pid}) do
139141
{response, token} = Connection.call(pid, {:stop, token}, :infinity)
140-
RethinkDB.Response.parse(response, token, pid)
142+
RethinkDB.Response.parse(response, token, pid, [])
141143
end
142144

143145
@doc """
144146
`noreply_wait` ensures that previous queries with the noreply flag have been processed by the server. Note that this guarantee only applies to queries run on the given connection.
145147
"""
146148
def noreply_wait(conn, timeout \\ 5000) do
147149
{response, token} = Connection.call(conn, :noreply_wait, timeout)
148-
case RethinkDB.Response.parse(response, token, conn) do
150+
case RethinkDB.Response.parse(response, token, conn, []) do
149151
%RethinkDB.Response{data: %{"t" => 4}} -> :ok
150152
r -> r
151153
end

lib/rethinkdb/pseudotypes.ex

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ defmodule RethinkDB.Pseudotypes do
44
@moduledoc false
55
defstruct data: nil
66

7-
def parse(%{"$reql_type$" => "BINARY", "data" => data}) do
8-
%__MODULE__{data: :base64.decode(data)}
7+
def parse(%{"$reql_type$" => "BINARY", "data" => data}, opts) do
8+
case Dict.get(opts, :binary_format) do
9+
:raw ->
10+
%__MODULE__{data: data}
11+
_ ->
12+
:base64.decode(data)
13+
end
914
end
1015
end
1116

@@ -41,33 +46,56 @@ defmodule RethinkDB.Pseudotypes do
4146
@moduledoc false
4247
defstruct epoch_time: nil, timezone: nil
4348

44-
def parse(%{"$reql_type$" => "TIME", "epoch_time" => epoch_time, "timezone" => timezone}) do
45-
%__MODULE__{epoch_time: epoch_time, timezone: timezone}
49+
def parse(%{"$reql_type$" => "TIME", "epoch_time" => epoch_time, "timezone" => timezone}, opts) do
50+
case Dict.get(opts, :time_format) do
51+
:raw ->
52+
%__MODULE__{epoch_time: epoch_time, timezone: timezone}
53+
_ ->
54+
{seconds, ""} = Calendar.ISO.parse_offset(timezone)
55+
zone_abbr = case seconds do
56+
0 -> "UTC"
57+
_ -> timezone
58+
end
59+
negative = seconds < 0
60+
seconds = abs(seconds)
61+
time_zone = case {div(seconds,3600),rem(seconds,3600)} do
62+
{0,0} -> "Etc/UTC"
63+
{hours,0} ->
64+
"Etc/GMT" <> if negative do "+" else "-" end <> Integer.to_string(hours)
65+
{hours,seconds} ->
66+
"Etc/GMT" <> if negative do "+" else "-" end <> Integer.to_string(hours) <> ":" <>
67+
String.pad_leading(Integer.to_string(seconds), 2, "0")
68+
end
69+
epoch_time * 1000
70+
|> trunc()
71+
|> DateTime.from_unix!(:millisecond)
72+
|> struct(utc_offset: seconds, zone_abbr: zone_abbr, time_zone: time_zone)
73+
end
4674
end
4775
end
4876

49-
def convert_reql_pseudotypes(nil), do: nil
50-
def convert_reql_pseudotypes(%{"$reql_type$" => "BINARY"} = data) do
51-
Binary.parse(data)
77+
def convert_reql_pseudotypes(nil, opts), do: nil
78+
def convert_reql_pseudotypes(%{"$reql_type$" => "BINARY"} = data, opts) do
79+
Binary.parse(data, opts)
5280
end
53-
def convert_reql_pseudotypes(%{"$reql_type$" => "GEOMETRY"} = data) do
81+
def convert_reql_pseudotypes(%{"$reql_type$" => "GEOMETRY"} = data, opts) do
5482
Geometry.parse(data)
5583
end
56-
def convert_reql_pseudotypes(%{"$reql_type$" => "GROUPED_DATA"} = data) do
84+
def convert_reql_pseudotypes(%{"$reql_type$" => "GROUPED_DATA"} = data, opts) do
5785
parse_grouped_data(data)
5886
end
59-
def convert_reql_pseudotypes(%{"$reql_type$" => "TIME"} = data) do
60-
Time.parse(data)
87+
def convert_reql_pseudotypes(%{"$reql_type$" => "TIME"} = data, opts) do
88+
Time.parse(data, opts)
6189
end
62-
def convert_reql_pseudotypes(list) when is_list(list) do
63-
Enum.map(list, &convert_reql_pseudotypes/1)
90+
def convert_reql_pseudotypes(list, opts) when is_list(list) do
91+
Enum.map(list, fn data -> convert_reql_pseudotypes(data, opts) end)
6492
end
65-
def convert_reql_pseudotypes(map) when is_map(map) do
93+
def convert_reql_pseudotypes(map, opts) when is_map(map) do
6694
Enum.map(map, fn {k, v} ->
67-
{k, convert_reql_pseudotypes(v)}
95+
{k, convert_reql_pseudotypes(v, opts)}
6896
end) |> Enum.into(%{})
6997
end
70-
def convert_reql_pseudotypes(string), do: string
98+
def convert_reql_pseudotypes(string, opts), do: string
7199

72100
def parse_grouped_data(%{"$reql_type$" => "GROUPED_DATA", "data" => data}) do
73101
Enum.map(data, fn ([k, data]) ->

lib/rethinkdb/query.ex

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,13 @@ defmodule RethinkDB.Query do
188188
@doc """
189189
Encapsulate binary data within a query.
190190
191+
The type of data binary accepts depends on the client language. In
192+
Elixir, it expects a Binary. Using a Buffer object within a query implies the use of binary and the ReQL driver will automatically perform the coercion.
193+
194+
Binary objects returned to the client in JavaScript will also be Node.js
195+
Buffer objects. This can be changed with the binaryFormat option provided
196+
to run to return “raw” objects.
197+
191198
Only a limited subset of ReQL commands may be chained after binary:
192199
193200
* coerce_to can coerce binary objects to string types
@@ -198,9 +205,9 @@ defmodule RethinkDB.Query do
198205
* info will return information on a binary object.
199206
"""
200207
@spec binary(Q.reql_binary) :: Q.t
201-
def binary(%RethinkDB.Pseudotypes.Binary{data: data}), do: binary(data)
202-
def binary(data), do: do_binary(%{"$reql_type$" => "BINARY", "data" => :base64.encode(data)})
203-
def do_binary(data), do: %Q{query: [155, [data]]}
208+
def binary(%RethinkDB.Pseudotypes.Binary{data: data}), do: do_binary(data)
209+
def binary(data), do: do_binary(:base64.encode(data))
210+
def do_binary(data), do: %Q{query: [155, [%{"$reql_type$" => "BINARY", "data" => data}]]}
204211

205212
@doc """
206213
Call an anonymous function using return values from other ReQL commands or

lib/rethinkdb/query/macros.ex

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ defmodule RethinkDB.Query.Macros do
106106
m = Map.from_struct(t) |> Map.put_new("$reql_type$", "TIME")
107107
wrap(m)
108108
end
109+
def wrap(t = %DateTime{utc_offset: utc_offset, std_offset: std_offset}) do
110+
offset = utc_offset + std_offset
111+
offset_negative = offset < 0
112+
offset_hour = div(abs(offset), 3600)
113+
offset_minute = rem(abs(offset), 3600)
114+
time_zone =
115+
if offset_negative do "-" else "+" end <>
116+
String.pad_leading(Integer.to_string(offset_hour), 2, "0") <>
117+
":" <>
118+
String.pad_leading(Integer.to_string(offset_minute), 2, "0")
119+
wrap(%{
120+
"$reql_type$" => "TIME",
121+
"epoch_time" => DateTime.to_unix(t, :millisecond) / 1000,
122+
"timezone" => time_zone
123+
})
124+
end
109125
def wrap(map) when is_map(map) do
110126
Enum.map(map, fn {k,v} ->
111127
{k, wrap(v)}

lib/rethinkdb/response.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ end
1919

2020
defmodule RethinkDB.Feed do
2121
@moduledoc false
22-
defstruct token: nil, data: nil, pid: nil, note: nil, profile: nil
22+
defstruct token: nil, data: nil, pid: nil, note: nil, profile: nil, opts: nil
2323

2424
defimpl Enumerable, for: __MODULE__ do
2525
def reduce(changes, acc, fun) do
@@ -45,13 +45,13 @@ defmodule RethinkDB.Response do
4545
@moduledoc false
4646
defstruct token: nil, data: "", profile: nil
4747

48-
def parse(raw_data, token, pid) do
48+
def parse(raw_data, token, pid, opts) do
4949
d = Poison.decode!(raw_data)
50-
data = RethinkDB.Pseudotypes.convert_reql_pseudotypes(d["r"])
50+
data = RethinkDB.Pseudotypes.convert_reql_pseudotypes(d["r"], opts)
5151
{code, resp} = case d["t"] do
5252
1 -> {:ok, %RethinkDB.Record{data: hd(data)}}
5353
2 -> {:ok, %RethinkDB.Collection{data: data}}
54-
3 -> {:ok, %RethinkDB.Feed{token: token, data: data, pid: pid, note: d["n"]}}
54+
3 -> {:ok, %RethinkDB.Feed{token: token, data: data, pid: pid, note: d["n"], opts: opts}}
5555
4 -> {:ok, %RethinkDB.Response{token: token, data: d}}
5656
16 -> {:error, %RethinkDB.Response{token: token, data: d}}
5757
17 -> {:error, %RethinkDB.Response{token: token, data: d}}

test/changes_test.exs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,32 @@ defmodule ChangesTest do
7171
[h|[]] = Task.await(t)
7272
assert %{"new_val" => %{"id" => "0"}} = h
7373
end
74+
75+
test "changes opts binary native" do
76+
q = table(@table_name) |> get("0") |> changes
77+
{:ok, changes} = {:ok, %Feed{}} = run(q)
78+
t = Task.async fn ->
79+
changes |> Enum.take(1)
80+
end
81+
data = %{"id" => "0", "binary" => binary(<<1>>)}
82+
q = table(@table_name) |> insert(data)
83+
{:ok, res} = run(q)
84+
expected = res.data["id"]
85+
[h|[]] = Task.await(t)
86+
assert %{"new_val" => %{"id" => "0", "binary" => <<1>>}} = h
87+
end
88+
89+
test "changes opts binary raw" do
90+
q = table(@table_name) |> get("0") |> changes
91+
{:ok, changes} = {:ok, %Feed{}} = run(q, [binary_format: :raw])
92+
t = Task.async fn ->
93+
changes |> Enum.take(1)
94+
end
95+
data = %{"id" => "0", "binary" => binary(<<1>>)}
96+
q = table(@table_name) |> insert(data)
97+
{:ok, res} = run(q)
98+
expected = res.data["id"]
99+
[h|[]] = Task.await(t)
100+
assert %{"new_val" => %{"id" => "0", "binary" => %RethinkDB.Pseudotypes.Binary{data: "AQ=="}}} = h
101+
end
74102
end

test/query/control_structures_test.exs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,23 @@ defmodule ControlStructuresTest do
1717
assert data == [%{"a" => 5}, %{"a" => 4, "c" => 7}]
1818
end
1919

20-
test "binary" do
20+
test "binary raw" do
21+
d = << 220, 2, 3, 4, 5, 192 >>
22+
q = binary d
23+
{:ok, %Record{data: data}} = run q, [binary_format: :raw]
24+
assert data == %RethinkDB.Pseudotypes.Binary{data: :base64.encode(d)}
25+
q = binary data
26+
{:ok, %Record{data: result}} = run q, [binary_format: :raw]
27+
assert data == result
28+
end
29+
30+
test "binary native" do
2131
d = << 220, 2, 3, 4, 5, 192 >>
2232
q = binary d
2333
{:ok, %Record{data: data}} = run q
24-
assert data == %RethinkDB.Pseudotypes.Binary{data: d}
34+
assert data == d
2535
q = binary data
26-
{:ok, %Record{data: result}} = run q
36+
{:ok, %Record{data: result}} = run q, [binary_format: :native]
2737
assert data == result
2838
end
2939

@@ -39,10 +49,10 @@ defmodule ControlStructuresTest do
3949
test "branch" do
4050
q = branch(true, 1, 2)
4151
{:ok, %Record{data: data}} = run q
42-
assert data == 1
52+
assert data == 1
4353
q = branch(false, 1, 2)
4454
{:ok, %Record{data: data}} = run q
45-
assert data == 2
55+
assert data == 2
4656
end
4757

4858
test "error" do
@@ -105,7 +115,7 @@ defmodule ControlStructuresTest do
105115
end
106116

107117
test "uuid" do
108-
q = uuid
118+
q = uuid
109119
{:ok, %Record{data: data}} = run q
110120
assert String.length(String.replace(data, "-", "")) == 32
111121
end

test/query/date_time_test.exs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,64 @@ defmodule DateTimeTest do
1111
:ok
1212
end
1313

14-
test "now" do
14+
test "now native" do
1515
{:ok, %Record{data: data}} = now |> run
16+
assert %DateTime{} = data
17+
end
18+
19+
test "now raw" do
20+
{:ok, %Record{data: data}} = now |> run [time_format: :raw]
1621
assert %Time{} = data
1722
end
1823

19-
test "time" do
24+
test "time native" do
2025
{:ok, %Record{data: data}} = time(1970,1,1,"Z") |> run
26+
assert data == DateTime.from_unix!(0, :millisecond)
27+
{:ok, %Record{data: data}} = time(1970,1,1,0,0,1,"Z") |> run [binary_format: :native]
28+
assert data == DateTime.from_unix!(1000, :millisecond)
29+
end
30+
31+
test "time raw" do
32+
{:ok, %Record{data: data}} = time(1970,1,1,"Z") |> run [time_format: :raw]
2133
assert data.epoch_time == 0
22-
{:ok, %Record{data: data}} = time(1970,1,1,0,0,1,"Z") |> run
34+
{:ok, %Record{data: data}} = time(1970,1,1,0,0,1,"Z") |> run [time_format: :raw]
2335
assert data.epoch_time == 1
2436
end
2537

26-
test "epoch_time" do
38+
test "epoch_time native" do
2739
{:ok, %Record{data: data}} = epoch_time(1) |> run
40+
assert data == DateTime.from_unix!(1000, :millisecond)
41+
end
42+
43+
test "epoch_time raw" do
44+
{:ok, %Record{data: data}} = epoch_time(1) |> run [time_format: :raw]
2845
assert data.epoch_time == 1
2946
assert data.timezone == "+00:00"
3047
end
3148

32-
test "iso8601" do
49+
test "iso8601 native" do
3350
{:ok, %Record{data: data}} = iso8601("1970-01-01T00:00:00+00:00") |> run
51+
assert data == DateTime.from_unix!(0, :millisecond)
52+
{:ok, %Record{data: data}} = iso8601("1970-01-01T00:00:00", default_timezone: "+01:00") |> run
53+
assert data == DateTime.from_unix!(-3600000, :millisecond) |> struct(utc_offset: 3600, time_zone: "Etc/GMT-1", zone_abbr: "+01:00")
54+
end
55+
56+
test "iso8601 raw" do
57+
{:ok, %Record{data: data}} = iso8601("1970-01-01T00:00:00+00:00") |> run [time_format: :raw]
3458
assert data.epoch_time == 0
3559
assert data.timezone == "+00:00"
36-
{:ok, %Record{data: data}} = iso8601("1970-01-01T00:00:00", default_timezone: "+01:00") |> run
60+
{:ok, %Record{data: data}} = iso8601("1970-01-01T00:00:00", default_timezone: "+01:00") |> run [time_format: :raw]
3761
assert data.epoch_time == -3600
3862
assert data.timezone == "+01:00"
3963
end
4064

41-
test "in_timezone" do
65+
test "in_timezone native" do
4266
{:ok, %Record{data: data}} = epoch_time(0) |> in_timezone("+01:00") |> run
67+
assert data == DateTime.from_unix!(0, :millisecond) |> struct(utc_offset: 3600, time_zone: "Etc/GMT-1", zone_abbr: "+01:00")
68+
end
69+
70+
test "in_timezone raw" do
71+
{:ok, %Record{data: data}} = epoch_time(0) |> in_timezone("+01:00") |> run [time_format: :raw]
4372
assert data.timezone == "+01:00"
4473
assert data.epoch_time == 0
4574
end
@@ -59,8 +88,13 @@ defmodule DateTimeTest do
5988
assert data == false
6089
end
6190

62-
test "date" do
91+
test "date native" do
6392
{:ok, %Record{data: data}} = epoch_time(5) |> date |> run
93+
assert data == DateTime.from_unix!(0, :millisecond)
94+
end
95+
96+
test "date raw" do
97+
{:ok, %Record{data: data}} = epoch_time(5) |> date |> run [time_format: :raw]
6498
assert data.epoch_time == 0
6599
end
66100

@@ -81,12 +115,12 @@ defmodule DateTimeTest do
81115

82116
test "day" do
83117
{:ok, %Record{data: data}} = epoch_time(3*60*60*24) |> day |> run
84-
assert data == 4
118+
assert data == 4
85119
end
86120

87121
test "day_of_week" do
88122
{:ok, %Record{data: data}} = epoch_time(3*60*60*24) |> day_of_week |> run
89-
assert data == 7
123+
assert data == 7
90124
end
91125

92126
test "day_of_year" do

0 commit comments

Comments
 (0)