Packages
ash_sql
0.2.50
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.93
0.2.92
0.2.91
0.2.90
0.2.89
0.2.88
0.2.87
0.2.86
0.2.85
0.2.84
0.2.83
0.2.82
0.2.81
0.2.80
0.2.79
0.2.78
0.2.77
0.2.76
0.2.75
0.2.74
0.2.73
0.2.72
0.2.71
0.2.70
0.2.69
0.2.68
0.2.67
0.2.66
0.2.65
0.2.64
0.2.63
0.2.62
0.2.61
0.2.60
0.2.59
0.2.58
0.2.57
0.2.56
0.2.55
0.2.54
0.2.53
0.2.52
0.2.51
0.2.50
0.2.49
0.2.48
0.2.47
0.2.46
0.2.45
0.2.44
0.2.43
0.2.42
0.2.41
0.2.40
0.2.39
0.2.38
0.2.37
0.2.36
0.2.35
0.2.34
0.2.33
0.2.32
0.2.31
0.2.30
0.2.29
0.2.28
0.2.27
0.2.26
0.2.25
0.2.24
0.2.23
0.2.22
0.2.21
0.2.20
0.2.19
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.3
0.1.2
0.1.1-rc.20
0.1.1-rc.19
0.1.1-rc.18
0.1.1-rc.17
0.1.1-rc.16
0.1.1-rc.15
0.1.1-rc.14
0.1.1-rc.13
0.1.1-rc.12
0.1.1-rc.11
0.1.1-rc.10
0.1.1-rc.9
0.1.1-rc.8
0.1.1-rc.7
0.1.1-rc.6
0.1.1-rc.5
0.1.1-rc.4
0.1.1-rc.3
0.1.1-rc.2
0.1.1-rc.1
0.1.1-rc.0
Shared utilities for ecto-based sql data layers.
Current section
Files
Jump to
Current section
Files
lib/atomics.ex
defmodule AshSql.Atomics do
require Ecto.Query
def select_atomics(_resource, query, []) do
{:ok, query}
end
# sobelow_skip ["DOS.StringToAtom"]
def select_atomics(resource, query, atomics) do
atomics = type_atomics(query.__ash_bindings__.sql_behaviour, resource, atomics)
atomics
|> Enum.reverse()
|> Enum.reduce_while({:ok, query, []}, fn {field, expr}, {:ok, query, dynamics} ->
attribute = Ash.Resource.Info.attribute(resource, field)
type =
query.__ash_bindings__.sql_behaviour.storage_type(resource, attribute.name) ||
query.__ash_bindings__.sql_behaviour.parameterized_type(
attribute.type,
attribute.constraints
)
case AshSql.Expr.dynamic_expr(
query,
expr,
Map.merge(query.__ash_bindings__, %{
location: :update
}),
false,
type
) do
{dynamic, acc} ->
new_field = String.to_atom("__new_#{field}")
{:cont,
{:ok, AshSql.Expr.merge_accumulator(query, acc),
dynamics ++ [{new_field, {dynamic, field}}]}}
other ->
{:halt, other}
end
end)
|> case do
{:ok, query, dynamics} ->
query = Ecto.Query.exclude(query, :select)
pkey_dynamics =
resource
|> Ash.Resource.Info.primary_key()
|> Enum.map(fn key ->
{key, {Ecto.Query.dynamic([row], field(row, ^key)), key}}
end)
dynamics = Keyword.merge(dynamics, pkey_dynamics)
{params, selects, subqueries, _, query} =
Enum.reduce(
dynamics,
{[], [], [], 0, query},
fn {key, {value, original_field}}, {params, select, subqueries, count, query} ->
case AshSql.Expr.dynamic_expr(query, value, query.__ash_bindings__) do
{%Ecto.Query.DynamicExpr{} = dynamic, acc} ->
result =
Ecto.Query.Builder.Dynamic.partially_expand(
query,
dynamic,
params,
subqueries,
%{},
count
)
expr = elem(result, 0)
new_params = elem(result, 1)
new_subqueries = elem(result, 2)
new_count =
result |> Tuple.to_list() |> List.last()
{new_params, [{key, expr} | select], new_subqueries, new_count,
AshSql.Expr.merge_accumulator(query, acc)}
{other, acc} ->
{[{other, {0, original_field}} | params], [{key, {:^, [], [count]}} | select],
subqueries, count + 1, AshSql.Expr.merge_accumulator(query, acc)}
end
end
)
query =
Map.put(query, :select, %Ecto.Query.SelectExpr{
expr: {:%{}, [], Enum.reverse(selects)},
subqueries: subqueries,
params: Enum.reverse(params)
})
{:ok, query}
other ->
other
end
end
# sobelow_skip ["DOS.StringToAtom"]
def query_with_atomics(
resource,
%{__ash_bindings__: %{atomics_in_binding: binding}} = query,
filter,
atomics,
updating_one_changes,
existing_set
) do
{:ok, query} =
if is_nil(filter) do
{:ok, query}
else
AshSql.Filter.filter(query, filter, resource)
end
{query, dynamics} =
atomics
|> Enum.reverse()
|> Enum.reduce({query, []}, fn {field, _expr}, {query, set} ->
mapped_field = String.to_atom("__new_#{field}")
{query, [{field, Ecto.Query.dynamic([], field(as(^binding), ^mapped_field))} | set]}
end)
{params, set, count} =
updating_one_changes
|> Map.to_list()
|> Enum.reduce({[], [], 0}, fn {key, value}, {params, set, count} ->
{[{value, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1}
end)
{params, set, _, query} =
Enum.reduce(
dynamics ++ existing_set,
{params, set, count, query},
fn {key, value}, {params, set, count, query} ->
case AshSql.Expr.dynamic_expr(query, value, query.__ash_bindings__) do
{%Ecto.Query.DynamicExpr{} = dynamic, acc} ->
result =
Ecto.Query.Builder.Dynamic.partially_expand(
:update,
query,
dynamic,
params,
count
)
expr = elem(result, 0)
new_params = elem(result, 1)
new_count =
result |> Tuple.to_list() |> List.last()
{new_params, [{key, expr} | set], new_count,
AshSql.Expr.merge_accumulator(query, acc)}
{other, acc} ->
{[{other, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1,
AshSql.Expr.merge_accumulator(query, acc)}
end
end
)
case set do
[] ->
{:empty, query}
set ->
{:ok,
Map.put(query, :updates, [
%Ecto.Query.QueryExpr{
# why do I have to reverse the `set`???
# it breaks if I don't
expr: [set: Enum.reverse(set)],
params: Enum.reverse(params)
}
])}
end
end
@moduledoc false
def query_with_atomics(
resource,
query,
filter,
atomics,
updating_one_changes,
existing_set
) do
atomics = type_atomics(query.__ash_bindings__.sql_behaviour, resource, atomics)
{:ok, query} =
if is_nil(filter) do
{:ok, query}
else
AshSql.Filter.filter(query, filter, resource)
end
atomics_result =
atomics
|> Enum.reverse()
|> Enum.reduce_while({:ok, query, []}, fn {field, expr}, {:ok, query, set} ->
attribute = Ash.Resource.Info.attribute(resource, field)
type =
query.__ash_bindings__.sql_behaviour.storage_type(resource, attribute.name) ||
query.__ash_bindings__.sql_behaviour.parameterized_type(
attribute.type,
attribute.constraints
)
case AshSql.Expr.dynamic_expr(
query,
expr,
Map.merge(query.__ash_bindings__, %{
location: :update
}),
false,
type
) do
{dynamic, acc} ->
{:cont,
{:ok, AshSql.Expr.merge_accumulator(query, acc), Keyword.put(set, field, dynamic)}}
other ->
{:halt, other}
end
end)
case atomics_result do
{:ok, query, dynamics} ->
{params, set, count} =
updating_one_changes
|> Map.to_list()
|> Enum.reduce({[], [], 0}, fn {key, value}, {params, set, count} ->
{[{value, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1}
end)
{params, set, _, query} =
Enum.reduce(
dynamics ++ existing_set,
{params, set, count, query},
fn {key, value}, {params, set, count, query} ->
case AshSql.Expr.dynamic_expr(query, value, query.__ash_bindings__) do
{%Ecto.Query.DynamicExpr{} = dynamic, acc} ->
result =
Ecto.Query.Builder.Dynamic.partially_expand(
:select,
query,
dynamic,
params,
count
)
expr = elem(result, 0)
new_params = elem(result, 1)
new_count =
result |> Tuple.to_list() |> List.last()
{new_params, [{key, expr} | set], new_count,
AshSql.Expr.merge_accumulator(query, acc)}
{other, acc} ->
{[{other, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1,
AshSql.Expr.merge_accumulator(query, acc)}
end
end
)
case set do
[] ->
{:empty, query}
set ->
{:ok,
Map.put(query, :updates, [
%Ecto.Query.QueryExpr{
# why do I have to reverse the `set`???
# it breaks if I don't
expr: [set: Enum.reverse(set)],
params: Enum.reverse(params)
}
])}
end
{:error, error} ->
{:error, error}
end
end
defp type_atomics(sql_behaviour, resource, atomics) do
Enum.map(atomics, fn {key, expr} ->
attribute = Ash.Resource.Info.attribute(resource, key)
expr =
case sql_behaviour.storage_type(resource, attribute.name) do
nil ->
%Ash.Query.Function.Type{arguments: [expr, attribute.type, attribute.constraints]}
_ ->
expr
end
{key, expr}
end)
end
end