Architecture

This page describes the internal architecture of pg_ripple as of v0.60.0.

Overview

pg_ripple is a PostgreSQL 18 extension written in Rust (pgrx 0.18) that implements a high-performance RDF triple store with native SPARQL query execution. All user-visible functions live in the pg_ripple schema; internal tables and VP (Vertical Partitioning) tables live in the _pg_ripple schema.

Component map

graph TD
    Client["SQL client / SPARQL tool"]
    HTTP["pg_ripple_http\n(Axum companion service)\n/health · /ready · /metrics"]
    RAG["RAG / LLM pipeline\nllm/mod.rs + rag_cache"]

    subgraph Extension["pg_ripple extension (Rust + pgrx 0.18)"]
        API["SQL API layer\nlib.rs + *_api.rs"]
        SPARQL["SPARQL engine\nsparql/mod.rs"]
        SPARQLDL["SPARQL-DL engine\nsparql/sparqldl.rs"]
        QLRW["OWL 2 QL rewrite\nsparql/ql_rewrite.rs"]
        TRANS["Algebra → SQL\nsparql/translate/\nfilter_dispatch + filter_expr"]
        SQLGEN["SQL generator\nsparql/sqlgen.rs"]
        DICT["Dictionary\ndictionary/mod.rs\n(XXH3-128 + LRU cache)"]
        SHACL["SHACL validator\nshacl/mod.rs\n(Core + SPARQL constraints)"]
        DL["Datalog engine\ndatalog/\nseminaive + coordinator\nmagic_sets + demand"]
        EXP["Serialisers\nexport/mod.rs\n(Turtle/JSON-LD/N-Triples)"]
        STOR["Storage layer\nstorage/mod.rs\n(HTAP delta/main/tombstones)"]
        MERGE["Merge worker\nstorage/merge.rs\n(atomic rename-swap, v0.60.0)"]
        CAT["Predicate catalog\nstorage/catalog.rs"]
        HINTS["SHACL hints\nshacl/hints.rs"]
        FED["Federation planner\nsparql/federation.rs\n(cost-based, parallel)"]
        CDC["CDC bridge\nstorage/cdc_bridge.rs"]
        CITUS["Citus integration\nsrc/citus.rs\n(shard-pruning, rebalance)"]
        TENANT["Multi-tenant isolation\nsrc/tenant.rs"]
        KGE["KG embeddings\nsrc/kge.rs"]
        TEMPORAL["Temporal RDF\nsrc/temporal.rs"]
        GUCS["GUC subsystem\ngucs/{sparql, datalog, shacl,\nfederation, llm, storage,\nobservability}.rs"]
    end

    subgraph PG["PostgreSQL 18"]
        VP["VP tables\n_pg_ripple.vp_{id}_delta\n_pg_ripple.vp_{id}_main\n_pg_ripple.vp_{id}_tombstones"]
        DICT_TBL["dictionary table\n_pg_ripple.dictionary"]
        PRED["predicates table\n_pg_ripple.predicates"]
        RARE["rare predicates\n_pg_ripple.vp_rare"]
        SHAPE_HINTS["shape_hints table\n_pg_ripple.shape_hints"]
        RAG_CACHE["rag_cache\n_pg_ripple.rag_cache"]
        CDC_SUBS["cdc_subscriptions\n_pg_ripple.cdc_subscriptions"]
    end

    Client --> API
    HTTP --> API
    HTTP --> RAG
    RAG --> RAG_CACHE
    API --> SPARQL
    API --> SPARQLDL
    API --> SHACL
    API --> DL
    API --> EXP
    API --> FED
    API --> CDC
    API --> CITUS
    API --> TENANT
    API --> KGE
    API --> TEMPORAL
    SPARQL --> TRANS
    SPARQLDL --> SPARQL
    QLRW --> SPARQL
    TRANS --> SQLGEN
    SQLGEN --> CAT
    CAT --> HINTS
    HINTS --> SHAPE_HINTS
    SQLGEN --> DICT
    DICT --> DICT_TBL
    SPARQL --> VP
    SHACL --> VP
    DL --> VP
    STOR --> VP
    MERGE --> STOR
    MERGE --> CDC_SUBS
    CDC --> CDC_SUBS
    VP --> PRED
    VP --> RARE
    FED --> HTTP
    GUCS -.->|"configures"| SPARQL
    GUCS -.->|"configures"| DL
    GUCS -.->|"configures"| FED
    GUCS -.->|"configures"| MERGE
