Skip to content

Commit 7fccf51

Browse files
authored
Update to support Ecto 2.2. (#193)
* Drop support for Elixir 1.3.x, as was done in corresponding Ecto master branch during that time. * Update deps to match Ecto 2.2 branch. * Drop support for OTP 18.0. Continue to test against OTP 18.2 as does Ecto 2.2.x release. * Sadly, we now have to punt on Decimal support. There's really no way to store precision accurately. * Prepare version 2.2.0 release. * Add note about Ecto 2.x version compatibility. This version only works with Ecto 2.2.x. I tried it with the latest 2.1.x release and it was broken (relying on APIs that don't exist in that series).
1 parent 33b9859 commit 7fccf51

File tree

10 files changed

+291
-289
lines changed

10 files changed

+291
-289
lines changed

.travis.yml

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
language: elixir
22

33
elixir:
4-
- 1.3.4
54
- 1.4.4
65
- 1.5.1
76

87
otp_release:
9-
- 18.0
8+
- 18.2
109
- 19.3
1110
- 20.0
1211

1312
matrix:
1413
exclude:
15-
- elixir: 1.3.4
16-
otp_release: 19.3
17-
- elixir: 1.3.4
18-
otp_release: 20.0
1914
- elixir: 1.4.4
2015
otp_release: 20.0
2116
- elixir: 1.5.1
22-
otp_release: 18.0
17+
otp_release: 18.2
2318

2419
script:
2520
- mix deps.get && ./integration/hack_out_incompatible_tests.sh && mix test --cover
@@ -28,4 +23,3 @@ script:
2823
branches:
2924
only:
3025
- master
31-
- ecto-2.2

CHANGELOG.md

+8-96
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,16 @@
1-
# Changelog for v2.0
1+
# Changelog for v2.2.x Series
22

