if Code.ensure_loaded?(Ecto.Query.API) do defmodule QuackDB.Ecto.List do @moduledoc """ DuckDB LIST/ARRAY expression helpers for Ecto queries. The macros map directly to DuckDB list functions and are useful for querying `LIST` columns or Ecto `{:array, type}` fields backed by DuckDB lists. """ @doc "Builds `len(list)`. Named to avoid `Kernel.length/1` import ambiguity." defmacro list_length(list) do quote do fragment("len(?)", unquote(list)) end end @doc "Builds `list_extract(list, index)`. DuckDB list indexes are 1-based." defmacro extract(list, index) do quote do fragment("list_extract(?, ?)", unquote(list), unquote(index)) end end @doc "Builds `list_slice(list, begin, end)`." defmacro slice(list, begin_index, end_index) do quote do fragment("list_slice(?, ?, ?)", unquote(list), unquote(begin_index), unquote(end_index)) end end @doc "Builds `list_slice(list, begin, end, step)`." defmacro slice(list, begin_index, end_index, step) do quote do fragment( "list_slice(?, ?, ?, ?)", unquote(list), unquote(begin_index), unquote(end_index), unquote(step) ) end end @doc "Builds `list_sort(list)`." defmacro sort(list) do quote do fragment("list_sort(?)", unquote(list)) end end @doc "Builds `list_reverse_sort(list)`." defmacro reverse_sort(list) do quote do fragment("list_reverse_sort(?)", unquote(list)) end end @doc "Builds `list_distinct(list)`." defmacro distinct(list) do quote do fragment("list_distinct(?)", unquote(list)) end end @doc "Builds `list_unique(list)`." defmacro unique(list) do quote do fragment("list_unique(?)", unquote(list)) end end @doc "Builds `list_position(list, value)`." defmacro position(list, value) do quote do fragment("list_position(?, ?)", unquote(list), unquote(value)) end end @doc "Builds `list_filter(list, lambda x : ...)`." defmacro list_filter(list, lambda) do lambda_fragment("list_filter", list, lambda, "list_filter/2", [1, 2]) end @doc "Builds `list_transform(list, lambda x : ...)`." defmacro list_transform(list, lambda) do lambda_fragment("list_transform", list, lambda, "list_transform/2", [1, 2]) end @doc "Builds `list_reduce(list, lambda acc, x : ...)`." defmacro list_reduce(list, lambda) do lambda_fragment("list_reduce", list, lambda, "list_reduce/2", [2, 3]) end @doc "Builds `list_reduce(list, lambda acc, x : ..., initial_value)`." defmacro list_reduce(list, lambda, initial_value) do {lambda_sql, params} = QuackDB.Ecto.Lambda.to_sql!(lambda, function: "list_reduce/3", arities: [2, 3]) fragment_sql = "list_reduce(?, #{lambda_sql}, ?)" quote do fragment( unquote(fragment_sql), unquote(list), unquote_splicing(params), unquote(initial_value) ) end end @doc "Builds `list_contains(list, value)`." defmacro contains(list, value) do quote do fragment("list_contains(?, ?)", unquote(list), unquote(value)) end end @doc "Alias for `contains/2` that avoids shared `contains/2` import ambiguity." defmacro contains_list(list, value) do quote do fragment("list_contains(?, ?)", unquote(list), unquote(value)) end end @doc "Builds `list_has_any(left, right)`." defmacro has_any(left, right) do quote do fragment("list_has_any(?, ?)", unquote(left), unquote(right)) end end @doc "Builds `list_has_all(left, right)`." defmacro has_all(left, right) do quote do fragment("list_has_all(?, ?)", unquote(left), unquote(right)) end end @doc "Builds `list_intersect(left, right)`. Named to avoid Ecto `intersect/2` import ambiguity." defmacro intersect_list(left, right) do quote do fragment("list_intersect(?, ?)", unquote(left), unquote(right)) end end @doc "Builds `list_concat(left, right)`." defmacro concat(left, right) do quote do fragment("list_concat(?, ?)", unquote(left), unquote(right)) end end @doc "Builds `unnest(list)`." defmacro unnest(list) do quote do fragment("unnest(?)", unquote(list)) end end defp lambda_fragment(function_name, list, lambda, label, arities) do {lambda_sql, params} = QuackDB.Ecto.Lambda.to_sql!(lambda, function: label, arities: arities) fragment_sql = "#{function_name}(?, #{lambda_sql})" quote do fragment(unquote(fragment_sql), unquote(list), unquote_splicing(params)) end end end end