Packages
ash_sql
0.2.72
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)
expr =
case expr do
%Ash.Query.Function.Type{arguments: [expr | _]} ->
expr
%Ash.Query.Call{name: :type, args: [expr | _]} ->
expr
_ ->
expr
end
expr =
if AshSql.Calculation.map_type?(
attribute.type,
attribute.constraints || []
) do
expr
else
type_cast_unless_list_of_embedded(expr, attribute)
end
case AshSql.Expr.dynamic_expr(
query,
expr,
Map.merge(query.__ash_bindings__, %{
location: :update
}),
false
) do
{dynamic, acc} ->
new_field = String.to_atom("__new_#{field}")
dynamic =
if is_map(dynamic) and not is_struct(dynamic) do
Ecto.Query.dynamic(type(^dynamic, :map))
else
dynamic
end
{: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: Enum.map(subqueries, &set_subquery_prefix(&1, query)),
params: Enum.reverse(params)
})
{:ok, query}
other ->
other
end
end
def set_subquery_prefix(sub_query, query) do
%{
sub_query
| query: %{
sub_query.query
| prefix:
subquery_prefix(
sub_query,
query,
sub_query.query.__ash_bindings__.resource
)
}
}
end
defp subquery_prefix(sub_query, base_query, resource) do
if Ash.Resource.Info.multitenancy_strategy(resource) == :context do
sub_query.query.__ash_bindings__.sql_behaviour.schema(resource) ||
Map.get(Map.get(base_query, :__ash_bindings__), :tenant) ||
base_query.prefix ||
sub_query.query.__ash_bindings__.sql_behaviour.repo(resource, :mutate).config()[
:default_prefix
]
else
sub_query.query.__ash_bindings__.sql_behaviour.schema(resource) ||
sub_query.query.__ash_bindings__.sql_behaviour.repo(resource, :mutate).config()[
:default_prefix
]
end
end
defp type_cast_unless_list_of_embedded(expr, attribute) do
type_cast? =
if is_list(expr) do
first = Enum.at(expr, 0)
first_embedded? =
is_struct(first) and Ash.Resource.Info.resource?(first.__struct__) and
Ash.Resource.Info.embedded?(first.__struct__)
is_map? = attribute.type in [:map, :jsonb, :json]
not (first_embedded? && !is_map?)
else
true
end
if type_cast? do
{:ok, expr} =
Ash.Query.Function.Type.new([
expr,
attribute.type,
attribute.constraints || []
])
expr
else
expr
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)
expr =
case expr do
%Ash.Query.Function.Type{arguments: [expr | _]} ->
expr
%Ash.Query.Call{name: :type, args: [expr | _]} ->
expr
_ ->
expr
end
expr =
if AshSql.Calculation.map_type?(
attribute.type,
attribute.constraints || []
) do
expr
else
type_cast_unless_list_of_embedded(expr, attribute)
end
case AshSql.Expr.dynamic_expr(
query,
expr,
Map.merge(query.__ash_bindings__, %{
location: :update
}),
false
) 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