惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享
雷峰网
雷峰网
Hugging Face - Blog
Hugging Face - Blog
V
V2EX
大猫的无限游戏
大猫的无限游戏
博客园 - 司徒正美
D
Docker
T
The Blog of Author Tim Ferriss
罗磊的独立博客
博客园 - 叶小钗
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
月光博客
月光博客
J
Java Code Geeks
Jina AI
Jina AI
博客园 - 【当耐特】
C
Check Point Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
腾讯CDC
Last Week in AI
Last Week in AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Visual Studio Blog

Show HN

GitHub - astefanutti/shaderbang: Shebang for Shaders Show HN: Generate Claude Code Workflows using Spec Driven Development approach GitHub - nixys/nxs-universal-chart: The Helm chart you can use to install any of your applications into Kubernetes/OpenShift Show HN: AI agents for UK GDAD PCF roles and their skills The Two Pillars: Mixer Mode and Meta-Software in the Reorganization of Software Work After AI GitHub - JaiCode08/teleport-env What 1,000+ Harness Experiments Taught Me About Self-Improving Agents Show HN: Liiists, a Markdown-first, iOS and CLI list app SwiperTab – Get this Extension for 🦊 Firefox (en-US) GitHub - kouhxp/fftext: Summarize, explain, fact-check, or translate any text, URL, or file. No GPU. No cloud. One command GitHub - sweetpad-dev/sweetpad: Develop Swift/iOS projects using VSCode GitHub - dogmaticdev/IRON: IRON a.k.a. Intermediate Representation Object Notation is a Interpreter/Database that is used to create Programming Languages. GitHub - sjhalani7/vaen: Package your AI coding harness into a portable .agent file, and share it across repos, teams, & the community without ever having to copy-paste instructions, skills, MCP config, or secrets. Show HN: Gandalf the Grader Show HN: Citadeld – replay any CI failure locally from a single file GitHub - tdortman/cuSBF: High-Performance GPU Super Bloom Filter coral-ai/claude-code-token-xray at main · Coral-Bricks-AI/coral-ai GitHub - ulyssestenn/funes: Funes is a Git-based framework for LLM-managed knowledge work: an AI Librarian ingests raw sources, builds an interlinked Markdown knowledge base, and uses it to produce cited reports, analyses, and other outputs. GitHub - ThatXliner/gah: Git Add Hunk, built for agents to use GitHub - harmont-dev/harmont-cli: Command-line client for the Harmont CI platform GitHub - brooksmcmillin/mcp-authflow: OAuth 2.0 Authorization Server framework for MCP servers GitHub - javaid-codes/audit-supply-chain-agents GitHub - amorey/gochan: A small library of common channel architectures for Go, inspired by Rust GitHub - arifozgun/OpenGem: Free, Open-Source AI API Gateway with Gemini, OpenAI & Anthropic Compatibility in 1 file GitHub - Pranesh950/BioPetals: 🌸 Run BIOxAI models at home, BitTorrent-style. Fine-tuning and inference up to 10x faster than offloading GitHub - cnguyen14/bounty-doctor: Diagnose a GitHub bounty issue before you waste hours: detects honeypot scam repos, AI-bot attempt swarms, and stale contests. Show HN: CoreMCP – MCP Server for On-Prem DBs Show HN: KittyHTML – Render HTML/CSS as an inline image in your terminal GitHub - bingud/filemat: Web-based file manager Show HN: TruthLens – Free multi-signal deepfake image detector
GitHub - viggy28/pg_savior: A postgres extension to avoid...
vira28 · 2026-04-27 · via Show HN

pg_savior is a PostgreSQL extension that prevents accidental data loss from DELETE and UPDATE statements that have no WHERE clause. It hooks the parser, raises an ERROR on unguarded statements, and aborts the transaction so the application notices.

pg_savior catching a DELETE without WHERE and a DROP TABLE on a large table

Background: pg_savior — a seatbelt for Postgres explains why this exists and how it's designed.

Regenerate the GIF with vhs demo.tape (see demo.tape).

