From 34ed10d11a479cbab8afed224f1718de099d0f35 Mon Sep 17 00:00:00 2001
From: Pavel Borisov <pashkin.elfe@gmail.com>
Date: Mon, 8 Feb 2021 12:26:08 +0400
Subject: [PATCH v4] Make amcheck checking UNIQUE constraint for btree index.
 On index with unique constraint ake check that only one table entry for the
 equal keys (including all posting list entries) is visible. Report error if
 not and show all index entries violating the constraint under warning level.

Authors: Anastasia Lubennikova <a.lubennikova@postgrespro.ru>, Pavel Borisov <pashkin.elfe@gmail.com>
---
 contrib/amcheck/Makefile                   |   2 +-
 contrib/amcheck/amcheck--1.3--1.4.sql      |  28 ++
 contrib/amcheck/amcheck.control            |   2 +-
 contrib/amcheck/expected/check_btree.out   | 146 +++++++++
 contrib/amcheck/expected/check_btree_1.out | 333 +++++++++++++++++++++
 contrib/amcheck/sql/check_btree.sql        |  27 ++
 contrib/amcheck/verify_nbtree.c            | 311 ++++++++++++++++++-
 7 files changed, 832 insertions(+), 17 deletions(-)
 create mode 100644 contrib/amcheck/amcheck--1.3--1.4.sql
 create mode 100644 contrib/amcheck/expected/check_btree_1.out

diff --git a/contrib/amcheck/Makefile b/contrib/amcheck/Makefile
index b82f221e50b..88271687a3e 100644
--- a/contrib/amcheck/Makefile
+++ b/contrib/amcheck/Makefile
@@ -7,7 +7,7 @@ OBJS = \
 	verify_nbtree.o
 
 EXTENSION = amcheck
-DATA = amcheck--1.2--1.3.sql amcheck--1.1--1.2.sql amcheck--1.0--1.1.sql amcheck--1.0.sql
+DATA = amcheck--1.3--1.4.sql 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"
 
 REGRESS = check check_btree check_heap
