Skip to content

Add support for Ecto 3.10 #107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 21 commits into from
Closed
2 changes: 1 addition & 1 deletion integration_test/constraints_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ defmodule Ecto.Integration.ConstraintsTest do
assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to insert struct/, fn ->
PoolRepo.insert(overlapping_changeset)
end
assert exception.message =~ "cannot_overlap (check_constraint)"
assert exception.message =~ ~r/cannot_overlap.*\(check_constraint\)/
assert exception.message =~ "The changeset has not defined any constraint."
assert exception.message =~ "call `check_constraint/3`"

Expand Down
15 changes: 0 additions & 15 deletions integration_test/hints_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,6 @@ defmodule Ecto.Integration.HintsTest do
alias Ecto.Integration.Post
alias Ecto.Integration.TestRepo

test "join hints" do
{:ok, _} = TestRepo.query("CREATE INDEX post_id_idx ON posts (id)")
TestRepo.insert!(%Post{id: 1})

results =
from(p in Post,
join: p2 in Post,
on: p.id == p2.id,
hints: ["INDEXED BY post_id_idx"]
)
|> TestRepo.all()

assert [%Post{id: 1}] = results
end

test "from hints" do
{:ok, _} = TestRepo.query("CREATE INDEX post_id_idx ON posts (id)")
TestRepo.insert!(%Post{id: 1})
Expand Down
6 changes: 6 additions & 0 deletions integration_test/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,24 @@ ExUnit.start(
:alter_foreign_key,
:assigns_id_type,
:modify_column,
:restrict,

# SQLite3 does not support the concat function
:concat,
# SQLite3 does not support placeholders
:placeholders,
# SQLite3 stores booleans as integers, causing Ecto's json_extract_path tests to fail
:json_extract_path,
# SQLite3 doesn't support specifying columns for ON DELETE SET NULL
:on_delete_nilify_column_list,

# We don't support selected_as
:selected_as_with_group_by,
:selected_as_with_order_by,
:selected_as_with_order_by_expression,
:selected_as_with_having,

# Distinct with options not supported
:distinct_count
]
)
215 changes: 189 additions & 26 deletions lib/ecto/adapters/sqlite3/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -332,11 +332,11 @@ defmodule Ecto.Adapters.SQLite3.Connection do
end
end

defp build_explain_query(query, :query_plan) do
def build_explain_query(query, :query_plan) do
IO.iodata_to_binary(["EXPLAIN QUERY PLAN ", query])
end

defp build_explain_query(query, :instructions) do
def build_explain_query(query, :instructions) do
IO.iodata_to_binary(["EXPLAIN ", query])
end

Expand Down Expand Up @@ -444,6 +444,141 @@ defmodule Ecto.Adapters.SQLite3.Connection do
end)
end

@impl true
def execute_ddl({_, %Index{concurrently: true}}) do
raise ArgumentError, "`concurrently` is not supported with SQLite3"
end

@impl true
def execute_ddl({_, %Index{only: true}}) do
raise ArgumentError, "`only` is not supported with SQLite3"
end

@impl true
def execute_ddl({_, %Index{include: x}}) when length(x) != 0 do
raise ArgumentError, "`include` is not supported with SQLite3"
end

@impl true
def execute_ddl({_, %Index{using: x}}) when not is_nil(x) do
raise ArgumentError, "`using` is not supported with SQLite3"
end

@impl true
def execute_ddl({_, %Index{nulls_distinct: x}}) when not is_nil(x) do
raise ArgumentError, "`nulls_distinct` is not supported with SQLite3"
end

@impl true
def execute_ddl({:create, %Index{} = index}) do
fields = intersperse_map(index.columns, ", ", &index_expr/1)

[
[
"CREATE ",
if_do(index.unique, "UNIQUE "),
"INDEX ",
quote_name(index.name),
" ON ",
quote_table(index.prefix, index.table),
" (",
fields,
?),
if_do(index.where, [" WHERE ", to_string(index.where)])
]
]
end

@impl true
def execute_ddl({:create_if_not_exists, %Index{} = index}) do
fields = intersperse_map(index.columns, ", ", &index_expr/1)

