defmodule QuackDB.Columns do
@moduledoc """
Column-oriented query result.
This struct preserves column order and result metadata while exposing vectors in
a map keyed by disambiguated column names. It is intended for analytical
workflows and as a stable bridge toward future Arrow/columnar integrations.
"""
@type t :: %__MODULE__{
names: [String.t()],
original_names: [String.t()],
columns: %{String.t() => [term()]},
num_rows: non_neg_integer(),
command: QuackDB.Result.command() | nil,
connection_id: String.t() | nil,
messages: [map()] | nil,
metadata: map()
}
defstruct names: [],
original_names: [],
columns: %{},
num_rows: 0,
command: nil,
connection_id: nil,
messages: nil,
metadata: %{}
@doc "Returns a column vector by disambiguated name."
@spec fetch(t(), String.t()) :: {:ok, [term()]} | :error
def fetch(%__MODULE__{columns: columns}, name), do: Map.fetch(columns, name)
@doc "Returns a column vector by disambiguated name, or `default` when absent."
@spec get(t(), String.t(), term()) :: [term()] | term()
def get(%__MODULE__{columns: columns}, name, default \\ nil),
do: Map.get(columns, name, default)
@doc "Converts column vectors back to row lists."
@spec to_rows(t()) :: [[term()]]
def to_rows(%__MODULE__{names: names, columns: columns}) do
names
|> Enum.map(&Map.fetch!(columns, &1))
|> Enum.zip()
|> Enum.map(&Tuple.to_list/1)
end
@doc "Converts column vectors to row maps keyed by disambiguated column names."
@spec to_maps(t()) :: [%{String.t() => term()}]
def to_maps(%__MODULE__{names: names, columns: columns}) do
names
|> Enum.map(&Map.fetch!(columns, &1))
|> Enum.zip()
|> Enum.map(fn row -> names |> Enum.zip(Tuple.to_list(row)) |> Map.new() end)
end
@doc "Raises because column result structs are read-only."
def get_and_update(_columns, _key, _function),
do: raise(ArgumentError, "QuackDB.Columns is read-only")
@doc "Raises because column result structs are read-only."
def pop(_columns, _key), do: raise(ArgumentError, "QuackDB.Columns is read-only")
end
defimpl Enumerable, for: QuackDB.Columns do
def count(%QuackDB.Columns{names: names}), do: {:ok, length(names)}
def member?(%QuackDB.Columns{columns: columns}, {name, values}),
do: {:ok, Map.get(columns, name) == values}
def member?(_columns, _other), do: {:ok, false}
def slice(_columns), do: {:error, __MODULE__}
def reduce(%QuackDB.Columns{names: names, columns: columns}, acc, fun) do
names
|> Enum.map(fn name -> {name, Map.fetch!(columns, name)} end)
|> Enumerable.List.reduce(acc, fun)
end
end