Skip to content

Commit fa46610

Browse files
committed
refactor: convert is_distinct_from operators to functions
Following PR review feedback, convert IS DISTINCT FROM and IS NOT DISTINCT FROM from operators to functions. Changes: - Remove Ash.Query.Operator.DistinctFrom and NotDistinctFrom - Add Ash.Query.Function.IsDistinctFrom and IsNotDistinctFrom - Move optimization logic to new() instead of simplify() - Update test descriptions to reflect eager evaluation behavior Rationale: - Functions cannot be overloaded, which is appropriate here since is_distinct_from only needs [:any, :any] signature - Optimization (converting to != or == when NULL is impossible) happens at creation time in new() - simplify() is only for filter comparison context, not needed for these functions
1 parent 308e888 commit fa46610

File tree

5 files changed

+34
-48
lines changed

5 files changed

+34
-48
lines changed

lib/ash/filter/filter.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ defmodule Ash.Filter do
3232
Has,
3333
If,
3434
Intersects,
35+
IsDistinctFrom,
3536
IsNil,
37+
IsNotDistinctFrom,
3638
Lazy,
3739
Length,
3840
Minus,
@@ -51,14 +53,12 @@ defmodule Ash.Filter do
5153
}
5254

5355
alias Ash.Query.Operator.{
54-
DistinctFrom,
5556
Eq,
5657
GreaterThan,
5758
GreaterThanOrEqual,
5859
In,
5960
LessThan,
6061
LessThanOrEqual,
61-
NotDistinctFrom,
6262
NotEq
6363
}
6464

@@ -78,7 +78,9 @@ defmodule Ash.Filter do
7878
FromNow,
7979
GetPath,
8080
Has,
81+
IsDistinctFrom,
8182
IsNil,
83+
IsNotDistinctFrom,
8284
If,
8385
Intersects,
8486
Lazy,
@@ -109,9 +111,7 @@ defmodule Ash.Filter do
109111
LessThan,
110112
GreaterThan,
111113
LessThanOrEqual,
112-
GreaterThanOrEqual,
113-
DistinctFrom,
114-
NotDistinctFrom
114+
GreaterThanOrEqual
115115
] ++ Ash.Query.Operator.Basic.operator_modules()
116116

117117
@builtins @functions ++ @operators

lib/ash/query/operator/distinct_from.ex renamed to lib/ash/query/function/is_distinct_from.ex

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,34 @@
22
#
33
# SPDX-License-Identifier: MIT
44

5-
defmodule Ash.Query.Operator.DistinctFrom do
5+
defmodule Ash.Query.Function.IsDistinctFrom do
66
@moduledoc """
7-
left is_distinct_from right
7+
is_distinct_from(left, right)
88
99
SQL's IS DISTINCT FROM operator.
1010
Unlike `!=`, this operator treats NULL as a comparable value.
1111
1212
When both sides cannot return NULL, this simplifies to `!=` for better performance.
1313
"""
14-
use Ash.Query.Operator,
15-
operator: :is_distinct_from,
16-
name: :distinct_from,
17-
predicate?: true,
18-
types: [:same, :any]
19-
20-
@impl Ash.Query.Operator
21-
def new(left, right) do
14+
use Ash.Query.Function, name: :is_distinct_from, predicate?: true
15+
16+
def args, do: [[:any, :any]]
17+
18+
def returns, do: [:boolean]
19+
20+
def evaluate_nil_inputs?, do: true
21+
22+
def new([left, right]) do
2223
if Ash.Expr.can_return_nil?(left) || Ash.Expr.can_return_nil?(right) do
23-
{:ok, struct(__MODULE__, left: left, right: right)}
24+
{:ok, struct(__MODULE__, arguments: [left, right])}
2425
else
2526
Ash.Query.Operator.new(Ash.Query.Operator.NotEq, left, right)
2627
end
2728
end
2829

29-
@impl Ash.Query.Operator
30-
def evaluate(%{left: left, right: right}) do
30+
def evaluate(%{arguments: [left, right]}) do
3131
{:known, Comp.not_equal?(left, right)}
3232
end
3333

