4. Storage
Physical Schema
pg_liquid stores graph state in two core tables:
create table liquid.vertices (
id bigserial primary key,
literal text not null unique
);
create table liquid.edges (
subject_id bigint not null references liquid.vertices(id),
predicate_id bigint not null references liquid.vertices(id),
object_id bigint not null references liquid.vertices(id),
tx_id bigint not null default txid_current(),
is_deleted boolean not null default false,
primary key (subject_id, predicate_id, object_id)
);
It also stores normalizer metadata in internal extension tables:
create table liquid.row_normalizers (...);
create table liquid.row_normalizer_bindings (...);
There is no subgraph column and no auxiliary graph schema catalog.
Indexes
The shipped indexes match the evaluator’s main access paths:
(subject_id, predicate_id)for forward traversals(predicate_id, object_id)for reverse typed lookups(predicate_id)for predicate scans(subject_id)for subject scans- hash indexes on
vertices.literal,edges.subject_id,edges.predicate_id, andedges.object_idfor single-column equality probes
All indexes are partial on is_deleted = false.
Mutation Model
Writes are append/update in SQL terms but logically edge-oriented:
- vertices are created on demand from Liquid string identities
- facts are inserted as graph edges
- duplicate edge assertions reactivate tombstoned rows with
is_deleted = false - compounds are lowered to a canonical compound vertex plus role edges
- row normalizers project authoritative relational table rows into compounds
and track provenance in
liquid.row_normalizer_bindings
Rules are not stored persistently. They exist only for the duration of one
liquid.query(...) call.
Row Normalizer Lifecycle
Row normalizers are table-authoritative projections from one PostgreSQL base table into one Liquid compound type.
Each normalizer stores:
- the source table oid
- a stable normalizer name unique within that source table
- the target compound type
- a JSON role-to-column mapping
- the source table primary-key column list used to identify rows across updates and deletes
liquid.row_normalizer_bindings is the provenance ledger. It records which
source row currently supports which projected role edges. The graph itself does
not store source-row provenance.
The runtime lifecycle is:
INSERT: project the new row into a canonical compound identity and role edges, record bindings, and reactivate or insert graph edges as neededUPDATE: diff the old and new projected edge sets, delete obsolete bindings, tombstone edges only when no other binding still supports them, and insert any new bindingsDELETE: delete the row’s bindings and tombstone graph edges only if that binding was the last remaining source for the same triple
If any mapped role column is NULL, the row projects no compound. Duplicate
relational rows can still project the same compound; the compound remains live
until the last supporting row is deleted or updated away.
Execution Notes
The evaluator keeps an in-memory edge cache for the predicates needed by the current query plan and falls back to scanning the full visible graph when an edge constraint has a variable predicate. Results are returned as text bindings, not internal vertex ids.
The repository also ships a current benchmark script in
sql/liquid_bench.sql and a make bench BENCH_DB=<database> target for
repeatable smoke benchmarking against the current Liquid-blog surface.