Skip to content

Architecture overview

The extension enforces at three levels, because PostgreSQL offers three different points at which an operation can be intercepted, and no single one of them sees everything.


Why three layers

Layer Mechanism What it catches
1 Table access method Anything that modifies a row
2 ProcessUtility hook Statements that change a table's definition
3 object_access hook DROP and TRUNCATE, including cascades

Each exists because the others cannot see what it sees.

Layer 1 sees row changes but not DROP or TRUNCATE, because neither touches individual rows — TRUNCATE deallocates files directly.

Layer 2 sees statements, but only the objects the user actually named. DROP SCHEMA ... CASCADE never mentions the tables it destroys.

Layer 3 sees each object as PostgreSQL resolves it, cascades included — but fires too late for ALTER TABLE, where the change must be refused before a rewrite begins.


The wrapper principle

The vault access method is a thin wrapper over PostgreSQL's built-in heap. It does not implement storage.

At startup it copies heap's entire routine — a struct of around fifty function pointers — and replaces only the handful that can modify a row. Everything else is heap's own code, unchanged.

That has two consequences worth understanding:

  • Vault tables are ordinary tables. MVCC, TOAST, vacuum, indexing and WAL all behave exactly as they do for any table, because they are heap's implementations.
  • New PostgreSQL versions are inherited, not ported. A callback added in a future release arrives already populated with heap's version.

Version and platform coverage

The wrapper is what lets one set of sources compile unmodified across three PostgreSQL majors: churn in the forty-odd callbacks the extension does not override — the RelFileNode/RelFileLocator rename in 16, read-stream analyze in 17, the removal of scan_bitmap_next_block in 18 — never reaches this code. There is no #if PG_VERSION_NUM anywhere in the source.

PostgreSQL 16 is the floor, set by tuple_update's last argument changing from bool *update_indexes to TU_UpdateIndexes *update_indexes in that release. 15 and earlier are not supported.

The complete suite runs on two axes:

Axis Coverage
PostgreSQL version 16, 17 and 18, each on Ubuntu 24.04 and 26.04 LTS
Distribution Rocky Linux 8 and 9, Debian 12, Ubuntu 22.04, 24.04 and 26.04 LTS, and Fedora 44, on PostgreSQL 18

The compiler spread is deliberate rather than incidental — gcc 8 on Rocky 8 through gcc 15 on Fedora 44. New warnings are treated as build failures, and no single platform surfaces the whole diagnostic set.

Architecture coverage is x86-64. Nothing in the source is architecture-specific, but ARM is not currently in the matrix.


Fail closed

Wherever the extension cannot establish what a table permits, the answer is deny.

  • Options that cannot be read produce an empty permission set, which grants nothing.
  • A null retention deadline is treated as not yet expired.
  • The library refuses to load outside shared_preload_libraries, so a server that cannot enforce cannot open a vault table at all.

The failure mode of tampering is a frozen table, never an open one.


Files

File Responsibility
vault_tableam.c The access method: the routine struct and its callbacks
vault_enforce.c The decisions — permission checks, retention, insert-once
vault_reloptions.c Parsing and storing permissions and retention
vault_utility_hook.c Layer 2: creation-time handling and ALTER TABLE
vault_object_access.c Layer 3: DROP and TRUNCATE
vault_violation.c The violation record
vault_options_accessor.c The SQL-callable options accessor