Skip to content
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