DICT --> DICT_TBL
SQLGEN --> VP
CAT --> PRED
STOR --> VP
STOR --> RARE
SHACL --> DICT
DL --> STOR

## Source tree structure

| Path | Responsibility |
|------|---------------|
| `src/lib.rs` | pgrx entry points, GUC registration, `_PG_init`, hooks |
| `src/gucs.rs` | All GUC `static` declarations |
| `src/schema.rs` | `extension_sql!()` DDL blocks |
| `src/dictionary/` | IRI / blank-node / literal → `i64` encoder (XXH3-128 + LRU) |
| `src/storage/` | VP table I/O, HTAP delta/main partitions, merge worker |
| `src/storage/catalog.rs` | Predicate → VP table OID cache (SPI call reduction) |
| `src/sparql/` | SPARQL text → algebra → SQL → SPI → decode |
| `src/sparql/translate/` | Per-algebra-node translation stubs (BGP, Join, Filter, …) |
| `src/sparql/plan_cache.rs` | Per-backend plan cache keyed on algebra digest (XXH3-128) |
| `src/datalog/` | Datalog rule parser, stratifier, SQL compiler |
| `src/shacl/` | SHACL shapes → validation pipeline |
| `src/shacl/constraints/` | Per-constraint-type validation (count, string, logical, …) |
| `src/shacl/hints.rs` | SHACL → SQL generation hints (join type, DISTINCT) |
| `src/export/` | Turtle / N-Triples / JSON-LD serialisation |
| `src/federation_registry.rs` | SPARQL federation endpoint registry |
| `src/stats_admin.rs` | Monitoring, pg_stat_statements integration |
| `src/graphrag_admin.rs` | Vector embedding, hybrid search, GraphRAG pipeline |
| `src/*_api.rs` | SQL-exposed pg_extern wrappers |

## Storage model

Every IRI, blank node, and literal is mapped to a `BIGINT` (i64) through a
dictionary encoding step (XXH3-128 hash).  VP tables **never** contain raw
strings — all joins are integer joins.

      raw IRI/literal
           │
  dictionary.encode()
           │
       i64 hash
           │
 stored in VP table (s, o, g, i, source)

For each unique predicate there is one VP table: `_pg_ripple.vp_{predicate_id}`.
Predicates with fewer than `vp_promotion_threshold` (default: 1 000) triples are
stored in the consolidated `_pg_ripple.vp_rare` table instead.

## Query execution pipeline

SPARQL text │ ▼ spargebra::Query::parse() SPARQL algebra │ ▼ sparopt optimizer (BGP reorder, join order) Optimised algebra │ ▼ SPARQL→SQL translator (sqlgen.rs + translate/) SQL text │ ▼ PostgreSQL SPI executor Raw rows │ ▼ dictionary.decode() JSONB result set


## Plan cache

The per-backend plan cache (v0.13.0) maps an **algebra digest** to the
generated SQL.  The digest is computed as:

digest = XXH3-128( spargebra::Query::display(query) ) key = "{digest}\x00max_depth={n}\x00bgp_reorder={b}"


Using the algebra display form (rather than the raw query text) means
whitespace and prefix-alias variants of the same query share one cache slot.

## SHACL hints integration

After loading shapes with `pg_ripple.load_shacl()`, predicate-level hints are
written to `_pg_ripple.shape_hints`.  The SQL generator reads these hints
via the predicate catalog to:

- Omit `DISTINCT` when `sh:maxCount 1` is set for a predicate.
- Use `INNER JOIN` instead of `LEFT JOIN` when `sh:minCount 1` is set.

Hints are invalidated automatically when shapes are dropped
(`pg_ripple.invalidate_catalog_cache()`).