Bier.SchemaCache (bier v0.1.0)

Copy Markdown View Source

The per-instance, in-memory snapshot of the database introspection results.

One %Bier.SchemaCache{} per instance lives in :persistent_term under {Bier, :schema_cache, name}. Storing the four introspection results (relations, functions, media handlers, schema comment) as a single term makes a reload swap atomic: a request in flight during a reload sees either the old snapshot or the new one, never a mix.

:persistent_term.put/2 triggers a global GC pass when an existing key is replaced, so the snapshot must only be swapped at boot / reload frequency (DDL changes), never per request. Reads are effectively free.

The entry is not erased when an instance stops — mirroring the previous per-key behavior; a restarted instance simply overwrites it.

Summary

Functions

The callable functions map of the current snapshot, keyed by {schema, name}.

Returns the current snapshot for name (an empty one when never loaded).

Runs the full DB introspection for schemas against conn, atomically swaps the snapshot for name (see put/2), and returns it.

Whether a non-empty snapshot has been loaded for name.

The custom media handlers of the current snapshot.

Whether the postgis extension is installed (gates the geo+json producer).

Atomically swaps the snapshot for instance name.

The relations map of the current snapshot, keyed by {schema, name}.

Re-runs the database introspection for the running instance name and atomically swaps its snapshot — the programmatic equivalent of PostgREST's NOTIFY pgrst, 'reload schema'.

The default schema's COMMENT, used by the OpenAPI document.

Types

t()

@type t() :: %Bier.SchemaCache{
  functions: map(),
  generation: reference() | nil,
  media_handlers: list(),
  postgis: boolean(),
  relations: map(),
  schema_comment: String.t() | nil
}

Functions

functions(name)

@spec functions(Bier.name()) :: map()

The callable functions map of the current snapshot, keyed by {schema, name}.

get(name)

@spec get(Bier.name()) :: t()

Returns the current snapshot for name (an empty one when never loaded).

load!(name, conn, schemas, extra_search_path \\ [])

@spec load!(Bier.name(), term(), [String.t(), ...], [String.t()]) :: t()

Runs the full DB introspection for schemas against conn, atomically swaps the snapshot for name (see put/2), and returns it.

Wrapped in the [:bier, :schema_cache, :load, *] telemetry span with metadata %{instance: name, schemas: schemas}; the put/2 swap runs inside the span, before the :stop event fires, so a caller synchronizing on that event (e.g. Bier.SchemaCacheListener, or a test using :telemetry_test) is guaranteed the new snapshot is already visible by the time it observes :stop. A failing introspection raises and surfaces as the span's :exception event — nothing is swapped in that case, since put/2 only runs after introspect/2 succeeds.

A failing load also logs PostgREST's PGRST002 envelope (Bier.ErrorLogger.schema_cache_load_error/2) before re-raising — this is the one funnel every load goes through, so boot and reload failures are logged exactly once.

loaded?(name)

@spec loaded?(Bier.name()) :: boolean()

Whether a non-empty snapshot has been loaded for name.

media_handlers(name)

@spec media_handlers(Bier.name()) :: list()

The custom media handlers of the current snapshot.

postgis?(name)

@spec postgis?(Bier.name()) :: boolean()

Whether the postgis extension is installed (gates the geo+json producer).

put(name, cache)

@spec put(Bier.name(), t()) :: :ok

Atomically swaps the snapshot for instance name.

relations(name)

@spec relations(Bier.name()) :: map()

The relations map of the current snapshot, keyed by {schema, name}.

reload(name)

@spec reload(Bier.name()) :: :ok | {:error, term()}

Re-runs the database introspection for the running instance name and atomically swaps its snapshot — the programmatic equivalent of PostgREST's NOTIFY pgrst, 'reload schema'.

Resolves the instance's config and connection pool from Bier.Registry, so it works whether or not the LISTEN/NOTIFY listener (db_channel_enabled) is running. The swap happens only after a fully successful introspection: on any failure the previous snapshot stays in place and {:error, reason} is returned. An unregistered name returns {:error, :unknown_instance}.

Delegates to load!/3, which swaps the snapshot inside the [:bier, :schema_cache, :load, *] telemetry span, before the :stop event fires — so a caller synchronizing on that event (e.g. Bier.SchemaCacheListener, or a test using :telemetry_test) is guaranteed the new snapshot is already visible by the time it observes :stop.

schema_comment(name)

@spec schema_comment(Bier.name()) :: String.t() | nil

The default schema's COMMENT, used by the OpenAPI document.