Skip to content

Commit 6f1ca67

Browse files
author
Jason S
authored
SQLite Update to get it closer to Postgres with ecto. (#101)
1 parent 224bd01 commit 6f1ca67

File tree

9 files changed

+1237
-1525
lines changed

9 files changed

+1237
-1525
lines changed

integration_test/hints_test.exs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,6 @@ defmodule Ecto.Integration.HintsTest do
66
alias Ecto.Integration.Post
77
alias Ecto.Integration.TestRepo
88

9-
test "join hints" do
10-
{:ok, _} = TestRepo.query("CREATE INDEX post_id_idx ON posts (id)")
11-
TestRepo.insert!(%Post{id: 1})
12-
13-
results =
14-
from(p in Post,
15-
join: p2 in Post,
16-
on: p.id == p2.id,
17-
hints: ["INDEXED BY post_id_idx"]
18-
)
19-
|> TestRepo.all()
20-
21-
assert [%Post{id: 1}] = results
22-
end
23-
249
test "from hints" do
2510
{:ok, _} = TestRepo.query("CREATE INDEX post_id_idx ON posts (id)")
2611
TestRepo.insert!(%Post{id: 1})

integration_test/test_helper.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ ExUnit.start(
108108
:alter_foreign_key,
109109
:assigns_id_type,
110110
:modify_column,
111+
:restrict,
111112

112113
# SQLite3 does not support the concat function
113114
:concat,
@@ -121,5 +122,8 @@ ExUnit.start(
121122
:selected_as_with_order_by,
122123
:selected_as_with_order_by_expression,
123124
:selected_as_with_having,
125+
126+
# Distinct with options not supported
127+
:distinct_count
124128
]
125129
)

lib/ecto/adapters/sqlite3/connection.ex

Lines changed: 177 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -332,11 +332,11 @@ defmodule Ecto.Adapters.SQLite3.Connection do
332332
end
333333
end
334334

335-
defp build_explain_query(query, :query_plan) do
335+
def build_explain_query(query, :query_plan) do
336336
IO.iodata_to_binary(["EXPLAIN QUERY PLAN ", query])
337337
end
338338

339-
defp build_explain_query(query, :instructions) do
339+
def build_explain_query(query, :instructions) do
340340
IO.iodata_to_binary(["EXPLAIN ", query])
341341
end
342342

@@ -444,6 +444,141 @@ defmodule Ecto.Adapters.SQLite3.Connection do
444444
end)
445445
end
446446