diff --git a/contrib/amcheck/amcheck--1.3--1.4.sql b/contrib/amcheck/amcheck--1.3--1.4.sql
new file mode 100644
index 00000000000..f76e6941ffe
--- /dev/null
+++ b/contrib/amcheck/amcheck--1.3--1.4.sql
@@ -0,0 +1,28 @@
+/* contrib/amcheck/amcheck--1.3--1.4.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "ALTER EXTENSION amcheck UPDATE TO '1.4'" to load this file. \quit
+
+-- In order to avoid issues with dependencies when updating amcheck to 1.4,
+-- create new, overloaded version of the 1.3 function signature
+
+--
+-- bt_index_parent_check()
+--
+CREATE FUNCTION bt_index_parent_check(index regclass,
+    heapallindexed boolean, rootdescend boolean, checkunique boolean)
+RETURNS VOID
+AS 'MODULE_PATHNAME', 'bt_index_parent_check'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+--
+-- bt_index_check()
+--
+CREATE FUNCTION bt_index_check(index regclass,
+    heapallindexed boolean, checkunique boolean)
+RETURNS VOID
+AS 'MODULE_PATHNAME', 'bt_index_check'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+-- Don't want this to be available to public
+REVOKE ALL ON FUNCTION bt_index_parent_check(regclass, boolean, boolean, boolean) FROM PUBLIC;
+REVOKE ALL ON FUNCTION bt_index_check(regclass, boolean, boolean) FROM PUBLIC;
diff --git a/contrib/amcheck/amcheck.control b/contrib/amcheck/amcheck.control
index ab50931f754..e67ace01c99 100644
--- a/contrib/amcheck/amcheck.control
+++ b/contrib/amcheck/amcheck.control
@@ -1,5 +1,5 @@
 # amcheck extension
 comment = 'functions for verifying relation integrity'
-default_version = '1.3'
+default_version = '1.4'
 module_pathname = '$libdir/amcheck'
 relocatable = true
diff --git a/contrib/amcheck/expected/check_btree.out b/contrib/amcheck/expected/check_btree.out
index 5a3f1ef737c..b191a4df862 100644
--- a/contrib/amcheck/expected/check_btree.out
+++ b/contrib/amcheck/expected/check_btree.out
@@ -177,11 +177,157 @@ SELECT bt_index_check('toasty', true);
  
 (1 row)
 
+-- UNIQUE constraint check
+CREATE TABLE bttest_unique(a varchar(50), b varchar(1500), c bytea, d varchar(50));
+CREATE UNIQUE INDEX bttest_unique_idx ON bttest_unique(a,b);
+UPDATE pg_catalog.pg_index SET indisunique = false
+WHERE indrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = 'bttest_unique');
+INSERT INTO bttest_unique
+	SELECT 	i::text::varchar,
+			array_to_string(array(
+				SELECT substr('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', ((random()*(36-1)+1)::integer), 1)
+			FROM generate_series(1,1300)),'')::varchar,
+	i::text::bytea, i::text::varchar
+	FROM generate_series(0,1) AS i, generate_series(0,30) AS x;
+UPDATE pg_catalog.pg_index SET indisunique = true
+WHERE indrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = 'bttest_unique');
+DELETE FROM bttest_unique WHERE ctid::text='(0,2)';
+DELETE FROM bttest_unique WHERE ctid::text='(4,2)';
+DELETE FROM bttest_unique WHERE ctid::text='(4,3)';
+DELETE FROM bttest_unique WHERE ctid::text='(9,3)';
+-- Check unique index with no uniqueness check. Should not complain.
+SELECT bt_index_check('bttest_unique_idx', true);
+ bt_index_check 
+----------------
+ 
+(1 row)
+
+-- Check unique indes with uniquensee check. Should detect constraint violation cases.
+SELECT bt_index_check('bttest_unique_idx', true, true);
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 0 and posting 2 (point to heap tid=(0,1) and tid=(0,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 2 and posting 3 (point to heap tid=(0,3) and tid=(0,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 3 and posting 4 (point to heap tid=(0,4) and tid=(0,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 4 and tid=(1,3) posting 0 (point to heap tid=(0,5) and tid=(0,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 0 and posting 1 (point to heap tid=(0,6) and tid=(1,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 1 and posting 2 (point to heap tid=(1,1) and tid=(1,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 2 and posting 3 (point to heap tid=(1,2) and tid=(1,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 3 and posting 4 (point to heap tid=(1,3) and tid=(1,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 4 and tid=(1,4) posting 0 (point to heap tid=(1,4) and tid=(1,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 0 and posting 1 (point to heap tid=(1,5) and tid=(1,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 1 and posting 2 (point to heap tid=(1,6) and tid=(2,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 2 and posting 3 (point to heap tid=(2,1) and tid=(2,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 3 and posting 4 (point to heap tid=(2,2) and tid=(2,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 4 and tid=(1,5) posting 0 (point to heap tid=(2,3) and tid=(2,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 0 and posting 1 (point to heap tid=(2,4) and tid=(2,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 1 and posting 2 (point to heap tid=(2,5) and tid=(2,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 2 and posting 3 (point to heap tid=(2,6) and tid=(3,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 3 and posting 4 (point to heap tid=(3,1) and tid=(3,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 4 and tid=(1,6) posting 0 (point to heap tid=(3,2) and tid=(3,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 0 and posting 1 (point to heap tid=(3,3) and tid=(3,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 1 and posting 2 (point to heap tid=(3,4) and tid=(3,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 2 and posting 3 (point to heap tid=(3,5) and tid=(3,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 3 and posting 4 (point to heap tid=(3,6) and tid=(4,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 4 and tid=(2,2) posting 2 (point to heap tid=(4,1) and tid=(4,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(2,2) posting 2 and posting 3 (point to heap tid=(4,4) and tid=(4,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(2,2) posting 3 and posting 4 (point to heap tid=(4,5) and tid=(4,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(2,2) posting 4 and tid=(2,3) (point to heap tid=(4,6) and tid=(5,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 0 and posting 1 (point to heap tid=(5,2) and tid=(5,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 1 and posting 2 (point to heap tid=(5,3) and tid=(5,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 2 and posting 3 (point to heap tid=(5,4) and tid=(5,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 3 and posting 4 (point to heap tid=(5,5) and tid=(5,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 4 and tid=(4,3) posting 0 (point to heap tid=(5,6) and tid=(6,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 0 and posting 1 (point to heap tid=(6,1) and tid=(6,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 1 and posting 2 (point to heap tid=(6,2) and tid=(6,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 2 and posting 3 (point to heap tid=(6,3) and tid=(6,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 3 and posting 4 (point to heap tid=(6,4) and tid=(6,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 4 and tid=(4,4) posting 0 (point to heap tid=(6,5) and tid=(6,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 0 and posting 1 (point to heap tid=(6,6) and tid=(7,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 1 and posting 2 (point to heap tid=(7,1) and tid=(7,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 2 and posting 3 (point to heap tid=(7,2) and tid=(7,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 3 and posting 4 (point to heap tid=(7,3) and tid=(7,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 4 and tid=(4,5) posting 0 (point to heap tid=(7,4) and tid=(7,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 0 and posting 1 (point to heap tid=(7,5) and tid=(7,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 1 and posting 2 (point to heap tid=(7,6) and tid=(8,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 2 and posting 3 (point to heap tid=(8,1) and tid=(8,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 3 and posting 4 (point to heap tid=(8,2) and tid=(8,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 4 and tid=(4,6) posting 0 (point to heap tid=(8,3) and tid=(8,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,6) posting 0 and posting 1 (point to heap tid=(8,4) and tid=(8,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,6) posting 1 and posting 2 (point to heap tid=(8,5) and tid=(8,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,6) posting 2 and posting 3 (point to heap tid=(8,6) and tid=(9,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,6) posting 3 and posting 4 (point to heap tid=(9,1) and tid=(9,2)).
+WARNING:  index uniqueness may be violated for index "bttest_unique_idx": Index tid=(5,1) doesn't have visible heap tids and key is equal to the tid=(4,6) posting 4 (points to heap tid=(9,2)). Cross-page unique constraint violation can be missed. Vacuum the table and repeat the check.
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(5,2) and tid=(5,3) (point to heap tid=(9,4) and tid=(9,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(5,3) and tid=(5,4) (point to heap tid=(9,5) and tid=(9,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(5,4) and tid=(5,5) (point to heap tid=(9,6) and tid=(10,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(5,5) and tid=(5,6) (point to heap tid=(10,1) and tid=(10,2)).
+ERROR:  index "bttest_unique_idx" is corrupted. There are tuples violating UNIQUE constraint
+DETAIL:  Details are in the previous log messages under WARNING priority
+VACUUM bttest_unique;
+SELECT bt_index_check('bttest_unique_idx', true, true);
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 0 and posting 1 (point to heap tid=(0,1) and tid=(0,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 1 and posting 2 (point to heap tid=(0,3) and tid=(0,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 2 and posting 3 (point to heap tid=(0,4) and tid=(0,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 3 and tid=(1,3) posting 0 (point to heap tid=(0,5) and tid=(0,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 0 and posting 1 (point to heap tid=(0,6) and tid=(1,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 1 and posting 2 (point to heap tid=(1,1) and tid=(1,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 2 and posting 3 (point to heap tid=(1,2) and tid=(1,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 3 and posting 4 (point to heap tid=(1,3) and tid=(1,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 4 and tid=(1,4) posting 0 (point to heap tid=(1,4) and tid=(1,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 0 and posting 1 (point to heap tid=(1,5) and tid=(1,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 1 and posting 2 (point to heap tid=(1,6) and tid=(2,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 2 and posting 3 (point to heap tid=(2,1) and tid=(2,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 3 and posting 4 (point to heap tid=(2,2) and tid=(2,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 4 and tid=(1,5) posting 0 (point to heap tid=(2,3) and tid=(2,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 0 and posting 1 (point to heap tid=(2,4) and tid=(2,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 1 and posting 2 (point to heap tid=(2,5) and tid=(2,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 2 and posting 3 (point to heap tid=(2,6) and tid=(3,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 3 and posting 4 (point to heap tid=(3,1) and tid=(3,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 4 and tid=(1,6) posting 0 (point to heap tid=(3,2) and tid=(3,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 0 and posting 1 (point to heap tid=(3,3) and tid=(3,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 1 and posting 2 (point to heap tid=(3,4) and tid=(3,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 2 and posting 3 (point to heap tid=(3,5) and tid=(3,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 3 and posting 4 (point to heap tid=(3,6) and tid=(4,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 4 and tid=(2,2) posting 0 (point to heap tid=(4,1) and tid=(4,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(2,2) posting 0 and posting 1 (point to heap tid=(4,4) and tid=(4,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(2,2) posting 1 and posting 2 (point to heap tid=(4,5) and tid=(4,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(2,2) posting 2 and tid=(2,3) (point to heap tid=(4,6) and tid=(5,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 0 and posting 1 (point to heap tid=(5,2) and tid=(5,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 1 and posting 2 (point to heap tid=(5,3) and tid=(5,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 2 and posting 3 (point to heap tid=(5,4) and tid=(5,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 3 and posting 4 (point to heap tid=(5,5) and tid=(5,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 4 and tid=(4,3) posting 0 (point to heap tid=(5,6) and tid=(6,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 0 and posting 1 (point to heap tid=(6,1) and tid=(6,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 1 and posting 2 (point to heap tid=(6,2) and tid=(6,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 2 and posting 3 (point to heap tid=(6,3) and tid=(6,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 3 and posting 4 (point to heap tid=(6,4) and tid=(6,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 4 and tid=(4,4) posting 0 (point to heap tid=(6,5) and tid=(6,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 0 and posting 1 (point to heap tid=(6,6) and tid=(7,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 1 and posting 2 (point to heap tid=(7,1) and tid=(7,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 2 and posting 3 (point to heap tid=(7,2) and tid=(7,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 3 and posting 4 (point to heap tid=(7,3) and tid=(7,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 4 and tid=(4,5) posting 0 (point to heap tid=(7,4) and tid=(7,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 0 and posting 1 (point to heap tid=(7,5) and tid=(7,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 1 and posting 2 (point to heap tid=(7,6) and tid=(8,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 2 and posting 3 (point to heap tid=(8,1) and tid=(8,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 3 and posting 4 (point to heap tid=(8,2) and tid=(8,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 4 and tid=(4,6) posting 0 (point to heap tid=(8,3) and tid=(8,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,6) posting 0 and posting 1 (point to heap tid=(8,4) and tid=(8,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,6) posting 1 and posting 2 (point to heap tid=(8,5) and tid=(8,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,6) posting 2 and posting 3 (point to heap tid=(8,6) and tid=(9,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,6) posting 3 and posting 4 (point to heap tid=(9,1) and tid=(9,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,6) posting 4 and tid=(5,1) (point to heap tid=(9,2) and tid=(9,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(5,1) and tid=(5,2) (point to heap tid=(9,4) and tid=(9,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(5,2) and tid=(5,3) (point to heap tid=(9,5) and tid=(9,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(5,3) and tid=(5,4) (point to heap tid=(9,6) and tid=(10,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(5,4) and tid=(5,5) (point to heap tid=(10,1) and tid=(10,2)).
+ERROR:  index "bttest_unique_idx" is corrupted. There are tuples violating UNIQUE constraint
+DETAIL:  Details are in the previous log messages under WARNING priority
 -- cleanup
 DROP TABLE bttest_a;
 DROP TABLE bttest_b;
 DROP TABLE bttest_multi;
 DROP TABLE delete_test_table;
 DROP TABLE toast_bug;
+DROP TABLE bttest_unique;
 DROP OWNED BY regress_bttest_role; -- permissions
 DROP ROLE regress_bttest_role;
diff --git a/contrib/amcheck/expected/check_btree_1.out b/contrib/amcheck/expected/check_btree_1.out
new file mode 100644
index 00000000000..6aa66e42118
--- /dev/null
+++ b/contrib/amcheck/expected/check_btree_1.out
@@ -0,0 +1,333 @@
+CREATE TABLE bttest_a(id int8);
+CREATE TABLE bttest_b(id int8);
+CREATE TABLE bttest_multi(id int8, data int8);
+CREATE TABLE delete_test_table (a bigint, b bigint, c bigint, d bigint);
+-- Stabalize tests
+ALTER TABLE bttest_a SET (autovacuum_enabled = false);
+ALTER TABLE bttest_b SET (autovacuum_enabled = false);
+ALTER TABLE bttest_multi SET (autovacuum_enabled = false);
+ALTER TABLE delete_test_table SET (autovacuum_enabled = false);
+INSERT INTO bttest_a SELECT * FROM generate_series(1, 100000);
+INSERT INTO bttest_b SELECT * FROM generate_series(100000, 1, -1);
+INSERT INTO bttest_multi SELECT i, i%2  FROM generate_series(1, 100000) as i;
+CREATE INDEX bttest_a_idx ON bttest_a USING btree (id) WITH (deduplicate_items = ON);
+CREATE INDEX bttest_b_idx ON bttest_b USING btree (id);
+CREATE UNIQUE INDEX bttest_multi_idx ON bttest_multi
+USING btree (id) INCLUDE (data);
+CREATE ROLE regress_bttest_role;
+-- verify permissions are checked (error due to function not callable)
+SET ROLE regress_bttest_role;
+SELECT bt_index_check('bttest_a_idx'::regclass);
+ERROR:  permission denied for function bt_index_check
+SELECT bt_index_parent_check('bttest_a_idx'::regclass);
+ERROR:  permission denied for function bt_index_parent_check
+RESET ROLE;
+-- we, intentionally, don't check relation permissions - it's useful
+-- to run this cluster-wide with a restricted account, and as tested
+-- above explicit permission has to be granted for that.
+GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO regress_bttest_role;
+GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass) TO regress_bttest_role;
+GRANT EXECUTE ON FUNCTION bt_index_check(regclass, boolean) TO regress_bttest_role;
+GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass, boolean) TO regress_bttest_role;
+SET ROLE regress_bttest_role;
+SELECT bt_index_check('bttest_a_idx');
+ bt_index_check 
+----------------
+ 
+(1 row)
+
+SELECT bt_index_parent_check('bttest_a_idx');
+ bt_index_parent_check 
+-----------------------
+ 
+(1 row)
+
+RESET ROLE;
+-- verify plain tables are rejected (error)
+SELECT bt_index_check('bttest_a');
+ERROR:  "bttest_a" is not an index
+SELECT bt_index_parent_check('bttest_a');
+ERROR:  "bttest_a" is not an index
+-- verify non-existing indexes are rejected (error)
+SELECT bt_index_check(17);
+ERROR:  could not open relation with OID 17
+SELECT bt_index_parent_check(17);
+ERROR:  could not open relation with OID 17
+-- verify wrong index types are rejected (error)
+BEGIN;
+CREATE INDEX bttest_a_brin_idx ON bttest_a USING brin(id);
+SELECT bt_index_parent_check('bttest_a_brin_idx');
+ERROR:  only B-Tree indexes are supported as targets for verification
+DETAIL:  Relation "bttest_a_brin_idx" is not a B-Tree index.
+ROLLBACK;
+-- normal check outside of xact
+SELECT bt_index_check('bttest_a_idx');
+ bt_index_check 
+----------------
+ 
+(1 row)
+
+-- more expansive tests
+SELECT bt_index_check('bttest_a_idx', true);
+ bt_index_check 
+----------------
+ 
+(1 row)
+
+SELECT bt_index_parent_check('bttest_b_idx', true);
+ bt_index_parent_check 
+-----------------------
+ 
+(1 row)
+
+BEGIN;
+SELECT bt_index_check('bttest_a_idx');
+ bt_index_check 
+----------------
+ 
+(1 row)
+
+SELECT bt_index_parent_check('bttest_b_idx');
+ bt_index_parent_check 
+-----------------------
+ 
+(1 row)
+
+-- make sure we don't have any leftover locks
+SELECT * FROM pg_locks
+WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx']::regclass[])
+    AND pid = pg_backend_pid();
+ locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | mode | granted | fastpath | waitstart 
+----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----+------+---------+----------+-----------
+(0 rows)
+
+COMMIT;
+-- Deduplication
+TRUNCATE bttest_a;
+INSERT INTO bttest_a SELECT 42 FROM generate_series(1, 2000);
+SELECT bt_index_check('bttest_a_idx', true);
+ bt_index_check 
+----------------
+ 
+(1 row)
+
+-- normal check outside of xact for index with included columns
+SELECT bt_index_check('bttest_multi_idx');
+ bt_index_check 
+----------------
+ 
+(1 row)
+
+-- more expansive tests for index with included columns
+SELECT bt_index_parent_check('bttest_multi_idx', true, true);
+ bt_index_parent_check 
+-----------------------
+ 
+(1 row)
+
+-- repeat expansive tests for index built using insertions
+TRUNCATE bttest_multi;
+INSERT INTO bttest_multi SELECT i, i%2  FROM generate_series(1, 100000) as i;
+SELECT bt_index_parent_check('bttest_multi_idx', true, true);
+ bt_index_parent_check 
+-----------------------
+ 
+(1 row)
+
+--
+-- Test for multilevel page deletion/downlink present checks, and rootdescend
+-- checks
+--
+INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,80000) i;
+ALTER TABLE delete_test_table ADD PRIMARY KEY (a,b,c,d);
+-- Delete most entries, and vacuum, deleting internal pages and creating "fast
+-- root"
+DELETE FROM delete_test_table WHERE a < 79990;
+VACUUM delete_test_table;
+SELECT bt_index_parent_check('delete_test_table_pkey', true);
+ bt_index_parent_check 
+-----------------------
+ 
+(1 row)
+
+--
+-- BUG #15597: must not assume consistent input toasting state when forming
+-- tuple.  Bloom filter must fingerprint normalized index tuple representation.
+--
+CREATE TABLE toast_bug(buggy text);
+ALTER TABLE toast_bug ALTER COLUMN buggy SET STORAGE extended;
+CREATE INDEX toasty ON toast_bug(buggy);
+-- pg_attribute entry for toasty.buggy (the index) will have plain storage:
+UPDATE pg_attribute SET attstorage = 'p'
+WHERE attrelid = 'toasty'::regclass AND attname = 'buggy';
+-- Whereas pg_attribute entry for toast_bug.buggy (the table) still has extended storage:
+SELECT attstorage FROM pg_attribute
+WHERE attrelid = 'toast_bug'::regclass AND attname = 'buggy';
+ attstorage 
+------------
+ x
+(1 row)
+
+-- Insert compressible heap tuple (comfortably exceeds TOAST_TUPLE_THRESHOLD):
+INSERT INTO toast_bug SELECT repeat('a', 2200);
+-- Should not get false positive report of corruption:
+SELECT bt_index_check('toasty', true);
+ bt_index_check 
+----------------
+ 
+(1 row)
+
+-- UNIQUE constraint check
+CREATE TABLE bttest_unique(a varchar(50), b varchar(1500), c bytea, d varchar(50));
+CREATE UNIQUE INDEX bttest_unique_idx ON bttest_unique(a,b);
+UPDATE pg_catalog.pg_index SET indisunique = false
+WHERE indrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = 'bttest_unique');
+INSERT INTO bttest_unique
+	SELECT 	i::text::varchar,
+			array_to_string(array(
+				SELECT substr('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', ((random()*(36-1)+1)::integer), 1)
+			FROM generate_series(1,1300)),'')::varchar,
+	i::text::bytea, i::text::varchar
+	FROM generate_series(0,1) AS i, generate_series(0,30) AS x;
+UPDATE pg_catalog.pg_index SET indisunique = true
+WHERE indrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = 'bttest_unique');
+DELETE FROM bttest_unique WHERE ctid::text='(0,2)';
+DELETE FROM bttest_unique WHERE ctid::text='(4,2)';
+DELETE FROM bttest_unique WHERE ctid::text='(4,3)';
+DELETE FROM bttest_unique WHERE ctid::text='(9,3)';
+-- Check unique index with no uniqueness check. Should not complain.
+SELECT bt_index_check('bttest_unique_idx', true);
+ bt_index_check 
+----------------
+ 
+(1 row)
+
+-- Check unique indes with uniquensee check. Should detect constraint violation cases.
+SELECT bt_index_check('bttest_unique_idx', true, true);
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 0 and posting 2 (point to heap tid=(0,1) and tid=(0,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 2 and posting 3 (point to heap tid=(0,3) and tid=(0,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 3 and posting 4 (point to heap tid=(0,4) and tid=(0,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 4 and posting 5 (point to heap tid=(0,5) and tid=(0,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 5 and tid=(1,3) posting 0 (point to heap tid=(0,6) and tid=(1,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 0 and posting 1 (point to heap tid=(1,1) and tid=(1,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 1 and posting 2 (point to heap tid=(1,2) and tid=(1,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 2 and posting 3 (point to heap tid=(1,3) and tid=(1,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 3 and posting 4 (point to heap tid=(1,4) and tid=(1,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 4 and posting 5 (point to heap tid=(1,5) and tid=(1,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 5 and tid=(1,4) posting 0 (point to heap tid=(1,6) and tid=(2,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 0 and posting 1 (point to heap tid=(2,1) and tid=(2,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 1 and posting 2 (point to heap tid=(2,2) and tid=(2,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 2 and posting 3 (point to heap tid=(2,3) and tid=(2,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 3 and posting 4 (point to heap tid=(2,4) and tid=(2,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 4 and posting 5 (point to heap tid=(2,5) and tid=(2,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 5 and tid=(1,5) posting 0 (point to heap tid=(2,6) and tid=(3,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 0 and posting 1 (point to heap tid=(3,1) and tid=(3,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 1 and posting 2 (point to heap tid=(3,2) and tid=(3,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 2 and posting 3 (point to heap tid=(3,3) and tid=(3,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 3 and posting 4 (point to heap tid=(3,4) and tid=(3,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 4 and posting 5 (point to heap tid=(3,5) and tid=(3,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 5 and tid=(1,6) posting 0 (point to heap tid=(3,6) and tid=(4,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 0 and posting 3 (point to heap tid=(4,1) and tid=(4,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 3 and posting 4 (point to heap tid=(4,4) and tid=(4,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 4 and posting 5 (point to heap tid=(4,5) and tid=(4,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 5 and tid=(2,2) (point to heap tid=(4,6) and tid=(5,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 0 and posting 1 (point to heap tid=(5,2) and tid=(5,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 1 and posting 2 (point to heap tid=(5,3) and tid=(5,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 2 and posting 3 (point to heap tid=(5,4) and tid=(5,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 3 and posting 4 (point to heap tid=(5,5) and tid=(5,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 4 and posting 5 (point to heap tid=(5,6) and tid=(6,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 5 and tid=(4,2) posting 0 (point to heap tid=(6,1) and tid=(6,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 0 and posting 1 (point to heap tid=(6,2) and tid=(6,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 1 and posting 2 (point to heap tid=(6,3) and tid=(6,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 2 and posting 3 (point to heap tid=(6,4) and tid=(6,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 3 and posting 4 (point to heap tid=(6,5) and tid=(6,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 4 and posting 5 (point to heap tid=(6,6) and tid=(7,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 5 and tid=(4,3) posting 0 (point to heap tid=(7,1) and tid=(7,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 0 and posting 1 (point to heap tid=(7,2) and tid=(7,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 1 and posting 2 (point to heap tid=(7,3) and tid=(7,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 2 and posting 3 (point to heap tid=(7,4) and tid=(7,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 3 and posting 4 (point to heap tid=(7,5) and tid=(7,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 4 and posting 5 (point to heap tid=(7,6) and tid=(8,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 5 and tid=(4,4) posting 0 (point to heap tid=(8,1) and tid=(8,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 0 and posting 1 (point to heap tid=(8,2) and tid=(8,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 1 and posting 2 (point to heap tid=(8,3) and tid=(8,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 2 and posting 3 (point to heap tid=(8,4) and tid=(8,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 3 and posting 4 (point to heap tid=(8,5) and tid=(8,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 4 and posting 5 (point to heap tid=(8,6) and tid=(9,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 5 and tid=(4,5) posting 0 (point to heap tid=(9,1) and tid=(9,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 0 and posting 2 (point to heap tid=(9,2) and tid=(9,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 2 and posting 3 (point to heap tid=(9,4) and tid=(9,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 3 and posting 4 (point to heap tid=(9,5) and tid=(9,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 4 and posting 5 (point to heap tid=(9,6) and tid=(10,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 5 and tid=(4,6) (point to heap tid=(10,1) and tid=(10,2)).
+ERROR:  index "bttest_unique_idx" is corrupted. There are tuples violating UNIQUE constraint
+DETAIL:  Details are in the previous log messages under WARNING priority
+VACUUM bttest_unique;
+SELECT bt_index_check('bttest_unique_idx', true, true);
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 0 and posting 1 (point to heap tid=(0,1) and tid=(0,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 1 and posting 2 (point to heap tid=(0,3) and tid=(0,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 2 and posting 3 (point to heap tid=(0,4) and tid=(0,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 3 and posting 4 (point to heap tid=(0,5) and tid=(0,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,2) posting 4 and tid=(1,3) posting 0 (point to heap tid=(0,6) and tid=(1,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 0 and posting 1 (point to heap tid=(1,1) and tid=(1,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 1 and posting 2 (point to heap tid=(1,2) and tid=(1,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 2 and posting 3 (point to heap tid=(1,3) and tid=(1,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 3 and posting 4 (point to heap tid=(1,4) and tid=(1,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 4 and posting 5 (point to heap tid=(1,5) and tid=(1,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,3) posting 5 and tid=(1,4) posting 0 (point to heap tid=(1,6) and tid=(2,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 0 and posting 1 (point to heap tid=(2,1) and tid=(2,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 1 and posting 2 (point to heap tid=(2,2) and tid=(2,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 2 and posting 3 (point to heap tid=(2,3) and tid=(2,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 3 and posting 4 (point to heap tid=(2,4) and tid=(2,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 4 and posting 5 (point to heap tid=(2,5) and tid=(2,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,4) posting 5 and tid=(1,5) posting 0 (point to heap tid=(2,6) and tid=(3,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 0 and posting 1 (point to heap tid=(3,1) and tid=(3,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 1 and posting 2 (point to heap tid=(3,2) and tid=(3,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 2 and posting 3 (point to heap tid=(3,3) and tid=(3,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 3 and posting 4 (point to heap tid=(3,4) and tid=(3,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 4 and posting 5 (point to heap tid=(3,5) and tid=(3,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,5) posting 5 and tid=(1,6) posting 0 (point to heap tid=(3,6) and tid=(4,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 0 and posting 1 (point to heap tid=(4,1) and tid=(4,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 1 and posting 2 (point to heap tid=(4,4) and tid=(4,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 2 and posting 3 (point to heap tid=(4,5) and tid=(4,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(1,6) posting 3 and tid=(2,2) (point to heap tid=(4,6) and tid=(5,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 0 and posting 1 (point to heap tid=(5,2) and tid=(5,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 1 and posting 2 (point to heap tid=(5,3) and tid=(5,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 2 and posting 3 (point to heap tid=(5,4) and tid=(5,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 3 and posting 4 (point to heap tid=(5,5) and tid=(5,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 4 and posting 5 (point to heap tid=(5,6) and tid=(6,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,1) posting 5 and tid=(4,2) posting 0 (point to heap tid=(6,1) and tid=(6,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 0 and posting 1 (point to heap tid=(6,2) and tid=(6,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 1 and posting 2 (point to heap tid=(6,3) and tid=(6,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 2 and posting 3 (point to heap tid=(6,4) and tid=(6,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 3 and posting 4 (point to heap tid=(6,5) and tid=(6,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 4 and posting 5 (point to heap tid=(6,6) and tid=(7,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,2) posting 5 and tid=(4,3) posting 0 (point to heap tid=(7,1) and tid=(7,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 0 and posting 1 (point to heap tid=(7,2) and tid=(7,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 1 and posting 2 (point to heap tid=(7,3) and tid=(7,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 2 and posting 3 (point to heap tid=(7,4) and tid=(7,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 3 and posting 4 (point to heap tid=(7,5) and tid=(7,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 4 and posting 5 (point to heap tid=(7,6) and tid=(8,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,3) posting 5 and tid=(4,4) posting 0 (point to heap tid=(8,1) and tid=(8,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 0 and posting 1 (point to heap tid=(8,2) and tid=(8,3)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 1 and posting 2 (point to heap tid=(8,3) and tid=(8,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 2 and posting 3 (point to heap tid=(8,4) and tid=(8,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 3 and posting 4 (point to heap tid=(8,5) and tid=(8,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 4 and posting 5 (point to heap tid=(8,6) and tid=(9,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,4) posting 5 and tid=(4,5) posting 0 (point to heap tid=(9,1) and tid=(9,2)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 0 and posting 1 (point to heap tid=(9,2) and tid=(9,4)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 1 and posting 2 (point to heap tid=(9,4) and tid=(9,5)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 2 and posting 3 (point to heap tid=(9,5) and tid=(9,6)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 3 and posting 4 (point to heap tid=(9,6) and tid=(10,1)).
+WARNING:  index uniqueness is violated for index "bttest_unique_idx": Index tid=(4,5) posting 4 and tid=(4,6) (point to heap tid=(10,1) and tid=(10,2)).
+ERROR:  index "bttest_unique_idx" is corrupted. There are tuples violating UNIQUE constraint
+DETAIL:  Details are in the previous log messages under WARNING priority
+-- cleanup
+DROP TABLE bttest_a;
+DROP TABLE bttest_b;
+DROP TABLE bttest_multi;
+DROP TABLE delete_test_table;
+DROP TABLE toast_bug;
+DROP TABLE bttest_unique;
+DROP OWNED BY regress_bttest_role; -- permissions
+DROP ROLE regress_bttest_role;
diff --git a/contrib/amcheck/sql/check_btree.sql b/contrib/amcheck/sql/check_btree.sql
index 97a3e1a20d5..dbe8b48afc7 100644
--- a/contrib/amcheck/sql/check_btree.sql
+++ b/contrib/amcheck/sql/check_btree.sql
@@ -115,11 +115,38 @@ INSERT INTO toast_bug SELECT repeat('a', 2200);
 -- Should not get false positive report of corruption:
 SELECT bt_index_check('toasty', true);
 
+-- UNIQUE constraint check
+CREATE TABLE bttest_unique(a varchar(50), b varchar(1500), c bytea, d varchar(50));
+CREATE UNIQUE INDEX bttest_unique_idx ON bttest_unique(a,b);
+UPDATE pg_catalog.pg_index SET indisunique = false
+WHERE indrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = 'bttest_unique');
+INSERT INTO bttest_unique
+	SELECT 	i::text::varchar,
+			array_to_string(array(
+				SELECT substr('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', ((random()*(36-1)+1)::integer), 1)
+			FROM generate_series(1,1300)),'')::varchar,
+	i::text::bytea, i::text::varchar
+	FROM generate_series(0,1) AS i, generate_series(0,30) AS x;
+UPDATE pg_catalog.pg_index SET indisunique = true
+WHERE indrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = 'bttest_unique');
+
+DELETE FROM bttest_unique WHERE ctid::text='(0,2)';
+DELETE FROM bttest_unique WHERE ctid::text='(4,2)';
+DELETE FROM bttest_unique WHERE ctid::text='(4,3)';
+DELETE FROM bttest_unique WHERE ctid::text='(9,3)';
+-- Check unique index with no uniqueness check. Should not complain.
+SELECT bt_index_check('bttest_unique_idx', true);
+-- Check unique indes with uniquensee check. Should detect constraint violation cases.
+SELECT bt_index_check('bttest_unique_idx', true, true);
+VACUUM bttest_unique;
+SELECT bt_index_check('bttest_unique_idx', true, true);
+
 -- cleanup
 DROP TABLE bttest_a;
 DROP TABLE bttest_b;
 DROP TABLE bttest_multi;
 DROP TABLE delete_test_table;
 DROP TABLE toast_bug;
+DROP TABLE bttest_unique;
 DROP OWNED BY regress_bttest_role; -- permissions
 DROP ROLE regress_bttest_role;
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index c4ca6339182..68644fa3285 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -78,11 +78,20 @@ typedef struct BtreeCheckState
 	bool		heapallindexed;
 	/* Also making sure non-pivot tuples can be found by new search? */
 	bool		rootdescend;
