Lock & Safety Checks

pgfence maps every DDL statement to a Postgres lock mode and determines what it blocks.

Lock Mode Reference

Lock ModeBlocksSeverity
ACCESS SHARENothingSafe
ROW SHAREROW EXCLUSIVE+Low
ROW EXCLUSIVESHARE+Low
SHARE UPDATE EXCLUSIVESHARE UPDATE EXCLUSIVE+Medium
SHAREROW EXCLUSIVE+ (blocks writes)Medium
SHARE ROW EXCLUSIVEROW EXCLUSIVE+High
EXCLUSIVEROW SHARE+High
ACCESS EXCLUSIVEEverything (reads + writes)Critical

DDL Checks

#PatternLockRiskSafe Alternative
1ADD COLUMN ... NOT NULL (no DEFAULT)ACCESS EXCLUSIVEHIGHAdd nullable, backfill, SET NOT NULL
2ADD COLUMN ... DEFAULT <volatile>ACCESS EXCLUSIVEHIGHAdd without default, backfill in batches
3ADD COLUMN ... DEFAULT <constant> (PG11+)ACCESS EXCLUSIVE (instant)LOWSafe on PG11+ (metadata-only)
4ADD COLUMN ... GENERATED STOREDACCESS EXCLUSIVEHIGHRegular column + trigger + backfill
5CREATE INDEX (non-concurrent)SHAREMEDIUMCREATE INDEX CONCURRENTLY
6DROP INDEX (non-concurrent)ACCESS EXCLUSIVEMEDIUMDROP INDEX CONCURRENTLY
7aALTER COLUMN TYPE (text / varchar widening)ACCESS EXCLUSIVELOWMetadata-only, no table rewrite
7bALTER COLUMN TYPE varchar(N)ACCESS EXCLUSIVEMEDIUMSafe if widening; verify with schema
7cALTER COLUMN TYPE (cross-family)ACCESS EXCLUSIVEHIGHExpand/contract pattern
8ALTER COLUMN SET NOT NULLACCESS EXCLUSIVEMEDIUMCHECK NOT VALID + VALIDATE
9ADD FOREIGN KEY (no NOT VALID)SHARE ROW EXCLUSIVEHIGHNOT VALID + VALIDATE CONSTRAINT
10ADD CHECK (no NOT VALID)SHARE ROW EXCLUSIVEMEDIUMNOT VALID + VALIDATE CONSTRAINT
11aADD UNIQUESHARE ROW EXCLUSIVEHIGHConcurrent index + USING INDEX
11bADD UNIQUE USING INDEXSHARE UPDATE EXCLUSIVELOWInstant, attaches pre-built index
12ADD EXCLUDESHARE ROW EXCLUSIVEHIGHNo concurrent alternative; use lock_timeout
13DROP TABLEACCESS EXCLUSIVECRITICALSeparate release
14DROP COLUMNACCESS EXCLUSIVEHIGHRemove app references first
15TRUNCATEACCESS EXCLUSIVECRITICALBatched DELETE
16TRUNCATE ... CASCADEACCESS EXCLUSIVECRITICALExplicit per-table truncation
17RENAME COLUMNACCESS EXCLUSIVELOWInstant on PG14+
18RENAME TABLEACCESS EXCLUSIVEHIGHRename + view for backwards compat
19VACUUM FULLACCESS EXCLUSIVEHIGHUse pg_repack
20aALTER TYPE ... ADD VALUE (PG < 12)ACCESS EXCLUSIVEMEDIUMUpgrade to PG12+
20bALTER TYPE ... ADD VALUE (PG12+)EXCLUSIVELOWSafe; instant but can't run inside tx
21aATTACH PARTITION (PG < 12)ACCESS EXCLUSIVEHIGHCreate matching CHECK constraint first
21bATTACH PARTITION (PG12+)SHARE UPDATE EXCLUSIVE on parent, ACCESS EXCLUSIVE on partitionHIGHCreate matching CHECK constraint first
22aDETACH PARTITION (non-concurrent)ACCESS EXCLUSIVEHIGHDETACH CONCURRENTLY (PG14+)
22bDETACH PARTITION CONCURRENTLYSHARE UPDATE EXCLUSIVELOWSafe on PG14+
23aREFRESH MATERIALIZED VIEWACCESS EXCLUSIVEHIGHREFRESH ... CONCURRENTLY
23bREFRESH ... CONCURRENTLYEXCLUSIVEMEDIUMBlocks writes; requires unique index
24aREINDEX TABLESHAREHIGHREINDEX CONCURRENTLY (PG12+)
24bREINDEX INDEXACCESS EXCLUSIVEHIGHREINDEX CONCURRENTLY (PG12+)
24cREINDEX SCHEMA/DATABASEACCESS EXCLUSIVECRITICALREINDEX CONCURRENTLY (PG12+)
25CREATE TRIGGERSHARE ROW EXCLUSIVEMEDIUMUse lock_timeout to bound wait
26DROP TRIGGERACCESS EXCLUSIVEMEDIUMUse lock_timeout to bound wait
27ENABLE/DISABLE TRIGGERSHARE ROW EXCLUSIVELOWBlocks concurrent DDL only
28ADD PRIMARY KEY (no pre-built index)SHARE ROW EXCLUSIVEHIGHCreate index concurrently, then USING INDEX
29ADD PRIMARY KEY USING INDEXSHARE UPDATE EXCLUSIVELOWInstant, attaches pre-built index
30DROP SCHEMAACCESS EXCLUSIVECRITICALRemove app references, separate release
31DROP SCHEMA CASCADEACCESS EXCLUSIVECRITICALIrreversible data loss, explicit per-table drops
32DELETE without WHEREROW EXCLUSIVEHIGHBatched DELETE with FOR UPDATE SKIP LOCKED
33VALIDATE CONSTRAINT (standalone)SHARE UPDATE EXCLUSIVELOWNon-blocking scan, safe standalone
34REFRESH ... WITH NO DATAACCESS EXCLUSIVEMEDIUMBrief lock (truncate only), no data scan
35DROP DATABASEACCESS EXCLUSIVECRITICALIrreversible, destroys entire database and all data
36ALTER DOMAIN ADD CONSTRAINTSHAREHIGHValidates against all columns using the domain, blocking writes on those tables
37CREATE DOMAIN with constraintsACCESS SHARELOWDomains with constraints have poor migration support; use table-level CHECK constraints

Idempotency Checks

pgfence flags statements that could fail on re-run without guard clauses.

#PatternRiskSuggestion
38CREATE INDEX without IF NOT EXISTSLOWAdd IF NOT EXISTS for idempotent migrations
39CREATE TABLE without IF NOT EXISTSLOWAdd IF NOT EXISTS for idempotent migrations
40DROP INDEX without IF EXISTSLOWAdd IF EXISTS to prevent errors on re-run
41DROP TABLE without IF EXISTSLOWAdd IF EXISTS to prevent errors on re-run