447+
@impl true
448+
def execute_ddl({_, %Index{concurrently: true}}) do
449+
raise ArgumentError, "`concurrently` is not supported with SQLite3"
450+
end
451+
452+
@impl true
453+
def execute_ddl({_, %Index{only: true}}) do
454+
raise ArgumentError, "`only` is not supported with SQLite3"
455+
end
456+
457+
@impl true
458+
def execute_ddl({_, %Index{include: x}}) when length(x) != 0 do
459+
raise ArgumentError, "`include` is not supported with SQLite3"
460+
end
461+
462+
@impl true
463+
def execute_ddl({_, %Index{using: x}}) when not is_nil(x) do
464+
raise ArgumentError, "`using` is not supported with SQLite3"
465+
end
466+
467+
@impl true
468+
def execute_ddl({_, %Index{nulls_distinct: x}}) when not is_nil(x) do
469+
raise ArgumentError, "`nulls_distinct` is not supported with SQLite3"
470+
end
471+
472+
@impl true
473+
def execute_ddl({:create, %Index{} = index}) do
474+
fields = intersperse_map(index.columns, ", ", &index_expr/1)
475+
476+
[
477+
[
478+
"CREATE ",
479+
if_do(index.unique, "UNIQUE "),
480+
"INDEX ",
481+
quote_name(index.name),
482+
" ON ",
483+
quote_table(index.prefix, index.table),
484+
" (",
485+
fields,
486+
?),
487+
if_do(index.where, [" WHERE ", to_string(index.where)])
488+
]
489+
]
490+
end
491+
492+
@impl true
493+
def execute_ddl({:create_if_not_exists, %Index{} = index}) do
494+
fields = intersperse_map(index.columns, ", ", &index_expr/1)
495+
496+
[
497+
[
498+
"CREATE ",
499+
if_do(index.unique, "UNIQUE "),
500+
"INDEX IF NOT EXISTS ",
501+
quote_name(index.name),
502+
" ON ",
503+
quote_table(index.prefix, index.table),
504+
" (",
505+
fields,
506+
?),
507+
if_do(index.where, [" WHERE ", to_string(index.where)])
508+
]
509+
]
510+
end
511+
512+
@impl true
513+
def execute_ddl({:drop, %Index{} = index}) do
514+
[
515+
[
516+
"DROP INDEX ",
517+
quote_table(index.prefix, index.name)
518+
]
519+
]
520+
end
521+
522+
@impl true
523+
def execute_ddl({:drop, %Index{} = index, _mode}) do
524+
execute_ddl({:drop, index})
525+
end
526+
527+
@impl true
528+
def execute_ddl({:drop_if_exists, %Index{concurrently: true}}) do
529+
raise ArgumentError, "`concurrently` is not supported with SQLite3"
530+
end
531+
532+
@impl true
533+
def execute_ddl({:drop_if_exists, %Index{} = index}) do
534+
[
535+
[
536+
"DROP INDEX IF EXISTS ",
537+
quote_table(index.prefix, index.name)
538+
]
539+
]
540+
end
541+
542+
@impl true
543+
def execute_ddl({:drop_if_exists, %Index{} = index, _mode}) do
544+
execute_ddl({:drop_if_exists, index})
545+
end
546+
547+
@impl true
548+
def execute_ddl({:rename, %Table{} = current_table, %Table{} = new_table}) do
549+
[
550+
[
551+
"ALTER TABLE ",
552+
quote_table(current_table.prefix, current_table.name),
553+
" RENAME TO ",
554+
quote_table(nil, new_table.name)
555+
]
556+
]
557+
end
558+
559+
@impl true
560+
def execute_ddl({:rename, %Table{} = current_table, old_col, new_col}) do
561+
[
562+
[
563+
"ALTER TABLE ",
564+
quote_table(current_table.prefix, current_table.name),
565+
" RENAME COLUMN ",
566+
quote_name(old_col),
567+
" TO ",
568+
quote_name(new_col)
569+
]
570+
]
571+
end
572+
573+
@impl true
574+
def execute_ddl(string) when is_binary(string), do: [string]
575+
576+
@impl true
577+
def execute_ddl(keyword) when is_list(keyword) do
578+
raise ArgumentError, "SQLite3 adapter does not support keyword lists in execute"
579+
end
580+
581+
@impl true
447582
def execute_ddl({:create, %Index{} = index}) do
448583
fields = intersperse_map(index.columns, ", ", &index_expr/1)
449584

@@ -685,12 +820,11 @@ defmodule Ecto.Adapters.SQLite3.Connection do
685820

686821
def handle_call(fun, _arity), do: {:fun, Atom.to_string(fun)}
687822

688-
def distinct(nil, _sources, _query), do: []
689-
def distinct(%QueryExpr{expr: true}, _sources, _query), do: "DISTINCT "
690-
def distinct(%QueryExpr{expr: false}, _sources, _query), do: []
823+
defp distinct(nil, _sources, _query), do: []
824+
defp distinct(%QueryExpr{expr: true}, _sources, _query), do: "DISTINCT "
825+
defp distinct(%QueryExpr{expr: false}, _sources, _query), do: []
691826

692-
def distinct(%QueryExpr{expr: expression}, _sources, query)
693-
when is_list(expression) do
827+
defp distinct(%QueryExpr{expr: exprs}, _sources, query) when is_list(exprs) do
694828
raise Ecto.QueryError,
695829
query: query,
696830
message: "DISTINCT with multiple columns is not supported by SQLite3"
@@ -809,6 +943,18 @@ defmodule Ecto.Adapters.SQLite3.Connection do
809943
]
810944
end
811945

