Skip to main content

lib/ecto/adapters/quack_db.ex

if Code.ensure_loaded?(Ecto.Adapters.SQL) do
  defmodule Ecto.Adapters.QuackDB do
    @moduledoc """
    Minimal Ecto SQL adapter for QuackDB.

    Ecto SQL adapter for DuckDB over QuackDB. Supports raw SQL, analytical query
    generation, insert paths, common mutations, and basic migration DDL.

    ## Configuration

        config :my_app, MyApp.AnalyticsRepo,
          adapter: Ecto.Adapters.QuackDB,
          uri: "http://[::1]:9494",
          token: "super_secret"

    """

    use Ecto.Adapters.SQL,
      driver: :quackdb

    @impl Ecto.Adapter.Migration
    def supports_ddl_transaction?, do: true

    @impl Ecto.Adapter.Migration
    def lock_for_migrations(_meta, _options, fun), do: fun.()

    @impl Ecto.Adapter.Schema
    def autogenerate(:id), do: nil
    def autogenerate(:embed_id), do: Ecto.UUID.generate()
    def autogenerate(:binary_id), do: Ecto.UUID.bingenerate()

    def loaders({:map, _}, type), do: [&json_decode/1, &Ecto.Type.embedded_load(type, &1, :json)]
    def loaders(:map, type), do: [&json_decode/1, type]
    def loaders(:binary_id, type), do: [Ecto.UUID, type]
    def loaders(_, type), do: [type]

    def dumpers({:map, _}, type), do: [&Ecto.Type.embedded_dump(type, &1, :json)]
    def dumpers(:binary_id, type), do: [type, Ecto.UUID]
    def dumpers(_, type), do: [type]

    @impl Ecto.Adapter.Schema
    def insert_all(
          adapter_meta,
          schema_meta,
          header,
          rows,
          on_conflict,
          returning,
          placeholders,
          opts
        ) do
      case Keyword.get(opts, :insert_method, :sql) do
        :sql ->
          sql_insert_all(
            adapter_meta,
            schema_meta,
            header,
            rows,
            on_conflict,
            returning,
            placeholders,
            opts
          )

        :append ->
          Ecto.Adapters.QuackDB.AppendInsert.run(
            adapter_meta,
            schema_meta,
            header,
            rows,
            on_conflict,
            returning,
            placeholders,
            opts
          )

        other ->
          unsupported!(
            :schema_inserts,
            "unsupported insert_method for QuackDB: #{inspect(other)}"
          )
      end
    end

    defp sql_insert_all(
           adapter_meta,
           schema_meta,
           header,
           rows,
           on_conflict,
           returning,
           placeholders,
           opts
         ) do
      Ecto.Adapters.SQL.insert_all(
        adapter_meta,
        schema_meta,
        @conn,
        header,
        rows,
        on_conflict,
        returning,
        placeholders,
        opts
      )
    end

    defp json_decode(value) when is_binary(value), do: JSON.decode(value)
    defp json_decode(value), do: {:ok, value}

    defp unsupported!(feature, message) do
      raise QuackDB.Error.new(:ecto_feature_not_supported, message,
              source: :client,
              metadata: %{feature: feature}
            )
    end
  end
end