Skip to content

Commit 952f38b

Browse files
authored
Make map/array encoding configurable (#163)
* Make map encoding configurable * Make array encoding configurable * Add changelog entry
1 parent bbe7ebe commit 952f38b

File tree

5 files changed

+90
-10
lines changed

5 files changed

+90
-10
lines changed

Diff for: CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ All notable changes will be documented in this file.
55
The format is loosely based on [Keep a Changelog][keepachangelog], and this
66
project adheres to [Semantic Versioning][semver].
77

8+
## Unreleased
9+
- changed: Configurable encoding for `:map` and `:array`, allowing usage of SQLite's JSONB storage format.
10+
811
## v0.18.1
912
- fixed: Support both `Jason` and `JSON`.
1013

Diff for: lib/ecto/adapters/sqlite3.ex

+6
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ defmodule Ecto.Adapters.SQLite3 do
5151
* `:uuid_type` - Defaults to `:string`. Determines the type of `:uuid` columns.
5252
Possible values and column types are the same as for
5353
[binary IDs](#module-binary-id-types).
54+
* `:map_type` - Defaults to `:string`. Determines the type of `:map` columns.
55+
Set to `:binary` to use the [JSONB](https://sqlite.org/jsonb.html)
56+
storage format.
57+
* `:array_type` - Defaults to `:string`. Determines the type of `:array` columns.
58+
Arrays are serialized using JSON. Set to `:binary` to use the
59+
[JSONB](https://sqlite.org/jsonb.html) storage format.
5460
* `:datetime_type` - Defaults to `:iso8601`. Determines how datetime fields are
5561
stored in the database. The allowed values are `:iso8601` and `:text_datetime`.
5662
`:iso8601` corresponds to a string of the form `YYYY-MM-DDThh:mm:ss` and

Diff for: lib/ecto/adapters/sqlite3/data_type.ex

+28-4
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ defmodule Ecto.Adapters.SQLite3.DataType do
1717
def column_type(:string, _opts), do: "TEXT"
1818
def column_type(:float, _opts), do: "NUMERIC"
1919
def column_type(:binary, _opts), do: "BLOB"
20-
def column_type(:map, _opts), do: "TEXT"
21-
def column_type(:array, _opts), do: "TEXT"
22-
def column_type({:map, _}, _opts), do: "TEXT"
23-
def column_type({:array, _}, _opts), do: "TEXT"
2420
def column_type(:date, _opts), do: "TEXT"
2521
def column_type(:utc_datetime, _opts), do: "TEXT"
2622
def column_type(:utc_datetime_usec, _opts), do: "TEXT"
@@ -43,13 +39,41 @@ defmodule Ecto.Adapters.SQLite3.DataType do
4339
end
4440
end
4541

42+
def column_type(:array, _opts) do
43+
case Application.get_env(:ecto_sqlite3, :array_type, :string) do
44+
:string -> "TEXT"
45+
:binary -> "BLOB"
46+
end
47+
end
48+
49+
def column_type({:array, _}, _opts) do
50+
case Application.get_env(:ecto_sqlite3, :array_type, :string) do
51+
:string -> "TEXT"
52+
:binary -> "BLOB"
53+
end
54+
end
55+
4656
def column_type(:binary_id, _opts) do
4757
case Application.get_env(:ecto_sqlite3, :binary_id_type, :string) do
4858
:string -> "TEXT"
4959
:binary -> "BLOB"
5060
end
5161
end
5262

63+
def column_type(:map, _opts) do
64+
case Application.get_env(:ecto_sqlite3, :map_type, :string) do
65+
:string -> "TEXT"
66+
:binary -> "BLOB"
67+
end
68+
end
69+
70+
def column_type({:map, _}, _opts) do
71+
case Application.get_env(:ecto_sqlite3, :map_type, :string) do
72+
:string -> "TEXT"
73+
:binary -> "BLOB"
74+
end
75+
end
76+
5377
def column_type(:uuid, _opts) do
5478
case Application.get_env(:ecto_sqlite3, :uuid_type, :string) do
5579
:string -> "TEXT"

Diff for: test/ecto/adapters/sqlite3/data_type_test.exs

+24-4
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ defmodule Ecto.Adapters.SQLite3.DataTypeTest do
44
alias Ecto.Adapters.SQLite3.DataType
55

66
setup do
7+
Application.put_env(:ecto_sqlite3, :array_type, :string)
78
Application.put_env(:ecto_sqlite3, :binary_id_type, :string)
9+
Application.put_env(:ecto_sqlite3, :map_type, :string)
810
Application.put_env(:ecto_sqlite3, :uuid_type, :string)
911

1012
on_exit(fn ->
13+
Application.put_env(:ecto_sqlite3, :array_type, :string)
1114
Application.put_env(:ecto_sqlite3, :binary_id_type, :string)
15+
Application.put_env(:ecto_sqlite3, :map_type, :string)
1216
Application.put_env(:ecto_sqlite3, :uuid_type, :string)
1317
end)
1418
end
@@ -46,20 +50,36 @@ defmodule Ecto.Adapters.SQLite3.DataTypeTest do
4650
assert DataType.column_type(:uuid, nil) == "BLOB"
4751
end
4852

49-
test ":map is TEXT" do
53+
test ":map is TEXT or BLOB" do
5054
assert DataType.column_type(:map, nil) == "TEXT"
55+
56+
Application.put_env(:ecto_sqlite3, :map_type, :binary)
57+
58+
assert DataType.column_type(:map, nil) == "BLOB"
5159
end
5260

53-
test "{:map, _} is TEXT" do
61+
test "{:map, _} is TEXT or BLOB" do
5462
assert DataType.column_type({:map, %{}}, nil) == "TEXT"
63+
64+
Application.put_env(:ecto_sqlite3, :map_type, :binary)
65+
66+
assert DataType.column_type({:map, %{}}, nil) == "BLOB"
5567
end
5668

57-
test ":array is TEXT" do
69+
test ":array is TEXT or BLOB" do
5870
assert DataType.column_type(:array, nil) == "TEXT"
71+
72+
Application.put_env(:ecto_sqlite3, :array_type, :binary)
73+
74+
assert DataType.column_type(:array, nil) == "BLOB"
5975
end
6076

61-
test "{:array, _} is TEXT" do
77+
test "{:array, _} is TEXT or BLOB" do
6278
assert DataType.column_type({:array, []}, nil) == "TEXT"
79+
80+
Application.put_env(:ecto_sqlite3, :array_type, :binary)
81+
82+
assert DataType.column_type({:array, []}, nil) == "BLOB"
6383
end
6484

6585
test ":float is NUMERIC" do

Diff for: test/ecto/integration/json_test.exs

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,40 @@
11
defmodule Ecto.Integration.JsonTest do
2-
use Ecto.Integration.Case
2+
use Ecto.Integration.Case, async: false
33

44
alias Ecto.Adapters.SQL
55
alias Ecto.Integration.TestRepo
66
alias EctoSQLite3.Schemas.Setting
77

88
@moduletag :integration
99

10-
test "serializes json correctly" do
10+
setup do
11+
Application.put_env(:ecto_sqlite3, :map_type, :string)
12+
on_exit(fn -> Application.put_env(:ecto_sqlite3, :map_type, :string) end)
13+
end
14+
15+
test "serializes json correctly with string format" do
16+
# Insert a record purposefully with atoms as the map key. We are going to
17+
# verify later they were coerced into strings.
18+
setting =
19+
%Setting{}
20+
|> Setting.changeset(%{properties: %{foo: "bar", qux: "baz"}})
21+
|> TestRepo.insert!()
22+
23+
# Read the record back using ecto and confirm it
24+
assert %Setting{properties: %{"foo" => "bar", "qux" => "baz"}} =
25+
TestRepo.get(Setting, setting.id)
26+
27+
assert %{num_rows: 1, rows: [["bar"]]} =
28+
SQL.query!(
29+
TestRepo,
30+
"select json_extract(properties, '$.foo') from settings where id = ?1",
31+
[setting.id]
32+
)
33+
end
34+
35+
test "serializes json correctly with binary format" do
36+
Application.put_env(:ecto_sqlite3, :map_type, :binary)
37+
1138
# Insert a record purposefully with atoms as the map key. We are going to
1239
# verify later they were coerced into strings.
1340
setting =

0 commit comments

Comments
 (0)