lib/ash/query/operator/basic.ex

defmodule Ash.Query.Operator.Basic do
  @operators [
    plus: [
      symbol: :+,
      no_nils: true
    ],
    times: [
      symbol: :*,
      no_nils: true
    ],
    minus: [
      symbol: :-,
      no_nils: true
    ],
    div: [
      symbol: :/,
      no_nils: true
    ],
    concat: [
      symbol: :<>,
      no_nils: true
    ],
    or: [
      symbol: :||
    ],
    and: [
      symbol: :&&
    ]
  ]

  Module.register_attribute(__MODULE__, :operator_modules, accumulate: true)

  for {name, opts} <- @operators do
    mod = Module.concat([__MODULE__, String.capitalize(to_string(name))])
    @operator_modules mod

    Module.create(
      mod,
      quote generated: true do
        @moduledoc """
        left #{unquote(opts[:symbol])} right
        """

        use Ash.Query.Operator,
          operator: unquote(opts[:symbol]),
          name: unquote(name),
          predicate?: false,
          types: [:same, :any]

        if unquote(opts[:no_nils]) do
          def evaluate(%{left: left, right: right}) do
            if is_nil(left) || is_nil(right) do
              {:known, nil}
            else
              # delegate to function to avoid dialyzer warning
              # that this can only ever be one value (for each module we define)
              do_evaluate(unquote(opts[:symbol]), left, right)
            end
          end
        else
          def evaluate(%{left: left, right: right}) do
            # delegate to function to avoid dialyzer warning
            # that this can only ever be one value (for each module we define)
            do_evaluate(unquote(opts[:symbol]), left, right)
          end
        end

        defp do_evaluate(:<>, left, right) do
          {:known, to_string(left) <> to_string(right)}
        end

        defp do_evaluate(:||, left, right) do
          {:known, left || right}
        end

        defp do_evaluate(:&&, left, right) do
          {:known, left && right}
        end

        defp do_evaluate(op, left, right) do
          {:known, apply(Kernel, unquote(opts[:symbol]), [left, right])}
        end
      end,
      Macro.Env.location(__ENV__)
    )
  end

  def operator_modules do
    @operator_modules
  end
end