Content-Length: 717917 | pFad | http://github.com/electric-sql/electric/commit/1bdb1c58aa3335613e83d73b1f92a24c07eb30e3

C7 elixir-client: Add support for more Ecto field types · electric-sql/electric@1bdb1c5 · GitHub
Skip to content

Commit 1bdb1c5

Browse files
committed
elixir-client: Add support for more Ecto field types
including: - {:array, type} - :map - :bitstring - :binary
1 parent d0fab07 commit 1bdb1c5

File tree

4 files changed

+291
-15
lines changed

4 files changed

+291
-15
lines changed

packages/elixir-client/lib/electric/client/ecto_adapter.ex

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ if Code.ensure_loaded?(Ecto) do
33
@moduledoc false
44

55
alias Electric.Client.ShapeDefinition
6+
alias Electric.Client.EctoAdapter.ArrayDecoder
67

78
@behaviour Electric.Client.ValueMapper
89

@@ -164,7 +165,8 @@ if Code.ensure_loaded?(Ecto) do
164165
)
165166
end
166167

167-
defp cast_to(:boolean) do
168+
@doc false
169+
def cast_to(:boolean) do
168170
fn
169171
nil -> nil
170172
"t" -> true
@@ -173,7 +175,7 @@ if Code.ensure_loaded?(Ecto) do
173175
end
174176
end
175177

176-
defp cast_to({:parameterized, {_, _}} = type) do
178+
def cast_to({:parameterized, {_, _}} = type) do
177179
fn
178180
nil ->
179181
nil
@@ -192,7 +194,14 @@ if Code.ensure_loaded?(Ecto) do
192194
end
193195
end
194196

195-
defp cast_to(type) when is_atom(type) do
197+
def cast_to({:array, type}) do
198+
fn
199+
nil -> nil
200+
value -> ArrayDecoder.decode!(value, type)
201+
end
202+
end
203+
204+
def cast_to(type) when is_atom(type) do
196205
with {:module, _} <- Code.ensure_loaded(type),
197206
true <- function_exported?(type, :type, 0),
198207
true <- function_exported?(type, :load, 1) do
@@ -207,6 +216,18 @@ if Code.ensure_loaded?(Ecto) do
207216

208217
defp cast_ecto(_type, nil), do: nil
209218

