lib/adbc_result.ex

defmodule Adbc.Result do
  @moduledoc """
  A struct returned as result from queries.

  It has two fields:

    * `:data` - a list of `Adbc.Column`

    * `:num_rows` - the number of rows returned, if returned
      by the database
  """
  defstruct [:num_rows, :data]

  @type t :: %Adbc.Result{
          num_rows: non_neg_integer() | nil,
          data: [%Adbc.Column{}]
        }

  @doc """
  `materialize/1` converts the result set's data from reference type to regular Elixir terms.
  """
  @spec materialize(%Adbc.Result{} | {:ok, %Adbc.Result{}} | {:error, String.t()}) ::
          %Adbc.Result{} | {:ok, %Adbc.Result{}} | {:error, String.t()}
  def materialize(%Adbc.Result{data: data} = result) when is_list(data) do
    %{result | data: Enum.map(data, &Adbc.Column.materialize/1)}
  end

  # allow for the result to be wrapped in an `{:ok, result}` tuple
  # and also allow error tuples to pass through
  # easier to use in pipelines
  def materialize({:ok, %Adbc.Result{data: data} = result}) when is_list(data) do
    {:ok, %{result | data: Enum.map(data, &Adbc.Column.materialize/1)}}
  end

  def materialize({:error, reason}), do: {:error, reason}

  @doc """
  Returns a map of columns as a result.
  """
  def to_map(result = %Adbc.Result{}) do
    Map.new(to_list(materialize(result)).data, fn %Adbc.Column{name: name, type: type, data: data} ->
      case type do
        :list -> {name, Enum.map(data, &list_to_map/1)}
        _ -> {name, data}
      end
    end)
  end

  @doc """
  Convert any list view in the result set to normal lists.
  """
  @spec to_list(%Adbc.Result{}) :: %Adbc.Result{}
  def to_list(result = %Adbc.Result{data: data}) when is_list(data) do
    %{result | data: Enum.map(data, &Adbc.Column.to_list/1)}
  end

  defp list_to_map(nil), do: nil

  defp list_to_map(%Adbc.Column{name: name, type: type, data: data}) do
    case type do
      :list ->
        list = Enum.map(data, &list_to_map/1)

        if name == "item" do
          list
        else
          {name, list}
        end

      :struct ->
        Enum.map(data, &list_to_map/1)

      _ ->
        if name == "item" do
          data
        else
          {name, data}
        end
    end
  end
end