Filo.Executor behaviour (Filo v0.2.0)

Copy Markdown View Source

Behaviour a host application implements to back a Hrana stream with a real database connection.

Filo owns the protocol; the executor owns SQL. A stream opens one connection (open/1, or open/2 to receive the :authorize context), runs statements on it (execute/2), reports its autocommit state (autocommit?/1), and releases it when the stream closes (close/1). The connection handle is opaque to Filo.

Summary

Types

An opaque connection handle the host returns from open/1.

Callbacks

Reports whether the connection is currently in autocommit mode — i.e. not inside an explicit transaction.

Releases the connection when its stream closes.

Describes a statement without running it — its parameters, columns, and whether it is an EXPLAIN or read-only. Optional: an executor that does not implement it makes describe requests fail with an "unsupported" stream error.

Runs one statement on the connection.

Runs a SQL script — one or more statements — for its side effects, returning no rows. Used by the Hrana sequence request. Optional: an executor that does not implement it makes sequence requests fail with an "unsupported" error.

Opens a connection for a new stream. arg is host-supplied context (for example the database name extracted from the request).

Opens a connection for a new stream, given the host context that the :authorize callback returned for this connection (see Filo.Plug).

The process whose death invalidates this connection — e.g. a per-database coordinator that owns the underlying file's lifecycle and whose successor may flush/replace/drop that file once it believes no connections remain.

Types

conn()

@type conn() :: term()

An opaque connection handle the host returns from open/1.

Callbacks

autocommit?(conn)

@callback autocommit?(conn()) :: boolean()

Reports whether the connection is currently in autocommit mode — i.e. not inside an explicit transaction.

close(conn)

@callback close(conn()) :: :ok

Releases the connection when its stream closes.

describe(conn, sql)

(optional)
@callback describe(conn(), sql :: String.t()) ::
  {:ok, Filo.Describe.t()} | {:error, Filo.Error.t()}

Describes a statement without running it — its parameters, columns, and whether it is an EXPLAIN or read-only. Optional: an executor that does not implement it makes describe requests fail with an "unsupported" stream error.

execute(conn, t)

@callback execute(conn(), Filo.Stmt.t()) ::
  {:ok, Filo.StmtResult.t()} | {:error, Filo.Error.t()}

Runs one statement on the connection.

execute_sequence(conn, sql)

(optional)
@callback execute_sequence(conn(), sql :: String.t()) :: :ok | {:error, Filo.Error.t()}

Runs a SQL script — one or more statements — for its side effects, returning no rows. Used by the Hrana sequence request. Optional: an executor that does not implement it makes sequence requests fail with an "unsupported" error.

open(arg)

@callback open(arg :: term()) :: {:ok, conn()} | {:error, Filo.Error.t()}

Opens a connection for a new stream. arg is host-supplied context (for example the database name extracted from the request).

open(arg, context)

(optional)
@callback open(arg :: term(), context :: term()) ::
  {:ok, conn()} | {:error, Filo.Error.t()}

Opens a connection for a new stream, given the host context that the :authorize callback returned for this connection (see Filo.Plug).

This is how a verified per-connection credential — the token's scope, a tenant id, anything authorize decoded — reaches open without a process-local side-channel: authorize returns {:ok, context} and Filo threads that context here, on both transports (the HTTP stream that opens in its own process, and every WebSocket open_stream after the hello). context is nil when no :authorize callback is configured or it returned a bare :ok.

Optional. When implemented, Filo calls it in preference to open/1; an executor that only defines open/1 keeps working unchanged (the context is simply dropped).

owner(conn)

(optional)
@callback owner(conn()) :: pid() | nil

The process whose death invalidates this connection — e.g. a per-database coordinator that owns the underlying file's lifecycle and whose successor may flush/replace/drop that file once it believes no connections remain.

Optional. When implemented and non-nil, the stream holding the connection monitors the pid and tears itself down on :DOWN — closing the connection via close/1 — so a connection never outlives its owner and keeps writing into a file the owner's successor can pull out from under it. The client sees the stream as gone (STREAM_NOT_FOUND on next use) and reopens, landing on the successor. Return nil (or don't implement) when no such process exists.