946+
defp update_op(:push, _quoted_key, _value, _sources, query) do
947+
raise Ecto.QueryError,
948+
query: query,
949+
message: "Arrays are not supported for SQLite3"
950+
end
951+
952+
defp update_op(:pull, _quoted_key, _value, _sources, query) do
953+
raise Ecto.QueryError,
954+
query: query,
955+
message: "Arrays are not supported for SQLite3"
956+
end
957+
812958
defp update_op(command, _quoted_key, _value, _sources, query) do
813959
raise Ecto.QueryError,
814960
query: query,
@@ -823,15 +969,6 @@ defmodule Ecto.Adapters.SQLite3.Connection do
823969
%JoinExpr{qual: _qual, ix: ix, source: source} ->
824970
{join, name} = get_source(query, sources, ix, source)
825971
[join, " AS " | name]
826-
827-
# This is hold over from sqlite_ecto2. According to sqlite3
828-
# documentation, all of the join types are allowed.
829-
#
830-
# %JoinExpr{qual: qual} ->
831-
# raise Ecto.QueryError,
832-
# query: query,
833-
# message:
834-
# "SQLite3 adapter supports only inner joins on #{kind}, got: `#{qual}`"
835972
end)
836973

837974
wheres =
@@ -853,14 +990,19 @@ defmodule Ecto.Adapters.SQLite3.Connection do
853990
source: source,
854991
hints: hints
855992
} ->
993+
if hints != [] do
994+
raise Ecto.QueryError,
995+
query: query,
996+
message: "join hints are not supported by SQLite3"
997+
end
998+
856999
{join, name} = get_source(query, sources, ix, source)
8571000

8581001
[
8591002
join_qual(qual, query),
8601003
join,
8611004
" AS ",
8621005
name,
863-
Enum.map(hints, &[?\s | &1]),
8641006
join_on(qual, expression, sources, query)
8651007
]
8661008
end)
@@ -932,11 +1074,11 @@ defmodule Ecto.Adapters.SQLite3.Connection do
9321074
def order_by(%{order_bys: []}, _sources), do: []
9331075

9341076
def order_by(%{order_bys: order_bys} = query, sources) do
1077+
order_bys = Enum.flat_map(order_bys, & &1.expr)
1078+
9351079
[
9361080
" ORDER BY "
937-
| intersperse_map(order_bys, ", ", fn %QueryExpr{expr: expression} ->
938-
intersperse_map(expression, ", ", &order_by_expr(&1, sources, query))
939-
end)
1081+
| intersperse_map(order_bys, ", ", &order_by_expr(&1, sources, query))
9401082
]
9411083
end
9421084

@@ -1089,6 +1231,10 @@ defmodule Ecto.Adapters.SQLite3.Connection do
10891231
source
10901232
end
10911233

1234+
def expr({:in, _, [_left, "[]"]}, _sources, _query) do
1235+
"0"
1236+
end
1237+
10921238
def expr({:in, _, [_left, []]}, _sources, _query) do
10931239
"0"
10941240
end
@@ -1111,6 +1257,7 @@ defmodule Ecto.Adapters.SQLite3.Connection do
11111257
[expr(left, sources, query), " IN ", expr(subquery, sources, query)]
11121258
end
11131259

