mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 13:33:20 +12:00
improvement: handle nil
s in memory the same way sql would have
This commit is contained in:
parent
04fb305f10
commit
8e7815388e
7 changed files with 87 additions and 55 deletions
|
@ -373,11 +373,15 @@ defmodule Ash.DataLayer.Ets do
|
||||||
case Ash.Expr.eval_hydrated(expression, record: record, resource: resource) do
|
case Ash.Expr.eval_hydrated(expression, record: record, resource: resource) do
|
||||||
{:ok, value} ->
|
{:ok, value} ->
|
||||||
if calculation.load do
|
if calculation.load do
|
||||||
{:cont, {:ok, Map.put(record, calculation.load, value)}}
|
{:cont, {:ok, Map.put(record, calculation.load, value || calculation.default)}}
|
||||||
else
|
else
|
||||||
{:cont,
|
{:cont,
|
||||||
{:ok,
|
{:ok,
|
||||||
Map.update!(record, :calculations, &Map.put(&1, calculation.name, value))}}
|
Map.update!(
|
||||||
|
record,
|
||||||
|
:calculations,
|
||||||
|
&Map.put(&1, calculation.name, value || calculation.default)
|
||||||
|
)}}
|
||||||
end
|
end
|
||||||
|
|
||||||
:unknown ->
|
:unknown ->
|
||||||
|
@ -385,7 +389,12 @@ defmodule Ash.DataLayer.Ets do
|
||||||
{:cont, {:ok, Map.put(record, calculation.load, nil)}}
|
{:cont, {:ok, Map.put(record, calculation.load, nil)}}
|
||||||
else
|
else
|
||||||
{:cont,
|
{:cont,
|
||||||
{:ok, Map.update!(record, :calculations, &Map.put(&1, calculation.name, nil))}}
|
{:ok,
|
||||||
|
Map.update!(
|
||||||
|
record,
|
||||||
|
:calculations,
|
||||||
|
&Map.put(&1, calculation.name, calculation.default)
|
||||||
|
)}}
|
||||||
end
|
end
|
||||||
|
|
||||||
{:error, error} ->
|
{:error, error} ->
|
||||||
|
|
|
@ -193,7 +193,7 @@ defmodule Ash.Filter do
|
||||||
use Ash.Api
|
use Ash.Api
|
||||||
|
|
||||||
resources do
|
resources do
|
||||||
allow_unregistered? true
|
allow_unregistered?(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1717,7 +1717,7 @@ defmodule Ash.Filter do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp records_to_expression(records, relationship, path) do
|
defp records_to_expression(records, relationship, path) do
|
||||||
Enum.reduce_while(records, {:ok, nil}, fn record, {:ok, expression} ->
|
Enum.reduce_while(records, {:ok, true}, fn record, {:ok, expression} ->
|
||||||
case records_to_expression([record], relationship, path) do
|
case records_to_expression([record], relationship, path) do
|
||||||
{:ok, operator} ->
|
{:ok, operator} ->
|
||||||
{:cont, {:ok, BooleanExpression.optimized_new(:and, expression, operator)}}
|
{:cont, {:ok, BooleanExpression.optimized_new(:and, expression, operator)}}
|
||||||
|
@ -2074,7 +2074,7 @@ defmodule Ash.Filter do
|
||||||
do: {:ok, move_to_relationship_path(expression, context[:relationship_path] || [])}
|
do: {:ok, move_to_relationship_path(expression, context[:relationship_path] || [])}
|
||||||
|
|
||||||
defp parse_expression(statement, context) when is_list(statement) do
|
defp parse_expression(statement, context) when is_list(statement) do
|
||||||
Enum.reduce_while(statement, {:ok, nil}, fn expression_part, {:ok, expression} ->
|
Enum.reduce_while(statement, {:ok, true}, fn expression_part, {:ok, expression} ->
|
||||||
case add_expression_part(expression_part, context, expression) do
|
case add_expression_part(expression_part, context, expression) do
|
||||||
{:ok, new_expression} ->
|
{:ok, new_expression} ->
|
||||||
{:cont, {:ok, new_expression}}
|
{:cont, {:ok, new_expression}}
|
||||||
|
@ -2089,8 +2089,13 @@ defmodule Ash.Filter do
|
||||||
parse_expression([statement], context)
|
parse_expression([statement], context)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_expression_part(boolean, _context, expression) when is_boolean(boolean),
|
defp add_expression_part(boolean, context, nil) do
|
||||||
do: {:ok, BooleanExpression.optimized_new(:and, expression, boolean)}
|
add_expression_part(boolean, context, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_expression_part(boolean, _context, expression) when is_boolean(boolean) do
|
||||||
|
{:ok, BooleanExpression.optimized_new(:and, expression, boolean)}
|
||||||
|
end
|
||||||
|
|
||||||
defp add_expression_part(%__MODULE__{expression: adding_expression}, context, expression) do
|
defp add_expression_part(%__MODULE__{expression: adding_expression}, context, expression) do
|
||||||
{:ok,
|
{:ok,
|
||||||
|
@ -2544,7 +2549,7 @@ defmodule Ash.Filter do
|
||||||
|
|
||||||
value
|
value
|
||||||
|> Map.to_list()
|
|> Map.to_list()
|
||||||
|> Enum.reduce_while({:ok, nil}, fn {key, value}, {:ok, expression} ->
|
|> Enum.reduce_while({:ok, true}, fn {key, value}, {:ok, expression} ->
|
||||||
case add_expression_part({key, value}, context, expression) do
|
case add_expression_part({key, value}, context, expression) do
|
||||||
{:ok, new_expression} ->
|
{:ok, new_expression} ->
|
||||||
{:cont, {:ok, new_expression}}
|
{:cont, {:ok, new_expression}}
|
||||||
|
@ -3240,16 +3245,22 @@ defmodule Ash.Filter do
|
||||||
|
|
||||||
defp add_to_ref_path(other, _), do: other
|
defp add_to_ref_path(other, _), do: other
|
||||||
|
|
||||||
defp parse_and_join(statements, op, context) do
|
defp parse_and_join([statement | statements], op, context) do
|
||||||
Enum.reduce_while(statements, {:ok, nil}, fn statement, {:ok, expression} ->
|
case parse_expression(statement, context) do
|
||||||
case parse_expression(statement, context) do
|
{:ok, nested_expression} ->
|
||||||
{:ok, nested_expression} ->
|
Enum.reduce_while(statements, {:ok, nested_expression}, fn statement, {:ok, expression} ->
|
||||||
{:cont, {:ok, BooleanExpression.optimized_new(op, expression, nested_expression)}}
|
case parse_expression(statement, context) do
|
||||||
|
{:ok, nested_expression} ->
|
||||||
|
{:cont, {:ok, BooleanExpression.optimized_new(op, expression, nested_expression)}}
|
||||||
|
|
||||||
{:error, error} ->
|
{:error, error} ->
|
||||||
{:halt, {:error, error}}
|
{:halt, {:error, error}}
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
{:halt, {:error, error}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_predicates(value, field, context) when not is_list(value) and not is_map(value) do
|
defp parse_predicates(value, field, context) when not is_list(value) and not is_map(value) do
|
||||||
|
@ -3266,7 +3277,7 @@ defmodule Ash.Filter do
|
||||||
parse_predicates([eq: values], attr, context)
|
parse_predicates([eq: values], attr, context)
|
||||||
else
|
else
|
||||||
if is_map(values) || Keyword.keyword?(values) do
|
if is_map(values) || Keyword.keyword?(values) do
|
||||||
Enum.reduce_while(values, {:ok, nil}, fn
|
Enum.reduce_while(values, {:ok, true}, fn
|
||||||
{:not, value}, {:ok, expression} ->
|
{:not, value}, {:ok, expression} ->
|
||||||
case parse_predicates(List.wrap(value), attr, context) do
|
case parse_predicates(List.wrap(value), attr, context) do
|
||||||
{:ok, not_expression} ->
|
{:ok, not_expression} ->
|
||||||
|
|
|
@ -279,11 +279,17 @@ defmodule Ash.Filter.Runtime do
|
||||||
:unknown
|
:unknown
|
||||||
end
|
end
|
||||||
|
|
||||||
|
%Not{expression: nil} ->
|
||||||
|
{:ok, nil}
|
||||||
|
|
||||||
%Not{expression: expression} ->
|
%Not{expression: expression} ->
|
||||||
case do_match(record, expression, parent, resource) do
|
case do_match(record, expression, parent, resource) do
|
||||||
:unknown ->
|
:unknown ->
|
||||||
:unknown
|
:unknown
|
||||||
|
|
||||||
|
{:ok, nil} ->
|
||||||
|
{:ok, nil}
|
||||||
|
|
||||||
{:ok, match?} ->
|
{:ok, match?} ->
|
||||||
{:ok, !match?}
|
{:ok, !match?}
|
||||||
|
|
||||||
|
@ -744,7 +750,7 @@ defmodule Ash.Filter.Runtime do
|
||||||
{:ok, false}
|
{:ok, false}
|
||||||
|
|
||||||
{:ok, nil} ->
|
{:ok, nil} ->
|
||||||
{:ok, false}
|
{:ok, nil}
|
||||||
|
|
||||||
{:ok, true} ->
|
{:ok, true} ->
|
||||||
case do_match(record, right, parent) do
|
case do_match(record, right, parent) do
|
||||||
|
@ -752,7 +758,7 @@ defmodule Ash.Filter.Runtime do
|
||||||
{:ok, false}
|
{:ok, false}
|
||||||
|
|
||||||
{:ok, nil} ->
|
{:ok, nil} ->
|
||||||
{:ok, false}
|
{:ok, nil}
|
||||||
|
|
||||||
{:ok, _} ->
|
{:ok, _} ->
|
||||||
{:ok, true}
|
{:ok, true}
|
||||||
|
|
|
@ -8,8 +8,10 @@ defmodule Ash.Query.BooleanExpression do
|
||||||
alias Ash.Query.Ref
|
alias Ash.Query.Ref
|
||||||
|
|
||||||
def new(_, nil, nil), do: nil
|
def new(_, nil, nil), do: nil
|
||||||
def new(_, left, nil), do: left
|
def new(:or, left, nil), do: left
|
||||||
def new(_, nil, right), do: right
|
def new(:or, nil, right), do: right
|
||||||
|
def new(:and, _, nil), do: nil
|
||||||
|
def new(:and, nil, _), do: nil
|
||||||
|
|
||||||
def new(op, left, right) do
|
def new(op, left, right) do
|
||||||
%__MODULE__{op: op, left: left, right: right}
|
%__MODULE__{op: op, left: left, right: right}
|
||||||
|
@ -27,8 +29,12 @@ defmodule Ash.Query.BooleanExpression do
|
||||||
def optimized_new(:and, _, false), do: false
|
def optimized_new(:and, _, false), do: false
|
||||||
def optimized_new(:or, true, _), do: true
|
def optimized_new(:or, true, _), do: true
|
||||||
def optimized_new(:or, _, true), do: true
|
def optimized_new(:or, _, true), do: true
|
||||||
def optimized_new(_, nil, right), do: right
|
def optimized_new(:or, nil, right), do: right
|
||||||
def optimized_new(_, left, nil), do: left
|
def optimized_new(:or, left, nil), do: left
|
||||||
|
def optimized_new(:and, true, right), do: right
|
||||||
|
def optimized_new(:and, left, true), do: left
|
||||||
|
def optimized_new(:and, nil, _), do: nil
|
||||||
|
def optimized_new(:and, _, nil), do: nil
|
||||||
|
|
||||||
def optimized_new(
|
def optimized_new(
|
||||||
op,
|
op,
|
||||||
|
|
|
@ -29,16 +29,16 @@ defmodule Ash.Test.Actions.ReadTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
actions do
|
actions do
|
||||||
defaults [:read, :create, :update, :destroy]
|
defaults([:read, :create, :update, :destroy])
|
||||||
end
|
end
|
||||||
|
|
||||||
attributes do
|
attributes do
|
||||||
uuid_primary_key :id
|
uuid_primary_key(:id)
|
||||||
attribute :name, :string
|
attribute(:name, :string)
|
||||||
end
|
end
|
||||||
|
|
||||||
relationships do
|
relationships do
|
||||||
has_many :posts, Ash.Test.Actions.ReadTest.Post, destination_attribute: :author1_id
|
has_many(:posts, Ash.Test.Actions.ReadTest.Post, destination_attribute: :author1_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -47,39 +47,39 @@ defmodule Ash.Test.Actions.ReadTest do
|
||||||
use Ash.Resource, data_layer: Ash.DataLayer.Ets
|
use Ash.Resource, data_layer: Ash.DataLayer.Ets
|
||||||
|
|
||||||
identities do
|
identities do
|
||||||
identity :backup_id, [:uuid], pre_check_with: Api
|
identity(:backup_id, [:uuid], pre_check_with: Api)
|
||||||
end
|
end
|
||||||
|
|
||||||
ets do
|
ets do
|
||||||
private? true
|
private?(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
actions do
|
actions do
|
||||||
defaults [:read, :create, :update, :destroy]
|
defaults([:read, :create, :update, :destroy])
|
||||||
|
|
||||||
read :read_with_after_action do
|
read :read_with_after_action do
|
||||||
prepare PostPreparation
|
prepare(PostPreparation)
|
||||||
end
|
end
|
||||||
|
|
||||||
read :get_by_id do
|
read :get_by_id do
|
||||||
get_by :id
|
get_by(:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
read :get_by_id_and_uuid do
|
read :get_by_id_and_uuid do
|
||||||
get_by [:id, :uuid]
|
get_by([:id, :uuid])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attributes do
|
attributes do
|
||||||
uuid_primary_key :id
|
uuid_primary_key(:id)
|
||||||
attribute :uuid, :uuid, default: &Ash.UUID.generate/0
|
attribute(:uuid, :uuid, default: &Ash.UUID.generate/0)
|
||||||
attribute :title, :string
|
attribute(:title, :string)
|
||||||
attribute :contents, :string
|
attribute(:contents, :string)
|
||||||
end
|
end
|
||||||
|
|
||||||
relationships do
|
relationships do
|
||||||
belongs_to :author1, Ash.Test.Actions.ReadTest.Author
|
belongs_to(:author1, Ash.Test.Actions.ReadTest.Author)
|
||||||
belongs_to :author2, Ash.Test.Actions.ReadTest.Author
|
belongs_to(:author2, Ash.Test.Actions.ReadTest.Author)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ defmodule Ash.Test.Flow.Flows.Branching do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
branch :inner_branch_alt, expr(not (^arg(:do_inner_branch))) do
|
branch :inner_branch_alt, expr(not (^arg(:do_inner_branch) || false)) do
|
||||||
custom :inner_branch_alt_return, Ash.Test.Flow.Steps.SimpleReturn do
|
custom :inner_branch_alt_return, Ash.Test.Flow.Steps.SimpleReturn do
|
||||||
input %{return: "inner_branch didn't happen"}
|
input %{return: "inner_branch didn't happen"}
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,31 +4,31 @@ defmodule Ash.Test.Flow.Flows.Halting do
|
||||||
|
|
||||||
flow do
|
flow do
|
||||||
argument :on_step, :atom do
|
argument :on_step, :atom do
|
||||||
constraints one_of: [:a, :b, :c]
|
constraints(one_of: [:a, :b, :c])
|
||||||
end
|
end
|
||||||
|
|
||||||
returns :c
|
returns(:c)
|
||||||
end
|
end
|
||||||
|
|
||||||
steps do
|
steps do
|
||||||
custom :a, Ash.Test.Flow.Steps.SimpleReturn do
|
custom :a, Ash.Test.Flow.Steps.SimpleReturn do
|
||||||
input %{return: "a"}
|
input(%{return: "a"})
|
||||||
halt_if expr(not (^arg(:on_step) == :a))
|
halt_if(expr(not (^arg(:on_step) == :a || false)))
|
||||||
halt_reason :not_on_step_a
|
halt_reason(:not_on_step_a)
|
||||||
end
|
end
|
||||||
|
|
||||||
custom :b, Ash.Test.Flow.Steps.SimpleReturn do
|
custom :b, Ash.Test.Flow.Steps.SimpleReturn do
|
||||||
input %{return: "b"}
|
input(%{return: "b"})
|
||||||
halt_if expr(not (^arg(:on_step) == :b))
|
halt_if(expr(not (^arg(:on_step) == :b || false)))
|
||||||
wait_for result(:a)
|
wait_for(result(:a))
|
||||||
halt_reason :not_on_step_b
|
halt_reason(:not_on_step_b)
|
||||||
end
|
end
|
||||||
|
|
||||||
custom :c, Ash.Test.Flow.Steps.SimpleReturn do
|
custom :c, Ash.Test.Flow.Steps.SimpleReturn do
|
||||||
input %{return: "c"}
|
input(%{return: "c"})
|
||||||
halt_if expr(not (^arg(:on_step) == :c))
|
halt_if(expr(not (^arg(:on_step) == :c || false)))
|
||||||
wait_for result(:b)
|
wait_for(result(:b))
|
||||||
halt_reason :not_on_step_c
|
halt_reason(:not_on_step_c)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue