Extension file structure¶
Installed files¶
| File | Location |
|---|---|
pg_vault_tables.so |
$(pg_config --pkglibdir) |
pg_vault_tables.control |
$(pg_config --sharedir)/extension |
pg_vault_tables--1.0.sql |
$(pg_config --sharedir)/extension |
Source layout¶
pg_vault_tables/
├── pg_vault_tables.control
├── Makefile
├── src/
│ ├── vault_tableam.c the access method routine and its callbacks
│ ├── vault_enforce.c permission, retention and insert-once decisions
│ ├── vault_reloptions.c option parsing and catalogue storage
│ ├── vault_utility_hook.c ProcessUtility: creation-time option handling
│ ├── vault_alter_gate.c ALTER TABLE, policies and reserved schemas
│ ├── vault_object_access.c object_access: DROP and TRUNCATE
│ ├── vault_catalog_guard.c executor + per-statement and pre-commit
│ │ integrity check on a table's own rows
│ ├── vault_violation.c the violation record
│ └── vault_options_accessor.c the SQL-callable options accessor
├── sql/
│ └── pg_vault_tables--1.0.sql
├── test/
│ ├── sql/ pg_regress inputs, plus plain_plans.sql,
│ │ which is not in REGRESS and is consumed only
│ │ by the with-and-without comparison
│ ├── expected/ pg_regress expected output
│ └── specs/ isolation specifications
├── t/ TAP tests
└── docs/ this site
SQL objects created¶
All in the pgvault_tables schema, except the access method.
| Object | Kind | Notes |
|---|---|---|
vault |
access method | Not schema-scoped; lives in pg_am |
vault_tableam_handler(internal) |
function | Named by CREATE ACCESS METHOD |
pgvault_tables |
schema | USAGE granted to PUBLIC |
_table_options(regclass) |
function | Internal accessor; owner only |
vault_tables |
view | Internal; owner only |
view_vault_tables() |
function | The public registry call; SECURITY DEFINER |
purge_vault(name, name) |
procedure | Owner only; deletes rows |
Why pgvault_tables and not vault¶
Supabase's own vault extension installs into a schema literally named vault on every Supabase project. Using the bare name would be a genuine installation conflict, not a style preference.
The access method itself keeps the name vault, because access methods live in pg_am — a separate namespace — and are conventionally named for what they do rather than which package installed them, as heap, btree and gin are.
Test suites¶
| Suite | Harness | Covers |
|---|---|---|
vault_scaffold |
pg_regress | Delegation to heap |
vault_transparency |
pg_regress | Ordinary PostgreSQL is unaffected |
vault_reloptions |
pg_regress | Option parsing and storage |
vault_enforcement |
pg_regress | Row-level enforcement |
vault_statement |
pg_regress | ALTER, DROP, TRUNCATE, cascades |
vault_purge |
pg_regress | Registry and purge procedure |
vault_matrix |
pg_regress | Every refusal, as three different roles |
vault_maintenance |
pg_regress | Vacuum, indexes, triggers |
vault_definition |
pg_regress | The definition-level permissions |
vault_columns |
pg_regress | A narrowed update grant, and coldefault |
vault_catalog |
pg_regress | Definition integrity, and its scoping |
vault_catalog_merge |
pg_regress | The same via MERGE in a CTE, split out because its syntax is PostgreSQL 17+ |
vault_convert |
pg_regress | Converting an existing table in place |
plain_tables |
pg_regress | Ordinary tables in depth — every CREATE TABLE and ALTER TABLE form, partitioning, inheritance, foreign keys, maintenance |
plain_dml |
pg_regress | Ordinary data operations — joins, CTEs, window functions, COPY, MERGE, cursors, locking, isolation levels |
plain_ddl |
pg_regress | Every object type PostgreSQL offers, created, altered, commented and dropped — including event triggers |
plain_roles |
pg_regress | The same as an unprivileged role, and the privilege system itself |
plain_pg17, plain_pg18 |
pg_regress | Ordinary SQL only newer majors can parse, split out so their alternative expected files stay small |
insertonce |
isolation | Concurrent first insert |
001_dump_restore |
TAP | Dump and restore round trip |
002_fail_closed |
TAP | Behaviour without the library loaded |
003_replication |
TAP | Physical replication |
004_logical_replication |
TAP | Logical replication |
005_violations |
TAP | Violation record completeness |
006_prepared_transaction |
TAP | Definition integrity under two-phase commit |
007_ordinary_sql |
TAP | Ordinary SQL is byte-identical with and without the extension |
Proving ordinary PostgreSQL is unaffected¶
The plain_* files run last and use no vault tables at all. They assert that ordinary SQL still works; they cannot on their own assert that it is unchanged, because their expected output was captured from a server that already had the extension loaded.
007_ordinary_sql closes that gap. It builds two clusters from the same binaries differing in one line of configuration, feeds them the identical scripts, and requires byte-identical output — once with no vault tables present and once with them live. It also compares query plans, which no stored expected file could, since plan shape moves between major versions.
The same comparison is run against PostgreSQL's own regression suite in CI, on 16, 17 and 18: core's complete suite is executed twice against clusters identical but for shared_preload_libraries, and every result file must match. Identity between the two runs is the assertion rather than "core's suite passes", so a core test that fails for reasons of its own — a platform quirk, a packager's build options — cannot be mistaken for a problem with this extension.
All are run by make installcheck, which invokes all three harnesses in one target — pg_regress, then pg_isolation_regress, then prove. PGXS has no separate installcheck-isolation or installcheck-tap targets.
The whole of it runs on every platform in the matrix: PostgreSQL 16, 17 and 18, across Rocky Linux 8 and 9, Debian 12, Ubuntu 22.04, 24.04 and 26.04 LTS, and Fedora 44. See Version and platform coverage and Tested Platforms.