219+
defp cast_ecto(:map, value) do
220+
Jason.decode!(value)
221+
end
222+
223+
defp cast_ecto(:bitstring, value) when is_binary(value) do
224+
<<String.to_integer(value, 2)::size(byte_size(value))>>
225+
end
226+
227+
defp cast_ecto(:binary, "\\x" <> value) when is_binary(value) do
228+
Base.decode16!(value, case: :lower)
229+
end
230+
210231
defp cast_ecto(type, value) do
211232
Ecto.Type.cast!(type, value)
212233
end
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
defmodule Electric.Client.EctoAdapter.ArrayDecoder do
2+
alias Electric.Client.EctoAdapter
3+
4+
def decode!("{}", _type), do: []
5+
6+
def decode!(encoded_array, type) when is_binary(encoded_array) do
7+
{"", [result]} = decode_array(encoded_array, [], {EctoAdapter.cast_to(type), encoded_array})
8+
9+
result
10+
end
11+
12+
##############################
13+
14+
defp decode_array("", acc, _state) do
15+
{"", :lists.reverse(acc)}
16+
end
17+
18+
defp decode_array(<<"{}", rest::bitstring>>, acc, state) do
19+
decode_array(rest, [[] | acc], state)
20+
end
21+
22+
defp decode_array(<<"{", rest::bitstring>>, acc, state) do
23+
{rest, array} = decode_array(rest, [], state)
24+
decode_array(rest, [array | acc], state)
25+
end
26+
27+
defp decode_array(<<"}", rest::bitstring>>, acc, _state) do
28+
{rest, :lists.reverse(acc)}
29+
end
30+
31+
defp decode_array(<<?,, rest::bitstring>>, acc, state) do
32+
decode_array(rest, acc, state)
33+
end
34+
35+
defp decode_array(<<?", rest::bitstring>>, acc, state) do
36+
{rest, elem} = decode_quoted_elem(rest, [], state)
37+
decode_array(rest, [elem | acc], state)
38+
end
39+
40+
defp decode_array(rest, acc, state) do
41+
{rest, elem} = decode_elem(rest, [], state)
42+
decode_array(rest, [elem | acc], state)
43+
end
44+
45+
##############################
46+
47+
defp decode_elem(<<",", _::bitstring>> = rest, acc, state),
48+
do: {rest, cast(acc, state)}
49+
50+
defp decode_elem(<<"{", rest::bitstring>>, [], state),
51+
do: decode_array(rest, [], state)
52+
53+
defp decode_elem(<<"}", _::bitstring>> = rest, acc, state),
54+
do: {rest, cast(acc, state)}
55+
56+
defp decode_elem(<<c::utf8, rest::bitstring>>, acc, state),
57+
do: decode_elem(rest, [acc | <<c::utf8>>], state)
58+
59+
defp decode_elem("", _acc, {_cast_fun, source}) do
60+
raise "malformed array #{inspect(source)}"
61+
end
62+
63+
##############################
64+
65+
defp decode_quoted_elem(<<?", rest::bitstring>>, acc, state),
66+
do: {rest, cast_quoted(acc, state)}
67+
68+
defp decode_quoted_elem(<<"\\\"", rest::bitstring>>, acc, state),
69+
do: decode_quoted_elem(rest, [acc | [?"]], state)
70+
71+
defp decode_quoted_elem(<<c::utf8, rest::bitstring>>, acc, state),
72+
do: decode_quoted_elem(rest, [acc | <<c::utf8>>], state)
73+
74+
defp decode_quoted_elem("", _acc, {_cast_fun, source}) do
75+
raise "malformed array #{inspect(source)}"
76+
end
77+
78+
##############################
79+
80+
defp cast(iodata, {cast_fun, _source}) do
81+
iodata
82+
|> IO.iodata_to_binary()
83+
|> case do
84+
"NULL" -> nil
85+
value -> cast_fun.(value)
86+
end
87+
end
88+
89+
defp cast_quoted(iodata, {cast_fun, _source}) do
90+
iodata
91+
|> IO.iodata_to_binary()
92+
|> cast_fun.()
93+
end
94+
end
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
defmodule Electric.Client.EctoAdapter.ArrayDecoderTest do
2+
use ExUnit.Case, async: true
3+
alias Electric.Client.EctoAdapter.ArrayDecoder
4+
5+
test "simple string arrays" do
6+
assert ArrayDecoder.decode!(~s[{}], :string) == []
7+
assert ArrayDecoder.decode!(~s[{a,b,c}], :string) == ["a", "b", "c"]
8+
assert ArrayDecoder.decode!(~s[{"a","b","c"}], :string) == ["a", "b", "c"]
9+
assert ArrayDecoder.decode!(~s[{"a{}","b","c"}], :string) == ["a{}", "b", "c"]
10+
assert ArrayDecoder.decode!(~S[{"a\"","b","c"}], :string) == ["a\"", "b", "c"]
11+
12+
assert ArrayDecoder.decode!(~S[{"this isn't here","b","c"}], :string) == [
13+
"this isn't here",
14+
"b",
15+
"c"
16+
]
17+
18+
assert ArrayDecoder.decode!(~S[{NULL,NULL,"NULL"}], :string) == [nil, nil, "NULL"]
19+
end
20+
21+
test "nested string arrays" do
22+
assert ArrayDecoder.decode!(~s[{{},{}}], :string) == [[], []]
23+
24+
assert ArrayDecoder.decode!(~s[{{"a","b","c"},{},{c,d,e}], :string) == [
25+
["a", "b", "c"],
26+
[],
27+
["c", "d", "e"]
28+
]
29+
end
30+
31+
test "simple integer arrays" do
32+
assert ArrayDecoder.decode!(~s[{}], :integer) == []
33+
assert ArrayDecoder.decode!(~s[{1,2,3}], :integer) == [1, 2, 3]
34+
assert ArrayDecoder.decode!(~S[{NULL,NULL,23}], :integer) == [nil, nil, 23]
35+
end
36+
37+
test "nested integer arrays" do
38+
assert ArrayDecoder.decode!(~s[{{},{}}], :integer) == [[], []]
39+
40+
assert ArrayDecoder.decode!(~s[{{1,2,3},{},{4,5,6}], :integer) == [
41+
[1, 2, 3],
42+
[],
43+
[4, 5, 6]
44+
]
45+
46+
assert ArrayDecoder.decode!(~s[{{{1,{2,{7,8}},3},{},{4,5,6}}], :integer) == [
47+
[
48+
[1, [2, [7, 8]], 3],
49+
[],
50+
[4, 5, 6]
51+
]
52+
]
53+
end
54+
55+
test "nested jsonb arrays" do
56+
assert ArrayDecoder.decode!(~s[{{{"{\\\"this\\\":4}"},"{}"},{},{"{}","{}"}], :map) == [
57+
[[%{"this" => 4}], %{}],
58+
[],
59+
[%{}, %{}]
60+
]
61+
end
62+
end

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/electric-sql/electric/commit/1bdb1c58aa3335613e83d73b1f92a24c07eb30e3

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy