Extend amcheck to check heap pages.
Mark Dilger, reviewed by Peter Geoghegan, Andres Freund, Álvaro Herrera, Michael Paquier, Amul Sul, and by me. Some last-minute cosmetic revisions by me. Discussion: http://postgr.es/m/12ED3DA8-25F0-4B68-937D-D907CFBF08E7@enterprisedb.com
This commit is contained in:
parent
f8721bd752
commit
866e24d47d
@ -3,13 +3,16 @@
|
|||||||
MODULE_big = amcheck
|
MODULE_big = amcheck
|
||||||
OBJS = \
|
OBJS = \
|
||||||
$(WIN32RES) \
|
$(WIN32RES) \
|
||||||
|
verify_heapam.o \
|
||||||
verify_nbtree.o
|
verify_nbtree.o
|
||||||
|
|
||||||
EXTENSION = amcheck
|
EXTENSION = amcheck
|
||||||
DATA = amcheck--1.1--1.2.sql amcheck--1.0--1.1.sql amcheck--1.0.sql
|
DATA = amcheck--1.2--1.3.sql amcheck--1.1--1.2.sql amcheck--1.0--1.1.sql amcheck--1.0.sql
|
||||||
PGFILEDESC = "amcheck - function for verifying relation integrity"
|
PGFILEDESC = "amcheck - function for verifying relation integrity"
|
||||||
|
|
||||||
REGRESS = check check_btree
|
REGRESS = check check_btree check_heap
|
||||||
|
|
||||||
|
TAP_TESTS = 1
|
||||||
|
|
||||||
ifdef USE_PGXS
|
ifdef USE_PGXS
|
||||||
PG_CONFIG = pg_config
|
PG_CONFIG = pg_config
|
||||||
|
30
contrib/amcheck/amcheck--1.2--1.3.sql
Normal file
30
contrib/amcheck/amcheck--1.2--1.3.sql
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/* contrib/amcheck/amcheck--1.2--1.3.sql */
|
||||||
|
|
||||||
|
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||||
|
\echo Use "ALTER EXTENSION amcheck UPDATE TO '1.3'" to load this file. \quit
|
||||||
|
|
||||||
|
--
|
||||||
|
-- verify_heapam()
|
||||||
|
--
|
||||||
|
CREATE FUNCTION verify_heapam(relation regclass,
|
||||||
|
on_error_stop boolean default false,
|
||||||
|
check_toast boolean default false,
|
||||||
|
skip text default 'none',
|
||||||
|
startblock bigint default null,
|
||||||
|
endblock bigint default null,
|
||||||
|
blkno OUT bigint,
|
||||||
|
offnum OUT integer,
|
||||||
|
attnum OUT integer,
|
||||||
|
msg OUT text)
|
||||||
|
RETURNS SETOF record
|
||||||
|
AS 'MODULE_PATHNAME', 'verify_heapam'
|
||||||
|
LANGUAGE C;
|
||||||
|
|
||||||
|
-- Don't want this to be available to public
|
||||||
|
REVOKE ALL ON FUNCTION verify_heapam(regclass,
|
||||||
|
boolean,
|
||||||
|
boolean,
|
||||||
|
text,
|
||||||
|
bigint,
|
||||||
|
bigint)
|
||||||
|
FROM PUBLIC;
|
@ -1,5 +1,5 @@
|
|||||||
# amcheck extension
|
# amcheck extension
|
||||||
comment = 'functions for verifying relation integrity'
|
comment = 'functions for verifying relation integrity'
|
||||||
default_version = '1.2'
|
default_version = '1.3'
|
||||||
module_pathname = '$libdir/amcheck'
|
module_pathname = '$libdir/amcheck'
|
||||||
relocatable = true
|
relocatable = true
|
||||||
|
194
contrib/amcheck/expected/check_heap.out
Normal file
194
contrib/amcheck/expected/check_heap.out
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
CREATE TABLE heaptest (a integer, b text);
|
||||||
|
REVOKE ALL ON heaptest FROM PUBLIC;
|
||||||
|
-- Check that invalid skip option is rejected
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'rope');
|
||||||
|
ERROR: invalid skip option
|
||||||
|
HINT: Valid skip options are "all-visible", "all-frozen", and "none".
|
||||||
|
-- Check specifying invalid block ranges when verifying an empty table
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0);
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 5, endblock := 8);
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
-- Check that valid options are not rejected nor corruption reported
|
||||||
|
-- for an empty table, and that skip enum-like parameter is case-insensitive
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'None');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Frozen');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Visible');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'NONE');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-FROZEN');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-VISIBLE');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
-- Add some data so subsequent tests are not entirely trivial
|
||||||
|
INSERT INTO heaptest (a, b)
|
||||||
|
(SELECT gs, repeat('x', gs)
|
||||||
|
FROM generate_series(1,50) gs);
|
||||||
|
-- Check that valid options are not rejected nor corruption reported
|
||||||
|
-- for a non-empty table
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0);
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
CREATE ROLE regress_heaptest_role;
|
||||||
|
-- verify permissions are checked (error due to function not callable)
|
||||||
|
SET ROLE regress_heaptest_role;
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest');
|
||||||
|
ERROR: permission denied for function verify_heapam
|
||||||
|
RESET ROLE;
|
||||||
|
GRANT EXECUTE ON FUNCTION verify_heapam(regclass, boolean, boolean, text, bigint, bigint) TO regress_heaptest_role;
|
||||||
|
-- verify permissions are now sufficient
|
||||||
|
SET ROLE regress_heaptest_role;
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
RESET ROLE;
|
||||||
|
-- Check specifying invalid block ranges when verifying a non-empty table.
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 10000);
|
||||||
|
ERROR: ending block number must be between 0 and 0
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 10000, endblock := 11000);
|
||||||
|
ERROR: starting block number must be between 0 and 0
|
||||||
|
-- Vacuum freeze to change the xids encountered in subsequent tests
|
||||||
|
VACUUM FREEZE heaptest;
|
||||||
|
-- Check that valid options are not rejected nor corruption reported
|
||||||
|
-- for a non-empty frozen table
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible');
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0);
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
-- Check that partitioned tables (the parent ones) which don't have visibility
|
||||||
|
-- maps are rejected
|
||||||
|
CREATE TABLE test_partitioned (a int, b text default repeat('x', 5000))
|
||||||
|
PARTITION BY list (a);
|
||||||
|
SELECT * FROM verify_heapam('test_partitioned',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
ERROR: "test_partitioned" is not a table, materialized view, or TOAST table
|
||||||
|
-- Check that valid options are not rejected nor corruption reported
|
||||||
|
-- for an empty partition table (the child one)
|
||||||
|
CREATE TABLE test_partition partition OF test_partitioned FOR VALUES IN (1);
|
||||||
|
SELECT * FROM verify_heapam('test_partition',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
-- Check that valid options are not rejected nor corruption reported
|
||||||
|
-- for a non-empty partition table (the child one)
|
||||||
|
INSERT INTO test_partitioned (a) (SELECT 1 FROM generate_series(1,1000) gs);
|
||||||
|
SELECT * FROM verify_heapam('test_partition',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
-- Check that indexes are rejected
|
||||||
|
CREATE INDEX test_index ON test_partition (a);
|
||||||
|
SELECT * FROM verify_heapam('test_index',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
ERROR: "test_index" is not a table, materialized view, or TOAST table
|
||||||
|
-- Check that views are rejected
|
||||||
|
CREATE VIEW test_view AS SELECT 1;
|
||||||
|
SELECT * FROM verify_heapam('test_view',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
ERROR: "test_view" is not a table, materialized view, or TOAST table
|
||||||
|
-- Check that sequences are rejected
|
||||||
|
CREATE SEQUENCE test_sequence;
|
||||||
|
SELECT * FROM verify_heapam('test_sequence',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
ERROR: "test_sequence" is not a table, materialized view, or TOAST table
|
||||||
|
-- Check that foreign tables are rejected
|
||||||
|
CREATE FOREIGN DATA WRAPPER dummy;
|
||||||
|
CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy;
|
||||||
|
CREATE FOREIGN TABLE test_foreign_table () SERVER dummy_server;
|
||||||
|
SELECT * FROM verify_heapam('test_foreign_table',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
ERROR: "test_foreign_table" is not a table, materialized view, or TOAST table
|
||||||
|
-- cleanup
|
||||||
|
DROP TABLE heaptest;
|
||||||
|
DROP TABLE test_partition;
|
||||||
|
DROP TABLE test_partitioned;
|
||||||
|
DROP OWNED BY regress_heaptest_role; -- permissions
|
||||||
|
DROP ROLE regress_heaptest_role;
|
116
contrib/amcheck/sql/check_heap.sql
Normal file
116
contrib/amcheck/sql/check_heap.sql
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
CREATE TABLE heaptest (a integer, b text);
|
||||||
|
REVOKE ALL ON heaptest FROM PUBLIC;
|
||||||
|
|
||||||
|
-- Check that invalid skip option is rejected
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'rope');
|
||||||
|
|
||||||
|
-- Check specifying invalid block ranges when verifying an empty table
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0);
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 5, endblock := 8);
|
||||||
|
|
||||||
|
-- Check that valid options are not rejected nor corruption reported
|
||||||
|
-- for an empty table, and that skip enum-like parameter is case-insensitive
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'None');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Frozen');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Visible');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'NONE');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-FROZEN');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-VISIBLE');
|
||||||
|
|
||||||
|
-- Add some data so subsequent tests are not entirely trivial
|
||||||
|
INSERT INTO heaptest (a, b)
|
||||||
|
(SELECT gs, repeat('x', gs)
|
||||||
|
FROM generate_series(1,50) gs);
|
||||||
|
|
||||||
|
-- Check that valid options are not rejected nor corruption reported
|
||||||
|
-- for a non-empty table
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0);
|
||||||
|
|
||||||
|
CREATE ROLE regress_heaptest_role;
|
||||||
|
|
||||||
|
-- verify permissions are checked (error due to function not callable)
|
||||||
|
SET ROLE regress_heaptest_role;
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest');
|
||||||
|
RESET ROLE;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION verify_heapam(regclass, boolean, boolean, text, bigint, bigint) TO regress_heaptest_role;
|
||||||
|
|
||||||
|
-- verify permissions are now sufficient
|
||||||
|
SET ROLE regress_heaptest_role;
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest');
|
||||||
|
RESET ROLE;
|
||||||
|
|
||||||
|
-- Check specifying invalid block ranges when verifying a non-empty table.
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 10000);
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 10000, endblock := 11000);
|
||||||
|
|
||||||
|
-- Vacuum freeze to change the xids encountered in subsequent tests
|
||||||
|
VACUUM FREEZE heaptest;
|
||||||
|
|
||||||
|
-- Check that valid options are not rejected nor corruption reported
|
||||||
|
-- for a non-empty frozen table
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible');
|
||||||
|
SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0);
|
||||||
|
|
||||||
|
-- Check that partitioned tables (the parent ones) which don't have visibility
|
||||||
|
-- maps are rejected
|
||||||
|
CREATE TABLE test_partitioned (a int, b text default repeat('x', 5000))
|
||||||
|
PARTITION BY list (a);
|
||||||
|
SELECT * FROM verify_heapam('test_partitioned',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
|
||||||
|
-- Check that valid options are not rejected nor corruption reported
|
||||||
|
-- for an empty partition table (the child one)
|
||||||
|
CREATE TABLE test_partition partition OF test_partitioned FOR VALUES IN (1);
|
||||||
|
SELECT * FROM verify_heapam('test_partition',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
|
||||||
|
-- Check that valid options are not rejected nor corruption reported
|
||||||
|
-- for a non-empty partition table (the child one)
|
||||||
|
INSERT INTO test_partitioned (a) (SELECT 1 FROM generate_series(1,1000) gs);
|
||||||
|
SELECT * FROM verify_heapam('test_partition',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
|
||||||
|
-- Check that indexes are rejected
|
||||||
|
CREATE INDEX test_index ON test_partition (a);
|
||||||
|
SELECT * FROM verify_heapam('test_index',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
|
||||||
|
-- Check that views are rejected
|
||||||
|
CREATE VIEW test_view AS SELECT 1;
|
||||||
|
SELECT * FROM verify_heapam('test_view',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
|
||||||
|
-- Check that sequences are rejected
|
||||||
|
CREATE SEQUENCE test_sequence;
|
||||||
|
SELECT * FROM verify_heapam('test_sequence',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
|
||||||
|
-- Check that foreign tables are rejected
|
||||||
|
CREATE FOREIGN DATA WRAPPER dummy;
|
||||||
|
CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy;
|
||||||
|
CREATE FOREIGN TABLE test_foreign_table () SERVER dummy_server;
|
||||||
|
SELECT * FROM verify_heapam('test_foreign_table',
|
||||||
|
startblock := NULL,
|
||||||
|
endblock := NULL);
|
||||||
|
|
||||||
|
-- cleanup
|
||||||
|
DROP TABLE heaptest;
|
||||||
|
DROP TABLE test_partition;
|
||||||
|
DROP TABLE test_partitioned;
|
||||||
|
DROP OWNED BY regress_heaptest_role; -- permissions
|
||||||
|
DROP ROLE regress_heaptest_role;
|
242
contrib/amcheck/t/001_verify_heapam.pl
Normal file
242
contrib/amcheck/t/001_verify_heapam.pl
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
|
||||||
|
use Test::More tests => 65;
|
||||||
|
|
||||||
|
my ($node, $result);
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test set-up
|
||||||
|
#
|
||||||
|
$node = get_new_node('test');
|
||||||
|
$node->init;
|
||||||
|
$node->append_conf('postgresql.conf', 'autovacuum=off');
|
||||||
|
$node->start;
|
||||||
|
$node->safe_psql('postgres', q(CREATE EXTENSION amcheck));
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check a table with data loaded but no corruption, freezing, etc.
|
||||||
|
#
|
||||||
|
fresh_test_table('test');
|
||||||
|
check_all_options_uncorrupted('test', 'plain');
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check a corrupt table
|
||||||
|
#
|
||||||
|
fresh_test_table('test');
|
||||||
|
corrupt_first_page('test');
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('test')",
|
||||||
|
"plain corrupted table");
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('test', skip := 'all-visible')",
|
||||||
|
"plain corrupted table skipping all-visible");
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('test', skip := 'all-frozen')",
|
||||||
|
"plain corrupted table skipping all-frozen");
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('test', check_toast := false)",
|
||||||
|
"plain corrupted table skipping toast");
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('test', startblock := 0, endblock := 0)",
|
||||||
|
"plain corrupted table checking only block zero");
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check a corrupt table with all-frozen data
|
||||||
|
#
|
||||||
|
fresh_test_table('test');
|
||||||
|
$node->safe_psql('postgres', q(VACUUM FREEZE test));
|
||||||
|
corrupt_first_page('test');
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('test')",
|
||||||
|
"all-frozen corrupted table");
|
||||||
|
detects_no_corruption(
|
||||||
|
"verify_heapam('test', skip := 'all-frozen')",
|
||||||
|
"all-frozen corrupted table skipping all-frozen");
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check a corrupt table with corrupt page header
|
||||||
|
#
|
||||||
|
fresh_test_table('test');
|
||||||
|
corrupt_first_page_and_header('test');
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('test')",
|
||||||
|
"corrupted test table with bad page header");
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check an uncorrupted table with corrupt toast page header
|
||||||
|
#
|
||||||
|
fresh_test_table('test');
|
||||||
|
my $toast = get_toast_for('test');
|
||||||
|
corrupt_first_page_and_header($toast);
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('test', check_toast := true)",
|
||||||
|
"table with corrupted toast page header checking toast");
|
||||||
|
detects_no_corruption(
|
||||||
|
"verify_heapam('test', check_toast := false)",
|
||||||
|
"table with corrupted toast page header skipping toast");
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('$toast')",
|
||||||
|
"corrupted toast page header");
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check an uncorrupted table with corrupt toast
|
||||||
|
#
|
||||||
|
fresh_test_table('test');
|
||||||
|
$toast = get_toast_for('test');
|
||||||
|
corrupt_first_page($toast);
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('test', check_toast := true)",
|
||||||
|
"table with corrupted toast checking toast");
|
||||||
|
detects_no_corruption(
|
||||||
|
"verify_heapam('test', check_toast := false)",
|
||||||
|
"table with corrupted toast skipping toast");
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('$toast')",
|
||||||
|
"corrupted toast table");
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check an uncorrupted all-frozen table with corrupt toast
|
||||||
|
#
|
||||||
|
fresh_test_table('test');
|
||||||
|
$node->safe_psql('postgres', q(VACUUM FREEZE test));
|
||||||
|
$toast = get_toast_for('test');
|
||||||
|
corrupt_first_page($toast);
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('test', check_toast := true)",
|
||||||
|
"all-frozen table with corrupted toast checking toast");
|
||||||
|
detects_no_corruption(
|
||||||
|
"verify_heapam('test', check_toast := false)",
|
||||||
|
"all-frozen table with corrupted toast skipping toast");
|
||||||
|
detects_corruption(
|
||||||
|
"verify_heapam('$toast')",
|
||||||
|
"corrupted toast table of all-frozen table");
|
||||||
|
|
||||||
|
# Returns the filesystem path for the named relation.
|
||||||
|
sub relation_filepath
|
||||||
|
{
|
||||||
|
my ($relname) = @_;
|
||||||
|
|
||||||
|
my $pgdata = $node->data_dir;
|
||||||
|
my $rel = $node->safe_psql('postgres',
|
||||||
|
qq(SELECT pg_relation_filepath('$relname')));
|
||||||
|
die "path not found for relation $relname" unless defined $rel;
|
||||||
|
return "$pgdata/$rel";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Returns the fully qualified name of the toast table for the named relation
|
||||||
|
sub get_toast_for
|
||||||
|
{
|
||||||
|
my ($relname) = @_;
|
||||||
|
$node->safe_psql('postgres', qq(
|
||||||
|
SELECT 'pg_toast.' || t.relname
|
||||||
|
FROM pg_catalog.pg_class c, pg_catalog.pg_class t
|
||||||
|
WHERE c.relname = '$relname'
|
||||||
|
AND c.reltoastrelid = t.oid));
|
||||||
|
}
|
||||||
|
|
||||||
|
# (Re)create and populate a test table of the given name.
|
||||||
|
sub fresh_test_table
|
||||||
|
{
|
||||||
|
my ($relname) = @_;
|
||||||
|
$node->safe_psql('postgres', qq(
|
||||||
|
DROP TABLE IF EXISTS $relname CASCADE;
|
||||||
|
CREATE TABLE $relname (a integer, b text);
|
||||||
|
ALTER TABLE $relname SET (autovacuum_enabled=false);
|
||||||
|
ALTER TABLE $relname ALTER b SET STORAGE external;
|
||||||
|
INSERT INTO $relname (a, b)
|
||||||
|
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stops the test node, corrupts the first page of the named relation, and
|
||||||
|
# restarts the node.
|
||||||
|
sub corrupt_first_page_internal
|
||||||
|
{
|
||||||
|
my ($relname, $corrupt_header) = @_;
|
||||||
|
my $relpath = relation_filepath($relname);
|
||||||
|
|
||||||
|
$node->stop;
|
||||||
|
my $fh;
|
||||||
|
open($fh, '+<', $relpath);
|
||||||
|
binmode $fh;
|
||||||
|
|
||||||
|
# If we corrupt the header, postgres won't allow the page into the buffer.
|
||||||
|
syswrite($fh, '\xFF\xFF\xFF\xFF', 8) if ($corrupt_header);
|
||||||
|
|
||||||
|
# Corrupt at least the line pointers. Exactly what this corrupts will
|
||||||
|
# depend on the page, as it may run past the line pointers into the user
|
||||||
|
# data. We stop short of writing 2048 bytes (2k), the smallest supported
|
||||||
|
# page size, as we don't want to corrupt the next page.
|
||||||
|
seek($fh, 32, 0);
|
||||||
|
syswrite($fh, '\x77\x77\x77\x77', 500);
|
||||||
|
close($fh);
|
||||||
|
$node->start;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub corrupt_first_page
|
||||||
|
{
|
||||||
|
corrupt_first_page_internal($_[0], undef);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub corrupt_first_page_and_header
|
||||||
|
{
|
||||||
|
corrupt_first_page_internal($_[0], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub detects_corruption
|
||||||
|
{
|
||||||
|
my ($function, $testname) = @_;
|
||||||
|
|
||||||
|
my $result = $node->safe_psql('postgres',
|
||||||
|
qq(SELECT COUNT(*) > 0 FROM $function));
|
||||||
|
is($result, 't', $testname);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub detects_no_corruption
|
||||||
|
{
|
||||||
|
my ($function, $testname) = @_;
|
||||||
|
|
||||||
|
my $result = $node->safe_psql('postgres',
|
||||||
|
qq(SELECT COUNT(*) = 0 FROM $function));
|
||||||
|
is($result, 't', $testname);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check various options are stable (don't abort) and do not report corruption
|
||||||
|
# when running verify_heapam on an uncorrupted test table.
|
||||||
|
#
|
||||||
|
# The relname *must* be an uncorrupted table, or this will fail.
|
||||||
|
#
|
||||||
|
# The prefix is used to identify the test, along with the options,
|
||||||
|
# and should be unique.
|
||||||
|
sub check_all_options_uncorrupted
|
||||||
|
{
|
||||||
|
my ($relname, $prefix) = @_;
|
||||||
|
for my $stop (qw(true false))
|
||||||
|
{
|
||||||
|
for my $check_toast (qw(true false))
|
||||||
|
{
|
||||||
|
for my $skip ("'none'", "'all-frozen'", "'all-visible'")
|
||||||
|
{
|
||||||
|
for my $startblock (qw(NULL 0))
|
||||||
|
{
|
||||||
|
for my $endblock (qw(NULL 0))
|
||||||
|
{
|
||||||
|
my $opts = "on_error_stop := $stop, " .
|
||||||
|
"check_toast := $check_toast, " .
|
||||||
|
"skip := $skip, " .
|
||||||
|
"startblock := $startblock, " .
|
||||||
|
"endblock := $endblock";
|
||||||
|
|
||||||
|
detects_no_corruption(
|
||||||
|
"verify_heapam('$relname', $opts)",
|
||||||
|
"$prefix: $opts");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1447
contrib/amcheck/verify_heapam.c
Normal file
1447
contrib/amcheck/verify_heapam.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,12 +9,11 @@
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
The <filename>amcheck</filename> module provides functions that allow you to
|
The <filename>amcheck</filename> module provides functions that allow you to
|
||||||
verify the logical consistency of the structure of relations. If the
|
verify the logical consistency of the structure of relations.
|
||||||
structure appears to be valid, no error is raised.
|
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The functions verify various <emphasis>invariants</emphasis> in the
|
The B-Tree checking functions verify various <emphasis>invariants</emphasis> in the
|
||||||
structure of the representation of particular relations. The
|
structure of the representation of particular relations. The
|
||||||
correctness of the access method functions behind index scans and
|
correctness of the access method functions behind index scans and
|
||||||
other important operations relies on these invariants always
|
other important operations relies on these invariants always
|
||||||
@ -24,7 +23,7 @@
|
|||||||
collated lexical order). If that particular invariant somehow fails
|
collated lexical order). If that particular invariant somehow fails
|
||||||
to hold, we can expect binary searches on the affected page to
|
to hold, we can expect binary searches on the affected page to
|
||||||
incorrectly guide index scans, resulting in wrong answers to SQL
|
incorrectly guide index scans, resulting in wrong answers to SQL
|
||||||
queries.
|
queries. If the structure appears to be valid, no error is raised.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
Verification is performed using the same procedures as those used by
|
Verification is performed using the same procedures as those used by
|
||||||
@ -35,7 +34,22 @@
|
|||||||
functions.
|
functions.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
<filename>amcheck</filename> functions may only be used by superusers.
|
Unlike the B-Tree checking functions which report corruption by raising
|
||||||
|
errors, the heap checking function <function>verify_heapam</function> checks
|
||||||
|
a table and attempts to return a set of rows, one row per corruption
|
||||||
|
detected. Despite this, if facilities that
|
||||||
|
<function>verify_heapam</function> relies upon are themselves corrupted, the
|
||||||
|
function may be unable to continue and may instead raise an error.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Permission to execute <filename>amcheck</filename> functions may be granted
|
||||||
|
to non-superusers, but before granting such permissions careful consideration
|
||||||
|
should be given to data security and privacy concerns. Although the
|
||||||
|
corruption reports generated by these functions do not focus on the contents
|
||||||
|
of the corrupted data so much as on the structure of that data and the nature
|
||||||
|
of the corruptions found, an attacker who gains permission to execute these
|
||||||
|
functions, particularly if the attacker can also induce corruption, might be
|
||||||
|
able to infer something of the data itself from such messages.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<sect2>
|
<sect2>
|
||||||
@ -187,12 +201,221 @@ SET client_min_messages = DEBUG1;
|
|||||||
</para>
|
</para>
|
||||||
</tip>
|
</tip>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
<function>
|
||||||
|
verify_heapam(relation regclass,
|
||||||
|
on_error_stop boolean,
|
||||||
|
check_toast boolean,
|
||||||
|
skip cstring,
|
||||||
|
startblock bigint,
|
||||||
|
endblock bigint,
|
||||||
|
blkno OUT bigint,
|
||||||
|
offnum OUT integer,
|
||||||
|
attnum OUT integer,
|
||||||
|
msg OUT text)
|
||||||
|
returns record
|
||||||
|
</function>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Checks a table for structural corruption, where pages in the relation
|
||||||
|
contain data that is invalidly formatted, and for logical corruption,
|
||||||
|
where pages are structurally valid but inconsistent with the rest of the
|
||||||
|
database cluster. Example usage:
|
||||||
|
<screen>
|
||||||
|
test=# select * from verify_heapam('mytable', check_toast := true);
|
||||||
|
blkno | offnum | attnum | msg
|
||||||
|
-------+--------+--------+--------------------------------------------------------------------------------------------------
|
||||||
|
17 | 12 | | xmin 4294967295 precedes relation freeze threshold 17:1134217582
|
||||||
|
960 | 4 | | data begins at offset 152 beyond the tuple length 58
|
||||||
|
960 | 4 | | tuple data should begin at byte 24, but actually begins at byte 152 (3 attributes, no nulls)
|
||||||
|
960 | 5 | | tuple data should begin at byte 24, but actually begins at byte 27 (3 attributes, no nulls)
|
||||||
|
960 | 6 | | tuple data should begin at byte 24, but actually begins at byte 16 (3 attributes, no nulls)
|
||||||
|
960 | 7 | | tuple data should begin at byte 24, but actually begins at byte 21 (3 attributes, no nulls)
|
||||||
|
1147 | 2 | | number of attributes 2047 exceeds maximum expected for table 3
|
||||||
|
1147 | 10 | | tuple data should begin at byte 280, but actually begins at byte 24 (2047 attributes, has nulls)
|
||||||
|
1147 | 15 | | number of attributes 67 exceeds maximum expected for table 3
|
||||||
|
1147 | 16 | 1 | attribute 1 with length 4294967295 ends at offset 416848000 beyond total tuple length 58
|
||||||
|
1147 | 18 | 2 | final toast chunk number 0 differs from expected value 6
|
||||||
|
1147 | 19 | 2 | toasted value for attribute 2 missing from toast table
|
||||||
|
1147 | 21 | | tuple is marked as only locked, but also claims key columns were updated
|
||||||
|
1147 | 22 | | multitransaction ID 1775655 is from before relation cutoff 2355572
|
||||||
|
(14 rows)
|
||||||
|
</screen>
|
||||||
|
As this example shows, the Tuple ID (TID) of the corrupt tuple is given
|
||||||
|
in the (<literal>blkno</literal>, <literal>offnum</literal>) columns, and
|
||||||
|
for corruptions specific to a particular attribute in the tuple, the
|
||||||
|
<literal>attnum</literal> field shows which one.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Structural corruption can happen due to faulty storage hardware, or
|
||||||
|
relation files being overwritten or modified by unrelated software.
|
||||||
|
This kind of corruption can also be detected with
|
||||||
|
<link linkend="app-initdb-data-checksums"><application>data page
|
||||||
|
checksums</application></link>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Relation pages which are correctly formatted, internally consistent, and
|
||||||
|
correct relative to their own internal checksums may still contain
|
||||||
|
logical corruption. As such, this kind of corruption cannot be detected
|
||||||
|
with <application>checksums</application>. Examples include toasted
|
||||||
|
values in the main table which lack a corresponding entry in the toast
|
||||||
|
table, and tuples in the main table with a Transaction ID that is older
|
||||||
|
than the oldest valid Transaction ID in the database or cluster.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Multiple causes of logical corruption have been observed in production
|
||||||
|
systems, including bugs in the <productname>PostgreSQL</productname>
|
||||||
|
server software, faulty and ill-conceived backup and restore tools, and
|
||||||
|
user error.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Corrupt relations are most concerning in live production environments,
|
||||||
|
precisely the same environments where high risk activities are least
|
||||||
|
welcome. For this reason, <function>verify_heapam</function> has been
|
||||||
|
designed to diagnose corruption without undue risk. It cannot guard
|
||||||
|
against all causes of backend crashes, as even executing the calling
|
||||||
|
query could be unsafe on a badly corrupted system. Access to <link
|
||||||
|
linkend="catalogs-overview">catalog tables</link> are performed and could
|
||||||
|
be problematic if the catalogs themselves are corrupted.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The design principle adhered to in <function>verify_heapam</function> is
|
||||||
|
that, if the rest of the system and server hardware are correct, under
|
||||||
|
default options, <function>verify_heapam</function> will not crash the
|
||||||
|
server due merely to structural or logical corruption in the target
|
||||||
|
table.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The <literal>check_toast</literal> attempts to reconcile the target
|
||||||
|
table against entries in its corresponding toast table. This option is
|
||||||
|
disabled by default and is known to be slow.
|
||||||
|
If the target relation's corresponding toast table or toast index is
|
||||||
|
corrupt, reconciling the target table against toast values could
|
||||||
|
conceivably crash the server, although in many cases this would
|
||||||
|
just produce an error.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The following optional arguments are recognized:
|
||||||
|
</para>
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>on_error_stop</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
If true, corruption checking stops at the end of the first block on
|
||||||
|
which any corruptions are found.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Defaults to false.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>check_toast</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
If true, toasted values are checked gainst the corresponding
|
||||||
|
TOAST table.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Defaults to false.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>skip</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
If not <literal>none</literal>, corruption checking skips blocks that
|
||||||
|
are marked as all-visible or all-frozen, as given.
|
||||||
|
Valid options are <literal>all-visible</literal>,
|
||||||
|
<literal>all-frozen</literal> and <literal>none</literal>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Defaults to <literal>none</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>startblock</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
If specified, corruption checking begins at the specified block,
|
||||||
|
skipping all previous blocks. It is an error to specify a
|
||||||
|
<literal>startblock</literal> outside the range of blocks in the
|
||||||
|
target table.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
By default, does not skip any blocks.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>endblock</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
If specified, corruption checking ends at the specified block,
|
||||||
|
skipping all remaining blocks. It is an error to specify an
|
||||||
|
<literal>endblock</literal> outside the range of blocks in the target
|
||||||
|
table.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
By default, does not skip any blocks.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
<para>
|
||||||
|
For each corruption detected, <function>verify_heapam</function> returns
|
||||||
|
a row with the following columns:
|
||||||
|
</para>
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>blkno</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The number of the block containing the corrupt page.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>offnum</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The OffsetNumber of the corrupt tuple.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>attnum</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The attribute number of the corrupt column in the tuple, if the
|
||||||
|
corruption is specific to a column and not the tuple as a whole.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>msg</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
A human readable message describing the corruption in the page.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
</sect2>
|
</sect2>
|
||||||
|
|
||||||
<sect2>
|
<sect2>
|
||||||
<title>Optional <parameter>heapallindexed</parameter> Verification</title>
|
<title>Optional <parameter>heapallindexed</parameter> Verification</title>
|
||||||
<para>
|
<para>
|
||||||
When the <parameter>heapallindexed</parameter> argument to
|
When the <parameter>heapallindexed</parameter> argument to B-Tree
|
||||||
verification functions is <literal>true</literal>, an additional
|
verification functions is <literal>true</literal>, an additional
|
||||||
phase of verification is performed against the table associated with
|
phase of verification is performed against the table associated with
|
||||||
the target index relation. This consists of a <quote>dummy</quote>
|
the target index relation. This consists of a <quote>dummy</quote>
|
||||||
|
@ -47,6 +47,17 @@ RelationPutHeapTuple(Relation relation,
|
|||||||
*/
|
*/
|
||||||
Assert(!token || HeapTupleHeaderIsSpeculative(tuple->t_data));
|
Assert(!token || HeapTupleHeaderIsSpeculative(tuple->t_data));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do not allow tuples with invalid combinations of hint bits to be placed
|
||||||
|
* on a page. These combinations are detected as corruption by the
|
||||||
|
* contrib/amcheck logic, so if you disable one or both of these
|
||||||
|
* assertions, make corresponding changes there.
|
||||||
|
*/
|
||||||
|
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
|
||||||
|
(tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
|
||||||
|
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
|
||||||
|
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
|
||||||
|
|
||||||
/* Add the tuple to the page */
|
/* Add the tuple to the page */
|
||||||
pageHeader = BufferGetPage(buffer);
|
pageHeader = BufferGetPage(buffer);
|
||||||
|
|
||||||
|
@ -735,6 +735,25 @@ ReadNextMultiXactId(void)
|
|||||||
return mxid;
|
return mxid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ReadMultiXactIdRange
|
||||||
|
* Get the range of IDs that may still be referenced by a relation.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
ReadMultiXactIdRange(MultiXactId *oldest, MultiXactId *next)
|
||||||
|
{
|
||||||
|
LWLockAcquire(MultiXactGenLock, LW_SHARED);
|
||||||
|
*oldest = MultiXactState->oldestMultiXactId;
|
||||||
|
*next = MultiXactState->nextMXact;
|
||||||
|
LWLockRelease(MultiXactGenLock);
|
||||||
|
|
||||||
|
if (*oldest < FirstMultiXactId)
|
||||||
|
*oldest = FirstMultiXactId;
|
||||||
|
if (*next < FirstMultiXactId)
|
||||||
|
*next = FirstMultiXactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MultiXactIdCreateFromMembers
|
* MultiXactIdCreateFromMembers
|
||||||
* Make a new MultiXactId from the specified set of members
|
* Make a new MultiXactId from the specified set of members
|
||||||
|
@ -109,6 +109,7 @@ extern MultiXactId MultiXactIdCreateFromMembers(int nmembers,
|
|||||||
MultiXactMember *members);
|
MultiXactMember *members);
|
||||||
|
|
||||||
extern MultiXactId ReadNextMultiXactId(void);
|
extern MultiXactId ReadNextMultiXactId(void);
|
||||||
|
extern void ReadMultiXactIdRange(MultiXactId *oldest, MultiXactId *next);
|
||||||
extern bool MultiXactIdIsRunning(MultiXactId multi, bool isLockOnly);
|
extern bool MultiXactIdIsRunning(MultiXactId multi, bool isLockOnly);
|
||||||
extern void MultiXactIdSetOldestMember(void);
|
extern void MultiXactIdSetOldestMember(void);
|
||||||
extern int GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **xids,
|
extern int GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **xids,
|
||||||
|
@ -1020,6 +1020,7 @@ HbaToken
|
|||||||
HeadlineJsonState
|
HeadlineJsonState
|
||||||
HeadlineParsedText
|
HeadlineParsedText
|
||||||
HeadlineWordEntry
|
HeadlineWordEntry
|
||||||
|
HeapCheckContext
|
||||||
HeapScanDesc
|
HeapScanDesc
|
||||||
HeapTuple
|
HeapTuple
|
||||||
HeapTupleData
|
HeapTupleData
|
||||||
@ -2290,6 +2291,7 @@ SimpleStringList
|
|||||||
SimpleStringListCell
|
SimpleStringListCell
|
||||||
SingleBoundSortItem
|
SingleBoundSortItem
|
||||||
Size
|
Size
|
||||||
|
SkipPages
|
||||||
SlabBlock
|
SlabBlock
|
||||||
SlabChunk
|
SlabChunk
|
||||||
SlabContext
|
SlabContext
|
||||||
@ -2791,6 +2793,8 @@ XactCallback
|
|||||||
XactCallbackItem
|
XactCallbackItem
|
||||||
XactEvent
|
XactEvent
|
||||||
XactLockTableWaitInfo
|
XactLockTableWaitInfo
|
||||||
|
XidBoundsViolation
|
||||||
|
XidCommitStatus
|
||||||
XidHorizonPrefetchState
|
XidHorizonPrefetchState
|
||||||
XidStatus
|
XidStatus
|
||||||
XmlExpr
|
XmlExpr
|
||||||
|
Loading…
x
Reference in New Issue
Block a user