[
[
"CREATE ",
if_do(index.unique, "UNIQUE "),
"INDEX IF NOT EXISTS ",
quote_name(index.name),
" ON ",
quote_table(index.prefix, index.table),
" (",
fields,
?),
if_do(index.where, [" WHERE ", to_string(index.where)])
]
]
end

@impl true
def execute_ddl({:drop, %Index{} = index}) do
[
[
"DROP INDEX ",
quote_table(index.prefix, index.name)
]
]
end

@impl true
def execute_ddl({:drop, %Index{} = index, _mode}) do
execute_ddl({:drop, index})
end

@impl true
def execute_ddl({:drop_if_exists, %Index{concurrently: true}}) do
raise ArgumentError, "`concurrently` is not supported with SQLite3"
end

@impl true
def execute_ddl({:drop_if_exists, %Index{} = index}) do
[
[
"DROP INDEX IF EXISTS ",
quote_table(index.prefix, index.name)
]
]
end

@impl true
def execute_ddl({:drop_if_exists, %Index{} = index, _mode}) do
execute_ddl({:drop_if_exists, index})
end

@impl true
def execute_ddl({:rename, %Table{} = current_table, %Table{} = new_table}) do
[
[
"ALTER TABLE ",
quote_table(current_table.prefix, current_table.name),
" RENAME TO ",
quote_table(nil, new_table.name)
]
]
end

@impl true
def execute_ddl({:rename, %Table{} = current_table, old_col, new_col}) do
[
[
"ALTER TABLE ",
quote_table(current_table.prefix, current_table.name),
" RENAME COLUMN ",
quote_name(old_col),
" TO ",
quote_name(new_col)
]
]
end

@impl true
def execute_ddl(string) when is_binary(string), do: [string]

@impl true
def execute_ddl(keyword) when is_list(keyword) do
raise ArgumentError, "SQLite3 adapter does not support keyword lists in execute"
end

@impl true
def execute_ddl({:create, %Index{} = index}) do
fields = intersperse_map(index.columns, ", ", &index_expr/1)

Expand Down Expand Up @@ -546,6 +681,13 @@ defmodule Ecto.Adapters.SQLite3.Connection do
]
end

def execute_ddl({:rename, %Index{} = index, new_index}) do
[
execute_ddl({:drop, index}),
execute_ddl({:create, %Index{index | name: new_index}})
]
end

def execute_ddl(string) when is_binary(string), do: [string]

def execute_ddl(keyword) when is_list(keyword) do
Expand Down Expand Up @@ -685,12 +827,11 @@ defmodule Ecto.Adapters.SQLite3.Connection do

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

def distinct(nil, _sources, _query), do: []
def distinct(%QueryExpr{expr: true}, _sources, _query), do: "DISTINCT "
def distinct(%QueryExpr{expr: false}, _sources, _query), do: []
defp distinct(nil, _sources, _query), do: []
defp distinct(%QueryExpr{expr: true}, _sources, _query), do: "DISTINCT "
defp distinct(%QueryExpr{expr: false}, _sources, _query), do: []

def distinct(%QueryExpr{expr: expression}, _sources, query)
when is_list(expression) do
defp distinct(%QueryExpr{expr: exprs}, _sources, query) when is_list(exprs) do
raise Ecto.QueryError,
query: query,
message: "DISTINCT with multiple columns is not supported by SQLite3"
Expand Down Expand Up @@ -760,6 +901,10 @@ defmodule Ecto.Adapters.SQLite3.Connection do

def cte(%{with_ctes: _}, _), do: []

defp cte_expr({name, _opts, cte}, sources, query) do
cte_expr({name, cte}, sources, query)
end

defp cte_expr({name, cte}, sources, query) do
[
quote_name(name),
Expand Down Expand Up @@ -809,6 +954,18 @@ defmodule Ecto.Adapters.SQLite3.Connection do
]
end

defp update_op(:push, _quoted_key, _value, _sources, query) do
raise Ecto.QueryError,
query: query,
message: "Arrays are not supported for SQLite3"
end

defp update_op(:pull, _quoted_key, _value, _sources, query) do
raise Ecto.QueryError,
query: query,
message: "Arrays are not supported for SQLite3"
end

