pg_savior
Overview
| Package | Version | Category | License | Language |
|---|---|---|---|---|
pg_savior | 0.1.0 | ADMIN | Apache-2.0 | C |
| ID | Extension | Bin | Lib | Load | Create | Trust | Reloc | Schema |
|---|---|---|---|---|---|---|---|---|
| 5810 | pg_savior | No | Yes | No | Yes | No | Yes | - |
| Related | pg_upless safeupdate pg_drop_events pg_cheat_funcs table_log pg_snakeoil pg_auditor temporal_tables |
|---|
-tuplestore_donestoring , breaks on pg18 @ el
Version
| Type | Repo | Version | PG Ver | Package | Deps |
|---|---|---|---|---|---|
| EXT | PIGSTY | 0.1.0 | 1817161514 | pg_savior | - |
| RPM | PIGSTY | 0.1.0 | 1817161514 | pg_savior_$v | - |
| DEB | PIGSTY | 0.1.0 | 1817161514 | postgresql-$v-pg-savior | - |
Build
You can build the RPM / DEB packages for pg_savior using pig build:
pig build pkg pg_savior # build RPM / DEB packages
Install
You can install pg_savior directly. First, make sure the PGDG and PIGSTY repositories are added and enabled:
pig repo add pgsql -u # Add repo and update cache
Install the extension using pig or apt/yum/dnf:
pig install pg_savior; # Install for current active PG version
pig ext install -y pg_savior -v 18 # PG 18
pig ext install -y pg_savior -v 17 # PG 17
pig ext install -y pg_savior -v 16 # PG 16
pig ext install -y pg_savior -v 15 # PG 15
pig ext install -y pg_savior -v 14 # PG 14
dnf install -y pg_savior_18 # PG 18
dnf install -y pg_savior_17 # PG 17
dnf install -y pg_savior_16 # PG 16
dnf install -y pg_savior_15 # PG 15
dnf install -y pg_savior_14 # PG 14
apt install -y postgresql-18-pg-savior # PG 18
apt install -y postgresql-17-pg-savior # PG 17
apt install -y postgresql-16-pg-savior # PG 16
apt install -y postgresql-15-pg-savior # PG 15
apt install -y postgresql-14-pg-savior # PG 14
Create Extension:
CREATE EXTENSION pg_savior;
Usage
Sources: README, release 0.1.0, PGXN 0.1.0, SQL file, C source, pg_savior.control
pg_savior is a PostgreSQL safety extension for blocking specific high-risk DML and DDL statements before they run. Version 0.1.0 is a deliberate PGXN release and a major rewrite from 0.0.1; the README still labels it pre-1.0 and not production-ready.
Activation
CREATE EXTENSION alone does not activate the checks. The SQL file only documents that protection lives in the loaded shared library, so each backend must load pg_savior by one of the upstream-supported paths:
Cluster-wide activation uses shared_preload_libraries and requires a PostgreSQL restart:
shared_preload_libraries = 'pg_savior'
Per-session activation for new connections can use session_preload_libraries after a config reload:
session_preload_libraries = 'pg_savior'
For development or test sessions, load the library manually:
LOAD 'pg_savior';
CREATE EXTENSION pg_savior;
Once the library is loaded, _PG_init installs post_parse_analyze_hook, ExecutorStart_hook, and ProcessUtility_hook for that backend.
DML Guards
pg_savior blocks DELETE and UPDATE statements that have no WHERE clause. The parser hook checks the analyzed Query tree and raises ERROR, so the transaction aborts and the application sees the failure.
CREATE TABLE emp (id int);
INSERT INTO emp VALUES (1), (2), (3);
DELETE FROM emp;
-- ERROR: pg_savior: DELETE without WHERE clause is blocked
UPDATE emp SET id = id + 1;
-- ERROR: pg_savior: UPDATE without WHERE clause is blocked
DELETE FROM emp WHERE id = 1;
-- allowed
The optional row-count guard applies to DELETE and UPDATE statements whose planner estimate exceeds pg_savior.max_rows_affected. It runs from ExecutorStart_hook, after planning and before tuples are touched.
SET pg_savior.max_rows_affected = 100;
DELETE FROM emp WHERE id > 0;
-- blocked if the planner estimate is greater than 100 rows
DDL Guards
The ProcessUtility_hook guards only the DDL operations listed by upstream:
CREATE INDEXwithoutCONCURRENTLYis always blocked.DROP DATABASEis always blocked.ALTER TABLE ADD COLUMN ... DEFAULTis blocked when the target table is larger thanpg_savior.large_table_threshold_rows.ALTER TABLE ALTER COLUMN TYPEis blocked for large tables.TRUNCATEis blocked when any target table is large.DROP TABLEis blocked when any target table is large.
Large-table checks use pg_class.reltuples > pg_savior.large_table_threshold_rows.
CREATE INDEX emp_idx ON emp (id);
-- ERROR: pg_savior: CREATE INDEX without CONCURRENTLY is blocked
CREATE INDEX CONCURRENTLY emp_idx ON emp (id);
-- allowed by this guard
ALTER TABLE big_emp ADD COLUMN status text DEFAULT 'active';
-- blocked when big_emp is over pg_savior.large_table_threshold_rows
TRUNCATE big_emp;
-- blocked when big_emp is over pg_savior.large_table_threshold_rows
Configuration
All documented GUCs are session-scoped USERSET variables:
| GUC | Default | Effect |
|---|---|---|
pg_savior.enabled | on | Master switch; when off, checks do not run. |
pg_savior.bypass | off | Allows the current session through the guards. |
pg_savior.max_rows_affected | 0 | Blocks estimated DELETE/UPDATE row counts above this value; 0 disables the check. |
pg_savior.large_table_threshold_rows | 1000000 | Defines “large” for the guarded large-table DDL operations. |
Use SET LOCAL for a deliberate one-transaction bypass:
BEGIN;
SET LOCAL pg_savior.bypass = on;
DELETE FROM staging_table;
COMMIT;
Caveats
- The library must be loaded in the backend before protection exists;
CREATE EXTENSION pg_savioronly registers extension metadata. - The row-count and large-table guards depend on planner/catalog estimates. Run
ANALYZEwhen recent changes make estimates stale. UPDATEcoverage is limited to unguardedUPDATEand the optional planner-estimate threshold; the README does not claim semantic review of everyWHEREpredicate.- DDL coverage is limited to the listed
ProcessUtility_hookcases. Do not assume other schema changes are blocked. - The
ADD COLUMN ... DEFAULTguard is conservative and blocks any default on a large table, including non-volatile defaults that newer PostgreSQL versions may handle without a full table rewrite.
Feedback
Was this page helpful?
Thanks for the feedback! Please let us know how we can improve.
Sorry to hear that. Please let us know how we can improve.