Current section

Files

Jump to
ash_sql lib query.ex
Raw

lib/query.ex

defmodule AshSql.Query do
@moduledoc false
import Ecto.Query, only: [subquery: 1, from: 2]
def resource_to_query(resource, implementation) do
from(row in {implementation.table(resource) || "", resource}, [])
end
def set_context(resource, data_layer_query, sql_behaviour, context) do
start_bindings = context[:data_layer][:start_bindings_at] || 0
data_layer_query = from(row in data_layer_query, as: ^start_bindings)
data_layer_query =
if context[:data_layer][:table] do
%{
data_layer_query
| from: %{data_layer_query.from | source: {context[:data_layer][:table], resource}}
}
else
data_layer_query
end
data_layer_query =
if context[:data_layer][:schema] do
Ecto.Query.put_query_prefix(data_layer_query, to_string(context[:data_layer][:schema]))
else
data_layer_query
end
data_layer_query =
data_layer_query
|> AshSql.Bindings.default_bindings(resource, sql_behaviour, context)
|> AshSql.Bindings.add_parent_bindings(context)
data_layer_query =
case context[:data_layer][:lateral_join_source] do
{_data, path} ->
lateral_join_source_query = path |> List.first() |> elem(0)
lateral_join_source_query.resource
|> Ash.Query.set_context(%{
:data_layer => lateral_join_source_query.context[:data_layer]
})
|> Ash.Query.set_tenant(lateral_join_source_query.tenant)
|> set_lateral_join_prefix(data_layer_query)
|> case do
%{valid?: true} = query ->
Ash.Query.data_layer_query(query)
query ->
{:error, query}
end
|> case do
{:ok, lateral_join_source_query} ->
lateral_join_source_query =
if Enum.count(path) == 2 do
Map.update!(lateral_join_source_query, :__ash_bindings__, fn bindings ->
bindings
|> Map.put(:lateral_join_bindings, [start_bindings + 1])
|> Map.update!(:bindings, fn bindings ->
Map.put(
bindings,
start_bindings + 1,
%{
source: path |> Enum.at(1) |> elem(3) |> Map.get(:source),
path: [path |> Enum.at(1) |> elem(3) |> Map.get(:name)],
type: :inner
}
)
end)
end)
else
lateral_join_source_query
end
{:ok,
Map.update!(data_layer_query, :__ash_bindings__, fn bindings ->
Map.put(
bindings,
:lateral_join_source_query,
lateral_join_source_query
)
|> Map.update!(:current, &(&1 + 1))
end)}
{:error, error} ->
{:error, error}
end
_ ->
{:ok, data_layer_query}
end
case data_layer_query do
{:error, error} ->
{:error, error}
{:ok, data_layer_query} ->
case context[:data_layer][:lateral_join_source] do
{_, _} ->
data_layer_query =
data_layer_query
|> Map.update!(:__ash_bindings__, &Map.put(&1, :lateral_join?, true))
{:ok, data_layer_query}
_ ->
ash_bindings =
data_layer_query.__ash_bindings__
|> Map.put(:lateral_join?, false)
{:ok, %{data_layer_query | __ash_bindings__: ash_bindings}}
end
end
end
def return_query(%{__ash_bindings__: %{lateral_join?: true}} = query, resource) do
query =
AshSql.Bindings.default_bindings(query, resource, query.__ash_bindings__.sql_behaviour)
if query.__ash_bindings__[:sort_applied?] do
{:ok, query}
else
AshSql.Sort.apply_sort(
query,
query.__ash_bindings__[:sort],
query.__ash_bindings__.resource
)
end
end
def return_query(query, resource) do
query =
AshSql.Bindings.default_bindings(query, resource, query.__ash_bindings__.sql_behaviour)
with_sort_applied =
if query.__ash_bindings__[:sort_applied?] do
{:ok, query}
else
AshSql.Sort.apply_sort(query, query.__ash_bindings__[:sort], resource)
end
case with_sort_applied do
{:error, error} ->
{:error, error}
{:ok, query} ->
if query.__ash_bindings__[:__order__?] && query.windows[:order] do
if query.distinct do
query_with_order =
from(row in query, select_merge: %{__order__: over(row_number(), :order)})
query_without_limit_and_offset =
query_with_order
|> Ecto.Query.exclude(:limit)
|> Ecto.Query.exclude(:offset)
{:ok,
from(row in subquery(query_without_limit_and_offset),
select: row,
order_by: row.__order__
)
|> Map.put(:limit, query.limit)
|> Map.put(:offset, query.offset)}
else
order_by = %{query.windows[:order] | expr: query.windows[:order].expr[:order_by]}
{:ok,
%{
query
| windows: Keyword.delete(query.windows, :order),
order_bys: [order_by]
}}
end
else
{:ok, %{query | windows: Keyword.delete(query.windows, :order)}}
end
end
end
defp set_lateral_join_prefix(ash_query, query) do
if Ash.Resource.Info.multitenancy_strategy(ash_query.resource) == :context do
Ash.Query.set_tenant(ash_query, query.prefix)
else
ash_query
end
end
end