+	/* Also check uniqueness constraint if index is unique */
+	bool 		checkunique;
 	/* Per-page context */
 	MemoryContext targetcontext;
 	/* Buffer access strategy */
 	BufferAccessStrategy checkstrategy;
 
+	/*
+	 * Info for uniqueness checking.
+	 * Fill these fields once per index check.
+	 */
+	IndexInfo  *indexinfo;
+	Snapshot	snapshot;
+
 	/*
 	 * Mutable state, for verification of particular page:
 	 */
@@ -137,19 +146,33 @@ PG_FUNCTION_INFO_V1(bt_index_check);
 PG_FUNCTION_INFO_V1(bt_index_parent_check);
 
 static void bt_index_check_internal(Oid indrelid, bool parentcheck,
-									bool heapallindexed, bool rootdescend);
+									bool heapallindexed, bool rootdescend,
+									bool checkunique);
 static inline void btree_index_checkable(Relation rel);
 static inline bool btree_index_mainfork_expected(Relation rel);
 static void bt_check_every_level(Relation rel, Relation heaprel,
 								 bool heapkeyspace, bool readonly, bool heapallindexed,
-								 bool rootdescend);
+								 bool rootdescend, bool checkunique);
 static BtreeLevel bt_check_level_from_leftmost(BtreeCheckState *state,
 											   BtreeLevel level);
 static void bt_recheck_sibling_links(BtreeCheckState *state,
 									 BlockNumber btpo_prev_from_target,
 									 BlockNumber leftcurrent);