3-
This is a major rewrite of the previously-existing [`sqlite_ecto`](https://github.com/jazzyb/sqlite_ecto) that adds support for Ecto 2.1+.
4-
5-
6-
## v2.0.3
3+
## v2.2.0
74

85
_28 August 2017_
96

10-
* Add sqlitex to applications in mix.exs. (Thank you, @obmarg!)
11-
12-
13-
## v2.0.2
14-
15-
_12 August 2017_
16-
17-
* Update for Elixir 1.5.1. (Stop using a now-deprecated API; otherwise no significant changes.)
18-
19-
20-
## v2.0.1
21-
22-
_7 August 2017_
23-
24-
* Formally announce availability.
25-
* Fix a few README and CHANGELOG issues.
26-
27-
28-
## v2.0.0
29-
30-
_6 August 2017_
31-
32-
* Fix a few documentation errors.
33-
34-
35-
## v2.0.0-dev.8
36-
37-
_11 June 2017_
38-
39-
* Improve README content a bit.
40-
* Make db_connection a hard dependency. (Fixes #174.)
41-
* Update references to several libraries.
42-
43-
44-
## v2.0.0-dev.7
45-
46-
_06 May 2017_
47-
48-
* Improve test coverage (now 98%).
49-
* Add Dogma to CI build infrastructure to help enforce code format consistency.
50-
* Remove `Sqlite.DbConnection` module in favor of directly calling `DbConnection` itself.
51-
52-
53-
## v2.0.0-dev.6
54-
55-
_03 May 2017_
56-
57-
* Fix error messages that referred to PostgreSQL instead of SQLite.
58-
* Improve test coverage (now 91%).
59-
* Fix shell script issues flagged by Ebert.
60-
* Clean up and simplify implementation of `Sqlite.DbConnection.Protocol`.
61-
62-
63-
## v2.0.0-dev.5
64-
65-
_30 April 2017_
66-
67-
* Use iodata lists as much as possible during query generation.
68-
* Switch to numeric placeholders across the board.
69-
* Bring sqlite_ecto_test.exs up to date with corresponding postgres_test.exs from Ecto.
70-
71-
72-
## v2.0.0-dev.4
73-
74-
_17 April 2017_
75-
76-
* Port documentation from v1 `sqlite_ecto` repo to this repo. (Thank you, @taufiqkh!)
77-
* Enable automated code review by Ebert. Fix some of the issues it flagged.
78-
79-
80-
## v2.0.0-dev.3
81-
82-
_11 April 2017_
83-
84-
* Requires sqlitex version 1.3.2 which includes an important bug fix (https://github.com/mmmries/sqlitex/pull/59).
85-
86-
87-
## v2.0.0-dev.2
88-
89-
_26 March 2017_
90-
91-
* **BREAKING CHANGE:** Use the name `Sqlite.Ecto2` consistently in API names. Discontinue use of name `Sqlite.Ecto` (without the `2`)
92-
* Ensure db_connection app is started before relying on it.
93-
94-
95-
## v2.0.0-dev.1
96-
97-
_21 March 2017_
98-
99-
Initial public release of version 2.0 (alpha quality).
7+
* Update to support Ecto 2.2.x. (Aligned version numbers to match Ecto versioning series.)
8+
* Drop support for Elixir 1.3.x. New minimum version is 1.4.0.
9+
* Drop support for OTP 18.0. New minimum version is 18.2.
10+
* Decimal value support, while still present in the library, is no longer officially supported.
10011

10112

10213
## Previous versions
10314

104-
* See the CHANGELOG.md [for the v1.x series](https://github.com/jazzyb/sqlite_ecto/blob/master/CHANGELOG.md)
15+
* [v2.0.x series](https://github.com/scouten/sqlite_ecto2/blob/v2.0/CHANGELOG.md)
16+
* [v1.x series](https://github.com/jazzyb/sqlite_ecto/blob/master/CHANGELOG.md)

README.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,23 @@
55

66
# sqlite_ecto2
77

8-
`sqlite_ecto2` is an Ecto 2.x adapter that allows you to create and maintain SQLite3 databases.
8+
`sqlite_ecto2` is an Ecto 2.2.x adapter that allows you to create and maintain SQLite3 databases.
99

1010
Read [the tutorial](./docs/tutorial.md) for a detailed example of how to setup and use a SQLite repo with Ecto, or just check-out the CliffsNotes in the sections below if you want to get started quickly.
1111

1212

13+
## Ecto Version Compatibility
14+
15+
**IMPORTANT:** This release will _only_ work with Ecto 2.2.x. If you need compatibility with older versions of Ecto, please see:
16+
17+
* Ecto 2.1.x -> [`sqlite_ecto2` 2.0.x series](https://github.com/scouten/sqlite_ecto2/tree/v2.0)
18+
* Ecto 1.x -> [`sqlite_ecto` v1.x series](https://github.com/jazzyb/sqlite_ecto)
19+
20+
1321
## When to Use `sqlite_ecto2`
1422

1523
*(and when not to use it ...)*
1624

17-
If, for some reason, you still need to use Ecto 1.x, please look at [sqlite_ecto](https://github.com/jazzyb/sqlite_ecto), on which this project is based.
18-
1925
I strongly recommend reading [Appropriate Uses for SQLite](https://sqlite.org/whentouse.html) on the SQLite site itself. All of the considerations mentioned there apply to this library as well.
2026

2127
I will add one more: If there is *any* potential that more than one server node will need to write directly to the database at once (as often happens when using Elixir in a clustered environment), **do not use** `sqlite_ecto2`. Remember that there is no separate database process in this configuration, so each of your cluster nodes would be writing to its **own** copy of the database without any synchronization. You probably don't want that. Look for a true client/server database (Postgres, MySQL, or similar) in that case. SQLite's sweet spot is single-machine deployments (embedded, desktop, etc.).

integration/hack_out_incompatible_tests.sh

+8-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ sed -i "" '/test "Repo.insert_all escape/ i\
1919
@tag :insert_cell_wise_defaults
2020
' deps/ecto/integration_test/sql/sql.exs
2121

22-
sed -i "" '/subqueries with select expression/ i\
22+
sed -i "" '/subqueries with map and select expression/ i\
23+
@tag :map_boolean_in_subquery
24+
' deps/ecto/integration_test/sql/subquery.exs
25+
26+
sed -i "" '/subqueries with map update and select expression/ i\
2327
@tag :map_boolean_in_subquery
2428
' deps/ecto/integration_test/sql/subquery.exs
2529

@@ -31,6 +35,8 @@ sed -i '/failing child foreign key/ i @tag :foreign_key_constraint' deps/ecto/in
3135

3236
sed -i '/test "Repo.insert_all escape/ i @tag :insert_cell_wise_defaults' deps/ecto/integration_test/sql/sql.exs
3337

34-
sed -i '/subqueries with select expression/ i @tag :map_boolean_in_subquery' deps/ecto/integration_test/sql/subquery.exs
38+
sed -i '/subqueries with map and select expression/ i @tag :map_boolean_in_subquery' deps/ecto/integration_test/sql/subquery.exs
39+
40+
sed -i '/subqueries with map update and select expression/ i @tag :map_boolean_in_subquery' deps/ecto/integration_test/sql/subquery.exs
3541

3642
fi

integration/sqlite/test_helper.exs

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ ExUnit.start exclude: [:array_type,
2020
:map_boolean_in_subquery,
2121
:upsert_all,
2222
:with_conflict_target,
23-
:without_conflict_target]
23+
:without_conflict_target,
24+
:decimal_type]
2425

2526
# Configure Ecto for support and tests
2627
Application.put_env(:ecto, :primary_key_type, :id)

lib/sqlite_ecto.ex

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ defmodule Sqlite.Ecto2 do
5151
do: [&json_decode/1, &Ecto.Adapters.SQL.load_embed(type, &1)]
5252
def loaders(:map, type), do: [&json_decode/1, type]
5353
def loaders({:map, _}, type), do: [&json_decode/1, type]
54+
def loaders(:float, type), do: [&float_decode/1, type]
5455
def loaders(_primitive, type), do: [type]
5556

5657
defp bool_decode(0), do: {:ok, false}
@@ -81,6 +82,9 @@ defmodule Sqlite.Ecto2 do
8182
defp json_decode(x),
8283
do: {:ok, x}
8384

85+
defp float_decode(x) when is_integer(x), do: {:ok, x / 1}
86+
defp float_decode(x), do: {:ok, x}
87+
8488
def dumpers(:binary, type), do: [type, &blob_encode/1]
8589
def dumpers(:binary_id, type), do: [type, Ecto.UUID]
8690
def dumpers(:boolean, type), do: [type, &bool_encode/1]

lib/sqlite_ecto/connection.ex

+28-24
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ if Code.ensure_loaded?(Sqlitex.Server) do
6565
{:ok, value} = Ecto.DataType.dump(data_type)
6666
value
6767
%{} = value ->
68-
json_library().encode!(value)
68+
Ecto.Adapter.json_library().encode!(value)
6969
value ->
7070
value
7171
end
@@ -93,7 +93,7 @@ if Code.ensure_loaded?(Sqlitex.Server) do
9393
limit = limit(query, sources)
9494
offset = offset(query, sources)
9595

96-
IO.iodata_to_binary([select, from, join, where, group_by, having, order_by, limit, offset])
96+
[select, from, join, where, group_by, having, order_by, limit, offset]
9797
end
9898

9999
def update_all(%Ecto.Query{joins: [_ | _]}) do
@@ -107,7 +107,7 @@ if Code.ensure_loaded?(Sqlitex.Server) do
107107
fields = update_fields(query, sources)
108108
where = where(%{query | wheres: query.wheres}, sources)
109109

110-
IO.iodata_to_binary([prefix, fields, where | returning(query, sources, :update)])
110+
[prefix, fields, where | returning(query, sources, :update)]
111111
end
112112

113113
def delete_all(%Ecto.Query{joins: [_ | _]}) do
@@ -119,7 +119,7 @@ if Code.ensure_loaded?(Sqlitex.Server) do
119119

120120
where = where(%{query | wheres: query.wheres}, sources)
121121

122-
IO.iodata_to_binary(["DELETE FROM ", from, where | returning(query, sources, :delete)])
122+
["DELETE FROM ", from, where | returning(query, sources, :delete)]
123123
end
124124

125125
def insert(prefix, table, header, rows, on_conflict, returning) do
@@ -136,8 +136,7 @@ if Code.ensure_loaded?(Sqlitex.Server) do
136136
_ -> raise ArgumentError, "Upsert in SQLite must use on_conflict: :nothing"
137137
end
138138
returning = returning_clause(prefix, table, returning, "INSERT")
139-
IO.iodata_to_binary(["INSERT", on_conflict, " INTO ", quote_table(prefix, table),
140-
values, returning])
139+
["INSERT", on_conflict, " INTO ", quote_table(prefix, table), values, returning]
141140
end
142141

143142
defp insert_all(rows, counter) do
@@ -168,17 +167,17 @@ if Code.ensure_loaded?(Sqlitex.Server) do
168167

169168
return = returning_clause(prefix, table, returning, "UPDATE")
170169

171-
IO.iodata_to_binary(["UPDATE ", quote_table(prefix, table), " SET ",
172-
fields, " WHERE ", filters | return])
170+
["UPDATE ", quote_table(prefix, table), " SET ",
171+
fields, " WHERE ", filters | return]
173172
end
174173

175174
def delete(prefix, table, filters, returning) do
176175
{filters, _} = intersperse_reduce(filters, " AND ", 1, fn field, acc ->
177176
{[quote_name(field), " = ?" | Integer.to_string(acc)], acc + 1}
178177
end)
179178

180-
IO.iodata_to_binary(["DELETE FROM ", quote_table(prefix, table), " WHERE ",
181-
filters | returning_clause(prefix, table, returning, "DELETE")])
179+
["DELETE FROM ", quote_table(prefix, table), " WHERE ",
180+
filters | returning_clause(prefix, table, returning, "DELETE")]
182181
end
183182

184183
## Query generation
@@ -328,13 +327,10 @@ if Code.ensure_loaded?(Sqlitex.Server) do
328327
quote_qualified_name(field, sources, idx)
329328
end
330329

331-
defp expr({:&, _, [idx, fields, _counter]}, sources, query) do
332-
{source, name, schema} = elem(sources, idx)
333-
if is_nil(schema) and is_nil(fields) do
334-
error!(query, "SQLite does not support selecting all fields from #{source} without a schema. " <>
330+
defp expr({:&, _, [idx]}, sources, query) do
331+
{source, _name, _schema} = elem(sources, idx)
332+
error!(query, "SQLite does not support selecting all fields from #{source} without a schema. " <>
335333
"Please specify a schema or specify exactly which fields you want to select")
336-
end
337-
intersperse_map(fields, ", ", &[name, ?. | quote_name(&1)])
338334
end
339335

340336
defp expr({:in, _, [left, right]}, sources, query) when is_list(right) do
@@ -363,8 +359,8 @@ if Code.ensure_loaded?(Sqlitex.Server) do
363359
["NOT (", expr(expr, sources, query), ?)]
364360
end
365361

366-
defp expr(%Ecto.SubQuery{query: query, fields: fields}, _sources, _query) do
367-
query.select.fields |> put_in(fields) |> all()
362+
defp expr(%Ecto.SubQuery{query: query}, _sources, _query) do
363+
all(query)
368364
end
369365

370366
defp expr({:fragment, _, [kw]}, _sources, query) when is_list(kw) or tuple_size(kw) == 3 do
@@ -476,10 +472,11 @@ if Code.ensure_loaded?(Sqlitex.Server) do
476472
# transaction and trigger. See corresponding code in Sqlitex.
477473

478474
defp returning(%Query{select: nil}, _sources, _cmd), do: []
479-
defp returning(%Query{select: %{fields: [{:&, [], [_, fields, _]}]}}, sources, cmd) do
475+
defp returning(%Query{select: %{fields: field_tuples}}, sources, cmd) do
480476
cmd = cmd |> Atom.to_string |> String.upcase
481477
table = table_from_first_source(sources)
482-
fields = Enum.map_join([table | fields], ",", &quote_id/1)
478+
fields = Enum.map_join([table | Enum.map(field_tuples, &field_from_field_tuple/1)],
479+
",", &quote_id/1)
483480
[@pseudo_returning_statement, cmd, ?\s, fields]
484481
end
485482

@@ -491,6 +488,8 @@ if Code.ensure_loaded?(Sqlitex.Server) do
491488
|> String.trim("\"")
492489
end
493490

491+
defp field_from_field_tuple({{:., [], [{:&, [], [0]}, f]}, [], []}), do: f
492+
494493
defp returning_clause(_prefix, _table, [], _cmd), do: []
495494
defp returning_clause(prefix, table, returning, cmd) do
496495
fields = Enum.map_join([{prefix, table} | returning], ",", &quote_id/1)
@@ -741,11 +740,15 @@ if Code.ensure_loaded?(Sqlitex.Server) do
741740
do: [" DEFAULT '", escape_string(literal), ?']
742741
defp default_expr({:ok, literal}, _type) when is_number(literal) or is_boolean(literal),
743742
do: [" DEFAULT ", to_string(literal)]
743+
defp default_expr({:ok, %{} = map}, :map) do
744+
default = Ecto.Adapter.json_library().encode!(map)
745+
[" DEFAULT ", single_quote(default)]
746+
end
744747
defp default_expr({:ok, {:fragment, expr}}, _type),
745748
do: [" DEFAULT ", expr]
746749
defp default_expr({:ok, expr}, type),
747750
do: raise(ArgumentError, "unknown default `#{inspect expr}` for type `#{inspect type}`. " <>
748-
":default may be a string, number, boolean, empty list or a fragment(...)")
751+
":default may be a string, number, boolean, empty list, map (when type is Map), or a fragment(...)")
749752
defp default_expr(:error, _),
750753
do: []
751754

@@ -765,6 +768,7 @@ if Code.ensure_loaded?(Sqlitex.Server) do
765768
# precision regardless of the declared column type. Decimals are the
766769
# only exception.
767770
defp column_type(:serial, _opts), do: "INTEGER"
771+
defp column_type(:bigserial, _opts), do: "INTEGER"
768772
defp column_type(:string, _opts), do: "TEXT"
769773
defp column_type(:map, _opts), do: "TEXT"
770774
defp column_type({:map, _}, _opts), do: "TEXT"
@@ -794,6 +798,7 @@ if Code.ensure_loaded?(Sqlitex.Server) do
794798
do: quote_name(name)
795799

796800
defp reference_column_type(:serial, _opts), do: "INTEGER"
801+
defp reference_column_type(:bigserial, _opts), do: "INTEGER"
797802
defp reference_column_type(type, opts), do: column_type(type, opts)
798803

799804
defp reference_on_delete(:nilify_all), do: " ON DELETE SET NULL"
@@ -843,6 +848,8 @@ if Code.ensure_loaded?(Sqlitex.Server) do
843848
[?", name, ?"]
844849
end
845850

851+
defp single_quote(value), do: [?', escape_string(value), ?']
852+
846853
defp intersperse_map(list, separator, mapper, acc \\ [])
847854
defp intersperse_map([], _separator, _mapper, acc),
848855
do: acc
@@ -877,8 +884,5 @@ if Code.ensure_loaded?(Sqlitex.Server) do
877884
defp error!(query, message) do
878885
raise Ecto.QueryError, query: query, message: message
879886
end
880-
881-
# Use Ecto's JSON library (currently Poison) for embedded JSON datatypes.
882-
defp json_library, do: Application.get_env(:ecto, :json_library)
883887
end
884888
end

0 commit comments

Comments
 (0)