lib/paginator/ecto/query/desc_nulls_first.ex

defmodule Paginator.Ecto.Query.DescNullsFirst do
  @behaviour Paginator.Ecto.Query.DynamicFilterBuilder

  import Ecto.Query

  @impl Paginator.Ecto.Query.DynamicFilterBuilder
  def build_dynamic_filter(%{direction: :before, value: nil, next_filters: true}) do
    raise("unstable sort order: nullable columns can't be used as the last term")
  end

  def build_dynamic_filter(args = %{direction: :before, value: nil}) do
    dynamic(
      [{query, args.entity_position}],
      is_nil(field(query, ^args.column)) and ^args.next_filters
    )
  end

  def build_dynamic_filter(args = %{direction: :before, next_filters: true}) do
    dynamic(
      [{query, args.entity_position}],
      field(query, ^args.column) > ^args.value or is_nil(field(query, ^args.column))
    )
  end

  def build_dynamic_filter(args = %{direction: :before}) do
    dynamic(
      [{query, args.entity_position}],
      (field(query, ^args.column) == ^args.value and ^args.next_filters) or
        field(query, ^args.column) > ^args.value or
        is_nil(field(query, ^args.column))
    )
  end

  def build_dynamic_filter(%{direction: :after, value: nil, next_filters: true}) do
    raise("unstable sort order: nullable columns can't be used as the last term")
  end

  def build_dynamic_filter(args = %{direction: :after, value: nil}) do
    dynamic(
      [{query, args.entity_position}],
      (is_nil(field(query, ^args.column)) and ^args.next_filters) or
        not is_nil(field(query, ^args.column))
    )
  end

  def build_dynamic_filter(args = %{direction: :after, next_filters: true}) do
    dynamic([{query, args.entity_position}], field(query, ^args.column) < ^args.value)
  end

  def build_dynamic_filter(args = %{direction: :after}) do
    dynamic(
      [{query, args.entity_position}],
      (field(query, ^args.column) == ^args.value and ^args.next_filters) or
        field(query, ^args.column) < ^args.value
    )
  end
end