+static bool heap_entry_is_visible(BtreeCheckState *state, ItemPointer tid);
+static void bt_report_duplicate(BtreeCheckState *state, ItemPointer tid,
+								BlockNumber block, OffsetNumber offset,
+								int posting, ItemPointer nexttid,
+								BlockNumber nblock, OffsetNumber noffset,
+								int nposting);
+static void bt_entry_unique_check(BtreeCheckState *state, IndexTuple itup,
+								  BlockNumber targetblock,
+								  OffsetNumber offset, int *lVis_i,
+								  ItemPointer *lVis_tid,
+								  OffsetNumber *lVis_offset,
+								  BlockNumber *lVis_block);
 static void bt_target_page_check(BtreeCheckState *state);
-static BTScanInsert bt_right_page_check_scankey(BtreeCheckState *state);
+static BTScanInsert bt_right_page_check_scankey(BtreeCheckState *state,
+												OffsetNumber *rightfirstoffset);
 static void bt_child_check(BtreeCheckState *state, BTScanInsert targetkey,
 						   OffsetNumber downlinkoffnum);
 static void bt_child_highkey_check(BtreeCheckState *state,
@@ -187,9 +210,10 @@ static ItemId PageGetItemIdCareful(BtreeCheckState *state, BlockNumber block,
 static inline ItemPointer BTreeTupleGetHeapTIDCareful(BtreeCheckState *state,
 													  IndexTuple itup, bool nonpivot);
 static inline ItemPointer BTreeTupleGetPointsToTID(IndexTuple itup);
+static bool errflag; /* Output ERROR at the end of amcheck */
 
 /*
- * bt_index_check(index regclass, heapallindexed boolean)
+ * bt_index_check(index regclass, heapallindexed boolean, checkunique boolean)
  *
  * Verify integrity of B-Tree index.
  *
@@ -202,17 +226,20 @@ bt_index_check(PG_FUNCTION_ARGS)
 {
 	Oid			indrelid = PG_GETARG_OID(0);
 	bool		heapallindexed = false;
+	bool        checkunique = false;
 
-	if (PG_NARGS() == 2)
+	if (PG_NARGS() >= 2)
 		heapallindexed = PG_GETARG_BOOL(1);
+	if (PG_NARGS() == 3)
+		checkunique = PG_GETARG_BOOL(2);
 
-	bt_index_check_internal(indrelid, false, heapallindexed, false);
+	bt_index_check_internal(indrelid, false, heapallindexed, false, checkunique);
 
 	PG_RETURN_VOID();
 }
 
 /*
- * bt_index_parent_check(index regclass, heapallindexed boolean)
+ * bt_index_parent_check(index regclass, heapallindexed boolean, rootdescend boolean, checkunique boolean)
  *
  * Verify integrity of B-Tree index.
  *
@@ -226,13 +253,16 @@ bt_index_parent_check(PG_FUNCTION_ARGS)
 	Oid			indrelid = PG_GETARG_OID(0);
 	bool		heapallindexed = false;
 	bool		rootdescend = false;
+	bool		checkunique = false;
 
 	if (PG_NARGS() >= 2)
 		heapallindexed = PG_GETARG_BOOL(1);
-	if (PG_NARGS() == 3)
+	if (PG_NARGS() >= 3)
 		rootdescend = PG_GETARG_BOOL(2);
+	if (PG_NARGS() == 4)
+		checkunique = PG_GETARG_BOOL(3);
 
-	bt_index_check_internal(indrelid, true, heapallindexed, rootdescend);
+	bt_index_check_internal(indrelid, true, heapallindexed, rootdescend, checkunique);
 
 	PG_RETURN_VOID();
 }
@@ -242,7 +272,7 @@ bt_index_parent_check(PG_FUNCTION_ARGS)
  */
 static void
 bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed,
-						bool rootdescend)
+						bool rootdescend, bool checkunique)
 {
 	Oid			heapid;
 	Relation	indrel;
@@ -323,7 +353,7 @@ bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed,
 
 		/* Check index, possibly against table it is an index on */
 		bt_check_every_level(indrel, heaprel, heapkeyspace, parentcheck,
-							 heapallindexed, rootdescend);
+							 heapallindexed, rootdescend, checkunique);
 	}
 
 	/*
@@ -417,7 +447,8 @@ btree_index_mainfork_expected(Relation rel)
  */
 static void
 bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
