Current section

Files

Jump to
ash_sqlite lib sql_implementation.ex
Raw

lib/sql_implementation.ex

defmodule AshSqlite.SqlImplementation do
@moduledoc false
use AshSql.Implementation
require Ecto.Query
require Ash.Expr
@impl true
def manual_relationship_function, do: :ash_sqlite_join
@impl true
def manual_relationship_subquery_function, do: :ash_sqlite_subquery
@impl true
def strpos_function, do: "instr"
@impl true
def ilike?, do: false
@impl true
def expr(
query,
%like{arguments: [arg1, arg2], embedded?: pred_embedded?},
bindings,
embedded?,
acc,
type
)
when like in [AshSqlite.Functions.Like, AshSqlite.Functions.ILike] do
{arg1, acc} =
AshSql.Expr.dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string, acc)
{arg2, acc} =
AshSql.Expr.dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string, acc)
inner_dyn =
if like == AshSqlite.Functions.Like do
Ecto.Query.dynamic(like(^arg1, ^arg2))
else
Ecto.Query.dynamic(like(fragment("LOWER(?)", ^arg1), fragment("LOWER(?)", ^arg2)))
end
if type != Ash.Type.Boolean do
{:ok, inner_dyn, acc}
else
{:ok, Ecto.Query.dynamic(type(^inner_dyn, ^type)), acc}
end
end
def expr(
query,
%Ash.Query.Operator.In{
right: %Ash.Query.Function.Type{arguments: [right | _]} = type
} = op,
bindings,
embedded?,
acc,
type
)
when is_list(right) or is_struct(right, MapSet) do
expr(query, %{op | right: right}, bindings, embedded?, acc, type)
end
def expr(
query,
%Ash.Query.Operator.In{left: left, right: right, embedded?: pred_embedded?},
bindings,
embedded?,
acc,
type
)
when is_list(right) or is_struct(right, MapSet) do
right
|> Enum.reduce(nil, fn val, acc ->
if is_nil(acc) do
%Ash.Query.Operator.Eq{left: left, right: val}
else
%Ash.Query.BooleanExpression{
op: :or,
left: acc,
right: %Ash.Query.Operator.Eq{left: left, right: val}
}
end
end)
|> then(fn expr ->
{expr, acc} =
AshSql.Expr.dynamic_expr(query, expr, bindings, pred_embedded? || embedded?, type, acc)
{:ok, expr, acc}
end)
end
def expr(
query,
%Ash.Query.Function.GetPath{
arguments: [%Ash.Query.Ref{attribute: %{type: type}}, right]
} = get_path,
bindings,
embedded?,
acc,
nil
)
when is_atom(type) and is_list(right) do
if Ash.Type.embedded_type?(type) do
type = determine_type_at_path(type, right)
do_get_path(query, get_path, bindings, embedded?, acc, type)
else
do_get_path(query, get_path, bindings, embedded?, acc)
end
end
def expr(
query,
%Ash.Query.Function.GetPath{
arguments: [%Ash.Query.Ref{attribute: %{type: {:array, type}}}, right]
} = get_path,
bindings,
embedded?,
acc,
nil
)
when is_atom(type) and is_list(right) do
if Ash.Type.embedded_type?(type) do
type = determine_type_at_path(type, right)
do_get_path(query, get_path, bindings, embedded?, acc, type)
else
do_get_path(query, get_path, bindings, embedded?, acc)
end
end
def expr(
query,
%Ash.Query.Function.GetPath{} = get_path,
bindings,
embedded?,
acc,
type
) do
do_get_path(query, get_path, bindings, embedded?, acc, type)
end
@impl true
def expr(
_query,
_expr,
_bindings,
_embedded?,
_acc,
_type
) do
:error
end
@impl true
def type_expr(expr, nil), do: expr
def type_expr(expr, type) when is_atom(type) do
type = Ash.Type.get_type(type)
cond do
!Ash.Type.ash_type?(type) ->
Ecto.Query.dynamic(type(^expr, ^type))
Ash.Type.storage_type(type, []) == :ci_string ->
Ecto.Query.dynamic(fragment("(? COLLATE NOCASE)", ^expr))
true ->
Ecto.Query.dynamic(type(^expr, ^Ash.Type.storage_type(type, [])))
end
end
def type_expr(expr, type) do
case type do
{:parameterized, {inner_type, constraints}} ->
if inner_type.type(constraints) == :ci_string do
Ecto.Query.dynamic(fragment("(? COLLATE NOCASE)", ^expr))
else
Ecto.Query.dynamic(type(^expr, ^type))
end
nil ->
expr
type ->
Ecto.Query.dynamic(type(^expr, ^type))
end
end
@impl true
def table(resource) do
AshSqlite.DataLayer.Info.table(resource)
end
@impl true
def schema(_resource) do
nil
end
@impl true
def repo(resource, _kind) do
AshSqlite.DataLayer.Info.repo(resource)
end
@impl true
def multicolumn_distinct?, do: false
@impl true
def parameterized_type({:parameterized, _} = type, _) do
type
end
def parameterized_type({:parameterized, _, _} = type, _) do
type
end
def parameterized_type({:in, type}, constraints) do
parameterized_type({:array, type}, constraints)
end
def parameterized_type({:array, type}, constraints) do
case parameterized_type(type, constraints[:items] || []) do
nil ->
nil
type ->
{:array, type}
end
end
def parameterized_type({type, constraints}, []) do
parameterized_type(type, constraints)
end
def parameterized_type(type, _constraints)
when type in [Ash.Type.Map, Ash.Type.Map.EctoType],
do: nil
def parameterized_type(type, constraints) do
if Ash.Type.ash_type?(type) do
cast_in_query? =
if function_exported?(Ash.Type, :cast_in_query?, 2) do
Ash.Type.cast_in_query?(type, constraints)
else
Ash.Type.cast_in_query?(type)
end
if cast_in_query? do
parameterized_type(Ash.Type.ecto_type(type), constraints)
else
nil
end
else
if is_atom(type) && :erlang.function_exported(type, :type, 1) do
Ecto.ParameterizedType.init(type, constraints)
else
type
end
end
end
@impl true
def determine_types(mod, args, returns \\ nil) do
returns =
case returns do
{:parameterized, _} -> nil
{:array, {:parameterized, _}} -> nil
{:array, {type, constraints}} when type != :array -> {type, [items: constraints]}
{:array, _} -> nil
{type, constraints} -> {type, constraints}
other -> other
end
{types, new_returns} = Ash.Expr.determine_types(mod, args, returns)
{types, new_returns || returns}
end
defp do_get_path(
query,
%Ash.Query.Function.GetPath{arguments: [left, right], embedded?: pred_embedded?},
bindings,
embedded?,
acc,
type \\ nil
) do
path = "$." <> Enum.join(right, ".")
{expr, acc} =
AshSql.Expr.dynamic_expr(
query,
%Ash.Query.Function.Fragment{
embedded?: pred_embedded?,
arguments: [
raw: "json_extract(",
expr: left,
raw: ", ",
expr: path,
raw: ")"
]
},
bindings,
embedded?,
type,
acc
)
if type do
{expr, acc} =
AshSql.Expr.dynamic_expr(
query,
%Ash.Query.Function.Type{arguments: [expr, type, []]},
bindings,
embedded?,
type,
acc
)
{:ok, expr, acc}
else
{:ok, expr, acc}
end
end
defp determine_type_at_path(type, path) do
path
|> Enum.reject(&is_integer/1)
|> do_determine_type_at_path(type)
end
defp do_determine_type_at_path([], _), do: nil
defp do_determine_type_at_path([item], type) do
case Ash.Resource.Info.attribute(type, item) do
nil ->
nil
%{type: {:array, type}, constraints: constraints} ->
constraints = constraints[:items] || []
{type, constraints}
%{type: type, constraints: constraints} ->
{type, constraints}
end
end
defp do_determine_type_at_path([item | rest], type) do
case Ash.Resource.Info.attribute(type, item) do
nil ->
nil
%{type: {:array, type}} ->
if Ash.Type.embedded_type?(type) do
type
else
nil
end
%{type: type} ->
if Ash.Type.embedded_type?(type) do
type
else
nil
end
end
|> case do
nil ->
nil
type ->
do_determine_type_at_path(rest, type)
end
end
end