Status

Under active development. Pre-1.0. Not production-ready.

Features

  • Block DELETE without WHERE
  • Block UPDATE without WHERE
  • Row-count threshold guard (pg_savior.max_rows_affected)
  • Block CREATE INDEX without CONCURRENTLY
  • Block ALTER TABLE ADD COLUMN ... DEFAULT on large tables
  • Block ALTER TABLE ALTER COLUMN TYPE on large tables
  • Block TRUNCATE on large tables
  • Block DROP TABLE on large tables
  • Block DROP DATABASE
  • Per-session bypass GUC (pg_savior.bypass)
  • Global on/off GUC (pg_savior.enabled)
  • Per-table opt-out via reloptions
  • Volatility detection for ADD COLUMN (only block volatile defaults)

Installation

Build from source, or download from PGXN.

make
sudo make install

Activate the protection

CREATE EXTENSION alone does not activate pg_savior. The shared library must be loaded into Postgres backends. Pick one:

Option 1 — Cluster-wide (recommended for production)

Add to postgresql.conf:

shared_preload_libraries = 'pg_savior'

Then restart Postgres. Every backend forked from the postmaster will have the hook installed automatically.

Option 2 — Per-session, no restart

Add to postgresql.conf:

session_preload_libraries = 'pg_savior'

Then SELECT pg_reload_conf();. Every new connection from then on installs the hook.

Option 3 — Per-session, manual (development)

LOAD 'pg_savior';

Once loaded by any of the above, register the extension in each database:

CREATE EXTENSION pg_savior;

Usage

postgres=# CREATE EXTENSION pg_savior;
CREATE EXTENSION

postgres=# CREATE TABLE emp (id int);
CREATE TABLE

postgres=# INSERT INTO emp VALUES (1), (2), (3);
INSERT 0 3

postgres=# DELETE FROM emp;
ERROR:  pg_savior: DELETE without WHERE clause is blocked
HINT:  Add a WHERE clause, or set pg_savior.bypass = on for this session.

postgres=# SELECT count(*) FROM emp;
 count
-------
     3
(1 row)

postgres=# DELETE FROM emp WHERE id = 1;
DELETE 1

Configuration

GUC Default Scope Effect
pg_savior.enabled on session (USERSET) Master switch. When off, no checks run.
pg_savior.bypass off session (USERSET) When on, the current session's DELETE/UPDATE are allowed through unconditionally. Use to do an intentional bulk operation.
pg_savior.max_rows_affected 0 (disabled) session (USERSET) When > 0, refuse DELETE/UPDATE whose planner row estimate exceeds this. Catches destructive queries that do have a WHERE but match too much (e.g. DELETE FROM emp WHERE id > 0).
pg_savior.large_table_threshold_rows 1000000 session (USERSET) Tables with pg_class.reltuples greater than this are considered "large" for the DDL guards (currently: ALTER TABLE ADD COLUMN ... DEFAULT). Raise it for permissive environments, lower it for stricter ones.

Example bypass for an intentional cleanup:

BEGIN;
SET LOCAL pg_savior.bypass = on;
DELETE FROM staging_table;
COMMIT;

Example row-count guard for a destructive query that has a WHERE but matches too much:

postgres=# SET pg_savior.max_rows_affected = 100;
SET
postgres=# DELETE FROM emp WHERE id > 0;
ERROR:  pg_savior: DELETE estimated to affect 1000 rows, exceeds pg_savior.max_rows_affected (100)
HINT:  Refine the WHERE clause, raise pg_savior.max_rows_affected, or set pg_savior.bypass = on. Run ANALYZE if the estimate looks wrong.

The threshold uses the planner's row estimate, which depends on table statistics. For accurate enforcement on a recently-modified table, run ANALYZE first.

Example DDL guards:

postgres=# CREATE INDEX emp_idx ON emp (id);
ERROR:  pg_savior: CREATE INDEX without CONCURRENTLY is blocked
HINT:  Use CREATE INDEX CONCURRENTLY (it cannot run in a transaction block), or set pg_savior.bypass = on for this session.

