lib/ash/query/operator/is_nil.ex

defmodule Ash.Query.Operator.IsNil do
  @moduledoc """
  left is_nil true/false

  This predicate matches if the left is nil when the right is `true` or if the
  left is not nil when the right is `false`
  """
  use Ash.Query.Operator,
    operator: :is_nil,
    predicate?: true,
    types: [[:any, :boolean]]

  def new(nil, true), do: {:ok, true}
  def new(nil, false), do: {:ok, false}

  def new(left, right) do
    if right == false and not Ash.Expr.can_return_nil?(left) do
      {:known, false}
    else
      super(left, right)
    end
  end

  @impl Ash.Query.Operator
  def evaluate_nil_inputs?, do: true

  @impl Ash.Query.Operator
  def evaluate(%{right: nil}), do: {:known, nil}

  def evaluate(%{left: left, right: is_nil?}) do
    {:known, is_nil(left) == is_nil?}
  end

  def to_string(%{left: left, right: right}, opts) do
    import Inspect.Algebra

    cond do
      right == true ->
        concat([
          "is_nil(",
          to_doc(left, opts),
          ")"
        ])

      right == false ->
        concat([
          "not(is_nil(",
          to_doc(left, opts),
          ")"
        ])

      true ->
        concat([
          " is_nil(",
          to_doc(left, opts),
          ") == ",
          to_doc(right, opts)
        ])
    end
  end

  def can_return_nil?(%{right: right}), do: Ash.Expr.can_return_nil?(right)

  @impl Ash.Filter.Predicate
  def compare(%__MODULE__{left: %Ref{} = same_ref, right: true}, %Ash.Query.Operator.Eq{
        left: %Ref{} = same_ref,
        right: nil
      }) do
    :mutually_inclusive
  end

  def compare(%__MODULE__{left: %Ref{} = same_ref, right: false}, %Ash.Query.Operator.Eq{
        left: %Ref{} = same_ref,
        right: nil
      }) do
    :mutually_exclusive_and_collectively_exhaustive
  end

  def compare(%__MODULE__{left: %Ref{} = same_ref, right: false}, %Ash.Query.Operator.Eq{
        left: %Ref{} = same_ref,
        right: %Ref{}
      }) do
    :unknown
  end

  def compare(%__MODULE__{left: %Ref{} = same_ref, right: false}, %Ash.Query.Operator.Eq{
        left: %Ref{} = same_ref
      }) do
    :right_includes_left
  end

  def compare(%__MODULE__{left: %Ref{} = same_ref, right: true}, %__MODULE__{
        left: %Ref{} = same_ref,
        right: false
      }) do
    :mutually_exclusive_and_collectively_exhaustive
  end

  def compare(%__MODULE__{left: %Ref{} = same_ref, right: false}, %__MODULE__{
        left: %Ref{} = same_ref,
        right: true
      }) do
    :mutually_exclusive_and_collectively_exhaustive
  end

  def compare(%__MODULE__{left: %Ref{} = same_ref, right: right}, %__MODULE__{
        left: %Ref{} = same_ref,
        right: right
      })
      when is_boolean(right) do
    :mutually_inclusive
  end

  def compare(_left, _right) do
    :unknown
  end
end