ergon_sql (ergon v0.5.0)

View Source

Filesystem-backed SQL loader and executor.

Convention

Statements live at priv/queries/<domain>/<operation>.sql and use PostgreSQL positional parameters ($1..$n). Each file is keyed by {Domain, Operation}, so priv/queries/jobs/insert.sql becomes {jobs, insert}:

ergon_sql:query({jobs, insert}, [Queue, Worker, Payload, MaxAttempts, Dedup])

This gen_server walks the query roots once at boot and caches every statement in a read-optimised ETS table, so executing a named statement is a lock-free ets:lookup/2 plus a ergon_repo:query/3. It is supervised ahead of every consumer that depends on it.

Host query directories

Host applications register additional priv/queries-style directories through application environment, so one loader serves both Ergon's statements and host-owned domains (assets, telemetry, spatial, ...). The default is Ergon's own priv/queries only:

{ergon, [{ergon_sql, [{extra_roots, [
    {priv, my_app, "queries"},        %% <my_app>/priv/queries
    {app, my_app, "priv/queries"},    %% <my_app>/priv/queries, spelled the long way
    "/absolute/path/to/queries"       %% used verbatim
]}]}]}

Keys from extra roots share the {Domain, Operation} namespace; a collision across roots is rejected at load time exactly like one inside a single root.

Naming

find/1 returns {ok, SQL} | error; fetch/1 returns the statement or raises. That is the maps:find/2 / maps:get/2 pairing, and the raising form carries an EEP-54 error_info so a mistyped key prints the known keys and the path the file was expected at, rather than a bare badkey.

Summary

Functions

Like find/1, but raises for an unknown Key.

The raw SQL registered for Key, or error if there is none.

Every registered {Domain, Operation} key, sorted.

Execute the statement registered for Key with positional Params.

Like query/2 with extra pgo options, forwarded untouched to ergon_repo:query/3.

Re-read the query roots from disk. Returns the number of statements loaded.

Start the loader with options taken from application environment.

Start the loader, merging Opts over the application environment.

Types

query_options()

-type query_options() ::
          #{pool => atom(),
            trace => boolean(),
            include_statement_span_attribute => boolean(),
            queue => boolean(),
            decode_opts => list(),
            pool_options => list()}.

repo_result()

-type repo_result() :: {ok, pgo:result()} | {error, term()}.

sql_key()

-type sql_key() :: {atom(), atom()}.

sql_option()

-type sql_option() :: {root, file:filename_all()} | {extra_roots, [sql_root()]}.

sql_root()

-type sql_root() ::
          file:filename_all() | {priv, atom(), file:filename_all()} | {app, atom(), file:filename_all()}.

Functions

fetch(Key)

-spec fetch(sql_key()) -> binary().

Like find/1, but raises for an unknown Key.

find(Key)

-spec find(sql_key()) -> {ok, binary()} | error.

The raw SQL registered for Key, or error if there is none.

handle_call/3

handle_cast(Msg, Opts)

init(Opts)

-spec init([sql_option()]) -> {ok, [sql_option()]}.

keys()

-spec keys() -> [sql_key()].

Every registered {Domain, Operation} key, sorted.

query(Key, Params)

-spec query(sql_key(), [term()]) -> repo_result().

Execute the statement registered for Key with positional Params.

query(Key, Params, Opts)

-spec query(sql_key(), [term()], query_options()) -> repo_result().

Like query/2 with extra pgo options, forwarded untouched to ergon_repo:query/3.

Keep this pass-through: it is the only way a caller can pin a statement to a specific connection, which both the RLS tenant path and the test fixtures need.

reload()

-spec reload() -> non_neg_integer().

Re-read the query roots from disk. Returns the number of statements loaded.

Useful in development after editing a statement; production never needs it.

start_link()

-spec start_link() -> {ok, pid()} | {error, term()}.

Start the loader with options taken from application environment.

start_link(Opts)

-spec start_link([sql_option()]) -> {ok, pid()} | {error, term()}.

Start the loader, merging Opts over the application environment.

The root option replaces Ergon's own priv/queries rather than adding to it, which is how a test suite points the loader at a fixture directory.