1- -- Corruption: B-tree index integrity check (amcheck, non-blocking)
1+ -- Corruption: quick check — btree, GIN (PG18+), heap (PG14+). Safe for production.
22-- Requires: CREATE EXTENSION amcheck
3- -- Uses bt_index_check( ) — lightweight, safe for production primaries .
4- -- Does NOT lock tables (only AccessShareLock on indexes).
5- -- Checks internal page consistency of all btree indexes.
6- -- On PG14+, also runs verify_heapam() to detect heap corruption .
3+ -- All checks use AccessShareLock only (same as SELECT ) — no write blocking .
4+ -- Checks page-level consistency of btree indexes (bt_index_check ).
5+ -- On PG18+, also checks GIN indexes (gin_index_check) .
6+ -- On PG14+, also checks heap and TOAST integrity (verify_heapam) .
77
88do $$
99declare
@@ -12,6 +12,9 @@ declare
1212 idx_count int := 0 ;
1313 err_count int := 0 ;
1414 skip_count int := 0 ;
15+ gin_count int := 0 ;
16+ gin_err_count int := 0 ;
17+ gin_skip_count int := 0 ;
1518 tbl_count int := 0 ;
1619 tbl_err_count int := 0 ;
1720 tbl_skip_count int := 0 ;
2629
2730 select current_setting(' server_version_num' )::int into pg_version;
2831
32+ -- === B-tree indexes ===
2933 raise notice ' ' ;
30- raise notice ' === B-tree index integrity (bt_index_check) ===' ;
31- raise notice ' Checking all btree indexes in the current database...' ;
34+ raise notice ' === B-tree index check (bt_index_check, AccessShareLock) ===' ;
3235 raise notice ' ' ;
3336
3437 for rec in
4346 join pg_namespace n on n .oid = c .relnamespace
4447 join pg_am a on a .oid = c .relam
4548 where a .amname = ' btree'
46- and n .nspname not in (' pg_catalog' , ' information_schema' )
47- and n .nspname !~ ' ^pg_toast'
4849 and c .relpersistence != ' t'
4950 and i .indisvalid
5051 order by n .nspname , t .relname , c .relname
@@ -54,30 +55,75 @@ begin
5455 idx_count := idx_count + 1 ;
5556 exception
5657 when insufficient_privilege then
57- raise notice ' ⚠️ Permission denied for %.% — need superuser or amcheck privileges' , rec .schema_name , rec .index_name ;
5858 skip_count := skip_count + 1 ;
5959 when others then
60- raise warning ' ❌ CORRUPTION in %.% (table %.%): %' ,
61- rec .schema_name , rec .index_name ,
62- rec .schema_name , rec .table_name ,
63- sqlerrm;
60+ raise warning ' ❌ CORRUPTION in %.%: %' ,
61+ rec .schema_name , rec .index_name , sqlerrm;
6462 err_count := err_count + 1 ;
6563 end;
6664 end loop;
6765
6866 if err_count = 0 and skip_count = 0 then
69- raise notice ' ✅ All % btree indexes passed integrity check .' , idx_count;
67+ raise notice ' ✅ All % btree indexes OK .' , idx_count;
7068 elsif err_count = 0 then
71- raise notice ' ✅ % btree indexes passed , % skipped (insufficient privileges).' , idx_count, skip_count;
69+ raise notice ' ✅ % btree indexes OK , % skipped (insufficient privileges).' , idx_count, skip_count;
7270 else
7371 raise warning ' ❌ % of % btree indexes have corruption!' , err_count, idx_count + err_count + skip_count;
7472 end if;
7573
76- -- Heap verification (PG14+ only)
74+ -- === GIN indexes (PG18+) ===
75+ if pg_version >= 180000 then
76+ raise notice ' ' ;
77+ raise notice ' === GIN index check (gin_index_check, AccessShareLock) ===' ;
78+ raise notice ' ' ;
79+
80+ for rec in
81+ select
82+ n .nspname as schema_name,
83+ c .relname as index_name,
84+ t .relname as table_name,
85+ c .oid as index_oid
86+ from pg_index i
87+ join pg_class c on c .oid = i .indexrelid
88+ join pg_class t on t .oid = i .indrelid
89+ join pg_namespace n on n .oid = c .relnamespace
90+ join pg_am a on a .oid = c .relam
91+ where a .amname = ' gin'
92+ and c .relpersistence != ' t'
93+ and i .indisvalid
94+ order by n .nspname , t .relname , c .relname
95+ loop
96+ begin
97+ perform gin_index_check(rec .index_oid );
98+ gin_count := gin_count + 1 ;
99+ exception
100+ when insufficient_privilege then
101+ gin_skip_count := gin_skip_count + 1 ;
102+ when others then
103+ raise warning ' ❌ CORRUPTION in %.%: %' ,
104+ rec .schema_name , rec .index_name , sqlerrm;
105+ gin_err_count := gin_err_count + 1 ;
106+ end;
107+ end loop;
108+
109+ if gin_count + gin_err_count + gin_skip_count = 0 then
110+ raise notice ' No GIN indexes found.' ;
111+ elsif gin_err_count = 0 and gin_skip_count = 0 then
112+ raise notice ' ✅ All % GIN indexes OK.' , gin_count;
113+ elsif gin_err_count = 0 then
114+ raise notice ' ✅ % GIN indexes OK, % skipped (insufficient privileges).' , gin_count, gin_skip_count;
115+ else
116+ raise warning ' ❌ % of % GIN indexes have corruption!' , gin_err_count, gin_count + gin_err_count + gin_skip_count;
117+ end if;
118+ else
119+ raise notice ' ' ;
120+ raise notice ' ℹ️ GIN index checking requires PostgreSQL 18+. Skipped.' ;
121+ end if;
122+
123+ -- === Heap verification (PG14+) ===
77124 if pg_version >= 140000 then
78125 raise notice ' ' ;
79- raise notice ' === Heap integrity (verify_heapam) ===' ;
80- raise notice ' Checking all user tables for heap corruption...' ;
126+ raise notice ' === Heap check (verify_heapam, AccessShareLock) ===' ;
81127 raise notice ' ' ;
82128
83129 for rec in
88134 from pg_class c
89135 join pg_namespace n on n .oid = c .relnamespace
90136 where c .relkind = ' r'
91- and n .nspname not in (' pg_catalog' , ' information_schema' )
92137 and c .relpersistence != ' t'
93138 order by n .nspname , c .relname
94139 loop
@@ -107,7 +152,6 @@ begin
107152 end loop;
108153 exception
109154 when insufficient_privilege then
110- raise notice ' ⚠️ Permission denied for %.% — need superuser or amcheck privileges' , rec .schema_name , rec .table_name ;
111155 tbl_skip_count := tbl_skip_count + 1 ;
112156 when others then
113157 raise warning ' ERROR checking %.%: %' , rec .schema_name , rec .table_name , sqlerrm;
@@ -117,9 +161,9 @@ begin
117161 end loop;
118162
119163 if tbl_err_count = 0 and tbl_skip_count = 0 then
120- raise notice ' ✅ All % tables passed heap integrity check.' , tbl_count;
164+ raise notice ' ✅ All % tables passed heap check.' , tbl_count;
121165 elsif tbl_err_count = 0 then
122- raise notice ' ✅ % tables passed , % skipped (insufficient privileges).' , tbl_count - tbl_skip_count, tbl_skip_count;
166+ raise notice ' ✅ % tables OK , % skipped (insufficient privileges).' , tbl_count - tbl_skip_count, tbl_skip_count;
123167 else
124168 raise warning ' ❌ % of % tables have corruption!' , tbl_err_count, tbl_count;
125169 end if;
0 commit comments