defp update_op(command, _quoted_key, _value, _sources, query) do
raise Ecto.QueryError,
query: query,
Expand All @@ -823,15 +980,6 @@ defmodule Ecto.Adapters.SQLite3.Connection do
%JoinExpr{qual: _qual, ix: ix, source: source} ->
{join, name} = get_source(query, sources, ix, source)
[join, " AS " | name]

# This is hold over from sqlite_ecto2. According to sqlite3
# documentation, all of the join types are allowed.
#
# %JoinExpr{qual: qual} ->
# raise Ecto.QueryError,
# query: query,
# message:
# "SQLite3 adapter supports only inner joins on #{kind}, got: `#{qual}`"
end)

wheres =
Expand All @@ -853,14 +1001,19 @@ defmodule Ecto.Adapters.SQLite3.Connection do
source: source,
hints: hints
} ->
if hints != [] do
raise Ecto.QueryError,
query: query,
message: "join hints are not supported by SQLite3"
end

{join, name} = get_source(query, sources, ix, source)

[
join_qual(qual, query),
join,
" AS ",
name,
Enum.map(hints, &[?\s | &1]),
join_on(qual, expression, sources, query)
]
end)
Expand Down Expand Up @@ -932,11 +1085,11 @@ defmodule Ecto.Adapters.SQLite3.Connection do
def order_by(%{order_bys: []}, _sources), do: []

def order_by(%{order_bys: order_bys} = query, sources) do
order_bys = Enum.flat_map(order_bys, & &1.expr)

[
" ORDER BY "
| intersperse_map(order_bys, ", ", fn %QueryExpr{expr: expression} ->
intersperse_map(expression, ", ", &order_by_expr(&1, sources, query))
end)
| intersperse_map(order_bys, ", ", &order_by_expr(&1, sources, query))
]
end

Expand Down Expand Up @@ -971,7 +1124,7 @@ defmodule Ecto.Adapters.SQLite3.Connection do

def limit(%{limit: nil}, _sources), do: []

def limit(%{limit: %QueryExpr{expr: expression}} = query, sources) do
def limit(%{limit: %{expr: expression}} = query, sources) do
[" LIMIT " | expr(expression, sources, query)]
end

Expand Down Expand Up @@ -1089,6 +1242,10 @@ defmodule Ecto.Adapters.SQLite3.Connection do
source
end

def expr({:in, _, [_left, "[]"]}, _sources, _query) do
"0"
end

def expr({:in, _, [_left, []]}, _sources, _query) do
"0"
end
Expand All @@ -1111,6 +1268,7 @@ defmodule Ecto.Adapters.SQLite3.Connection do
[expr(left, sources, query), " IN ", expr(subquery, sources, query)]
end

# Super Hack to handle arrays in json
def expr({:in, _, [left, right]}, sources, query) do
[
expr(left, sources, query),
Expand Down Expand Up @@ -1227,8 +1385,13 @@ defmodule Ecto.Adapters.SQLite3.Connection do
def expr({fun, _, args}, sources, query) when is_atom(fun) and is_list(args) do
{modifier, args} =
case args do
[rest, :distinct] -> {"DISTINCT ", [rest]}
_ -> {[], args}
[_rest, :distinct] ->
raise Ecto.QueryError,
query: query,
message: "Distinct not supported in expressions"

_ ->
{[], args}
end

case handle_call(fun, length(args)) do
Expand All @@ -1245,7 +1408,7 @@ defmodule Ecto.Adapters.SQLite3.Connection do
def expr(list, _sources, query) when is_list(list) do
raise Ecto.QueryError,
query: query,
message: "Array type is not supported by SQLite3"
message: "Array literals are not supported by SQLite3"
end

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

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

def expr(%Ecto.Query.Tagged{value: other, type: type}, sources, query) do
Expand Down Expand Up @@ -1608,7 +1771,7 @@ defmodule Ecto.Adapters.SQLite3.Connection do
end)

if length(pks) > 1 do
composite_pk_expr = pks |> Enum.reverse() |> Enum.map_join(", ", &quote_name/1)
composite_pk_expr = pks |> Enum.reverse() |> Enum.map_join(",", &quote_name/1)

{
%{table | primary_key: :composite},
Expand Down
Loading