1260+
# Super Hack to handle arrays in json
11141261
def expr({:in, _, [left, right]}, sources, query) do
11151262
[
11161263
expr(left, sources, query),
@@ -1227,8 +1374,13 @@ defmodule Ecto.Adapters.SQLite3.Connection do
12271374
def expr({fun, _, args}, sources, query) when is_atom(fun) and is_list(args) do
12281375
{modifier, args} =
12291376
case args do
1230-
[rest, :distinct] -> {"DISTINCT ", [rest]}
1231-
_ -> {[], args}
1377+
[_rest, :distinct] ->
1378+
raise Ecto.QueryError,
1379+
query: query,
1380+
message: "Distinct not supported in expressions"
1381+
1382+
_ ->
1383+
{[], args}
12321384
end
12331385

12341386
case handle_call(fun, length(args)) do
@@ -1245,7 +1397,7 @@ defmodule Ecto.Adapters.SQLite3.Connection do
12451397
def expr(list, _sources, query) when is_list(list) do
12461398
raise Ecto.QueryError,
12471399
query: query,
1248-
message: "Array type is not supported by SQLite3"
1400+
message: "Array literals are not supported by SQLite3"
12491401
end
12501402

12511403
def expr(%Decimal{} = decimal, _sources, _query) do
@@ -1260,7 +1412,7 @@ defmodule Ecto.Adapters.SQLite3.Connection do
12601412

12611413
def expr(%Ecto.Query.Tagged{value: other, type: type}, sources, query)
12621414
when type in [:decimal, :float] do
1263-
["(", expr(other, sources, query), " + 0)"]
1415+
["CAST(", expr(other, sources, query), " AS REAL)"]
12641416
end
12651417

12661418
def expr(%Ecto.Query.Tagged{value: other, type: type}, sources, query) do
@@ -1608,7 +1760,7 @@ defmodule Ecto.Adapters.SQLite3.Connection do
16081760
end)
16091761

16101762
if length(pks) > 1 do
1611-
composite_pk_expr = pks |> Enum.reverse() |> Enum.map_join(", ", &quote_name/1)
1763+
composite_pk_expr = pks |> Enum.reverse() |> Enum.map_join(",", &quote_name/1)
16121764

16131765
{
16141766
%{table | primary_key: :composite},

lib/ecto/adapters/sqlite3/data_type.ex

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,17 @@ defmodule Ecto.Adapters.SQLite3.DataType do
1616
def column_type(:string, _opts), do: "TEXT"
1717
def column_type(:float, _opts), do: "NUMERIC"
1818
def column_type(:binary, _opts), do: "BLOB"
19-
def column_type(:map, _opts), do: "JSON"
20-
def column_type(:array, _opts), do: "JSON"
21-
def column_type({:map, _}, _opts), do: "JSON"
22-
def column_type({:array, _}, _opts), do: "JSON"
19+
def column_type(:map, _opts), do: "TEXT"
20+
def column_type(:array, _opts), do: "TEXT"
21+
def column_type({:map, _}, _opts), do: "TEXT"
22+
def column_type({:array, _}, _opts), do: "TEXT"
2323
def column_type(:utc_datetime, _opts), do: "TEXT"
2424
def column_type(:utc_datetime_usec, _opts), do: "TEXT"
2525
def column_type(:naive_datetime, _opts), do: "TEXT"
2626
def column_type(:naive_datetime_usec, _opts), do: "TEXT"
27+
def column_type(:time, _opts), do: "TEXT"
28+
def column_type(:time_usec, _opts), do: "TEXT"
29+
def column_type(:timestamp, _opts), do: "TEXT"
2730
def column_type(:decimal, nil), do: "DECIMAL"
2831

2932
def column_type(:decimal, opts) do
@@ -52,9 +55,15 @@ defmodule Ecto.Adapters.SQLite3.DataType do
5255
end
5356
end
5457

55-
def column_type(type, _) do
58+
def column_type(type, _) when is_atom(type) do
5659
type
5760
|> Atom.to_string()
5861
|> String.upcase()
5962
end
63+
64+
def column_type(type, _) do
65+
raise ArgumentError,
66+
"unsupported type `#{inspect(type)}`. The type can either be an atom, a string " <>
67+
"or a tuple of the form `{:map, t}` or `{:array, t}` where `t` itself follows the same conditions."
68+
end
6069
end

0 commit comments

Comments
 (0)