From 68a7aabde8723185efc37fb1059ac694d21873f0 Mon Sep 17 00:00:00 2001
From: Peter Smith <peter.b.smith@fujitsu.com>
Date: Mon, 2 Feb 2026 15:26:43 +1100
Subject: [PATCH v20260202] VCI tests

---
 contrib/vci/expected/bugs.out | 149 ++++++++++++++++++++++++++++++++++
 contrib/vci/expected/vci.out  | 128 +++++++++++++++++++++++++++++
 contrib/vci/sql/bugs.sql      |  87 ++++++++++++++++++++
 contrib/vci/sql/vci.sql       | 108 ++++++++++++++++++++++++
 contrib/vci/vci.conf          |   8 ++
 5 files changed, 480 insertions(+)
 create mode 100644 contrib/vci/expected/bugs.out
 create mode 100644 contrib/vci/expected/vci.out
 create mode 100644 contrib/vci/sql/bugs.sql
 create mode 100644 contrib/vci/sql/vci.sql
 create mode 100644 contrib/vci/vci.conf

diff --git a/contrib/vci/expected/bugs.out b/contrib/vci/expected/bugs.out
new file mode 100644
index 00000000000..be0f567ec57
--- /dev/null
+++ b/contrib/vci/expected/bugs.out
@@ -0,0 +1,149 @@
+-- Bug reported by Japin Li that caused a vci_beginscan PANIC
+-- See https://www.postgresql.org/message-id/ME0P300MB04457E24CA8965F008FB2CDBB648A%40ME0P300MB0445.AUSP300.PROD.OUTLOOK.COM
+CREATE TABLE t1 (id int, info text);
+CREATE INDEX t1_id_idx ON t1 USING vci (id);
+INSERT INTO t1 SELECT id, md5(id::text) FROM generate_series(1, 1000) id;
+SET enable_seqscan TO off;
+SELECT * FROM t1 WHERE id = 100;
+ id  |               info               
+-----+----------------------------------
+ 100 | f899139df5e1059396431415e770c6dd
+(1 row)
+
+DROP TABLE t1;
+-- Bug reported by Japin Li that VACUUM caused a TRAP
+-- See https://www.postgresql.org/message-id/SY8P300MB0442BEC3F5CF432F0121ACC4B642A%40SY8P300MB0442.AUSP300.PROD.OUTLOOK.COM
+CREATE TABLE t2 (id int, info text) WITH (autovacuum_enabled = off);
+CREATE INDEX t2_id_idx ON t2 USING vci (id);
+INSERT INTO t2 SELECT id, 'test' || id FROM generate_series(1, 1000) id;
+DELETE FROM t2 WHERE id % 10 = 0;
+VACUUM t2;
+DROP TABLE t2;
+-- Bug reported by Japin Li that caused a Segmentation Violation attempting to REFRESH a VCI internal relation
+-- See https://www.postgresql.org/message-id/ME0P300MB0445EBA04D6947DD717074DFB65CA%40ME0P300MB0445.AUSP300.PROD.OUTLOOK.COM
+CREATE TABLE t3 (id int, info text);
+CREATE INDEX ON t3 USING vci (id);
+SELECT relname FROM pg_class WHERE relname ~ '^pg_vci_*' LIMIT 1 \gset
+SELECT * FROM :relname;
+ bindata 
+---------
+ 
+(1 row)
+
+\d+ :relname
+                  Materialized view "public.pg_vci_0000016582_00000_d"
+ Column  | Type  | Collation | Nullable | Default | Storage | Stats target | Description 
+---------+-------+-----------+----------+---------+---------+--------------+-------------
+ bindata | bytea |           |          |         | plain   |              | 
+View definition:
+
+
+REFRESH MATERIALIZED VIEW :relname;
+ERROR:  extension "vci" prohibits this operation on view "pg_vci_0000016582_00000_d"
+DROP TABLE t3;
+-- Bug missing logic. Ensure VCI internal relations get removed when the TABLE is dropped.
+CREATE TABLE t4 (id int, info text);
+CREATE INDEX t4_idx ON t4 USING vci (id);
+SELECT relname FROM pg_class WHERE relname ~ '^pg_vci_*' ORDER BY relname;
+          relname          
+---------------------------
+ pg_vci_0000016602_00000_d
+ pg_vci_0000016602_00000_m
+ pg_vci_0000016602_65526_d
+ pg_vci_0000016602_65527_d
+ pg_vci_0000016602_65530_0
+ pg_vci_0000016602_65530_1
+ pg_vci_0000016602_65531_d
+ pg_vci_0000016602_65531_m
+ pg_vci_0000016602_65533_d
+ pg_vci_0000016602_65533_m
+ pg_vci_0000016602_65534_d
+ pg_vci_0000016602_65534_m
+ pg_vci_0000016602_65535_d
+ pg_vci_0000016602_65535_m
+(14 rows)
+
+DROP TABLE t4;
+SELECT relname FROM pg_class WHERE relname ~ '^pg_vci_*';
+ relname 
+---------
+(0 rows)
+
+-- Bug reported by Japin Li that REINDEX forgot to restore security context
+-- See https://www.postgresql.org/message-id/ME0P300MB0445827B6E9CC04E0FAEE446B624A%40ME0P300MB0445.AUSP300.PROD.OUTLOOK.COM
+CREATE TABLE t5 (id int, info text);
+CREATE INDEX t5_idx ON t5 USING vci (id);
+REINDEX TABLE t5;
+REINDEX TABLE t5;
+DROP TABLE t5;
+-- InstrStartNode bug:
+-- Unexpected error "InstrStartNode called twice in a row"
+-- NOTE -Change the EXPLAIN below to use TIMING TRUE reproduce the bug,
+-- otherwise leave it FALSE so timings don't cause 'make check' to fail.
+CREATE TABLE t6(id int, info text);
+CREATE INDEX t6_id_idx ON t6 USING vci (id);
+INSERT INTO t6 SELECT id, 'info' || id FROM generate_series(1, 500) id;
+ANALYZE t6;
+EXPLAIN (ANALYZE, COSTS FALSE, BUFFERS FALSE, TIMING FALSE, SUMMARY FALSE) SELECT max(id) FROM t6;
+                                    QUERY PLAN                                    
+----------------------------------------------------------------------------------
+ Custom Scan (VCI Aggregate) (actual rows=1.00 loops=1)
+   Disabled: true
+   ->  Custom Scan (VCI Scan) using t6_id_idx on t6 (actual rows=1000.00 loops=1)
+         Disabled: true
+(4 rows)
+
+DROP TABLE t6;
+-- Bug reported by Timur: VCI Sort does not work on top of a non-VCI join
+-- See https://www.postgresql.org/message-id/a27f68845af78d404459fcab940bfae2ec7755e5.camel%40postgrespro.ru
+CREATE TABLE main (id BIGSERIAL PRIMARY KEY);
+CREATE TABLE secondary (id BIGSERIAL PRIMARY KEY, main_id BIGINT REFERENCES main (id), val INTEGER);
+CREATE INDEX main_vci ON main USING vci (id);
+CREATE INDEX sec_vci ON secondary USING vci (id, main_id, val);
+-- Check VCI Sort is not put on top of non-VCI join
+EXPLAIN (ANALYZE, COSTS FALSE, BUFFERS FALSE, TIMING FALSE, SUMMARY FALSE)
+SELECT *
+  FROM main m
+  JOIN secondary s
+    ON m.id = s.main_id
+  WHERE s.val in (
+    SELECT MAX(val)
+      FROM secondary s2
+      WHERE s2.main_id = m.id)
+  ORDER BY s.val;
+                                            QUERY PLAN                                            
+--------------------------------------------------------------------------------------------------
+ Sort (actual rows=0.00 loops=1)
+   Sort Key: s.val
+   Sort Method: quicksort  Memory: 25kB
+   ->  Nested Loop (actual rows=0.00 loops=1)
+         Join Filter: (s.val = (max(s2.val)))
+         ->  Hash Join (actual rows=0.00 loops=1)
+               Hash Cond: (s.main_id = m.id)
+               ->  Custom Scan (VCI Scan) using sec_vci on secondary s (actual rows=0.00 loops=1)
+                     Disabled: true
+               ->  Hash (never executed)
+                     ->  Index Only Scan using main_pkey on main m (never executed)
+                           Heap Fetches: 0
+                           Index Searches: 0
+         ->  Custom Scan (VCI Aggregate) (never executed)
+               Disabled: true
+               ->  Custom Scan (VCI Scan) using sec_vci on secondary s2 (never executed)
+                     Disabled: true
+                     Filter: (main_id = m.id)
+(18 rows)
+
+-- Check VCI Sort is used if suitable
+EXPLAIN (ANALYZE, COSTS FALSE, BUFFERS FALSE, TIMING FALSE, SUMMARY FALSE)
+SELECT * FROM secondary s ORDER BY s.val;
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
+ Custom Scan (VCI Sort) (actual rows=0.00 loops=1)
+   Sort Key: val
+   Sort Method: quicksort  Memory: 25kB
+   ->  Custom Scan (VCI Scan) using sec_vci on secondary s (actual rows=0.00 loops=1)
+         Disabled: true
+(5 rows)
+
+DROP TABLE secondary;
+DROP TABLE main;
diff --git a/contrib/vci/expected/vci.out b/contrib/vci/expected/vci.out
new file mode 100644
index 00000000000..d5e55b0b719
--- /dev/null
+++ b/contrib/vci/expected/vci.out
@@ -0,0 +1,128 @@
+CREATE EXTENSION vci;
+SELECT amname, amhandler, amtype FROM pg_am WHERE amname = 'vci';
+ amname |  amhandler  | amtype 
+--------+-------------+--------
+ vci    | vci_handler | i
+(1 row)
+
+SET vci.table_rows_threshold = 0;
+CREATE TABLE testtable (
+       key  int,
+       cond int,
+       c01a bool,
+       c01b bool,
+       c02 bytea,
+       c03 "char",
+       c05 int8,
+       c06 int2,
+       c07 int4,
+       c08 text,
+       c09 float4,
+       c10 float8,
+       c13 interval,
+       c15 money,
+       c16 bpchar,
+       c17 varchar,
+       c18 date,
+       c19 time,
+       c20 timetz,
+       c21 timestamp,
+       c22 timestamptz,
+       c23a bit,
+       c23b bit,
+       c24a varbit,
+       c24b varbit,
+       c25 numeric,
+       c26 uuid);
+-- Input data
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26)
+SELECT
+        i % 10, -- key int
+        i % 21.000000000000, -- cond int
+        (i % 3.000000000000) > 0,  -- c01a bool
+        (i % 11.000000000000) > 0, -- c01b bool
+        CAST(to_char((i % 1001.000000000000), '9999') AS bytea), -- c02 bytea
+        CAST(CAST((i % 249.000000000000) AS character varying) AS "char"), -- c03 "char"
+        i % 651.000000000000 + i % 350, -- c05 int8
+        i % 1001.000000000000, -- c06 in2
+        i % 1001.000000000000, -- c07 int4
+        i % 1001.000000000000, -- c08 text
+        i % 1001.000000000000, -- c09 float4
+        i % 1001.000000000000, -- c10 float8
+        i % 1001.000000000000 * interval '1h', -- c13 interval
+        (i % 1001.000000000000)::integer::money, -- c15 money
+        i % 1001.000000000000, -- c16 bpchar
+        i % 1001.000000000000, -- c17 varchar
+        date '2015-12-21' +  1 % 1001.000000000000 * interval '1d', -- c18 date
+        TIMESTAMP '2015-12-21' + (i % 1001.000000000000) * interval '1h', -- c19 time
+        TIMESTAMP WITH TIME ZONE '2015-12-21 10:00:00+09' + (i % 1001.000000000000) * interval '1h', -- c20 timetz
+        TIMESTAMP '2015-12-21' + (i % 1001.000000000000) * interval '1h', -- c21 timestamp
+        TIMESTAMP WITH TIME ZONE '2015-12-21 10:00:00+09' + (i % 1001.000000000000) * interval '1h', -- c22 timestamptz
+        ((i % 3.000000000000)>0)::integer::bit(1),  -- c23a bit
+        ((i % 11.000000000000)>0)::integer::bit(1), -- c23b bit
+        (i % 1001.000000000000)::integer::bit(10),     -- c24a varbit
+        (i % 999.000000000000)::integer::bit(10), -- c24b varbit
+        i % 1001.000000000000, -- c25 numeric
+        'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid -- c26 uuid
+FROM generate_series(1, 10000) AS i;
+-- Testcase: insert with some NULL values
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26) SELECT i, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM generate_series(1, 9) AS i;
+-- Testcase: insert with some special values
+INSERT INTO testtable (key, cond, c09, c10, c18, c21, c22) VALUES (7, 1, 'Infinity', 'Infinity', 'Infinity', 'infinity', 'infinity');
+INSERT INTO testtable (key, cond, c09, c10, c18, c21, c22) VALUES (8, 1, 'Infinity', 'Infinity', 'Infinity', 'infinity', 'infinity');
+INSERT INTO testtable (key, cond, c09, c10, c18, c21, c22) VALUES (8, 1, '-Infinity', '-Infinity', '-Infinity', '-infinity', '-infinity');
+INSERT INTO testtable (key, cond, c09, c10) VALUES (9, 1, 'NaN', 'NaN');
+-- Testcase: NaN only
+INSERT INTO testtable (key, cond, c09, c10) VALUES (10, 1, 'NaN', 'NaN');
+INSERT INTO testtable (key, cond, c09, c10) VALUES (10, 1, 'NaN', 'NaN');
+INSERT INTO testtable (key, cond, c05)           VALUES (10, 1, 1);
+-- Testcase: Timestamp with timezone
+INSERT INTO testtable (key, cond, c18, c22) VALUES (11, 1, TIMESTAMP WITH TIME ZONE '2004-10-19 01:00:00+01', TIMESTAMP WITH TIME ZONE '2004-10-19 02:00:00+01');
+INSERT INTO testtable (key, cond, c18, c22) VALUES (11, 1, TIMESTAMP WITH TIME ZONE '2004-10-19 02:00:00+02', TIMESTAMP WITH TIME ZONE '2004-10-19 02:00:00+02');
+INSERT INTO testtable (key, cond, c18, c22) VALUES (11, 1, TIMESTAMP WITH TIME ZONE '2004-10-19 01:00:00+02', TIMESTAMP WITH TIME ZONE '2004-10-19 01:00:00+02');
+INSERT INTO testtable (key, cond, c05) VALUES (11, 1, 1);
+-- Testcase: few attributes are valid
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26) SELECT 98, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM generate_series(1, 50) AS i;
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25) VALUES (98, 1, true, true, 'text', 1::char, 1, 1, 1, 'text', 1.0, 1.0, 1, 'text', 'text', timestamp '2015-12-22', timestamp '2015-12-22', timestamp with time zone '2015-12-22 10:23:54+02', timestamp '2015-12-22', timestamp with time zone '2015-12-22 10:23:54+02', 1::bit(1), 1::bit(1), 1::bit(10), 1::bit(10), 1);
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26) SELECT 98, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM generate_series(1, 50) AS i;
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26) SELECT 99, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM generate_series(1, 100) AS i;
+-- Create an index which uses VCI index access method
+CREATE INDEX testindex ON testtable USING vci (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26);
+-- We expect VCI plans are chosen here
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF, BUFFERS OFF)
+SELECT key, count(*) AS count_star, count(c05) AS count_c05 FROM testtable WHERE NOT cond = 0 GROUP BY key ORDER BY key;
+                                           QUERY PLAN                                           
+------------------------------------------------------------------------------------------------
+ Sort (actual rows=14.00 loops=1)
+   Sort Key: key
+   Sort Method: quicksort  Memory: 25kB
+   ->  Custom Scan (VCI HashAggregate) (actual rows=14.00 loops=1)
+         Group Key: key
+         ->  Custom Scan (VCI Scan) using testindex on testtable (actual rows=10221.00 loops=1)
+               Filter: (cond <> 0)
+               Rows Removed by Filter: 476
+(8 rows)
+
+-- Confirms the aggregation can work. The first column indicates whether the
+-- VCI scan was used.
+SELECT vci_runs_in_query() AS vci_runs_in_query, key, count(*) AS count_star, count(c05) AS count_c05 FROM testtable WHERE NOT cond = 0 GROUP BY key ORDER BY key;
+ vci_runs_in_query | key | count_star | count_c05 
+-------------------+-----+------------+-----------
+ t                 |   0 |        953 |       953
+ t                 |   1 |        953 |       952
+ t                 |   2 |        953 |       952
+ t                 |   3 |        953 |       952
+ t                 |   4 |        953 |       952
+ t                 |   5 |        953 |       952
+ t                 |   6 |        953 |       952
+ t                 |   7 |        955 |       953
+ t                 |   8 |        956 |       953
+ t                 |   9 |        955 |       953
+ t                 |  10 |          3 |         1
+ t                 |  11 |          4 |         1
+ t                 |  98 |        101 |         1
+ t                 |  99 |        100 |         0
+(14 rows)
+
+-- cleanup
+DROP TABLE testtable;
diff --git a/contrib/vci/sql/bugs.sql b/contrib/vci/sql/bugs.sql
new file mode 100644
index 00000000000..6b77938ed8a
--- /dev/null
+++ b/contrib/vci/sql/bugs.sql
@@ -0,0 +1,87 @@
+-- Bug reported by Japin Li that caused a vci_beginscan PANIC
+-- See https://www.postgresql.org/message-id/ME0P300MB04457E24CA8965F008FB2CDBB648A%40ME0P300MB0445.AUSP300.PROD.OUTLOOK.COM
+
+CREATE TABLE t1 (id int, info text);
+CREATE INDEX t1_id_idx ON t1 USING vci (id);
+INSERT INTO t1 SELECT id, md5(id::text) FROM generate_series(1, 1000) id;
+SET enable_seqscan TO off;
+SELECT * FROM t1 WHERE id = 100;
+DROP TABLE t1;
+
+-- Bug reported by Japin Li that VACUUM caused a TRAP
+-- See https://www.postgresql.org/message-id/SY8P300MB0442BEC3F5CF432F0121ACC4B642A%40SY8P300MB0442.AUSP300.PROD.OUTLOOK.COM
+
+CREATE TABLE t2 (id int, info text) WITH (autovacuum_enabled = off);
+CREATE INDEX t2_id_idx ON t2 USING vci (id);
+INSERT INTO t2 SELECT id, 'test' || id FROM generate_series(1, 1000) id;
+DELETE FROM t2 WHERE id % 10 = 0;
+VACUUM t2;
+DROP TABLE t2;
+
+-- Bug reported by Japin Li that caused a Segmentation Violation attempting to REFRESH a VCI internal relation
+-- See https://www.postgresql.org/message-id/ME0P300MB0445EBA04D6947DD717074DFB65CA%40ME0P300MB0445.AUSP300.PROD.OUTLOOK.COM
+
+CREATE TABLE t3 (id int, info text);
+CREATE INDEX ON t3 USING vci (id);
+SELECT relname FROM pg_class WHERE relname ~ '^pg_vci_*' LIMIT 1 \gset
+SELECT * FROM :relname;
+\d+ :relname
+REFRESH MATERIALIZED VIEW :relname;
+DROP TABLE t3;
+
+-- Bug missing logic. Ensure VCI internal relations get removed when the TABLE is dropped.
+
+CREATE TABLE t4 (id int, info text);
+CREATE INDEX t4_idx ON t4 USING vci (id);
+SELECT relname FROM pg_class WHERE relname ~ '^pg_vci_*' ORDER BY relname;
+DROP TABLE t4;
+SELECT relname FROM pg_class WHERE relname ~ '^pg_vci_*';
+
+-- Bug reported by Japin Li that REINDEX forgot to restore security context
+-- See https://www.postgresql.org/message-id/ME0P300MB0445827B6E9CC04E0FAEE446B624A%40ME0P300MB0445.AUSP300.PROD.OUTLOOK.COM
+
+CREATE TABLE t5 (id int, info text);
+CREATE INDEX t5_idx ON t5 USING vci (id);
+REINDEX TABLE t5;
+REINDEX TABLE t5;
+DROP TABLE t5;
+
+-- InstrStartNode bug:
+-- Unexpected error "InstrStartNode called twice in a row"
+-- NOTE -Change the EXPLAIN below to use TIMING TRUE reproduce the bug,
+-- otherwise leave it FALSE so timings don't cause 'make check' to fail.
+
+CREATE TABLE t6(id int, info text);
+CREATE INDEX t6_id_idx ON t6 USING vci (id);
+INSERT INTO t6 SELECT id, 'info' || id FROM generate_series(1, 500) id;
+ANALYZE t6;
+EXPLAIN (ANALYZE, COSTS FALSE, BUFFERS FALSE, TIMING FALSE, SUMMARY FALSE) SELECT max(id) FROM t6;
+DROP TABLE t6;
+
+-- Bug reported by Timur: VCI Sort does not work on top of a non-VCI join
+-- See https://www.postgresql.org/message-id/a27f68845af78d404459fcab940bfae2ec7755e5.camel%40postgrespro.ru
+
+CREATE TABLE main (id BIGSERIAL PRIMARY KEY);
+CREATE TABLE secondary (id BIGSERIAL PRIMARY KEY, main_id BIGINT REFERENCES main (id), val INTEGER);
+
+CREATE INDEX main_vci ON main USING vci (id);
+CREATE INDEX sec_vci ON secondary USING vci (id, main_id, val);
+
+-- Check VCI Sort is not put on top of non-VCI join
+EXPLAIN (ANALYZE, COSTS FALSE, BUFFERS FALSE, TIMING FALSE, SUMMARY FALSE)
+SELECT *
+  FROM main m
+  JOIN secondary s
+    ON m.id = s.main_id
+  WHERE s.val in (
+    SELECT MAX(val)
+      FROM secondary s2
+      WHERE s2.main_id = m.id)
+  ORDER BY s.val;
+
+-- Check VCI Sort is used if suitable
+EXPLAIN (ANALYZE, COSTS FALSE, BUFFERS FALSE, TIMING FALSE, SUMMARY FALSE)
+SELECT * FROM secondary s ORDER BY s.val;
+
+DROP TABLE secondary;
+DROP TABLE main;
diff --git a/contrib/vci/sql/vci.sql b/contrib/vci/sql/vci.sql
new file mode 100644
index 00000000000..3fec0b396af
--- /dev/null
+++ b/contrib/vci/sql/vci.sql
@@ -0,0 +1,108 @@
+CREATE EXTENSION vci;
+SELECT amname, amhandler, amtype FROM pg_am WHERE amname = 'vci';
+
+SET vci.table_rows_threshold = 0;
+
+CREATE TABLE testtable (
+       key  int,
+       cond int,
+       c01a bool,
+       c01b bool,
+       c02 bytea,
+       c03 "char",
+       c05 int8,
+       c06 int2,
+       c07 int4,
+       c08 text,
+       c09 float4,
+       c10 float8,
+       c13 interval,
+       c15 money,
+       c16 bpchar,
+       c17 varchar,
+       c18 date,
+       c19 time,
+       c20 timetz,
+       c21 timestamp,
+       c22 timestamptz,
+       c23a bit,
+       c23b bit,
+       c24a varbit,
+       c24b varbit,
+       c25 numeric,
+       c26 uuid);
+
+-- Input data
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26)
+SELECT
+        i % 10, -- key int
+        i % 21.000000000000, -- cond int
+        (i % 3.000000000000) > 0,  -- c01a bool
+        (i % 11.000000000000) > 0, -- c01b bool
+        CAST(to_char((i % 1001.000000000000), '9999') AS bytea), -- c02 bytea
+        CAST(CAST((i % 249.000000000000) AS character varying) AS "char"), -- c03 "char"
+        i % 651.000000000000 + i % 350, -- c05 int8
+        i % 1001.000000000000, -- c06 in2
+        i % 1001.000000000000, -- c07 int4
+        i % 1001.000000000000, -- c08 text
+        i % 1001.000000000000, -- c09 float4
+        i % 1001.000000000000, -- c10 float8
+        i % 1001.000000000000 * interval '1h', -- c13 interval
+        (i % 1001.000000000000)::integer::money, -- c15 money
+        i % 1001.000000000000, -- c16 bpchar
+        i % 1001.000000000000, -- c17 varchar
+        date '2015-12-21' +  1 % 1001.000000000000 * interval '1d', -- c18 date
+        TIMESTAMP '2015-12-21' + (i % 1001.000000000000) * interval '1h', -- c19 time
+        TIMESTAMP WITH TIME ZONE '2015-12-21 10:00:00+09' + (i % 1001.000000000000) * interval '1h', -- c20 timetz
+        TIMESTAMP '2015-12-21' + (i % 1001.000000000000) * interval '1h', -- c21 timestamp
+        TIMESTAMP WITH TIME ZONE '2015-12-21 10:00:00+09' + (i % 1001.000000000000) * interval '1h', -- c22 timestamptz
+        ((i % 3.000000000000)>0)::integer::bit(1),  -- c23a bit
+        ((i % 11.000000000000)>0)::integer::bit(1), -- c23b bit
+        (i % 1001.000000000000)::integer::bit(10),     -- c24a varbit
+        (i % 999.000000000000)::integer::bit(10), -- c24b varbit
+        i % 1001.000000000000, -- c25 numeric
+        'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid -- c26 uuid
+FROM generate_series(1, 10000) AS i;
+
+-- Testcase: insert with some NULL values
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26) SELECT i, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM generate_series(1, 9) AS i;
+
+-- Testcase: insert with some special values
+INSERT INTO testtable (key, cond, c09, c10, c18, c21, c22) VALUES (7, 1, 'Infinity', 'Infinity', 'Infinity', 'infinity', 'infinity');
+INSERT INTO testtable (key, cond, c09, c10, c18, c21, c22) VALUES (8, 1, 'Infinity', 'Infinity', 'Infinity', 'infinity', 'infinity');
+INSERT INTO testtable (key, cond, c09, c10, c18, c21, c22) VALUES (8, 1, '-Infinity', '-Infinity', '-Infinity', '-infinity', '-infinity');
+INSERT INTO testtable (key, cond, c09, c10) VALUES (9, 1, 'NaN', 'NaN');
+
+-- Testcase: NaN only
+INSERT INTO testtable (key, cond, c09, c10) VALUES (10, 1, 'NaN', 'NaN');
+INSERT INTO testtable (key, cond, c09, c10) VALUES (10, 1, 'NaN', 'NaN');
+INSERT INTO testtable (key, cond, c05)           VALUES (10, 1, 1);
+
+-- Testcase: Timestamp with timezone
+INSERT INTO testtable (key, cond, c18, c22) VALUES (11, 1, TIMESTAMP WITH TIME ZONE '2004-10-19 01:00:00+01', TIMESTAMP WITH TIME ZONE '2004-10-19 02:00:00+01');
+INSERT INTO testtable (key, cond, c18, c22) VALUES (11, 1, TIMESTAMP WITH TIME ZONE '2004-10-19 02:00:00+02', TIMESTAMP WITH TIME ZONE '2004-10-19 02:00:00+02');
+INSERT INTO testtable (key, cond, c18, c22) VALUES (11, 1, TIMESTAMP WITH TIME ZONE '2004-10-19 01:00:00+02', TIMESTAMP WITH TIME ZONE '2004-10-19 01:00:00+02');
+INSERT INTO testtable (key, cond, c05) VALUES (11, 1, 1);
+
+-- Testcase: few attributes are valid
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26) SELECT 98, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM generate_series(1, 50) AS i;
+
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25) VALUES (98, 1, true, true, 'text', 1::char, 1, 1, 1, 'text', 1.0, 1.0, 1, 'text', 'text', timestamp '2015-12-22', timestamp '2015-12-22', timestamp with time zone '2015-12-22 10:23:54+02', timestamp '2015-12-22', timestamp with time zone '2015-12-22 10:23:54+02', 1::bit(1), 1::bit(1), 1::bit(10), 1::bit(10), 1);
+
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26) SELECT 98, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM generate_series(1, 50) AS i;
+
+INSERT INTO testtable (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26) SELECT 99, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM generate_series(1, 100) AS i;
+
+-- Create an index which uses VCI index access method
+CREATE INDEX testindex ON testtable USING vci (key, cond, c01a, c01b, c02, c03, c05, c06, c07, c08, c09, c10, c13, c15, c16, c17, c18, c19, c20, c21, c22, c23a, c23b, c24a, c24b, c25, c26);
+
+-- We expect VCI plans are chosen here
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF, BUFFERS OFF)
+SELECT key, count(*) AS count_star, count(c05) AS count_c05 FROM testtable WHERE NOT cond = 0 GROUP BY key ORDER BY key;
+
+-- Confirms the aggregation can work. The first column indicates whether the
+-- VCI scan was used.
+SELECT vci_runs_in_query() AS vci_runs_in_query, key, count(*) AS count_star, count(c05) AS count_c05 FROM testtable WHERE NOT cond = 0 GROUP BY key ORDER BY key;
+
+-- cleanup
+DROP TABLE testtable;
diff --git a/contrib/vci/vci.conf b/contrib/vci/vci.conf
new file mode 100644
index 00000000000..70f597c1ce3
--- /dev/null
+++ b/contrib/vci/vci.conf
@@ -0,0 +1,8 @@
+shared_preload_libraries = 'vci'
+max_worker_processes = 20
+vci.table_rows_threshold = 0
+vci.cost_threshold = 0
+vci.enable_ros_control_daemon = true
+vci.control_naptime = 60s
+log_min_messages = debug2
+autovacuum = off
-- 
2.47.3

