improvement: handle nils in memory the same way sql would have

This commit is contained in:
Zach Daniel 2023-06-05 16:50:11 -04:00
parent 04fb305f10
commit 8e7815388e
7 changed files with 87 additions and 55 deletions

View file

@ -373,11 +373,15 @@ defmodule Ash.DataLayer.Ets do
case Ash.Expr.eval_hydrated(expression, record: record, resource: resource) do
{:ok, value} ->
if calculation.load do
{:cont, {:ok, Map.put(record, calculation.load, value)}}
{:cont, {:ok, Map.put(record, calculation.load, value || calculation.default)}}
else
{:cont,
{:ok,
Map.update!(record, :calculations, &Map.put(&1, calculation.name, value))}}
Map.update!(
record,
:calculations,
&Map.put(&1, calculation.name, value || calculation.default)
)}}
end
:unknown ->
@ -385,7 +389,12 @@ defmodule Ash.DataLayer.Ets do
{:cont, {:ok, Map.put(record, calculation.load, nil)}}
else
{: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
{:error, error} ->

View file

@ -193,7 +193,7 @@ defmodule Ash.Filter do
use Ash.Api
resources do
allow_unregistered? true
allow_unregistered?(true)
end
end
@ -1717,7 +1717,7 @@ defmodule Ash.Filter do
end
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
{:ok, 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] || [])}
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
{:ok, new_expression} ->
{:cont, {:ok, new_expression}}
@ -2089,8 +2089,13 @@ defmodule Ash.Filter do
parse_expression([statement], context)
end
defp add_expression_part(boolean, _context, expression) when is_boolean(boolean),
do: {:ok, BooleanExpression.optimized_new(:and, expression, boolean)}
defp add_expression_part(boolean, context, nil) do
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
{:ok,
@ -2544,7 +2549,7 @@ defmodule Ash.Filter do
value
|> 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
{:ok, new_expression} ->
{:cont, {:ok, new_expression}}
@ -3240,16 +3245,22 @@ defmodule Ash.Filter do
defp add_to_ref_path(other, _), do: other
defp parse_and_join(statements, op, context) do
Enum.reduce_while(statements, {:ok, nil}, fn statement, {:ok, expression} ->
case parse_expression(statement, context) do
{:ok, nested_expression} ->
{:cont, {:ok, BooleanExpression.optimized_new(op, expression, nested_expression)}}
defp parse_and_join([statement | statements], op, context) do
case parse_expression(statement, context) do
{:ok, nested_expression} ->
Enum.reduce_while(statements, {:ok, nested_expression}, fn statement, {:ok, expression} ->
case parse_expression(statement, context) do
{:ok, nested_expression} ->
{:cont, {:ok, BooleanExpression.optimized_new(op, expression, nested_expression)}}
{:error, error} ->
{:halt, {:error, error}}
end
end)
{:error, error} ->
{:halt, {:error, error}}
end
end)
{:error, error} ->
{:halt, {:error, error}}
end
end
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)
else
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} ->
case parse_predicates(List.wrap(value), attr, context) do
{:ok, not_expression} ->

View file

@ -279,11 +279,17 @@ defmodule Ash.Filter.Runtime do
:unknown
end
%Not{expression: nil} ->
{:ok, nil}
%Not{expression: expression} ->
case do_match(record, expression, parent, resource) do
:unknown ->
:unknown
{:ok, nil} ->
{:ok, nil}
{:ok, match?} ->
{:ok, !match?}
@ -744,7 +750,7 @@ defmodule Ash.Filter.Runtime do
{:ok, false}
{:ok, nil} ->
{:ok, false}
{:ok, nil}
{:ok, true} ->
case do_match(record, right, parent) do
@ -752,7 +758,7 @@ defmodule Ash.Filter.Runtime do
{:ok, false}
{:ok, nil} ->
{:ok, false}
{:ok, nil}
{:ok, _} ->
{:ok, true}

View file

@ -8,8 +8,10 @@ defmodule Ash.Query.BooleanExpression do
alias Ash.Query.Ref
def new(_, nil, nil), do: nil
def new(_, left, nil), do: left
def new(_, nil, right), do: right
def new(:or, left, nil), do: left
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
%__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(:or, true, _), do: true
def optimized_new(:or, _, true), do: true
def optimized_new(_, nil, right), do: right
def optimized_new(_, left, nil), do: left
def optimized_new(:or, nil, right), do: right
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(
op,

View file

@ -29,16 +29,16 @@ defmodule Ash.Test.Actions.ReadTest do
end
actions do
defaults [:read, :create, :update, :destroy]
defaults([:read, :create, :update, :destroy])
end
attributes do
uuid_primary_key :id
attribute :name, :string
uuid_primary_key(:id)
attribute(:name, :string)
end
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
@ -47,39 +47,39 @@ defmodule Ash.Test.Actions.ReadTest do
use Ash.Resource, data_layer: Ash.DataLayer.Ets
identities do
identity :backup_id, [:uuid], pre_check_with: Api
identity(:backup_id, [:uuid], pre_check_with: Api)
end
ets do
private? true
private?(true)
end
actions do
defaults [:read, :create, :update, :destroy]
defaults([:read, :create, :update, :destroy])
read :read_with_after_action do
prepare PostPreparation
prepare(PostPreparation)
end
read :get_by_id do
get_by :id
get_by(:id)
end
read :get_by_id_and_uuid do
get_by [:id, :uuid]
get_by([:id, :uuid])
end
end
attributes do
uuid_primary_key :id
attribute :uuid, :uuid, default: &Ash.UUID.generate/0
attribute :title, :string
attribute :contents, :string
uuid_primary_key(:id)
attribute(:uuid, :uuid, default: &Ash.UUID.generate/0)
attribute(:title, :string)
attribute(:contents, :string)
end
relationships do
belongs_to :author1, Ash.Test.Actions.ReadTest.Author
belongs_to :author2, Ash.Test.Actions.ReadTest.Author
belongs_to(:author1, Ash.Test.Actions.ReadTest.Author)
belongs_to(:author2, Ash.Test.Actions.ReadTest.Author)
end
end

View file

@ -29,7 +29,7 @@ defmodule Ash.Test.Flow.Flows.Branching do
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
input %{return: "inner_branch didn't happen"}
end

View file

@ -4,31 +4,31 @@ defmodule Ash.Test.Flow.Flows.Halting do
flow do
argument :on_step, :atom do
constraints one_of: [:a, :b, :c]
constraints(one_of: [:a, :b, :c])
end
returns :c
returns(:c)
end
steps do
custom :a, Ash.Test.Flow.Steps.SimpleReturn do
input %{return: "a"}
halt_if expr(not (^arg(:on_step) == :a))
halt_reason :not_on_step_a
input(%{return: "a"})
halt_if(expr(not (^arg(:on_step) == :a || false)))
halt_reason(:not_on_step_a)
end
custom :b, Ash.Test.Flow.Steps.SimpleReturn do
input %{return: "b"}
halt_if expr(not (^arg(:on_step) == :b))
wait_for result(:a)
halt_reason :not_on_step_b
input(%{return: "b"})
halt_if(expr(not (^arg(:on_step) == :b || false)))
wait_for(result(:a))
halt_reason(:not_on_step_b)
end
custom :c, Ash.Test.Flow.Steps.SimpleReturn do
input %{return: "c"}
halt_if expr(not (^arg(:on_step) == :c))
wait_for result(:b)
halt_reason :not_on_step_c
input(%{return: "c"})
halt_if(expr(not (^arg(:on_step) == :c || false)))
wait_for(result(:b))
halt_reason(:not_on_step_c)
end
end
end