Skip to main content

lib/quack_db/list.ex

defmodule QuackDB.List do
  @moduledoc """
  SQL expression helpers for DuckDB LIST/ARRAY values.

  These helpers return iodata and map directly to DuckDB list functions. They are
  intentionally small building blocks for larger query modules.
  """

  @doc "Builds `len(list)`."
  @spec length(iodata()) :: iodata()
  def length(list_expression), do: call("len", [list_expression])

  @doc "Builds `list_extract(list, index)`. DuckDB list indexes are 1-based."
  @spec extract(iodata(), iodata()) :: iodata()
  def extract(list_expression, index_expression) do
    call("list_extract", [list_expression, index_expression])
  end

  @doc "Builds `list_slice(list, begin, end)`."
  @spec slice(iodata(), iodata(), iodata()) :: iodata()
  def slice(list_expression, begin_expression, end_expression) do
    call("list_slice", [list_expression, begin_expression, end_expression])
  end

  @doc "Builds `list_slice(list, begin, end, step)`."
  @spec slice(iodata(), iodata(), iodata(), iodata()) :: iodata()
  def slice(list_expression, begin_expression, end_expression, step_expression) do
    call("list_slice", [list_expression, begin_expression, end_expression, step_expression])
  end

  @doc "Builds `list_sort(list)`."
  @spec sort(iodata()) :: iodata()
  def sort(list_expression), do: call("list_sort", [list_expression])

  @doc "Builds `list_reverse_sort(list)`."
  @spec reverse_sort(iodata()) :: iodata()
  def reverse_sort(list_expression), do: call("list_reverse_sort", [list_expression])

  @doc "Builds `list_distinct(list)`."
  @spec distinct(iodata()) :: iodata()
  def distinct(list_expression), do: call("list_distinct", [list_expression])

  @doc "Builds `list_unique(list)`."
  @spec unique(iodata()) :: iodata()
  def unique(list_expression), do: call("list_unique", [list_expression])

  @doc "Builds `list_position(list, value)`."
  @spec position(iodata(), iodata()) :: iodata()
  def position(list_expression, value_expression) do
    call("list_position", [list_expression, value_expression])
  end

  @doc "Builds `list_contains(list, value)`."
  @spec contains(iodata(), iodata()) :: iodata()
  def contains(list_expression, value_expression) do
    call("list_contains", [list_expression, value_expression])
  end

  @doc "Builds `list_has_any(left, right)`."
  @spec has_any(iodata(), iodata()) :: iodata()
  def has_any(left_expression, right_expression) do
    call("list_has_any", [left_expression, right_expression])
  end

  @doc "Builds `list_has_all(left, right)`."
  @spec has_all(iodata(), iodata()) :: iodata()
  def has_all(left_expression, right_expression) do
    call("list_has_all", [left_expression, right_expression])
  end

  @doc "Builds `list_intersect(left, right)`."
  @spec intersect(iodata(), iodata()) :: iodata()
  def intersect(left_expression, right_expression) do
    call("list_intersect", [left_expression, right_expression])
  end

  @doc "Builds `list_concat(left, right)`."
  @spec concat(iodata(), iodata()) :: iodata()
  def concat(left_expression, right_expression) do
    call("list_concat", [left_expression, right_expression])
  end

  @doc "Builds `unnest(list)`."
  @spec unnest(iodata()) :: iodata()
  def unnest(list_expression), do: call("unnest", [list_expression])

  defp call(function, args) do
    [function, "(", Enum.intersperse(args, ", "), ")"]
  end
end