-					 bool readonly, bool heapallindexed, bool rootdescend)
+					 bool readonly, bool heapallindexed, bool rootdescend,
+					 bool checkunique)
 {
 	BtreeCheckState *state;
 	Page		metapage;
@@ -449,6 +480,18 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 	state->readonly = readonly;
 	state->heapallindexed = heapallindexed;
 	state->rootdescend = rootdescend;
+	state->checkunique = checkunique;
+	state->snapshot = InvalidSnapshot;
+	/*
+	 * We need a snapshot it to check uniqueness of the index
+	 * For better performance, take it once per index check.
+	 */
+	if (state->checkunique)
+	{
+		state->indexinfo = BuildIndexInfo(state->rel);
+		if (state->indexinfo->ii_Unique)
+			state->snapshot = RegisterSnapshot(GetTransactionSnapshot());
+	}
 
 	if (state->heapallindexed)
 	{
@@ -632,7 +675,16 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 	}
 
 	/* Be tidy: */
+	if (state->snapshot != InvalidSnapshot)
+		UnregisterSnapshot(state->snapshot);
 	MemoryContextDelete(state->targetcontext);
+
+	if (errflag)
+		ereport(ERROR,
+				(errcode(ERRCODE_INDEX_CORRUPTED),
+				errmsg("index \"%s\" is corrupted. There are tuples violating UNIQUE constraint",
+						RelationGetRelationName(state->rel)),
+				errdetail_internal("Details are in the previous log messages under WARNING priority")));
 }
 
 /*
@@ -1006,6 +1058,149 @@ bt_recheck_sibling_links(BtreeCheckState *state,
 								btpo_prev_from_target)));
 }
 
+/* Check visibility of the table entry referenced from nbtree index */
+static bool heap_entry_is_visible(BtreeCheckState *state, ItemPointer tid)
+{
+	bool tid_visible;
+
+	TupleTableSlot *slot = table_slot_create(state->heaprel, NULL);
+	tid_visible = table_tuple_fetch_row_version(state->heaprel,
+							  tid, state->snapshot, slot);
+	if (slot != NULL)
+		ExecDropSingleTupleTableSlot(slot);
+
+	return tid_visible;
+}
+
+/*
+ * Prepare and print error message for unique constrain violation in the btree
+ * index under WARNING level and set flag to report ERROR at the end of check
+ */
+static void bt_report_duplicate(BtreeCheckState *state,
+				 ItemPointer tid, BlockNumber block, OffsetNumber offset,
+				 int posting,
+				 ItemPointer nexttid, BlockNumber nblock, OffsetNumber noffset,
+				 int nposting)
+{
+	char	   	*htid,
+				*nhtid,
+				*itid,
+				*nitid = "",
+				*pposting = "",
+				*pnposting = "";
+
+	errflag = true;
+	htid = psprintf("tid=(%u,%u)",
+					ItemPointerGetBlockNumberNoCheck(tid),
+					ItemPointerGetOffsetNumberNoCheck(tid));
+	nhtid = psprintf("tid=(%u,%u)",
+					ItemPointerGetBlockNumberNoCheck(nexttid),
+					ItemPointerGetOffsetNumberNoCheck(nexttid));
+	itid = psprintf("tid=(%u,%u)", block, offset);
+
+	if (nblock != block || noffset != offset)
+		nitid = psprintf(" tid=(%u,%u)", nblock, noffset);
+
+	if (posting >= 0)
+		pposting = psprintf(" posting %u", posting);
+
+	if (nposting >= 0)
+		pnposting = psprintf(" posting %u", nposting);
+
+		ereport(WARNING,
+			(errcode(ERRCODE_INDEX_CORRUPTED),
+			errmsg("index uniqueness is violated for index \"%s\": "
+					"Index %s%s and%s%s "
+					"(point to heap %s and %s).",
+					RelationGetRelationName(state->rel),
+					itid, pposting, nitid, pnposting, htid, nhtid)));
+}
+
+/* Check if current nbtree leaf entry complies with UNIQUE constraint */
+static void bt_entry_unique_check(BtreeCheckState *state, IndexTuple itup,
+		BlockNumber targetblock, OffsetNumber offset, int *lVis_i, ItemPointer *lVis_tid,
+		OffsetNumber *lVis_offset, BlockNumber *lVis_block)
+{
+	ItemPointer tid;
+	bool has_visible_entry = false;
+
+	/*
+	 * Current tuple has posting list. If TID of any posting list entry is
+	 * visible, and lVis_tid is already valid report duplicate.
+	 */
+	if (BTreeTupleIsPosting(itup))
+	{
+		for (int i = 0; i < BTreeTupleGetNPosting(itup); i++)
+		{
+			tid = BTreeTupleGetPostingN(itup, i);
+			if (heap_entry_is_visible(state, tid))
+			{
+				has_visible_entry = true;
+				if (ItemPointerIsValid (*lVis_tid))
+				{
+					bt_report_duplicate(state,
+											*lVis_tid, *lVis_block,
+											*lVis_offset, *lVis_i,
+											tid, targetblock,
+											offset, i);
+				}
+				/*
+				 * Prevent double reporting unique violation between the posting
+				 * list entries of a first tuple on the page after cross-page check.
+				 */
+				if (*lVis_block != targetblock && ItemPointerIsValid (*lVis_tid))
+					return;
+
+				*lVis_i = i;
+				*lVis_tid = tid;
+				*lVis_offset = offset;
+				*lVis_block = targetblock;
+			}
+		}
+	}
+
+	/*
+	 * Current tuple has no posting list.
+	 * If TID is visible, save info about it for next comparisons in the loop in
+	 * bt_page_check(). If also lVis_tid is already valid, report duplicate.
+	 */
+	else
+	{
+		tid = BTreeTupleGetHeapTID(itup);
+		if (heap_entry_is_visible(state, tid))
+		{
+			has_visible_entry = true;
+			if (ItemPointerIsValid (*lVis_tid))
+			{
+				bt_report_duplicate(state,
+											*lVis_tid, *lVis_block,
+											*lVis_offset, *lVis_i,
+											tid, targetblock,
+											offset, -1);
+			}
+			*lVis_i = -1;
+			*lVis_tid = tid;
+			*lVis_offset = offset;
+			*lVis_block = targetblock;
+		}
+	}
+
+	if (!has_visible_entry && *lVis_block != InvalidBlockNumber &&
+									   *lVis_block != targetblock)
+		ereport(WARNING,
+			(errcode(ERRCODE_INDEX_CORRUPTED),
+			errmsg("index uniqueness may be violated for index \"%s\": "
+					"Index tid=(%u,%u) doesn't have visible heap tids and key "
+					"is equal to the tid=(%u,%u)%s (points to heap tid=(%u,%u)). "
+					"Cross-page unique constraint violation can be missed. "
+					"Vacuum the table and repeat the check.",
+					RelationGetRelationName(state->rel),
+					targetblock, offset,
+					*lVis_block, *lVis_offset, psprintf(" posting %u", *lVis_i),
+					ItemPointerGetBlockNumberNoCheck(*lVis_tid),
+					ItemPointerGetOffsetNumberNoCheck(*lVis_tid))));
+}
+
 /*
  * Function performs the following checks on target page, or pages ancillary to
  * target page:
@@ -1026,6 +1221,9 @@ bt_recheck_sibling_links(BtreeCheckState *state,
  * - Various checks on the structure of tuples themselves.  For example, check
  *	 that non-pivot tuples have no truncated attributes.
  *
+ * - For index with unique constraint check that only one of table entries for
+ *   equal keys is visible.
+ *
  * Furthermore, when state passed shows ShareLock held, function also checks:
  *
  * - That all child pages respect strict lower bound from parent's pivot
@@ -1047,6 +1245,13 @@ bt_target_page_check(BtreeCheckState *state)
 	OffsetNumber offset;
 	OffsetNumber max;
 	BTPageOpaque topaque;
+	/* last visible entry info for checking indexes with unique constraint */
+	int			 lVis_i = -1; /* the position of last visible item for posting
+							   * tuple. for non-posting tuple (-1)
+							   */
+	ItemPointer	 lVis_tid = NULL;
+	BlockNumber	 lVis_block = InvalidBlockNumber;
+	OffsetNumber lVis_offset = InvalidOffsetNumber;
 
 	topaque = (BTPageOpaque) PageGetSpecialPointer(state->target);
 	max = PageGetMaxOffsetNumber(state->target);