postgres=# ALTER TABLE big_emp ADD COLUMN status text DEFAULT 'active';
ERROR:  pg_savior: ALTER TABLE ADD COLUMN with DEFAULT on a large table (5000000 rows) is blocked
HINT:  Adding a column with a volatile default rewrites the whole table. Add the column without a default first, then backfill in batches; raise pg_savior.large_table_threshold_rows; or set pg_savior.bypass = on. Run ANALYZE if the row estimate looks wrong.

postgres=# DROP TABLE big_emp;
ERROR:  pg_savior: DROP TABLE on a large table "big_emp" (5000000 rows) is blocked
HINT:  Verify the target, raise pg_savior.large_table_threshold_rows, or set pg_savior.bypass = on. Run ANALYZE if the row estimate looks wrong.

postgres=# DROP DATABASE production_db;
ERROR:  pg_savior: DROP DATABASE "production_db" is blocked
HINT:  Set pg_savior.bypass = on for this session if you really mean it.

postgres=# TRUNCATE big_emp;
ERROR:  pg_savior: TRUNCATE on a large table "big_emp" (5000000 rows) is blocked
HINT:  Verify the target, raise pg_savior.large_table_threshold_rows, or set pg_savior.bypass = on. Run ANALYZE if the row estimate looks wrong.

postgres=# ALTER TABLE big_emp ALTER COLUMN id TYPE bigint;
ERROR:  pg_savior: ALTER TABLE ALTER COLUMN TYPE on a large table (5000000 rows) is blocked
HINT:  This operation rewrites the whole table. Plan a batched migration; raise pg_savior.large_table_threshold_rows; or set pg_savior.bypass = on. Run ANALYZE if the row estimate looks wrong.

The ADD COLUMN guard is conservative — it blocks any DEFAULT on a large table, even non-volatile ones that PG14+ handles via fast-default and would not actually rewrite. If you frequently add non-volatile defaults, raise pg_savior.large_table_threshold_rows or use bypass.

Tests

The extension uses pg_regress (the standard PGXS test framework). Run against a local cluster:

make installcheck

Each test file uses LOAD 'pg_savior' so the framework works whether or not pg_savior is in shared_preload_libraries.

Docker-based integration test

A self-contained integration test that builds Postgres + pg_savior in a container and runs the suite end-to-end:

./docker/test.sh

Test against a different Postgres major version:

PG_MAJOR=15 ./docker/test.sh

If you change a test's SQL, regenerate its expected output:

# clear stale expected file, leave an empty placeholder so pg_regress
# runs the test instead of bailing out, then capture
> expected/<testname>.out
./docker/test.sh --capture-expected

How it works

pg_savior installs three hooks:

  1. post_parse_analyze_hook — fires after parse-analyze, before planning. Inspects the Query tree: if the statement is CMD_DELETE/CMD_UPDATE and query->jointree->quals is NULL (no WHERE), it raises ERROR. Independent of plan shape; parameterized statements handled correctly; no planner work wasted on a query that will be refused.

  2. ExecutorStart_hook — fires after planning, before execution. If pg_savior.max_rows_affected > 0, reads the planner's row estimate from the source plan beneath the ModifyTable node and raises ERROR if it exceeds the threshold. The transaction aborts before any tuples are touched.

  3. ProcessUtility_hook — fires for utility statements (DDL). Refuses:

    • CREATE INDEX without CONCURRENTLY (always)
    • ALTER TABLE ADD COLUMN ... DEFAULT when the target table is over the threshold
    • ALTER TABLE ALTER COLUMN TYPE when the target table is over the threshold (rewrites the table)
    • TRUNCATE when any target table is over the threshold (multi-table truncates blocked if any target is large)
    • DROP TABLE when any target table is over the threshold (multi-table drops blocked if any target is large)
    • DROP DATABASE (always)

    "Over the threshold" means pg_class.reltuples > pg_savior.large_table_threshold_rows.

All checks honour pg_savior.enabled and pg_savior.bypass.