34-
@impl Ash.Query.Operator
35-
def evaluate_nil_inputs?, do: true
36-
3734
def can_return_nil?(_), do: false
3835
end

lib/ash/query/operator/not_distinct_from.ex renamed to lib/ash/query/function/is_not_distinct_from.ex

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,34 @@
22
#
33
# SPDX-License-Identifier: MIT
44

5-
defmodule Ash.Query.Operator.NotDistinctFrom do
5+
defmodule Ash.Query.Function.IsNotDistinctFrom do
66
@moduledoc """
7-
left is_not_distinct_from right
7+
is_not_distinct_from(left, right)
88
99
SQL's IS NOT DISTINCT FROM operator (NULL-safe equality).
1010
Unlike `==`, this operator treats NULL as equal to NULL.
1111
1212
When both sides cannot return NULL, this simplifies to `==` for better performance.
1313
"""
14-
use Ash.Query.Operator,
15-
operator: :is_not_distinct_from,
16-
name: :not_distinct_from,
17-
predicate?: true,
18-
types: [:same, :any]
19-
20-
@impl Ash.Query.Operator
21-
def new(left, right) do
14+
use Ash.Query.Function, name: :is_not_distinct_from, predicate?: true
15+
16+
def args, do: [[:any, :any]]
17+
18+
def returns, do: [:boolean]
19+
20+
def evaluate_nil_inputs?, do: true
21+
22+
def new([left, right]) do
2223
if Ash.Expr.can_return_nil?(left) || Ash.Expr.can_return_nil?(right) do
23-
{:ok, struct(__MODULE__, left: left, right: right)}
24+
{:ok, struct(__MODULE__, arguments: [left, right])}
2425
else
2526
Ash.Query.Operator.new(Ash.Query.Operator.Eq, left, right)
2627
end
2728
end
2829

29-
@impl Ash.Query.Operator
30-
def evaluate(%{left: left, right: right}) do
30+
def evaluate(%{arguments: [left, right]}) do
3131
{:known, Comp.equal?(left, right)}
3232
end
3333

34-
@impl Ash.Query.Operator
35-
def evaluate_nil_inputs?, do: true
36-
37-
@impl Ash.Filter.Predicate
38-
def simplify(%__MODULE__{left: left, right: right}) do
39-
{:ok, op} = Ash.Query.Operator.new(Ash.Query.Operator.DistinctFrom, left, right)
40-
Ash.Query.Not.new(op)
41-
end
42-
4334
def can_return_nil?(_), do: false
4435
end

lib/ash/query/operator/operator.ex

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,7 @@ defmodule Ash.Query.Operator do
380380
Ash.Query.Operator.IsNil,
381381
Ash.Query.Operator.LessThanOrEqual,
382382
Ash.Query.Operator.LessThan,
383-
Ash.Query.Operator.NotEq,
384-
Ash.Query.Operator.DistinctFrom,
385-
Ash.Query.Operator.NotDistinctFrom
383+
Ash.Query.Operator.NotEq
386384
] ++ Ash.Query.Operator.Basic.operator_modules()
387385
end
388386

test/filter/filter_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,7 +1298,7 @@ defmodule Ash.Test.Filter.FilterTest do
12981298
assert hd(results).id == post1.id
12991299
end
13001300

1301-
test "is_distinct_from evaluates to !=" do
1301+
test "is_distinct_from with literals is eagerly evaluated" do
13021302
assert Image
13031303
|> Ash.Query.filter(is_distinct_from("jpg", "png"))
13041304
|> inspect() =~
@@ -1315,7 +1315,7 @@ defmodule Ash.Test.Filter.FilterTest do
13151315
~S(filter: #Ash.Filter<false>)
13161316
end
13171317

1318-
test "is_not_distinct_from evaluates to ==" do
1318+
test "is_not_distinct_from with literals is eagerly evaluated" do
13191319
assert Image
13201320
|> Ash.Query.filter(is_not_distinct_from("jpg", "png"))
13211321
|> inspect() =~

0 commit comments

Comments
 (0)