@@ -1438,6 +1643,39 @@ bt_target_page_check(BtreeCheckState *state)
 										LSN_FORMAT_ARGS(state->targetlsn))));
 		}
 
+		/*
+		 * If the index is unique, verify entries uniqueness by checking
+		 * heap tuples visibility.
+		 */
+		if (state->checkunique && state->indexinfo->ii_Unique && P_ISLEAF(topaque))
+			bt_entry_unique_check(state, itup, state->targetblock, offset,
+					&lVis_i, &lVis_tid, &lVis_offset, &lVis_block);
+
+		if (state->checkunique && state->indexinfo->ii_Unique && P_ISLEAF(topaque) &&
+				 OffsetNumberNext(offset) <= max)
+		{
+			/* Save current scankey tid */
+			scantid = skey->scantid;
+			/* Invalidate scankey tid to make _bt_compare compare only keys
+			 * in the item to report equality even if heap TIDs are different
+			 */
+			skey->scantid = NULL;
+
+			/*
+			 * If next key tuple is different, invalidate last visible entry
+			 * data (whole index tuple or last posting in index tuple).
+			 */
+			if (_bt_compare(state->rel, skey, state->target,
+						OffsetNumberNext(offset)) != 0)
+			{
+				lVis_i = -1;
+				lVis_tid = NULL;
+				lVis_block = InvalidBlockNumber;
+				lVis_offset = InvalidOffsetNumber;
+			}
+			skey->scantid = scantid; /* Restore saved scan key state */
+		}
+
 		/*
 		 * * Last item check *
 		 *
@@ -1455,12 +1693,14 @@ bt_target_page_check(BtreeCheckState *state)
 		 * available from sibling for various reasons, though (e.g., target is
 		 * the rightmost page on level).
 		 */
