QuackDB.Server (quackdb v0.7.0)

Copy Markdown View Source

Supervises a local DuckDB Quack server process with MuonTrap.

This is a convenience for local development, tests, demos, and notebooks. It starts the external duckdb executable and serves DuckDB's Quack HTTP protocol. It is not an embedded DuckDB driver and is not required when your Quack server runs elsewhere.

children =
  QuackDB.Server.child_specs(
    server: [name: MyApp.DuckDB, duckdb: :managed, endpoint: "quack:localhost:9494"],
    client: [name: MyApp.QuackDB, pool_size: 5]
  )

Use duckdb: :managed to download and cache DuckDB's official CLI binary via QuackDB.Binary. Pass duckdb: "/path/to/duckdb" or set QUACKDB_BINARY_PATH when you want to provide the executable yourself.

child_specs/1 generates one shared random token when neither side provides :token, then injects the same token and URI into the server and client specs.

By default the server runs DuckDB directly under MuonTrap with -interactive so the process stays alive after quack_serve/2 starts. It installs and loads the quack extension before serving, unless load_quack?: false is set. Boot SQL is written to an Elixir-owned temporary init file by default so generated local tokens are not embedded in the operating-system argument vector.

Startup waits until the Quack endpoint is ready. For the default DuckDB CLI command, readiness is detected from the quack_serve/2 result row printed to stdout. :poll_interval is only the fallback probe interval for custom daemon output handling or custom commands that do not expose that row. Probes run in a cancellable task so daemon exits and output remain visible while HTTP waits; the startup deadline also cancels an in-flight probe.

Startup failures report :server_start_failed, :server_start_timeout, or :database_locked for recognized DuckDB file-lock conflicts. Error metadata includes :database, :uri, :last_error, an :output_tail of at most 8 KiB, and :exit_reason when the daemon exits. The retained output redacts the server token, replaces invalid UTF-8, and truncates on a character boundary. Explicitly configured logger callbacks still receive original output. Capture is best-effort, newline-delimited MuonTrap output; disabling stderr forwarding can omit diagnostics. Other secrets in custom boot output are not automatically redacted.

Stopping this process signals the daemon; it does not run CHECKPOINT or guarantee an immediately copyable database file. Preserve a remaining WAL for recovery. Quiesce writers and explicitly checkpoint before a single-file copy, then stop clients and the supervised server and verify OS-process termination. A supervised server is a permanent child: use Supervisor.terminate_child/2 rather than GenServer.stop/1 to stop it without automatic restart.

Summary

Functions

Returns a specification to start this module under a supervisor.

Returns matching server and client child specs, in startup order.

Returns the local MuonTrap wrapper's OS PID, not the DuckDB child PID.

Types

option()

@type option() ::
  {:name, GenServer.name()}
  | {:duckdb, String.t() | :managed}
  | {:duckdb_options, keyword()}
  | {:database, String.t()}
  | {:endpoint, String.t()}
  | {:uri, String.t()}
  | {:token, String.t()}
  | {:load_quack?, boolean()}
  | {:install_quack?, boolean()}
  | {:boot_sql, String.t()}
  | {:boot_sql_source, :init_file | :cmd}
  | {:settings, keyword(QuackDB.SQL.parameter())}
  | {:global_settings, keyword(QuackDB.SQL.parameter())}
  | {:serve_options, keyword()}
  | {:recovery_mode, :no_wal_writes | String.t()}
  | {:attach_as, atom() | String.t()}
  | {:wait, boolean()}
  | {:wait_timeout, timeout()}
  | {:poll_interval, pos_integer()}
  | {:daemon_options, Keyword.t()}
  | {:daemon_command, {String.t(), [String.t()]}}

Functions

child_spec(init_arg)

@spec child_spec([option()]) :: Supervisor.child_spec()

Returns a specification to start this module under a supervisor.

See Supervisor.

child_specs(options \\ [])

@spec child_specs(keyword()) :: [Supervisor.child_spec()]

Returns matching server and client child specs, in startup order.

:server is a list of Server options. :client accepts a keyword list for a QuackDB pool, or {module, options} for another client such as an Ecto Repo. The client must accept :uri and :token options. URI/token precedence is server, then client, then generated defaults. Both specs retain the generated credentials across restarts.

alias QuackDB.Server

children =
  Server.child_specs(
    server: [name: MyApp.DuckDB],
    client: {MyApp.Repo, pool_size: 2}
  )

A :rest_for_one supervisor can restart the client when its server restarts and invalidates existing sessions.

info(server)

@spec info(GenServer.server()) :: map()

os_pid(server)

@spec os_pid(GenServer.server()) :: non_neg_integer() | :error

Returns the local MuonTrap wrapper's OS PID, not the DuckDB child PID.

This does not identify the process holding a conflicting database file lock.

start_link(options \\ [])

@spec start_link([option()]) :: GenServer.on_start()

statistics(server)

@spec statistics(GenServer.server()) :: map()

token(server)

@spec token(GenServer.server()) :: String.t()

uri(server)

@spec uri(GenServer.server()) :: String.t()