-		else if (offset == max)
+		if (offset == max)
 		{
 			BTScanInsert rightkey;
+			/* first offset on a right index page (log only) */
+			OffsetNumber rightfirstoffset = InvalidOffsetNumber;
 
 			/* Get item in next/right page */
-			rightkey = bt_right_page_check_scankey(state);
+			rightkey = bt_right_page_check_scankey(state, &rightfirstoffset);
 
 			if (rightkey &&
 				!invariant_g_offset(state, rightkey, max))
@@ -1494,6 +1734,44 @@ bt_target_page_check(BtreeCheckState *state)
 											state->targetblock, offset,
 											LSN_FORMAT_ARGS(state->targetlsn))));
 			}
+
+			/*
+			 * If index has unique constraint check that not more than one found
+			 * equal items is visible.
+			 */
+			if (state->checkunique && state->indexinfo->ii_Unique &&
+					rightkey && P_ISLEAF(topaque))
+			{
+				elog(DEBUG2, "check cross page unique condition");
+
+				/*
+				 * Make _bt_compare compare only index keys without heap TIDs.
+				 * rightkey->scantid is modified destructively but it is ok
+				 * for it is not used later
+				 */
+				rightkey->scantid = NULL;
+
+				/* First key on next page is same */
+				if (_bt_compare(state->rel, rightkey, state->target, max) == 0)
+				{
+					elog(DEBUG2, "cross page equal keys");
+					state->target = palloc_btree_page(state,
+													  state->targetblock + 1);
+					topaque = (BTPageOpaque) PageGetSpecialPointer(state->target);
+
+					if (P_IGNORE(topaque) || !P_ISLEAF(topaque))
+							break;
+
+					itemid = PageGetItemIdCareful(state, state->targetblock + 1,
+												  state->target,
+												  rightfirstoffset);
+					itup = (IndexTuple) PageGetItem(state->target, itemid);
+
+					bt_entry_unique_check(state, itup, state->targetblock + 1, rightfirstoffset,
+									&lVis_i, &lVis_tid, &lVis_offset,
+									&lVis_block);
+				}
+			}
 		}
 
 		/*
@@ -1539,9 +1817,11 @@ bt_target_page_check(BtreeCheckState *state)
  *
  * Note that !readonly callers must reverify that target page has not
  * been concurrently deleted.
+ *
+ * Save rightfirstdataoffset for detailed error message.
  */
 static BTScanInsert
-bt_right_page_check_scankey(BtreeCheckState *state)
+bt_right_page_check_scankey(BtreeCheckState *state, OffsetNumber *rightfirstoffset)
 {
 	BTPageOpaque opaque;
 	ItemId		rightitem;
@@ -1704,6 +1984,7 @@ bt_right_page_check_scankey(BtreeCheckState *state)
 		/* Return first data item (if any) */
 		rightitem = PageGetItemIdCareful(state, targetnext, rightpage,
 										 P_FIRSTDATAKEY(opaque));
+		*rightfirstoffset = P_FIRSTDATAKEY(opaque);
 	}
 	else if (!P_ISLEAF(opaque) &&
 			 nline >= OffsetNumberNext(P_FIRSTDATAKEY(opaque)))
-- 
2.28.0

