v20251223-0002-VCI-main-part1.patch
application/octet-stream
Filename: v20251223-0002-VCI-main-part1.patch
Type: application/octet-stream
Part: 2
From 907eec874f7612c7e019afe4912ac7f9d14ab4d0 Mon Sep 17 00:00:00 2001
From: Peter Smith <peter.b.smith@fujitsu.com>
Date: Tue, 23 Dec 2025 15:23:53 +1100
Subject: [PATCH v20251223] VCI - main - part1
---
contrib/Makefile | 3 +-
contrib/meson.build | 1 +
contrib/vci/.gitignore | 4 +
contrib/vci/Makefile | 40 ++
contrib/vci/README | 976 ++++++++++++++++++++++++++++++++++++
contrib/vci/include/vci.h | 153 ++++++
contrib/vci/include/vci_utils.h | 238 +++++++++
contrib/vci/meson.build | 67 +++
contrib/vci/utils/Makefile | 20 +
contrib/vci/utils/meson.build | 10 +
contrib/vci/utils/nodes.t | 448 +++++++++++++++++
contrib/vci/utils/vci_symbols.c | 48 ++
contrib/vci/vci--1.0.sql | 76 +++
contrib/vci/vci.control | 5 +
contrib/vci/vci_supported_funcs.c | 851 +++++++++++++++++++++++++++++++
contrib/vci/vci_supported_funcs.sql | 114 +++++
contrib/vci/vci_supported_types.c | 244 +++++++++
17 files changed, 3297 insertions(+), 1 deletion(-)
create mode 100644 contrib/vci/.gitignore
create mode 100644 contrib/vci/Makefile
create mode 100755 contrib/vci/README
create mode 100644 contrib/vci/include/vci.h
create mode 100644 contrib/vci/include/vci_utils.h
create mode 100644 contrib/vci/meson.build
create mode 100644 contrib/vci/utils/Makefile
create mode 100644 contrib/vci/utils/meson.build
create mode 100644 contrib/vci/utils/nodes.t
create mode 100644 contrib/vci/utils/vci_symbols.c
create mode 100644 contrib/vci/vci--1.0.sql
create mode 100644 contrib/vci/vci.control
create mode 100644 contrib/vci/vci_supported_funcs.c
create mode 100644 contrib/vci/vci_supported_funcs.sql
create mode 100644 contrib/vci/vci_supported_types.c
diff --git a/contrib/Makefile b/contrib/Makefile
index 2f0a88d..c0c2f6d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -51,7 +51,8 @@ SUBDIRS = \
tsm_system_rows \
tsm_system_time \
unaccent \
- vacuumlo
+ vacuumlo \
+ vci
ifeq ($(with_ssl),openssl)
SUBDIRS += pgcrypto sslinfo
diff --git a/contrib/meson.build b/contrib/meson.build
index ed30ee7..d8bd5c8 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -70,4 +70,5 @@ subdir('tsm_system_time')
subdir('unaccent')
subdir('uuid-ossp')
subdir('vacuumlo')
+subdir('vci')
subdir('xml2')
diff --git a/contrib/vci/.gitignore b/contrib/vci/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/contrib/vci/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/contrib/vci/Makefile b/contrib/vci/Makefile
new file mode 100644
index 0000000..9e31650
--- /dev/null
+++ b/contrib/vci/Makefile
@@ -0,0 +1,40 @@
+# contrib/vci/Makefile
+
+MODULE_big = vci
+
+OBJS = \
+# vci_main.o \
+# vci_read_guc.o \
+# vci_shmem.o \
+ vci_supported_funcs.o \
+ vci_supported_types.o
+SUBDIRS = \
+ executor \
+ storage \
+ utils
+
+OBJS += \
+ $(patsubst $(top_srcdir)/contrib/vci/%.c,%.o,$(foreach dir,$(SUBDIRS), $(sort $(wildcard $(top_srcdir)/contrib/vci/$(dir)/*.c))))
+
+EXTENSION = vci
+DATA = vci--1.0.sql
+
+PG_CPPFLAGS = -I $(top_srcdir)/contrib/vci/include
+
+REGRESS = vci bugs
+REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/vci/vci.conf
+
+# Disabled because these tests require "shared_preload_libraries=vci",
+# which typical installcheck users do not have (e.g. buildfarm clients).
+NO_INSTALLCHECK = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/vci
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/vci/README b/contrib/vci/README
new file mode 100755
index 0000000..5f62a0f
--- /dev/null
+++ b/contrib/vci/README
@@ -0,0 +1,976 @@
+src/contrib/vci/README
+
+VCI (Vertical Clustered Indexing)
+
+1. Overview
+2. Core Architecture
+ 2.1 Dual Storage - WOS and ROS
+ 2.2 WOS (Write-Optimized Storage)
+ 2.2.1 MVCC Handling
+ 2.2.2 Data WOS and Whiteout WOS
+ 2.3 ROS (Read-Optimized Storage)
+ 2.3.1 Column data
+ 2.3.2 TID-CRID mapping
+ 2.3.3 Delete Vector
+ 2.3.4 NULL information
+ 2.4. VCI "Internal Relations"
+ 2.5. Extent-based Storage Management
+ 2.5.1 Locating an extent
+ 2.5.2 Locating column data within an extent
+ 2.5.3 Garbage Collection
+ 2.6. Compression System
+ 2.6.1 Dictionaries
+3. VCI Integration with PostgreSQL
+ 3.1 Standard hooks
+ 3.1.1 IndexAccessMethod hooks
+ 3.1.2 Other standard hooks
+ 3.2 Executor hooks
+ 3.3 Known problems
+ 3.3.1 Ad-hoc hooks
+ 3.3.2 Embedded code
+4. Data Flow and Conversion
+ 4.1 WOS-to-ROS Background Process
+ 4.2 Overview Diagram
+ 4.3 Local ROS Creation
+ 4.4 Existing Table Data
+5. MVCC and Transaction Handling
+6. Query Execution
+ 6.1 Custom Plan Integration
+ 6.1.1 VCI mode requirements
+ 6.2 Custom Plan Execution Steps
+ 6.3 Is VCI getting used?
+7. Configuration Parameters
+ 7.1 VCI-specific parameters
+ 7.1.1 Core Parameters
+ 7.1.2 Memory Management
+ 7.1.3 Background Worker Control
+ 7.1.4 Data Management Thresholds
+ 7.2 Affected PostgreSQL Parameters
+8. Known Restrictions/Limitations/Differences
+ 8.1 Restrictions
+ 8.1.1 DROP EXTENSION vci
+ 8.1.2 Backup and Restore
+ 8.1.3 Version Upgrades
+ 8.2 Limitations
+ 8.2.1 CREATE INDEX
+ 8.2.2 Supported Relation Types
+ 8.2.3 Supported Data Types
+ 8.2.4 Performance Testing
+ 8.3 Differences
+ 8.3.1 Configuration (Planner GUCs)
+ 8.3.2 Disk Size and Estimation
+
+
+
+==============================================================================
+1. Overview
+==============================================================================
+
+VCI (Vertical Clustered Indexing) is a PostgreSQL extension that implements a
+hybrid storage architecture combining row-oriented OLTP capabilities with
+column-oriented OLAP performance. It provides an in-memory column store while
+maintaining PostgreSQL's transactional guarantees and row-based architecture.
+
+The extension provides a new indexing method "vci", which can be specified as
+the method for the CREATE INDEX statement, to create a VCI index for a
+nominated set of columns:
+┌─────────────────────────────────────────────────────────────────┐
+│ CREATE INDEX index ON table │
+│ USING vci (column [, ...]) │
+│ [WITH (storage_parameter = value, [...])] │
+│ [TABLESPACE tablespace] │
+└─────────────────────────────────────────────────────────────────┘
+
+
+
+==============================================================================
+2. Core Architecture
+==============================================================================
+
+2.1 Dual storage - WOS and ROS
+==============================
+
+VCI implements two storage areas (WOS and ROS) that work together.
+- WOS: Write Optimized Storage
+- ROS: Read Optimized Storage
+
+The purpose of VCI is to maintain the ROS as the column-oriented storage for
+live table data.
+
+┌─────────────────────────────────────────────────────────────────┐
+│ VCI Architecture │
+├─────────────────────────────────┬───────────────────────────────┤
+│ WOS │ ROS │
+│ (Write Optimized Storage) │ (Read Optimized Storage) │
+├─────────────────────────────────┼───────────────────────────────┤
+│ • Row-oriented format │ • Column-oriented format │
+│ • Handles INSERT/UPDATE/DELETE │ • Optimized for SELECT/OLAP │
+│ • MVCC transaction data │ • Compressed storage │
+│ • Temporary buffer │ • Frozen committed data │
+│ • Tuple IDs (TID) │ • Columnar Record IDs (CRID) │
+│ │ • Extent-based storage units │
+└─────────────────────────────────┴───────────────────────────────┘
+ │
+ Background Worker
+ (WOS → ROS Conversion)
+
+
+The WOS is a row-oriented temporary buffer for incoming write operations. It
+maintains MVCC consistency.
+
+The ROS provides column-oriented storage for efficient OLAP queries.
+
+At intervals, a Background Worker performs WOS-to-ROS conversion for any
+frozen/committed WOS rows.
+
+Additional WOS/ROS related components, are included to defer the need for
+WOS-to-ROS conversion for every query:
+e.g.
+- Whiteout WOS = TID records of WOS rows that are marked for deletion on ROS
+- ROS delete vector = Records of ROS data marked for deletion
+- Local ROS = Temporary ROS (scope of SELECT) for any unconverted WOS data
+
+
+2.2 WOS (Write Optimized Storage)
+=================================
+
+Purpose: Buffer incoming write operations and maintain MVCC consistency
+
+Data Structure:
+- Row-oriented format same as PostgreSQL's native storage
+- Contains both actual tuple data and MVCC metadata
+- Stores transaction information (xmin, xmax, cmin, cmax)
+- Maintains TID for each record
+
+
+2.2.1 MVCC Handling:
+--------------------
+Uses cmin,cmax,xmin,xmax
+
+
+2.2.2 Data WOS and Whiteout WOS
+-------------------------------
+(internal relations)
+
+There are two kinds of data in the WOS:
+a. Data WOS -- Actual tuple data from INSERT/UPDATE operations
+b. Whiteout WOS -- TID records of WOS rows that are marked for deletion on ROS
+
+
+Example:
+
+INSERT Operation (Data WOS):
+┌────────────────────────────────────────────────────────────┐
+│ TID │ xmin │ xmax │ cmin │ cmax │ Column Data │
+├─────┼──────┼──────┼──────┼──────┼──────────────────────────┤
+│ 100 │ T1 │ - │ 1 │ 1 │ customer_id=123, amt=500 │
+└────────────────────────────────────────────────────────────┘
+
+DELETE Operation (Whiteout WOS):
+┌────────────────────────────────────┐
+│ TID │ xmax │ Status │
+├─────┼──────┼───────────────────────┤
+│ 100 │ T2 │ Marked for deletion │
+└────────────────────────────────────┘
+
+
+2.3 ROS (Read Optimized Storage)
+================================
+
+Purpose: Provide columnar storage for efficient analytical queries
+
+Data Organization:
+- Column-oriented storage with independent compression per column
+- Uses CRID (Columnar Record ID) instead of TID for internal addressing
+- Organized into fixed-size "extents" (262,144 records each)
+- Maintains TID-to-CRID mapping for consistency
+
+ROS Structure:
+┌─────────────────────────────────────────────────────────────────┐
+│ ROS Components │
+├─────────────────────┬───────────────────┬───────────────────────┤
+│ Management Info │ Data Storage │ Support Structures │
+├─────────────────────┼───────────────────┼───────────────────────┤
+│ • TID-CRID mapping │ • Column data │ • Delete vector │
+│ • Extent metadata │ • Compression │ • NULL information │
+│ • Dictionary info │ • TOAST links │ • TID relation │
+└─────────────────────┴───────────────────┴───────────────────────┘
+
+
+2.3.1. Column data
+------------------
+(multiple internal relations)
+
+Each VCI indexed column is stored as an internal relation. Records are
+addresses by CRID (Columnar Record ID) instead of by TID. The CRID gives
+the logical position of the columnar data, and is generated in increasing
+order of record registration.
+
+CRID is used to address the data (from each different column-data relation)
+that comprised the whole record.
+
+ROS column data Relations:
+┌────────────────────────────────────────────────────────────────┐
+│ ROS column data │
+├────────────────────────────────────────────────────────────────┤
+│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
+│ [CRID=0] │ col1 │ │ col2 │ │ col3 │ │ colN │ │
+│ [CRID=1] │ col1 │ │ col2 │ │ col3 │ │ colN │ │
+│ [CRID=2] │ col1 │ │ col2 │ │ col3 │ ... │ colN │ │
+│ [CRID=3] │ col1 │ │ col2 │ │ col3 │ │ colN │ │
+│ [CRID=4] │ col1 │ │ col2 │ │ col3 │ │ colN │ │
+│ [CRID=5] │ col1 │ │ col2 │ │ col3 │ │ colN │ │
+│ ... └──────┘ └──────┘ └──────┘ └──────┘ │
+│ │
+└────────────────────────────────────────────────────────────────┘
+
+
+2.3.2. TID-CRID mapping
+-----------------------
+(internal relation)
+
+This maps TID to the CRID. When a WOS record (identified by TID) is deleted,
+this mapping is needed to identify the matching records from the ROS
+
+There is also a TID relation that maps a CRID back to the original TID
+
+
+2.3.3. Delete Vector
+--------------------
+(internal relation)
+
+Instead of immediately removing deleted records, VCI uses a bit vector for
+efficient tracking of CRIDs of deleted records.
+
+Delete Vector (Bit Array):
+┌────────────────────────────────────────────────────────────────┐
+│ CRID: 0 1 2 3 4 5 6 7 8 9 ... │
+│ Status: [0] [1] [0] [0] [1] [0] [0] [1] [0] [0] ... │
+│ Live Del Live Live Del Live Live Del Live Live │
+└────────────────────────────────────────────────────────────────┘
+
+Actual deletion of the ROS records (called "deleted-rows-collection")
+happens during the WOS-to-ROS conversion, but it is triggered only when the
+deleted records exceeds some configurable threshold.
+
+
+2.3.4. NULL information
+------------------------
+(internal relation)
+
+Bit vector implemented as a fixed-length column element of an internal table.
+This indicates (with 1 bit) whether the column element at this CRID is null or
+not null.
+
+
+2.4. VCI "Internal Relations"
+=============================
+
+Each VCI index results in the creation of multiple internal relations.
+
+Notice that most of the VCI data structures of the WOS and ROS etc are stored
+within (e.g. binary data columns of) these internal relations.
+
+These internal relations have a common name pattern "pg_vci_%010d_%05d_%c".
+
+Where:
+┌────────────────────────────────────────────────────────────────┐
+│ %010d: Original table OID (the table with the VCI index) │
+│ %05d: Internal relation type identifier │
+│ %c: Special meanings -- e.g. Metadata vs Data indicator │
+└────────────────────────────────────────────────────────────────┘
+
+Internal Relation Types:
+- -1: TID relation (maps CRID to original TID)
+- -2: NULL vector (bit array for NULL values)
+- -3: Delete vector (bit array for deleted records)
+- -5: TID-CRID mappings
+- -6: TID-CRID mappings (update list)
+- -9: Data WOS (buffered row data)
+- -10: Whiteout WOS (deletion markers)
+- 0-N: ROS column data relations (one per indexed column)
+
+Example:
+For a VCI index on sales(customer_id, amount, date):
+
+Generated relations include:
+pg_vci_0000012345_00000_d → Column 0 data (customer_id)
+pg_vci_0000012345_00000_m ... and metadata
+pg_vci_0000012345_00001_d → Column 1 data (amount)
+pg_vci_0000012345_00001_m ... and metadata
+pg_vci_0000012345_00002_d → Column 2 data (date)
+pg_vci_0000012345_00002_m ... and metadata
+pg_vci_0000012345_65526_d → Whiteout WOS
+pg_vci_0000012345_65527_d → Data WOS
+pg_vci_0000012345_65531_d → TID-CRID mappings
+pg_vci_0000012345_65531_m ... and metadata
+pg_vci_0000012345_65530_0 ... and update list #0
+pg_vci_0000012345_65530_1 ... and update list #1
+pg_vci_0000012345_65533_d → Delete vector
+pg_vci_0000012345_65533_m ... and metadata
+pg_vci_0000012345_65534_d → NULL vector
+pg_vci_0000012345_65534_m ... and metadata
+pg_vci_0000012345_65535_d → TID relation
+pg_vci_0000012345_65535_m ... and metadata
+
+These relations (implemented as materialized views) are for internal VCI use
+only. Normal users do not need to be aware of them and are not allowed to
+tamper with them.
+
+
+2.5. Extent-Based Storage Management
+====================================
+
+VCI introduces the concept of "extents". Extents are logical units of data
+management used by the ROS.
+
+Each extent contains a fixed number of consecutive CRIDs/data; there are
+always exactly 262,144 (= 256 * 1024) records per extent, including used and
+unused CRIDs.
+
+Notice that even though the number of records per extent is fixed, the size of
+extents might vary according to the VCI index column data type sizes and
+compression.
+
+When a large number of records is transferred during WOS-to-ROS conversion
+that work is divided into units of extents. The NULL information and
+compression is also executed in units of extents.
+
+Extent Layout:
+┌────────────────────────────────────────────────────────────────┐
+│ Extent N │
+├────────────────────────────────────────────────────────────────┤
+│ Header: │
+│ • Extent ID │
+│ • Compression dictionary │
+│ • Offset information (for variable-length data) │
+│ • Record count and capacity │
+├────────────────────────────────────────────────────────────────┤
+│ Data Section: │
+│ ┌─────────────────────────────────────────────┐ │
+│ │ │ Data0 │ Data1 │ ... │ DataN │ │ │
+│ └─────────────────────────────────────────────┘ │
+└────────────────────────────────────────────────────────────────┘
+
+
+2.5.1 Locating an Extent
+------------------------
+
+Extents can be different sizes. Also, due to the garbage collection, they can
+become shuffled and fragmented.
+
+Extents can be located by offset. The approriate offset is found using
+extent-ID as an index, as shown below.
+
+┌────────────────────────────────────────────────────────────────┐
+│ Locating where is Extent N in memory? │
+├────────────────────────────────────────────────────────────────┤
+│ │
+│ Relation (meta) Relation (data) │
+│ ┌───────────┐ ┌───────────┐ │
+│ │ Offsets: │ │ Extent 0 │ │
+│ │ │ │ │ │
+│ │ [Extent0] │ ├───────────┤ │
+│ │ [Extent1] │ │ Extent 1 │ │
+│ │ [Extent2] │ ├───────────┤ │
+│ │ [Extent3] │ │ Extent 5 │ │
+│ │ [Extent4] │ ├───────────┤ │
+│ │ [Extent5] │ │///////////│ │
+│ └───────────┘ │/ gap /│ │
+│ │///////////│ │
+│ ├───────────┤ │
+│ │ Extent 3 │ │
+│ ├───────────┤ │
+│ │ Extent 4 │ │
+│ │ │ │
+│ │ │ │
+│ │ │ │
+│ ├───────────┤ │
+│ │ Extent 2 │ │
+│ └───────────┘ │
+│ │
+└────────────────────────────────────────────────────────────────┘
+
+
+2.5.2 Locating column data within an extent
+-------------------------------------------
+
+Fixed-Length Data Access:
+
+Since the extent ID is known and the extent always has a fixed number of
+records, the column data position of fixed-length data can be directly
+calculated.
+
+e.g. Position = Extent_Base + (CRID % 262144) * Element_Size
+
+┌────────────────────────────────────────────────────────────────┐
+│ Addressing fixed-length data: Direct CRID-based addressing │
+├────────────────────────────────────────────────────────────────┤
+│ Extent │
+│ ┌─────────────────────────────────────────────────────────┐ │
+│ │ Header │ [pos0] │ [pos1] │ │ [posN] ││ │
+│ │ │ Data0 │ Data1 │ ... │ DataN ││ │
+│ └─────────────────────────────────────────────────────────┘ │
+└────────────────────────────────────────────────────────────────┘
+
+Variable-Length Data Access:
+
+- Data offsets recorded in the extent header
+- TOAST links stored for very large data
+- TOAST link vs normal data is indicated by reserved bits in the offset
+
+┌────────────────────────────────────────────────────────────────┐
+│ Addressing variable-length data: Offset array + data │
+├────────────────────────────────────────────────────────────────┤
+│ Extent │
+│ ┌────────────────────────────────────────────────────────────┐ │
+│ │ Header │ │ │
+│ │ Offset array │ [pos0] │ [pos1] │ ... │ [posN] │ │ │
+│ │ [pos1...posN] │ Data0 │ Data1 │ │ DataN │ │ │
+│ └────────────────────────────────────────────────────────────┘ │
+└────────────────────────────────────────────────────────────────┘
+
+
+2.5.3 Garbage collection
+------------------------
+
+During WOS-to-ROS conversion, when the delete vector is processed to delete the
+ROS data (aka "deleted-rows-collection"), VCI makes a "copy" of the extent.
+It writes the modified extent back to the ROS after the necessary data is
+deleted, and removes the original extent.
+
+Copying like this allows VCI queries to run even during the
+"deleted-rows-collection" phase, but it can lead to some fragmentation (i.e.
+"gaps") between extents as they are copied/deleted. VCI includes logic to
+relocate extents such that fragmentation is minimized.
+
+
+2.6. Compression System
+========================
+
+//
+// Note: compression logic is currently disabled in contrib/vci
+//
+
+Column data compression occurs (if enabled) at the time of WOS-to-ROS
+conversion.
+
+The only compression method currently implemented is run-length encoding,
+which is used for integer columns with low cardinality.
+
+e.g.
+Original Data: [5, 5, 5, 7, 7, 2, 2, 2, 2, 5, 5]
+Compressed: [(5,3), (7,2), (2,4), (5,2)]
+Dictionary: {[0]=5, [1]=7, [2]=2}
+Final Encoding: [(0,3), (1,2), (2,4), (0,2)]
+
+
+2.6.1 Dictionaries
+------------------
+
+- Each extent of each column is compressed independently.
+- Each extent can have its own independent compression dictionary or all
+ extents can share a common dictionary
+
+Independent Dictionaries (per extent):
+- Stored in the column-data internal relation in each extent header, along with
+- size/position/count information.
+
+Common Dictionaries (shared across extents):
+- Stored in the column-data internal relation in the first extent header
+- The size/positions/counts etc needed for the "common" dictionaries are
+ stored in the column-metadata internal relation
+
+
+
+==============================================================================
+3. VCI Integration with PostgreSQL
+==============================================================================
+
+In general, the VCI extension integrates with the PostgreSQL core via hooks.
+
+e.g. Some primary hooks are for:
+- adding data to the WOS (see 'add_should_index_insert_hook')
+- deleting data from the WOS (see 'add_index_delete_hook')
+
+
+3.1 Standard hooks
+===========================
+
+Mostly VCI is implemented as per other indexes; many of the hooks are
+implementations of the Index Access Method (IAM) routine.
+
+
+3.1.1 IndexAccessMethod hooks
+-----------------------------
+
+(see vci_handler)
+• amroutine->ambuild = vci_build;
+• amroutine->ambuildempty = vci_buildempty;
+• amroutine->aminsert = vci_insert;
+• amroutine->aminsertcleanup = NULL;
+• amroutine->ambulkdelete = vci_bulkdelete;
+• amroutine->amvacuumcleanup = vci_vacuumcleanup;
+• amroutine->amcanreturn = NULL;
+• amroutine->amcostestimate = vci_costestimate;
+• amroutine->amgettreeheight = vci_gettreeheight;
+• amroutine->amoptions = vci_options;
+• amroutine->amvalidate = vci_validate;
+• amroutine->amadjustmembers = NULL;
+• amroutine->ambeginscan = vci_beginscan;
+• amroutine->amrescan = vci_rescan;
+• amroutine->amgettuple = NULL;
+• amroutine->amgetbitmap = NULL;
+• amroutine->amendscan = vci_endscan;
+• amroutine->ammarkpos = vci_markpos;
+• amroutine->amrestrpos = vci_restrpos;
+
+
+3.1.2 Other standard hooks
+---------------------------
+
+(see _PG_init)
+• ProcessUtility_hook = vci_process_utility;
+• shmem_request_hook = vci_shmem_request;
+
+(see vci_setup_shmem)
+• shmem_startup_hook = vci_shmem_startup_routine;
+
+
+3.2 Executor hooks
+==================
+VCI also implements Executor hooks. These enable the Query planner to identify
+which queries are capable of using the ROS data.
+
+(see function vci_setup_executor_hook)
+• ExecutorStart_hook = vci_executor_start_routine;
+• ExecutorRun_hook = vci_executor_run_routine;
+• ExecutorEnd_hook = vci_executor_end_routine;
+• ExplainOneQuery_hook = vci_explain_one_query_routine;
+• ExprEvalVar_hook = VciExecEvalScalarVarFromColumnStore;
+• ExprEvalParam_hook = VciExecEvalParamExec;
+
+
+3.3 Known problems
+==================
+
+NOTE:
+There are some artifacts in the current VCI implementation since these patches
+historically were implemented in vendor-specific source code, forked from the
+PostgreSQL master code.
+
+These are known problems that will need to be addressed for the VCI
+implementation to be accepted by the OSS community.
+
+3.3.1 Ad-hoc hooks
+------------------
+There are places where VCI uses non-standard, hardwired non-extensible hooks,
+instead of implementing callbacks from well documented APIs such as IAM.
+
+(see _PG_init)
+• add_index_delete_hook = vci_add_index_delete;
+• add_should_index_insert_hook = vci_add_should_index_insert;
+• add_drop_relation_hook = vci_add_drop_relation;
+• add_reindex_index_hook = vci_add_reindex_index;
+• add_skip_vci_index_hook = vci_add_skip_vci_index;
+• add_alter_tablespace_hook = vci_add_alter_tablespace;
+• add_alter_table_change_owner_hook = vci_alter_table_change_owner;
+• add_alter_table_change_schema_hook = vci_alter_table_change_schema;
+• add_snapshot_satisfies_hook = VCITupleSatisfiesVisibility;
+• add_skip_vacuum_hook = vci_isVciAdditionalRelation;
+
+
+3.3.2 Embedded code
+--------------------
+There are some places where VCI code is simply embedded in the PostgreSQL core.
+
+
+
+==============================================================================
+4. Data Flow and Conversion
+==============================================================================
+
+
+4.1. WOS-to-ROS Background Process
+==================================
+
+A dedicated background worker continuously converts "freezable" data from WOS
+to ROS:
+
+Conversion Process:
+┌-───────────────────────────────────────────────────────────────┐
+│ Background Worker Cycle │
+├────────────────────────────────────────────────────────────────┤
+│ 1. Check Whiteout WOS → Update ROS delete vector │
+│ 2. Execute "deleted-rows-collection" if threshold exceeded │
+│ 3. Identify freezable tuples in WOS │
+│ 4. Convert freezable data to columnar format │
+│ 5. Apply compression algorithms │
+│ 6. Update TID-CRID mapping │
+│ 7. Truncate processed WOS data │
+└────────────────────────────────────────────────────────────────┘
+
+Freezable Data Criteria:
+- Transaction must be committed
+- No active transactions started before the commit timestamp
+- Ensures MVCC consistency during WOS-to-ROS conversion
+
+
+4.2 Overview Diagram
+====================
+Details of some of these concepts (e.g. extents) are given later.
+
+┌-───────────────────────────────────────────────────────────────┐
+│ WOS-to-ROS conversion │
+├────────────────────────────────────────────────────────────────┤
+│BEFORE: │
+│ │
+│ Data WOS (new rows) ROS extent (before) │
+│ ┌────────────────────────┐ ┌──────────────────────────┐ │
+│ │ newA-5 │ newB-5 │ ... │ │ delete vector │ │
+│ │ newA-6 │ newB-6 │ ... │ │CRID ▼ column data: │ │
+│ └────────────────────────┘ │ 1 │ 0 │ ColA-1 │ ColB-1 │ │
+│ │ 2 │ 1 │ ColA-2 │ ColB-2 │ │
+│ Whiteout WOS: │ 3 │ 0 │ ColA-3 │ ColB-3 │ │
+│ ┌────────────────────────┐ │ 4 │ 0 │ ColA-4 │ ColB-4 │ │
+│ │ delete rec with CRID 4 │ └──────────────────────────┘ │
+│ └────────────────────────┘ │ │
+│ • Remove WOS Whiteout records │
+│ • Remove delete vector rows │
+│ • Add new records from Data WOS │
+│ • Renumber CRIDs │
+│ • Compress data │
+│ │ │
+│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│
+│AFTER: │ │
+│ ▼ │
+│ Data WOS: ROS extent (after) │
+│ ┌─────────────────────────┐ ┌──────────────────────────┐ │
+│ └─────────────────────────┘ │ 1 │ │ ColA-1 │ ColB-1 │ │
+│ │ 2 │ │ ColA-3 │ ColB-3 │ │
+│ Whiteout WOS: │ 3 │ │ new5-1 │ new5-1 │ │
+│ ┌─────────────────────────┐ │ 4 │ │ new6-1 │ new6-1 │ │
+│ └─────────────────────────┘ └──────────────────────────┘ │
+│ │
+└────────────────────────────────────────────────────────────────┘
+
+
+4.3 Local ROS Creation
+======================
+During SELECT operations, if WOS contains unconverted data, VCI creates a
+temporary Local ROS. This is combined with the persisted ROS during query
+execution. At the end of the query the Local ROS is discarded.
+
+Later, a full WOS-to-ROS conversion will be performed, so any unconverted WOS
+data will be permanently converted to the ROS.
+
+Query Execution with Local ROS:
+┌────────────────────────────────────────────────────────────────┐
+│ SELECT Operation │
+├────────────────────────────────────────────────────────────────┤
+│ │
+│ ROS │
+│ /-------------------------\ │
+│ ┌─────────┐ ┌────────────────────────┐ │
+│ │ WOS │───▶│ Local ROS │ } │
+│ │(Partial)│ │(Temporary) │ } Combined ROS │
+│ └─────────┘ └────────────────────────┘ } Columnar │
+│ ┌────────────────────────┐ } Processing │
+│ │ Persistent ROS │ } for SELECT │
+│ │ (Previously Converted) │ } │
+│ └────────────────────────┘ │
+│ │
+└────────────────────────────────────────────────────────────────┘
+
+
+4.4 Existing table data
+=======================
+The whole point of the asynchronous WOS-to-ROS conversion is to maintain the
+column-oriented data in sync with row-based table data on-the-fly.
+
+Be aware that creating a VCI index for a table with lots of existing data is
+costly because VCI has to initialise the ROS for all that data up-front.
+
+
+
+==============================================================================
+5. MVCC and Transaction Handling
+==============================================================================
+
+Transaction Visibility
+
+VCI maintains MVCC consistency across both WOS and ROS:
+
+e.g. Transaction Timeline:
+┌────────────────────────────────────────────────────────────────┐
+│ T1: BEGIN → INSERT → COMMIT (timestamp: 100) │
+│ T2: BEGIN (timestamp: 99) → SELECT → ... │
+│ T3: BEGIN (timestamp: 101) → SELECT → ... │
+└────────────────────────────────────────────────────────────────┘
+- T2 cannot see T1's insert (it started before T1 committed)
+- T3 can see T1's insert (it started after T1 committed)
+
+Visibility Rules:
+Data remains in WOS until no transaction needs old versions -- see
+"freezable" criteria.
+
+WOS: Handles active transaction visibility using xmin/xmax
+ROS: Contains only frozen data visible to all current transactions
+Local ROS: Applies snapshot visibility rules during query execution
+
+
+
+==============================================================================
+6. Query Execution
+==============================================================================
+
+6.1 Custom Plan Integration
+===========================
+
+VCI integrates with PostgreSQL's query planner by replacing standard plan
+nodes with custom plan nodes for VCI when possible.
+
+VCI registers a set of callbacks as custom plan providers.
+
+There are 4 types of operations that can potentially be replaced:
+table scan, aggregation (e.g. SUM, COUNT, AVG), sort, join.
+
+Furthermore, instead of replacing one plan node with one custom plan node,
+plan nodes are collectively replaced.
+
+┌─────────────────────────────────────────────────────────────────┐
+│ Replacing a Plan Tree │
+├─────────────────────────────────────────────────────────────────┤
+│ Standard PostgreSQL Plan │ VCI Optimized Plan │
+│ │ (e.g. VCI judges Agg and │
+│ │ SeqScan are replaceable) │
+├─────────────────────────────────────────────────────────────────┤
+│ ┌─────────────┐ │ ┌─────────────┐ │
+│ │ │ │ │ │ │
+│ └─────┬───────┘ │ └─────┬───────┘ │
+│ │ │ │ │
+│ ┌─────▼───────┐ │ ┌─────▼───────┐ │
+│ │ Sort │ │ │ Sort │ │
+│ └─────┬───────┘ │ └─────┬───────┘ │
+│ │ │ │ │
+│ ┌─────▼───────┐ │ ┌─────▼───────────┐ │
+│ │ Agg │ │ │ VCI CustomPlan │ │
+│ └─────┬───────┘ │ │ │ │
+│ │ │ │ Agg and SeqScan │ │
+│ ┌─────▼───────┐ │ │ combined │ │
+│ │ SeqScan │ │ └─────────────────┘ │
+│ └─────────────┘ │ │
+└─────────────────────────────────────────────────────────────────┘
+
+
+6.1.1 VCI mode requirements
+----------------------------
+
+There are a number of conditions that must be met for nodes to be replaced:
+
+Scan node - must be for a relation (table) with a VCI index
+Agg node - plan tree must be for scan node as above
+Sort node - plan tree must be for scan node as above
+Join node - plan tree must be for scan node as above
+
+Expression node:
+- All columns in the expression node tree must be indexed by VCI
+- Cannot have any SubPlan in the expression node tree
+- If an expression has functions then there are other restrictions:
+ - not all functions are supported
+ - function data types must be supported by VCI
+ - user-defined functions not supported
+ - user-defined aggregate functions not supported
+
+
+6.2 Custom Plan Execution Steps
+===============================
+
+Preparation Phase:
+- Create Local ROS from visible WOS data
+- Build delete TID list from Whiteout WOS
+- Prepare extent access structures
+
+Execution Phase:
+- Process ROS extents using columnar operations
+- Apply delete vector filtering
+- Combine results with Local ROS data
+- Execute aggregations and sorting in columnar format
+
+VCI implements optimized hash joins for columnar data.
+
+
+6.3 Is VCI getting used?
+========================
+
+Use EXPLAIN ANALYZE to see if VCI custom nodes are in the query plan.
+
+Boolean function vci_runs_in_query() returns true if a VCI index and custom
+scan are used by the current query execution.
+
+e.g.
+┌────────────────────────────────────────────────────────────────┐
+│ SELECT │
+│ vci_runs_in_query() AS vci_runs_in_query, key, count(*) │
+│ FROM test_table; │
+└────────────────────────────────────────────────────────────────┘
+
+
+
+==============================================================================
+7. Configuration Parameters
+==============================================================================
+
+7.1 VCI-specific Parameters
+=============================
+VCI provides numerous configuration parameters that can be set in
+postgresql.conf. These parameters control various aspects of VCI behavior,
+from basic functionality to advanced performance tuning.
+
+There are also many DEVELOPER_OPTIONS (see code contrib/vci/vci_read_guc.c).
+
+
+7.1.1 Core Parameters
+----------------------
+
+- vci.enable
+ Controls whether VCI functionality is active.
+
+- vci.enable_compression
+ Enables compression of column data in ROS storage.
+
+- vci.log_query
+ Logs detailed information when queries fail to execute through VCI's
+ columnar processing path, useful for debugging query execution issues.
+
+
+7.1.2 Memory Management
+------------------------
+
+- vci.maintenance_work_mem
+ Memory limit for VCI background operations, including WOS-to-ROS conversions
+ and garbage collection processes.
+
+- vci.max_local_ros
+ Maximum memory allowed for temporary Local ROS creation during query
+ execution when WOS contains unconverted data.
+
+
+7.1.3 Background Worker Control
+--------------------------------
+
+- vci.enable_ros_control_daemon
+ Enables the background worker responsible for WOS-to-ROS conversion.
+ Essential for maintaining columnar storage efficiency.
+
+- vci.control_max_workers
+ Maximum number of concurrent VCI background workers. Should be balanced
+ with system resources and PostgreSQL's max_worker_processes setting.
+
+- vci.control_naptime
+ Sleep interval between background worker cycles. Lower values provide
+ more responsive WOS-to-ROS conversion but increase system overhead.
+
+- vci.cost_threshold
+ CPU load threshold above which VCI background workers are paused to
+ avoid impacting foreground query performance.
+
+
+7.1.4 Data Management Thresholds
+---------------------------------
+
+- vci.wosros_conv_threshold
+ Number of WOS rows that trigger automatic conversion to ROS format.
+ Lower values reduce WOS size but increase conversion overhead.
+
+- vci.cdr_threshold
+ Percentage of deleted rows in ROS that triggers garbage collection.
+ Typical values range from 20-40% depending on workload patterns.
+
+
+
+7.2 Affected PostgreSQL Parameters
+===================================
+
+- max_worker_processes
+ Must be increased from default values to accommodate VCI background
+ workers. Recommended minimum increase of 4-8 workers for VCI operation.
+
+
+
+==============================================================================
+8. Known Restrictions/Limitations/Differences
+==============================================================================
+
+8.1 Restrictions
+==================
+
+8.1.1 DROP EXTENSION vci
+-------------------------
+Unloading the extension is not supported.
+
+
+8.1.2 Backup and Restore
+--------------------------
+Databases containing VCI indexes cannot be restored to PostgreSQL
+installations without VCI extension. Cross-compatibility requires
+dropping VCI indexes before backup or ensuring target system has
+VCI available.
+
+
+8.1.3 Version Upgrades
+-----------------------
+pg_upgrade is not currently supported for VCI-enabled databases.
+Upgrades require dump/restore procedures with VCI-compatible target
+systems.
+
+
+8.2 Limitations
+================
+
+8.2.1 CREATE INDEX
+-------------------
+Since the internal structure of VCI indexes is different from other indexes,
+some of the CREATE INDEX options are not supported for VCI.
+- expressions instead of columns
+- UNIQUE, CONCURRENTLY, WHERE clauses
+- ASC/DESC, NULLS FIRST/LAST operator class options
+
+
+8.2.2 Supported Relation Types
+-------------------------------
+Cannot create a VCI index for a view.
+
+
+8.2.3 Supported Data Types
+---------------------------
+Not all data types are supported for VCI indexing.
+
+
+8.2.4 Performance Testing
+--------------------------
+Standard benchmarks like pgbench focuses on OLTP workloads and will not
+demonstrate VCI's analytical query performance benefits. Custom OLAP
+benchmarks are needed to evaluate VCI effectiveness.
+
+
+8.3 Differences
+================
+
+8.3.1 Configuration (Planner GUCs)
+-----------------------------------
+VCI may execute operations (such as hash joins) even when corresponding
+PostgreSQL planner options are disabled, as VCI uses its own execution
+strategies within custom plan nodes.
+
+8.3.2 Disk Size and Estimation
+--------------------------------
+VCI maintains both WOS and ROS storage simultaneously, requiring
+additional disk space during data conversion periods.
+
+Standard PostgreSQL index size functions (pg_relation_size) do not
+account for VCI's distributed storage architecture. Use the provided
+pg_vci_index_size() function for VCI storage measurements.
+
+
+[END]
diff --git a/contrib/vci/include/vci.h b/contrib/vci/include/vci.h
new file mode 100644
index 0000000..428aa33
--- /dev/null
+++ b/contrib/vci/include/vci.h
@@ -0,0 +1,153 @@
+/*-------------------------------------------------------------------------
+ *
+ * vci.h
+ * Primary include file for VCI .c files
+ *
+ * This should be the first file included by VCI modules.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/vci/include/vci.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef VCI_H
+#define VCI_H
+
+/* define our text domain for translations */
+#undef TEXTDOMAIN
+#define TEXTDOMAIN PG_TEXTDOMAIN("vci")
+
+#include "postgres.h"
+#include "access/heapam.h"
+#include "access/htup.h"
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "access/relscan.h"
+#include "c.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_am.h"
+#include "executor/nodeModifyTable.h"
+#include "storage/itemptr.h" /* for ItemPointer */
+#include "tcop/utility.h"
+#include "utils/rel.h"
+#include "utils/relcache.h" /* for Relation */
+#include "utils/syscache.h"
+
+#define VCI_STRING "vci"
+
+#define VCI_INTERNAL_RELATION_TEMPLATE "pg_vci_%010d_%05d_%c"
+
+/** Use compact form to keep varlena with short header at some parts */
+#define VCI_USE_COMPACT_VARLENA
+
+#ifdef WIN32
+#define strtok_r strtok_s
+#endif
+
+/** Restart time for Daemon(Background Worker) */
+#define VCI_DAEMON_RESTART_TIME (3)
+
+/**
+ * Scan policy
+ */
+typedef enum
+{
+ VCI_TABLE_SCAN_POLICY_NONE,
+ VCI_TABLE_SCAN_POLICY_COLUMN_ONLY, /* Only reads column store */
+} VciTableScanPolicy;
+
+/**
+ * VCI Scan mode
+ */
+typedef enum
+{
+ VCI_SCAN_MODE_NONE,
+ VCI_SCAN_MODE_COLUMN_STORE, /* Reads column store */
+} VciScanMode;
+
+typedef struct VciFetchPos
+{
+ int64 fetch_starting_crid;
+
+ int32 current_extent_id;
+
+ int num_rows_in_extent;
+
+ int offset_in_extent;
+
+ int num_fetched_rows;
+
+ int current_row;
+} VciFetchPos;
+
+extern void vci_add_index_delete(Relation heapRel, const ItemPointerData *heap_tid, TransactionId xmin);
+extern List *vci_add_should_index_insert(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, ItemPointer tupleid, EState *estate);
+extern bool vci_add_drop_relation(const ObjectAddress *object, int flags);
+extern bool vci_add_reindex_index(Relation indexRel);
+extern bool vci_add_skip_vci_index(Relation indexRel);
+extern bool vci_add_alter_tablespace(Relation indexRel);
+extern void vci_process_utility(PlannedStmt *pstmt, const char *queryString,
+ bool readOnlyTree,
+ ProcessUtilityContext context,
+ ParamListInfo params,
+ QueryEnvironment *queryEnv,
+ DestReceiver *dest,
+ QueryCompletion *qc);
+extern void vci_alter_table_change_owner(Oid relOid, char relKind, Oid newOwnerId);
+extern void vci_alter_table_change_schema(Oid relOid, char relKind, Oid newNspOid);
+
+extern void vci_read_guc_variables(void);
+extern void vci_setup_shmem(void);
+extern void vci_shmem_startup_routine(void);
+extern void vci_setup_executor_hook(void);
+extern void vci_xact_change_handler(XactEvent event);
+extern void vci_subxact_change_handler(SubXactEvent event, SubTransactionId mySubid);
+extern void vci_set_copy_transaction_and_command_id(TransactionId xid,
+ CommandId cid);
+
+extern bool VCITupleSatisfiesVisibility(HeapTuple htup, Snapshot snapshot, Buffer buffer);
+
+/* for index_build */
+typedef enum
+{
+ vcirc_invalid = 0,
+ vcirc_reindex,
+ vcirc_truncate,
+ vcirc_vacuum_full,
+ vcirc_cluster,
+ vcirc_alter_table,
+
+ vcirc_num
+} vci_RebuildCommand;
+
+extern vci_RebuildCommand vci_rebuild_command;
+
+extern bool vci_is_in_vci_create_extension;
+
+extern ProcessUtility_hook_type process_utility_prev;
+extern ProcessUtility_hook_type post_process_utility_prev;
+
+static inline
+bool
+isVciIndexRelation(Relation rel)
+{
+ if (rel->rd_rel->relam != 0)
+ {
+ Form_pg_am aform;
+ HeapTuple amtuple;
+
+ amtuple = SearchSysCache1(AMOID, ObjectIdGetDatum(rel->rd_rel->relam));
+ if (!HeapTupleIsValid(amtuple))
+ elog(ERROR, "cache lookup failed for access method %u",
+ rel->rd_rel->relam);
+ aform = (Form_pg_am) GETSTRUCT(amtuple);
+ ReleaseSysCache(amtuple);
+
+ if (strcmp(NameStr(aform->amname), "vci") == 0)
+ return true;
+ }
+ return false;
+}
+#endif /* VCI_H */
diff --git a/contrib/vci/include/vci_utils.h b/contrib/vci/include/vci_utils.h
new file mode 100644
index 0000000..1095e8d
--- /dev/null
+++ b/contrib/vci/include/vci_utils.h
@@ -0,0 +1,238 @@
+/*-------------------------------------------------------------------------
+ *
+ * vci_utils.h
+ * Debugging functions and macros
+ *
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/vci/include/vci_utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef VCI_DEBUG_H
+#define VCI_DEBUG_H
+
+#include "postgres.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "nodes/nodes.h"
+
+#include "vci.h"
+
+/* obtain the node name of type */
+extern PGDLLEXPORT const char *VciGetNodeName(NodeTag type);
+
+/**
+ * @brief inlined memcpy(). When len is larger than 1024, call memcpy().
+ *
+ * @param[out] dst_ The data are copied to the memory area pointed by dst_.
+ * @param[in] src_ The source data address.
+ * @param[in] len The length of the data.
+ * @return dst_ itself.
+ *
+ * XXX: This is just a wrapper of memcpy() now, retained due to a backward
+ * compatibility.
+ */
+static inline void *
+MemCpy(void *dst_, const void *src_, Size len)
+{
+ return memcpy(dst_, src_, len);
+}
+
+/**
+ * @brief Find value from unsorted array of int16 and returns the position.
+ * @param[in] array Pointer to the array of int16.
+ * @param[in] len Length of the array.
+ * @param[in] value The value to find out.
+ * @return The position of value.
+ * When the value is not found, it returns -1.
+ */
+static inline int
+FindInt16(int16 *array, int len, int16 value)
+{
+ int ptr;
+
+ for (ptr = 0; ptr < len; ++ptr)
+ if (array[ptr] == value)
+ return ptr;
+ return -1;
+}
+
+/**
+ * @brief Pfree and make the pointer null.
+ *
+ * @param[in, out] ptr When *ptr, NOT ptr ITSELF, is not NULL, pfree *ptr.
+ * Then, put *ptr = NULL.
+ * So use like * vci_PfreeAndNull(& pointer).
+ */
+static inline void
+vci_PfreeAndNull(void *ptr)
+{
+ Assert(ptr);
+ if (NULL == *(void **) ptr)
+ return;
+ pfree(*(void **) ptr);
+ *(void **) ptr = NULL;
+}
+
+/**
+ * @brief Allocate memory area with given size, and copy the given source
+ * to the area newly allocated.
+ *
+ * @param[in] src Pointer to the array of byte data to be copied.
+ * @param[in] size The size of source data pointed by src.
+ */
+static inline void *
+vci_AllocateAndCopy(const void *src, Size size)
+{
+ if (src != NULL && size > 0)
+ {
+ void *dst = palloc(size);
+
+ MemCpy(dst, src, size);
+ return dst;
+ }
+ return NULL;
+}
+
+/**
+ * @brief A part of GetHighestBit().
+ *
+ * @note Do not use this function directly.
+ */
+static inline void
+vci_GetHighestBitSub(int *result, uint64 *value, uint64 mask, int inc)
+{
+ if (mask & *value)
+ {
+ *result += inc;
+ *value &= mask;
+ }
+ else
+ *value &= ~mask;
+}
+
+/**
+ * @brief Get the largest bit ID in bits set 1 in the given uint64 value.
+ *
+ * It should be 63 - CLZ(value) (Count Leading Zero).
+ * If the given value is 0, returns -1.
+ * Same as
+ * \code{.c}
+ * if (value & 0x8000000000000000) return 63;
+ * if (value & 0x4000000000000000) return 62;
+ * ...
+ * if (value & 0x0000000000000002) return 1;
+ * if (value & 0x0000000000000001) return 0;
+ * return -1;
+ * \endcode
+ *
+ * @param[in] value Value to examine.
+ * @return The bit ID of MSB set, in a manner of zero-origin.
+ * If no bit has 1, -1 is returned.
+ *
+ * @note Better to use count leading zero (CLZ), if possible.
+ */
+static inline int
+vci_GetHighestBit(uint64 value)
+{
+ int result = 0;
+
+ if (0 == value)
+ return -1;
+
+ vci_GetHighestBitSub(&result, &value, UINT64CONST(0xFFFFFFFF00000000), 32);
+ vci_GetHighestBitSub(&result, &value, UINT64CONST(0xFFFF0000FFFF0000), 16);
+ vci_GetHighestBitSub(&result, &value, UINT64CONST(0xFF00FF00FF00FF00), 8);
+ vci_GetHighestBitSub(&result, &value, UINT64CONST(0xF0F0F0F0F0F0F0F0), 4);
+ vci_GetHighestBitSub(&result, &value, UINT64CONST(0xCCCCCCCCCCCCCCCC), 2);
+ vci_GetHighestBitSub(&result, &value, UINT64CONST(0xAAAAAAAAAAAAAAAA), 1);
+
+ return result;
+}
+
+/**
+ * @brief Calculate the ID of least significant bit (LSB) set, 1.
+ *
+ * Same as
+ * \code{.c}
+ * if (value & 0x8000000000000001) return 0;
+ * if (value & 0x4000000000000002) return 1;
+ * ...
+ * if (value & 0x4000000000000000) return 62;
+ * if (value & 0x8000000000000000) return 63;
+ * return 63;
+ * \endcode
+ *
+ * @param[in] value Value to examine.
+ * @return The bit ID of LSB set, in a manner of zero-origin.
+ * If no bit has 1, -1 is returned.
+ *
+ * @note Better to use count leading zero (CLZ) and reverse bit, if possible.
+ */
+static inline int
+vci_GetLowestBit(uint64 value)
+{
+ int result = 63;
+
+ if (0 == value)
+ return -1;
+
+ vci_GetHighestBitSub(&result, &value, ~UINT64CONST(0xFFFFFFFF00000000), -32);
+ vci_GetHighestBitSub(&result, &value, ~UINT64CONST(0xFFFF0000FFFF0000), -16);
+ vci_GetHighestBitSub(&result, &value, ~UINT64CONST(0xFF00FF00FF00FF00), -8);
+ vci_GetHighestBitSub(&result, &value, ~UINT64CONST(0xF0F0F0F0F0F0F0F0), -4);
+ vci_GetHighestBitSub(&result, &value, ~UINT64CONST(0xCCCCCCCCCCCCCCCC), -2);
+ vci_GetHighestBitSub(&result, &value, ~UINT64CONST(0xAAAAAAAAAAAAAAAA), -1);
+ return result;
+}
+
+/**
+ * @brief Count number of bits set, 1.
+ *
+ * Same as
+ * \code{.c}
+ * uint64 count = 0;
+ * if (value & 0x0000000000000001) ++ count;
+ * if (value & 0x0000000000000002) ++ count;
+ * ...
+ * if (value & 0x4000000000000000) ++ count;
+ * if (value & 0x8000000000000000) ++ count;
+ * return count;
+ * \endcode
+ *
+ * @param[in] value Value to examine.
+ * @return The number of bit set.
+ */
+static inline int
+vci_GetBitCount(uint64 value)
+{
+ uint64 count = 0;
+
+ count = (value & UINT64CONST(0x5555555555555555)) + ((value >> 1) & UINT64CONST(0x5555555555555555));
+ count = (count & UINT64CONST(0x3333333333333333)) + ((count >> 2) & UINT64CONST(0x3333333333333333));
+ count = (count & UINT64CONST(0x0f0f0f0f0f0f0f0f)) + ((count >> 4) & UINT64CONST(0x0f0f0f0f0f0f0f0f));
+ count = (count & UINT64CONST(0x00ff00ff00ff00ff)) + ((count >> 8) & UINT64CONST(0x00ff00ff00ff00ff));
+ count = (count & UINT64CONST(0x0000ffff0000ffff)) + ((count >> 16) & UINT64CONST(0x0000ffff0000ffff));
+ count = (count & UINT64CONST(0x00000000ffffffff)) + ((count >> 32) & UINT64CONST(0x00000000ffffffff));
+ return (int) count;
+}
+
+/**
+ * @brief Set specified bit in char array to 1.
+ *
+ * @param[in, out] bitData Pointer to an array of char.
+ * @param bitID Bit ID to set.
+ */
+static inline void
+vci_SetBit(char *bitData, uint16 bitId)
+{
+ bitData[bitId >> 3] |= 1 << (bitId & 7);
+}
+
+#endif /* VCI_DEBUG_H */
diff --git a/contrib/vci/meson.build b/contrib/vci/meson.build
new file mode 100644
index 0000000..7560c1b
--- /dev/null
+++ b/contrib/vci/meson.build
@@ -0,0 +1,67 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+subdir('executor')
+subdir('storage')
+subdir('utils')
+
+
+vci_sources = files(
+# 'vci_main.c',
+# 'vci_read_guc.c',
+# 'vci_shmem.c',
+ 'vci_supported_funcs.c',
+ 'vci_supported_types.c',
+)
+
+vci_sources += vci_executor_sources
+
+vci_sources += vci_storage_sources
+
+vci_sources += vci_utils_sources
+
+if host_system == 'windows'
+ vci_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'vci',
+ '--FILEDESC', 'vci - vertical clustered index',])
+endif
+
+if host_system == 'solaris'
+ ldflags += ['-lc -lkstat']
+endif
+
+vci_cflags = []
+
+vci_cflags += var_cflags_sl
+
+vci = shared_module('vci',
+ vci_sources,
+ c_args : vci_cflags,
+ include_directories : include_directories('../../contrib/vci/include'),
+ link_args: ldflags,
+ kwargs: contrib_mod_args,
+)
+
+install_data(
+ 'vci.control',
+ 'vci--1.0.sql',
+ kwargs:contrib_data_args,
+)
+
+tests += {
+ 'name': 'vci',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'vci',
+ 'bugs',
+ ],
+ 'regress_args': ['--temp-config', files('vci.conf')],
+
+ # runningcheck is disabled because a running instance needs to have
+ # "shared_preload_libraries=vci", but typical runnigcheck users
+ # (e.g. build farm clients) won't have that setting so they would fail.
+ # Note: This is copied from contrib/pg_stat_statements/meson.build
+ 'runningcheck' : false,
+ }
+}
diff --git a/contrib/vci/utils/Makefile b/contrib/vci/utils/Makefile
new file mode 100644
index 0000000..25b49d2
--- /dev/null
+++ b/contrib/vci/utils/Makefile
@@ -0,0 +1,20 @@
+# contrib/vci/utils/Makefile
+
+SUBOBJS = \
+ vci_symbols.o
+
+EXTRA_CLEAN = SUBSYS.o $(SUBOBJS)
+
+PG_CPPFLAGS = -I $(top_srcdir)/contrib/vci/include
+
+ifdef USE_PGXS
+PGXS := $(shell pg_config --pgxs)
+include $(PGXS)
+else
+subdir = contrib/vci/utils
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+override CFLAGS += $(CFLAGS_SL)
diff --git a/contrib/vci/utils/meson.build b/contrib/vci/utils/meson.build
new file mode 100644
index 0000000..0a765df
--- /dev/null
+++ b/contrib/vci/utils/meson.build
@@ -0,0 +1,10 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+vci_utils_sources = files(
+ 'vci_symbols.c',
+)
+
+install_headers(
+ 'nodes.t',
+ install_dir: dir_include_extension / 'vci_utils',
+)
diff --git a/contrib/vci/utils/nodes.t b/contrib/vci/utils/nodes.t
new file mode 100644
index 0000000..82bbcc78
--- /dev/null
+++ b/contrib/vci/utils/nodes.t
@@ -0,0 +1,448 @@
+
+Item(IndexInfo )
+Item(ExprContext )
+Item(ProjectionInfo )
+Item(JunkFilter )
+Item(OnConflictSetState )
+Item(ResultRelInfo )
+Item(EState )
+Item(TupleTableSlot )
+
+Item(Result )
+Item(ProjectSet )
+Item(ModifyTable )
+Item(Append )
+Item(MergeAppend )
+Item(RecursiveUnion )
+Item(BitmapAnd )
+Item(BitmapOr )
+Item(SeqScan )
+Item(SampleScan )
+Item(IndexScan )
+Item(IndexOnlyScan )
+Item(BitmapIndexScan )
+Item(BitmapHeapScan )
+Item(TidScan )
+Item(TidRangeScan )
+Item(SubqueryScan )
+Item(FunctionScan )
+Item(ValuesScan )
+Item(TableFuncScan )
+Item(CteScan )
+Item(NamedTuplestoreScan )
+Item(WorkTableScan )
+Item(ForeignScan )
+Item(CustomScan )
+Item(CustomPlanMarkPos )
+Item(NestLoop )
+Item(MergeJoin )
+Item(HashJoin )
+Item(Material )
+Item(Memoize )
+Item(Sort )
+Item(IncrementalSort )
+Item(Group )
+Item(Agg )
+Item(WindowAgg )
+Item(Unique )
+Item(Gather )
+Item(GatherMerge )
+Item(Hash )
+Item(SetOp )
+Item(LockRows )
+Item(Limit )
+Item(NestLoopParam )
+Item(PlanRowMark )
+Item(PartitionPruneInfo )
+Item(PartitionedRelPruneInfo)
+Item(PartitionPruneStepOp )
+Item(PartitionPruneStepCombine)
+Item(PlanInvalItem )
+
+Item(ResultState )
+Item(ProjectSetState )
+Item(ModifyTableState )
+Item(AppendState )
+Item(MergeAppendState )
+Item(RecursiveUnionState )
+Item(BitmapAndState )
+Item(BitmapOrState )
+Item(ScanState )
+Item(SeqScanState )
+Item(SampleScanState )
+Item(IndexScanState )
+Item(IndexOnlyScanState )
+Item(BitmapIndexScanState )
+Item(BitmapHeapScanState )
+Item(TidScanState )
+Item(TidRangeScanState )
+Item(SubqueryScanState )
+Item(FunctionScanState )
+Item(TableFuncScanState )
+Item(ValuesScanState )
+Item(CteScanState )
+Item(NamedTuplestoreScanState)
+Item(WorkTableScanState )
+Item(ForeignScanState )
+Item(CustomScanState )
+Item(JoinState )
+Item(NestLoopState )
+Item(MergeJoinState )
+Item(HashJoinState )
+Item(MaterialState )
+Item(MemoizeState )
+Item(SortState )
+Item(IncrementalSortState )
+Item(GroupState )
+Item(AggState )
+Item(WindowAggState )
+Item(UniqueState )
+Item(GatherState )
+Item(GatherMergeState )
+Item(HashState )
+Item(SetOpState )
+Item(LockRowsState )
+Item(LimitState )
+
+Item(Alias )
+Item(RangeVar )
+Item(TableFunc )
+Item(Var )
+Item(Const )
+Item(Param )
+Item(Aggref )
+Item(GroupingFunc )
+Item(WindowFunc )
+Item(SubscriptingRef )
+Item(FuncExpr )
+Item(NamedArgExpr )
+Item(OpExpr )
+Item(DistinctExpr )
+Item(NullIfExpr )
+Item(ScalarArrayOpExpr )
+Item(BoolExpr )
+Item(SubLink )
+Item(SubPlan )
+Item(AlternativeSubPlan )
+Item(FieldSelect )
+Item(FieldStore )
+Item(RelabelType )
+Item(CoerceViaIO )
+Item(ArrayCoerceExpr )
+Item(ConvertRowtypeExpr )
+Item(CollateExpr )
+Item(CaseExpr )
+Item(CaseWhen )
+Item(CaseTestExpr )
+Item(ArrayExpr )
+Item(RowExpr )
+Item(RowCompareExpr )
+Item(CoalesceExpr )
+Item(MinMaxExpr )
+Item(SQLValueFunction )
+Item(XmlExpr )
+Item(NullTest )
+Item(BooleanTest )
+Item(CoerceToDomain )
+Item(CoerceToDomainValue )
+Item(SetToDefault )
+Item(CurrentOfExpr )
+Item(NextValueExpr )
+Item(InferenceElem )
+Item(TargetEntry )
+Item(RangeTblRef )
+Item(JoinExpr )
+Item(FromExpr )
+Item(OnConflictExpr )
+Item(IntoClause )
+
+Item(ExprState )
+Item(WindowFuncExprState )
+Item(SetExprState )
+Item(SubPlanState )
+Item(DomainConstraintState )
+
+Item(PlannerInfo )
+Item(PlannerGlobal )
+Item(RelOptInfo )
+Item(IndexOptInfo )
+Item(ForeignKeyOptInfo )
+Item(ParamPathInfo )
+
+Item(Path )
+Item(IndexPath )
+Item(BitmapHeapPath )
+Item(BitmapAndPath )
+Item(BitmapOrPath )
+Item(TidPath )
+Item(TidRangePath )
+Item(SubqueryScanPath )
+Item(ForeignPath )
+Item(CustomPath )
+Item(NestPath )
+Item(MergePath )
+Item(HashPath )
+Item(AppendPath )
+Item(MergeAppendPath )
+Item(GroupResultPath )
+Item(MaterialPath )
+Item(MemoizePath )
+Item(UniquePath )
+Item(GatherPath )
+Item(GatherMergePath )
+Item(ProjectionPath )
+Item(ProjectSetPath )
+Item(SortPath )
+Item(IncrementalSortPath )
+Item(GroupPath )
+Item(AggPath )
+Item(GroupingSetsPath )
+Item(MinMaxAggPath )
+Item(WindowAggPath )
+Item(SetOpPath )
+Item(RecursiveUnionPath )
+Item(LockRowsPath )
+Item(ModifyTablePath )
+Item(LimitPath )
+
+Item(EquivalenceClass )
+Item(EquivalenceMember )
+Item(PathKey )
+Item(PathTarget )
+Item(RestrictInfo )
+Item(IndexClause )
+Item(PlaceHolderVar )
+Item(SpecialJoinInfo )
+Item(AppendRelInfo )
+Item(RowIdentityVarInfo )
+Item(PlaceHolderInfo )
+Item(MinMaxAggInfo )
+Item(PlannerParamItem )
+Item(RollupData )
+Item(GroupingSetData )
+Item(StatisticExtInfo )
+
+Item(AllocSetContext )
+Item(SlabContext )
+Item(GenerationContext )
+
+Item(Integer )
+Item(Float )
+Item(String )
+Item(BitString )
+
+Item(List )
+Item(IntList )
+Item(OidList )
+
+Item(ExtensibleNode )
+
+Item(RawStmt )
+Item(Query )
+Item(PlannedStmt )
+Item(InsertStmt )
+Item(DeleteStmt )
+Item(UpdateStmt )
+Item(SelectStmt )
+Item(ReturnStmt )
+Item(PLAssignStmt )
+Item(AlterTableStmt )
+Item(AlterTableCmd )
+Item(AlterDomainStmt )
+Item(SetOperationStmt )
+Item(GrantStmt )
+Item(GrantRoleStmt )
+Item(AlterDefaultPrivilegesStmt)
+Item(ClosePortalStmt )
+Item(ClusterStmt )
+Item(CopyStmt )
+Item(CreateStmt )
+Item(DefineStmt )
+Item(DropStmt )
+Item(TruncateStmt )
+Item(CommentStmt )
+Item(FetchStmt )
+Item(IndexStmt )
+Item(CreateFunctionStmt )
+Item(AlterFunctionStmt )
+Item(DoStmt )
+Item(RenameStmt )
+Item(RuleStmt )
+Item(NotifyStmt )
+Item(ListenStmt )
+Item(UnlistenStmt )
+Item(TransactionStmt )
+Item(ViewStmt )
+Item(LoadStmt )
+Item(CreateDomainStmt )
+Item(CreatedbStmt )
+Item(DropdbStmt )
+Item(VacuumStmt )
+Item(ExplainStmt )
+Item(CreateTableAsStmt )
+Item(CreateSeqStmt )
+Item(AlterSeqStmt )
+Item(VariableSetStmt )
+Item(VariableShowStmt )
+Item(DiscardStmt )
+Item(CreateTrigStmt )
+Item(CreatePLangStmt )
+Item(CreateRoleStmt )
+Item(AlterRoleStmt )
+Item(DropRoleStmt )
+Item(LockStmt )
+Item(ConstraintsSetStmt )
+Item(ReindexStmt )
+Item(CheckPointStmt )
+Item(CreateSchemaStmt )
+Item(AlterDatabaseStmt )
+Item(AlterDatabaseSetStmt )
+Item(AlterRoleSetStmt )
+Item(CreateConversionStmt )
+Item(CreateCastStmt )
+Item(CreateOpClassStmt )
+Item(CreateOpFamilyStmt )
+Item(AlterOpFamilyStmt )
+Item(PrepareStmt )
+Item(ExecuteStmt )
+Item(DeallocateStmt )
+Item(DeclareCursorStmt )
+Item(CreateTableSpaceStmt )
+Item(DropTableSpaceStmt )
+Item(AlterObjectDependsStmt )
+Item(AlterObjectSchemaStmt )
+Item(AlterOwnerStmt )
+Item(AlterOperatorStmt )
+Item(AlterTypeStmt )
+Item(DropOwnedStmt )
+Item(ReassignOwnedStmt )
+Item(CompositeTypeStmt )
+Item(CreateEnumStmt )
+Item(CreateRangeStmt )
+Item(AlterEnumStmt )
+Item(AlterTSDictionaryStmt )
+Item(AlterTSConfigurationStmt)
+Item(CreateFdwStmt )
+Item(AlterFdwStmt )
+Item(CreateForeignServerStmt)
+Item(AlterForeignServerStmt )
+Item(CreateUserMappingStmt )
+Item(AlterUserMappingStmt )
+Item(DropUserMappingStmt )
+Item(AlterTableSpaceOptionsStmt)
+#if PG_VERSION_NUM >= 90400
+Item(AlterTableMoveAllStmt )
+#endif
+Item(SecLabelStmt )
+Item(CreateForeignTableStmt )
+Item(ImportForeignSchemaStmt)
+Item(CreateExtensionStmt )
+Item(AlterExtensionStmt )
+Item(AlterExtensionContentsStmt)
+#if PG_VERSION_NUM >= 90300
+Item(CreateEventTrigStmt )
+Item(AlterEventTrigStmt )
+Item(RefreshMatViewStmt )
+#endif
+#if PG_VERSION_NUM >= 90400
+Item(ReplicaIdentityStmt )
+Item(AlterSystemStmt )
+#endif
+Item(CreatePolicyStmt )
+Item(AlterPolicyStmt )
+Item(CreateTransformStmt )
+Item(CreateAmStmt )
+Item(CreatePublicationStmt )
+Item(AlterPublicationStmt )
+Item(CreateSubscriptionStmt )
+Item(AlterSubscriptionStmt )
+Item(DropSubscriptionStmt )
+Item(CreateStatsStmt )
+Item(AlterCollationStmt )
+Item(CallStmt )
+Item(AlterStatsStmt )
+
+Item(A_Expr )
+Item(ColumnRef )
+Item(ParamRef )
+Item(A_Const )
+Item(FuncCall )
+Item(A_Star )
+Item(A_Indices )
+Item(A_Indirection )
+Item(A_ArrayExpr )
+Item(ResTarget )
+Item(MultiAssignRef )
+Item(TypeCast )
+Item(CollateClause )
+Item(SortBy )
+Item(WindowDef )
+Item(RangeSubselect )
+Item(RangeFunction )
+Item(RangeTableSample )
+Item(RangeTableFunc )
+Item(RangeTableFuncCol )
+Item(TypeName )
+Item(ColumnDef )
+Item(IndexElem )
+Item(StatsElem )
+Item(Constraint )
+Item(DefElem )
+Item(RangeTblEntry )
+#if PG_VERSION_NUM >= 90400
+Item(RangeTblFunction )
+#endif
+Item(TableSampleClause )
+#if PG_VERSION_NUM >= 90400
+Item(WithCheckOption )
+#endif
+Item(SortGroupClause )
+Item(GroupingSet )
+Item(WindowClause )
+Item(ObjectWithArgs )
+Item(AccessPriv )
+Item(CreateOpClassItem )
+Item(TableLikeClause )
+Item(FunctionParameter )
+Item(LockingClause )
+Item(RowMarkClause )
+Item(XmlSerialize )
+Item(WithClause )
+Item(InferClause )
+Item(OnConflictClause )
+Item(CTESearchClause )
+Item(CTECycleClause )
+Item(CommonTableExpr )
+Item(RoleSpec )
+Item(TriggerTransition )
+Item(PartitionElem )
+Item(PartitionSpec )
+Item(PartitionBoundSpec )
+Item(PartitionRangeDatum )
+Item(PartitionCmd )
+Item(VacuumRelation )
+
+Item(IdentifySystemCmd )
+Item(BaseBackupCmd )
+Item(CreateReplicationSlotCmd)
+Item(DropReplicationSlotCmd )
+Item(StartReplicationCmd )
+Item(TimeLineHistoryCmd )
+
+Item(TriggerData )
+Item(EventTriggerData )
+Item(ReturnSetInfo )
+Item(WindowObjectData )
+Item(TIDBitmap )
+Item(InlineCodeBlock )
+Item(FdwRoutine )
+Item(IndexAmRoutine )
+Item(TableAmRoutine )
+Item(TsmRoutine )
+Item(ForeignKeyCacheInfo )
+Item(CallContext )
+Item(SupportRequestSimplify )
+Item(SupportRequestSelectivity)
+Item(SupportRequestCost )
+Item(SupportRequestRows )
+Item(SupportRequestIndexCondition)
diff --git a/contrib/vci/utils/vci_symbols.c b/contrib/vci/utils/vci_symbols.c
new file mode 100644
index 0000000..e2c6763
--- /dev/null
+++ b/contrib/vci/utils/vci_symbols.c
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * vci_symbols.c
+ * Converts a string from a node tag
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/vci/utils/vci_symbols.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/nodes.h"
+
+#include "vci.h"
+#include "vci_utils.h"
+
+#define Item(X) {T_ ## X, # X},
+
+typedef struct
+{
+ NodeTag type;
+ const char *name;
+} node_info_t;
+
+static const node_info_t node_info_table[] = {
+#include "nodes.t"
+};
+
+#undef Item
+
+/*
+ * Returns a literal from a node
+ *
+ * XXX This is used for debugging or error reporting purposes. Performance is
+ * ignored for now, the linear search is used.
+ */
+const char *
+VciGetNodeName(NodeTag type)
+{
+ for (int i = 0; i < lengthof(node_info_table); i++)
+ if (node_info_table[i].type == type)
+ return node_info_table[i].name;
+
+ return "Unknown";
+}
diff --git a/contrib/vci/vci--1.0.sql b/contrib/vci/vci--1.0.sql
new file mode 100644
index 0000000..4ba6c2d
--- /dev/null
+++ b/contrib/vci/vci--1.0.sql
@@ -0,0 +1,76 @@
+CREATE FUNCTION vci_handler(internal)
+RETURNS index_am_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C VOLATILE STRICT;
+
+CREATE ACCESS METHOD vci TYPE index HANDLER vci_handler;
+
+CREATE OPERATOR CLASS bool_ops DEFAULT FOR TYPE bool USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS bytea_ops DEFAULT FOR TYPE bytea USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS char_ops DEFAULT FOR TYPE "char" USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS name_ops DEFAULT FOR TYPE name USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS int8_ops DEFAULT FOR TYPE int8 USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS int2_ops DEFAULT FOR TYPE int2 USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS int4_ops DEFAULT FOR TYPE int4 USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS text_ops DEFAULT FOR TYPE text USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS float4_ops DEFAULT FOR TYPE float4 USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS float8_ops DEFAULT FOR TYPE float8 USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS money_ops DEFAULT FOR TYPE money USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS bpchar_ops DEFAULT FOR TYPE bpchar USING vci AS OPERATOR 1 =;
+-- CREATE OPERATOR CLASS varchar_ops DEFAULT FOR TYPE varchar USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS date_ops DEFAULT FOR TYPE date USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS time_ops DEFAULT FOR TYPE time USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS timestamp_ops DEFAULT FOR TYPE timestamp USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS timestamptz_ops DEFAULT FOR TYPE timestamptz USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS interval_ops DEFAULT FOR TYPE interval USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS timetz_ops DEFAULT FOR TYPE timetz USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS bit_ops DEFAULT FOR TYPE bit USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS varbit_ops DEFAULT FOR TYPE varbit USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS numeric_ops DEFAULT FOR TYPE numeric USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS uuid_ops DEFAULT FOR TYPE uuid USING vci AS OPERATOR 1 =;
+CREATE OPERATOR CLASS tid_ops DEFAULT FOR TYPE tid USING vci AS OPERATOR 1 =;
+-- CREATE OPERATOR CLASS nvarchar_ops DEFAULT FOR TYPE nvarchar USING vci AS OPERATOR 1 =;
+
+CREATE FUNCTION vci_check_supported_functions()
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STABLE STRICT;
+
+CREATE FUNCTION vci_check_supported_types()
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STABLE STRICT;
+
+CREATE FUNCTION vci_index_size(IN vci_index_name text, OUT size int8)
+AS 'MODULE_PATHNAME'
+LANGUAGE C VOLATILE STRICT;
+
+CREATE FUNCTION vci_enable() RETURNS void AS $$
+ BEGIN
+ SET vci.enable = on;
+ END
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION vci_disable() RETURNS void AS $$
+ BEGIN
+ SET vci.enable = off;
+ END
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION vci_runs_in_query()
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C STABLE STRICT;
+
+CREATE FUNCTION vci_runs_in_plan()
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C VOLATILE STRICT;
+
+CREATE FUNCTION vci_always_return_true()
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C VOLATILE STRICT;
+
+SELECT vci_check_supported_functions();
+SELECT vci_check_supported_types();
diff --git a/contrib/vci/vci.control b/contrib/vci/vci.control
new file mode 100644
index 0000000..0863f8d
--- /dev/null
+++ b/contrib/vci/vci.control
@@ -0,0 +1,5 @@
+# vci extension
+comment = 'vertical clustered index'
+default_version = '1.0'
+module_pathname = '$libdir/vci'
+relocatable = false
diff --git a/contrib/vci/vci_supported_funcs.c b/contrib/vci/vci_supported_funcs.c
new file mode 100644
index 0000000..8162f03
--- /dev/null
+++ b/contrib/vci/vci_supported_funcs.c
@@ -0,0 +1,851 @@
+/*-------------------------------------------------------------------------
+ *
+ * vci_supported_func.c
+ * Function that VCI supports that can be called with FuncExpr
+ *
+ * vci_supported_func_table[] is created with the following SQL and then examined individually.
+ *
+ * SELECT oid, proname FROM pg_proc WHERE prokind = 'f' AND NOT proretset
+ * AND NOT EXISTS (SELECT funcoid FROM sys_func_table WHERE pg_proc.oid = sys_func_table.funcoid)
+ * AND (SELECT bool_and(i IN (SELECT typeoid FROM safe_types)) FROM unnest(array_prepend(prorettype, proargtypes)) AS t(i))
+ * AND oid < 16384 ORDER BY oid;
+ *
+ * - prokind = 'f' is to include only normal functions (e.g. exclude aggregate functions and window functions).
+ * - NOT proretset is to exclude SRF
+ * - NOT EXISTS (SELECT ...) is to exclude system related functions
+ * - (SELECT bool_and( ...) is to exclude the appearance of unauthorized types in return values and arguments.
+ * - oid < 16384 is to exclude user-defined types
+ *
+ * sys_func_table and safe_types are in reference to vci_supported_funcs.sql.
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/vci/vci_supported_funcs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup.h"
+#include "access/htup_details.h"
+#include "access/transam.h"
+#include "c.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h" /* for ProcedureRelationId, Form_pg_proc */
+#include "catalog/pg_type.h"
+#include "fmgr.h"
+#include "utils/elog.h"
+#include "utils/memutils.h"
+#include "utils/relcache.h"
+#include "utils/syscache.h"
+
+#include "vci.h"
+#include "vci_mem.h"
+#include "vci_supported_oid.h"
+
+/**
+ * Smallest OID among functions supported by VCI
+ */
+#define VCI_SUPPORTED_FUNC_MIN (77)
+
+/**
+ * Biggest OID among functions supported by VCI
+ */
+#define VCI_SUPPORTED_FUNC_MAX (6204)
+
+/**
+ * Data to record special user defined functions
+ */
+vci_special_udf_info_t vci_special_udf_info;
+
+/**
+ * Array of information about functions supported by VCI
+ * Note when modifying vci_supported_func_table array:
+ * 1.OIDs are in ascending order.
+ * 2.Don't forget to change the macro value of VCI_SUPPORTED_FUNC_MIN/VCI_SUPPORTED_FUNC_MAX.
+ */
+static const struct
+{
+ Oid oid;
+ const char *name;
+ bool is_support;
+} vci_supported_func_table[] = {
+ {77, "int4", true}, /* immutable, internal(12) {char,int4} */
+ {78, "char", true}, /* immutable, internal(12) {char,int4} */
+ {89, "version", false}, /* stable, internal(12) {text} */
+ {228, "dround", true}, /* immutable, internal(12) {float8,float8} */
+ {229, "dtrunc", true}, /* immutable, internal(12) {float8,float8} */
+ {233, "dexp", true}, /* immutable, internal(12) {float8,float8} */
+ {234, "dlog1", true}, /* immutable, internal(12) {float8,float8} */
+ {235, "float8", true}, /* immutable, internal(12) {int2,float8} */
+ {236, "float4", true}, /* immutable, internal(12) {int2,float4} */
+ {237, "int2", true}, /* immutable, internal(12) {int2,float8} */
+ {238, "int2", true}, /* immutable, internal(12) {int2,float4} */
+ {274, "timeofday", true}, /* volatile, internal(12) {text} */
+ {311, "float8", true}, /* immutable, internal(12) {float4,float8} */
+ {312, "float4", true}, /* immutable, internal(12) {float4,float8} */
+ {313, "int4", true}, /* immutable, internal(12) {int2,int4} */
+ {314, "int2", true}, /* immutable, internal(12) {int2,int4} */
+ {316, "float8", true}, /* immutable, internal(12) {int4,float8} */
+ {317, "int4", true}, /* immutable, internal(12) {int4,float8} */
+ {318, "float4", true}, /* immutable, internal(12) {int4,float4} */
+ {319, "int4", true}, /* immutable, internal(12) {int4,float4} */
+ {320, "width_bucket", true}, /* immutable, internal(12)
+ * {int4,int4,float8,float8,float8} */
+ {376, "string_to_array", false}, /* immutable, internal(12)
+ * {text,text,text,_text} */
+ {384, "array_to_string", false}, /* stable, internal(12)
+ * {text,text,text,anyarray} */
+ {394, "string_to_array", false}, /* immutable, internal(12)
+ * {text,text,_text} */
+ {395, "array_to_string", false}, /* stable, internal(12)
+ * {text,text,anyarray} */
+ {401, "text", true}, /* immutable, internal(12) {text,bpchar} */
+ {406, "text", true}, /* immutable, internal(12) {name,text} */
+ {407, "name", true}, /* immutable, internal(12) {name,text} */
+ {408, "bpchar", true}, /* immutable, internal(12) {name,bpchar} */
+ {409, "name", true}, /* immutable, internal(12) {name,bpchar} */
+ {480, "int4", true}, /* immutable, internal(12) {int8,int4} */
+ {481, "int8", true}, /* immutable, internal(12) {int8,int4} */
+ {482, "float8", true}, /* immutable, internal(12) {int8,float8} */
+ {483, "int8", true}, /* immutable, internal(12) {int8,float8} */
+ {652, "float4", true}, /* immutable, internal(12) {int8,float4} */
+ {653, "int8", true}, /* immutable, internal(12) {int8,float4} */
+ {668, "bpchar", true}, /* immutable, internal(12)
+ * {bool,int4,bpchar,bpchar} */
+ {710, "getpgusername", false}, /* stable, internal(12) {name} */
+ {714, "int2", true}, /* immutable, internal(12) {int8,int2} */
+ {720, "octet_length", true}, /* immutable, internal(12) {bytea,int4} */
+ {721, "get_byte", true}, /* immutable, internal(12) {bytea,int4,int4} */
+ {722, "set_byte", true}, /* immutable, internal(12)
+ * {bytea,bytea,int4,int4} */
+ {723, "get_bit", true}, /* immutable, internal(12) {bytea,int8,int4} */
+ {724, "set_bit", true}, /* immutable, internal(12)
+ * {bytea,bytea,int8,int4} */
+ {745, "current_user", false}, /* stable, internal(12) {name} */
+ {746, "session_user", false}, /* stable, internal(12) {name} */
+ {747, "array_dims", false}, /* immutable, internal(12) {text,anyarray} */
+ {748, "array_ndims", false}, /* immutable, internal(12) {int4,anyarray} */
+ {749, "overlay", true}, /* immutable, internal(12)
+ * {bytea,bytea,bytea,int4,int4} */
+ {752, "overlay", true}, /* immutable, internal(12)
+ * {bytea,bytea,bytea,int4} */
+ {754, "int8", true}, /* immutable, internal(12) {int8,int2} */
+ {766, "int4inc", true}, /* immutable, internal(12) {int4,int4} */
+ {810, "pg_client_encoding", true}, /* stable, internal(12) {name} */
+ {817, "current_query", false}, /* volatile, internal(12) {text} */
+ {849, "position", true}, /* immutable, internal(12) {int4,text,text} */
+ {860, "bpchar", true}, /* immutable, internal(12) {char,bpchar} */
+ {861, "current_database", false}, /* stable, internal(12) {name} */
+ {868, "strpos", true}, /* immutable, internal(12) {int4,text,text} */
+ {870, "lower", true}, /* immutable, internal(12) {text,text} */
+ {871, "upper", true}, /* immutable, internal(12) {text,text} */
+ {872, "initcap", true}, /* immutable, internal(12) {text,text} */
+ {873, "lpad", true}, /* immutable, internal(12)
+ * {int4,text,text,text} */
+ {874, "rpad", true}, /* immutable, internal(12)
+ * {int4,text,text,text} */
+ {875, "ltrim", true}, /* immutable, internal(12) {text,text,text} */
+ {876, "rtrim", true}, /* immutable, internal(12) {text,text,text} */
+ {877, "substr", true}, /* immutable, internal(12)
+ * {int4,int4,text,text} */
+ {878, "translate", true}, /* immutable, internal(12)
+ * {text,text,text,text} */
+ {879, "lpad", true}, /* immutable, sql(14) {int4,text,text} */
+ {880, "rpad", true}, /* immutable, sql(14) {int4,text,text} */
+ {881, "ltrim", true}, /* immutable, internal(12) {text,text} */
+ {882, "rtrim", true}, /* immutable, internal(12) {text,text} */
+ {883, "substr", true}, /* immutable, internal(12) {int4,text,text} */
+ {884, "btrim", true}, /* immutable, internal(12) {text,text,text} */
+ {885, "btrim", true}, /* immutable, internal(12) {text,text} */
+ {935, "cash_words", true}, /* immutable, internal(12) {text,money} */
+ {936, "substring", true}, /* immutable, internal(12)
+ * {int4,int4,text,text} */
+ {937, "substring", true}, /* immutable, internal(12) {int4,text,text} */
+ {940, "mod", true}, /* immutable, internal(12) {int2,int2,int2} */
+ {941, "mod", true}, /* immutable, internal(12) {int4,int4,int4} */
+ {944, "char", true}, /* immutable, internal(12) {char,text} */
+ {946, "text", true}, /* immutable, internal(12) {char,text} */
+ {947, "mod", true}, /* immutable, internal(12) {int8,int8,int8} */
+ {1026, "timezone", true}, /* immutable, internal(12)
+ * {timestamp,timestamptz,interval} */
+ {1039, "getdatabaseencoding", false}, /* stable, internal(12) {name} */
+ {1158, "to_timestamp", true}, /* immutable, sql(14) {float8,timestamptz} */
+ {1159, "timezone", true}, /* immutable, internal(12)
+ * {text,timestamp,timestamptz} */
+ {1171, "date_part", true}, /* stable, internal(12)
+ * {text,float8,timestamptz} */
+ {1172, "date_part", true}, /* immutable, internal(12)
+ * {text,float8,interval} */
+ {1174, "timestamptz", true}, /* stable, internal(12)
+ * {date,timestamptz} */
+ {1175, "justify_hours", true}, /* immutable, internal(12)
+ * {interval,interval} */
+ {1176, "timestamptz", true}, /* stable, sql(14)
+ * {date,time,timestamptz} */
+ {1178, "date", true}, /* stable, internal(12) {date,timestamptz} */
+ {1193, "array_fill", false}, /* immutable, internal(12)
+ * {_int4,anyarray,anyelement} */
+ {1199, "age", true}, /* immutable, internal(12)
+ * {timestamptz,timestamptz,interval} */
+ {1200, "interval", true}, /* immutable, internal(12)
+ * {int4,interval,interval} */
+ {1217, "date_trunc", true}, /* stable, internal(12)
+ * {text,timestamptz,timestamptz} */
+ {1218, "date_trunc", true}, /* immutable, internal(12)
+ * {text,interval,interval} */
+ {1257, "textlen", true}, /* immutable, internal(12) {int4,text} */
+ {1264, "pg_char_to_encoding", true}, /* stable, internal(12)
+ * {name,int4} */
+ {1269, "pg_column_size", false}, /* stable, internal(12) {int4,any} */
+ {1271, "overlaps", true}, /* immutable, internal(12)
+ * {bool,timetz,timetz,timetz,timetz} */
+ {1273, "date_part", true}, /* immutable, internal(12)
+ * {text,float8,timetz} */
+ {1282, "quote_ident", true}, /* immutable, internal(12) {text,text} */
+ {1283, "quote_literal", true}, /* immutable, internal(12) {text,text} */
+ {1285, "quote_literal", true}, /* stable, sql(14) {text,anyelement} */
+ {1286, "array_fill", false}, /* immutable, internal(12)
+ * {_int4,_int4,anyarray,anyelement} */
+ {1289, "quote_nullable", true}, /* immutable, internal(12) {text,text} */
+ {1290, "quote_nullable", true}, /* stable, sql(14) {text,anyelement} */
+ {1295, "justify_days", true}, /* immutable, internal(12)
+ * {interval,interval} */
+ {1299, "now", true}, /* stable, internal(12) {timestamptz} */
+ {1304, "overlaps", true}, /* immutable, internal(12)
+ * {bool,timestamptz,timestamptz,timestamptz,timestamptz} */
+ {1305, "overlaps", true}, /* stable, sql(14)
+ * {bool,timestamptz,timestamptz,interval,interval} */
+ {1306, "overlaps", true}, /* stable, sql(14)
+ * {bool,timestamptz,timestamptz,timestamptz,interval} */
+ {1307, "overlaps", true}, /* stable, sql(14)
+ * {bool,timestamptz,timestamptz,timestamptz,interval} */
+ {1308, "overlaps", true}, /* immutable, internal(12)
+ * {bool,time,time,time,time} */
+ {1309, "overlaps", true}, /* immutable, sql(14)
+ * {bool,time,time,interval,interval} */
+ {1310, "overlaps", true}, /* immutable, sql(14)
+ * {bool,time,time,time,interval} */
+ {1311, "overlaps", true}, /* immutable, sql(14)
+ * {bool,time,time,time,interval} */
+ {1316, "time", true}, /* immutable, internal(12) {time,timestamp} */
+ {1317, "length", true}, /* immutable, internal(12) {int4,text} */
+ {1318, "length", true}, /* immutable, internal(12) {int4,bpchar} */
+ {1339, "dlog10", true}, /* immutable, internal(12) {float8,float8} */
+ {1340, "log", true}, /* immutable, internal(12) {float8,float8} */
+ {1341, "ln", true}, /* immutable, internal(12) {float8,float8} */
+ {1342, "round", true}, /* immutable, internal(12) {float8,float8} */
+ {1343, "trunc", true}, /* immutable, internal(12) {float8,float8} */
+ {1344, "sqrt", true}, /* immutable, internal(12) {float8,float8} */
+ {1345, "cbrt", true}, /* immutable, internal(12) {float8,float8} */
+ {1346, "pow", true}, /* immutable, internal(12)
+ * {float8,float8,float8} */
+ {1347, "exp", true}, /* immutable, internal(12) {float8,float8} */
+ {1359, "timestamptz", true}, /* immutable, internal(12)
+ * {date,timestamptz,timetz} */
+ {1367, "character_length", true}, /* immutable, internal(12)
+ * {int4,bpchar} */
+ {1368, "power", true}, /* immutable, internal(12)
+ * {float8,float8,float8} */
+ {1369, "character_length", true}, /* immutable, internal(12) {int4,text} */
+ {1370, "interval", true}, /* immutable, internal(12) {time,interval} */
+ {1372, "char_length", true}, /* immutable, internal(12) {int4,bpchar} */
+ {1373, "isfinite", true}, /* immutable, internal(12) {bool,date} */
+ {1374, "octet_length", true}, /* immutable, internal(12) {int4,text} */
+ {1375, "octet_length", true}, /* immutable, internal(12) {int4,bpchar} */
+ {1376, "factorial", true}, /* immutable, internal(12) {int8,numeric} */
+ {1381, "char_length", true}, /* immutable, internal(12) {int4,text} */
+ {1384, "date_part", true}, /* immutable, sql(14) {text,float8,date} */
+ {1385, "date_part", true}, /* immutable, internal(12) {text,float8,time} */
+ {1386, "age", true}, /* stable, sql(14) {timestamptz,interval} */
+ {1388, "timetz", true}, /* stable, internal(12)
+ * {timestamptz,timetz} */
+ {1389, "isfinite", true}, /* immutable, internal(12) {bool,timestamptz} */
+ {1390, "isfinite", true}, /* immutable, internal(12) {bool,interval} */
+ {1394, "abs", true}, /* immutable, internal(12) {float4,float4} */
+ {1395, "abs", true}, /* immutable, internal(12) {float8,float8} */
+ {1396, "abs", true}, /* immutable, internal(12) {int8,int8} */
+ {1397, "abs", true}, /* immutable, internal(12) {int4,int4} */
+ {1398, "abs", true}, /* immutable, internal(12) {int2,int2} */
+ {1402, "current_schema", false}, /* stable, internal(12) {name} */
+ {1403, "current_schemas", false}, /* stable, internal(12)
+ * {bool,_name} */
+ {1404, "overlay", true}, /* immutable, internal(12)
+ * {int4,int4,text,text,text} */
+ {1405, "overlay", true}, /* immutable, internal(12)
+ * {int4,text,text,text} */
+ {1419, "time", true}, /* immutable, internal(12) {time,interval} */
+ {1569, "like", true}, /* immutable, internal(12) {bool,text,text} */
+ {1570, "notlike", true}, /* immutable, internal(12) {bool,text,text} */
+ {1571, "like", true}, /* immutable, internal(12) {bool,name,text} */
+ {1572, "notlike", true}, /* immutable, internal(12) {bool,name,text} */
+ {1597, "pg_encoding_to_char", true}, /* stable, internal(12)
+ * {name,int4} */
+ {1598, "random", false}, /* volatile, internal(12) {float8} */
+ {1599, "setseed", false}, /* volatile, internal(12) {float8,void} */
+ {1600, "asin", true}, /* immutable, internal(12) {float8,float8} */
+ {1601, "acos", true}, /* immutable, internal(12) {float8,float8} */
+ {1602, "atan", true}, /* immutable, internal(12) {float8,float8} */
+ {1603, "atan2", true}, /* immutable, internal(12)
+ * {float8,float8,float8} */
+ {1604, "sin", true}, /* immutable, internal(12) {float8,float8} */
+ {1605, "cos", true}, /* immutable, internal(12) {float8,float8} */
+ {1606, "tan", true}, /* immutable, internal(12) {float8,float8} */
+ {1607, "cot", true}, /* immutable, internal(12) {float8,float8} */
+ {1608, "degrees", true}, /* immutable, internal(12) {float8,float8} */
+ {1609, "radians", true}, /* immutable, internal(12) {float8,float8} */
+ {1610, "pi", true}, /* immutable, internal(12) {float8} */
+ {1620, "ascii", true}, /* immutable, internal(12) {int4,text} */
+ {1621, "chr", true}, /* immutable, internal(12) {int4,text} */
+ {1622, "repeat", true}, /* immutable, internal(12) {int4,text,text} */
+ {1623, "similar_escape", true}, /* immutable, internal(12)
+ * {text,text,text} */
+ {1637, "like_escape", true}, /* immutable, internal(12)
+ * {text,text,text} */
+ {1640, "pg_get_viewdef", false}, /* stable, internal(12) {text,text} */
+ {1665, "pg_get_serial_sequence", false}, /* stable, internal(12)
+ * {text,text,text} */
+ {1680, "substring", true}, /* immutable, internal(12) {int4,int4,bit,bit} */
+ {1681, "length", true}, /* immutable, internal(12) {int4,bit} */
+ {1682, "octet_length", true}, /* immutable, internal(12) {int4,bit} */
+ {1683, "bit", true}, /* immutable, internal(12) {int4,int4,bit} */
+ {1684, "int4", true}, /* immutable, internal(12) {int4,bit} */
+ {1685, "bit", true}, /* immutable, internal(12) {bool,int4,bit,bit} */
+ {1698, "position", true}, /* immutable, internal(12) {int4,bit,bit} */
+ {1699, "substring", true}, /* immutable, internal(12) {int4,bit,bit} */
+ {1703, "numeric", true}, /* immutable, internal(12)
+ * {int4,numeric,numeric} */
+ {1705, "abs", true}, /* immutable, internal(12) {numeric,numeric} */
+ {1706, "sign", true}, /* immutable, internal(12) {numeric,numeric} */
+ {1707, "round", true}, /* immutable, internal(12)
+ * {int4,numeric,numeric} */
+ {1708, "round", true}, /* immutable, sql(14) {numeric,numeric} */
+ {1709, "trunc", true}, /* immutable, internal(12)
+ * {int4,numeric,numeric} */
+ {1710, "trunc", true}, /* immutable, sql(14) {numeric,numeric} */
+ {1711, "ceil", true}, /* immutable, internal(12) {numeric,numeric} */
+ {1712, "floor", true}, /* immutable, internal(12) {numeric,numeric} */
+ {1713, "length", true}, /* stable, internal(12) {bytea,name,int4} */
+ {1714, "convert_from", true}, /* stable, internal(12)
+ * {bytea,name,text} */
+ {1717, "convert_to", true}, /* stable, internal(12) {bytea,name,text} */
+ {1728, "mod", true}, /* immutable, internal(12)
+ * {numeric,numeric,numeric} */
+ {1730, "sqrt", true}, /* immutable, internal(12) {numeric,numeric} */
+ {1731, "numeric_sqrt", true}, /* immutable, internal(12)
+ * {numeric,numeric} */
+ {1732, "exp", true}, /* immutable, internal(12) {numeric,numeric} */
+ {1733, "numeric_exp", true}, /* immutable, internal(12)
+ * {numeric,numeric} */
+ {1734, "ln", true}, /* immutable, internal(12) {numeric,numeric} */
+ {1735, "numeric_ln", true}, /* immutable, internal(12) {numeric,numeric} */
+ {1736, "log", true}, /* immutable, internal(12)
+ * {numeric,numeric,numeric} */
+ {1737, "numeric_log", true}, /* immutable, internal(12)
+ * {numeric,numeric,numeric} */
+ {1738, "pow", true}, /* immutable, internal(12)
+ * {numeric,numeric,numeric} */
+ {1740, "numeric", true}, /* immutable, internal(12) {int4,numeric} */
+ {1741, "log", true}, /* immutable, sql(14) {numeric,numeric} */
+ {1742, "numeric", true}, /* immutable, internal(12) {float4,numeric} */
+ {1743, "numeric", true}, /* immutable, internal(12) {float8,numeric} */
+ {1744, "int4", true}, /* immutable, internal(12) {int4,numeric} */
+ {1745, "float4", true}, /* immutable, internal(12) {float4,numeric} */
+ {1746, "float8", true}, /* immutable, internal(12) {float8,numeric} */
+ {1764, "numeric_inc", true}, /* immutable, internal(12)
+ * {numeric,numeric} */
+ {1768, "to_char", true}, /* stable, internal(12)
+ * {text,text,interval} */
+ {1770, "to_char", true}, /* stable, internal(12)
+ * {text,text,timestamptz} */
+ {1772, "to_char", true}, /* stable, internal(12) {text,text,numeric} */
+ {1773, "to_char", true}, /* stable, internal(12) {int4,text,text} */
+ {1774, "to_char", true}, /* stable, internal(12) {int8,text,text} */
+ {1775, "to_char", true}, /* stable, internal(12) {text,text,float4} */
+ {1776, "to_char", true}, /* stable, internal(12) {text,text,float8} */
+ {1777, "to_number", true}, /* stable, internal(12) {text,text,numeric} */
+ {1778, "to_timestamp", true}, /* stable, internal(12)
+ * {text,text,timestamptz} */
+ {1779, "int8", true}, /* immutable, internal(12) {int8,numeric} */
+ {1780, "to_date", true}, /* stable, internal(12) {text,text,date} */
+ {1781, "numeric", true}, /* immutable, internal(12) {int8,numeric} */
+ {1782, "numeric", true}, /* immutable, internal(12) {int2,numeric} */
+ {1783, "int2", true}, /* immutable, internal(12) {int2,numeric} */
+ {1810, "bit_length", true}, /* immutable, sql(14) {bytea,int4} */
+ {1811, "bit_length", true}, /* immutable, sql(14) {int4,text} */
+ {1812, "bit_length", true}, /* immutable, sql(14) {int4,bit} */
+ {1813, "convert", true}, /* stable, internal(12)
+ * {bytea,bytea,name,name} */
+ {1842, "int8_sum", true}, /* immutable, internal(12)
+ * {int8,numeric,numeric} */
+ {1845, "to_ascii", true}, /* immutable, internal(12) {text,text} */
+ {1846, "to_ascii", true}, /* immutable, internal(12) {int4,text,text} */
+ {1847, "to_ascii", true}, /* immutable, internal(12) {name,text,text} */
+ {1946, "encode", true}, /* immutable, internal(12) {bytea,text,text} */
+ {1947, "decode", true}, /* immutable, internal(12) {bytea,text,text} */
+ {1961, "timestamp", true}, /* immutable, internal(12)
+ * {int4,timestamp,timestamp} */
+ {1967, "timestamptz", true}, /* immutable, internal(12)
+ * {int4,timestamptz,timestamptz} */
+ {1968, "time", true}, /* immutable, internal(12) {int4,time,time} */
+ {1969, "timetz", true}, /* immutable, internal(12)
+ * {int4,timetz,timetz} */
+ {1973, "div", true}, /* immutable, internal(12)
+ * {numeric,numeric,numeric} */
+ {1980, "numeric_div_trunc", true}, /* immutable, internal(12)
+ * {numeric,numeric,numeric} */
+ {1986, "similar_to_escape", true}, /* immutable, internal(12)
+ * {text,text,text} */
+ {1987, "similar_to_escape", true}, /* immutable, internal(12) {text,text} */
+ {2007, "like", true}, /* immutable, internal(12) {bool,bytea,bytea} */
+ {2008, "notlike", true}, /* immutable, internal(12) {bool,bytea,bytea} */
+ {2009, "like_escape", true}, /* immutable, internal(12)
+ * {bytea,bytea,bytea} */
+ {2010, "length", true}, /* immutable, internal(12) {bytea,int4} */
+ {2012, "substring", true}, /* immutable, internal(12)
+ * {bytea,bytea,int4,int4} */
+ {2013, "substring", true}, /* immutable, internal(12) {bytea,bytea,int4} */
+ {2014, "position", true}, /* immutable, internal(12) {bytea,bytea,int4} */
+ {2015, "btrim", true}, /* immutable, internal(12) {bytea,bytea,bytea} */
+ {2019, "time", true}, /* stable, internal(12) {time,timestamptz} */
+ {2020, "date_trunc", true}, /* immutable, internal(12)
+ * {text,timestamp,timestamp} */
+ {2021, "date_part", true}, /* immutable, internal(12)
+ * {text,float8,timestamp} */
+ {2024, "timestamp", true}, /* immutable, internal(12) {date,timestamp} */
+ {2025, "timestamp", true}, /* immutable, internal(12)
+ * {date,time,timestamp} */
+ {2026, "pg_backend_pid", false}, /* stable, internal(12) {int4} */
+ {2027, "timestamp", true}, /* stable, internal(12)
+ * {timestamp,timestamptz} */
+ {2028, "timestamptz", true}, /* stable, internal(12)
+ * {timestamp,timestamptz} */
+ {2029, "date", true}, /* immutable, internal(12) {date,timestamp} */
+ {2034, "pg_conf_load_time", false}, /* stable, internal(12)
+ * {timestamptz} */
+ {2037, "timezone", true}, /* volatile, internal(12)
+ * {text,timetz,timetz} */
+ {2038, "timezone", true}, /* immutable, internal(12)
+ * {interval,timetz,timetz} */
+ {2041, "overlaps", true}, /* immutable, internal(12)
+ * {bool,timestamp,timestamp,timestamp,timestamp} */
+ {2042, "overlaps", true}, /* immutable, sql(14)
+ * {bool,timestamp,timestamp,interval,interval} */
+ {2043, "overlaps", true}, /* immutable, sql(14)
+ * {bool,timestamp,timestamp,timestamp,interval} */
+ {2044, "overlaps", true}, /* immutable, sql(14)
+ * {bool,timestamp,timestamp,timestamp,interval} */
+ {2046, "time", true}, /* immutable, internal(12) {time,timetz} */
+ {2047, "timetz", true}, /* stable, internal(12) {time,timetz} */
+ {2048, "isfinite", true}, /* immutable, internal(12) {bool,timestamp} */
+ {2049, "to_char", true}, /* stable, internal(12)
+ * {text,text,timestamp} */
+ {2058, "age", true}, /* immutable, internal(12)
+ * {timestamp,timestamp,interval} */
+ {2059, "age", true}, /* stable, sql(14) {timestamp,interval} */
+ {2069, "timezone", true}, /* immutable, internal(12)
+ * {text,timestamp,timestamptz} */
+ {2070, "timezone", true}, /* immutable, internal(12)
+ * {timestamp,timestamptz,interval} */
+ {2073, "substring", true}, /* immutable, internal(12) {text,text,text} */
+ {2074, "substring", true}, /* immutable, sql(14) {text,text,text,text} */
+ {2075, "bit", true}, /* immutable, internal(12) {int8,int4,bit} */
+ {2076, "int8", true}, /* immutable, internal(12) {int8,bit} */
+ {2077, "current_setting", false}, /* stable, internal(12) {text,text} */
+ {2078, "set_config", false}, /* volatile, internal(12)
+ * {bool,text,text,text} */
+ {2085, "substr", true}, /* immutable, internal(12)
+ * {bytea,bytea,int4,int4} */
+ {2086, "substr", true}, /* immutable, internal(12) {bytea,bytea,int4} */
+ {2087, "replace", true}, /* immutable, internal(12)
+ * {text,text,text,text} */
+ {2088, "split_part", true}, /* immutable, internal(12)
+ * {int4,text,text,text} */
+ {2089, "to_hex", true}, /* immutable, internal(12) {int4,text} */
+ {2090, "to_hex", true}, /* immutable, internal(12) {int8,text} */
+ {2091, "array_lower", true}, /* immutable, internal(12)
+ * {int4,int4,anyarray} */
+ {2092, "array_upper", true}, /* immutable, internal(12)
+ * {int4,int4,anyarray} */
+ {2167, "ceiling", true}, /* immutable, internal(12) {numeric,numeric} */
+ {2169, "power", true}, /* immutable, internal(12)
+ * {numeric,numeric,numeric} */
+ {2170, "width_bucket", true}, /* immutable, internal(12)
+ * {int4,int4,numeric,numeric,numeric} */
+ {2176, "array_length", false}, /* immutable, internal(12)
+ * {int4,int4,anyarray} */
+ {2284, "regexp_replace", true}, /* immutable, internal(12)
+ * {text,text,text,text} */
+ {2285, "regexp_replace", true}, /* immutable, internal(12)
+ * {text,text,text,text,text} */
+ {2288, "pg_size_pretty", false}, /* volatile, internal(12) {int8,text} */
+ {2308, "ceil", true}, /* immutable, internal(12) {float8,float8} */
+ {2309, "floor", true}, /* immutable, internal(12) {float8,float8} */
+ {2310, "sign", true}, /* immutable, internal(12) {float8,float8} */
+ {2311, "md5", true}, /* immutable, internal(12) {text,text} */
+ {2319, "pg_encoding_max_length", false}, /* immutable, internal(12)
+ * {int4,int4} */
+ {2320, "ceiling", true}, /* immutable, internal(12) {float8,float8} */
+ {2321, "md5", true}, /* immutable, internal(12) {bytea,text} */
+ {2557, "bool", true}, /* immutable, internal(12) {bool,int4} */
+ {2558, "int4", true}, /* immutable, internal(12) {bool,int4} */
+ {2559, "lastval", false}, /* volatile, internal(12) {int8} */
+ {2560, "pg_postmaster_start_time", false}, /* stable, internal(12)
+ * {timestamptz} */
+ {2621, "pg_reload_conf", false}, /* volatile, internal(12) {bool} */
+ {2622, "pg_rotate_logfile", false}, /* volatile, internal(12) {bool} */
+ {2623, "pg_stat_file", false}, /* volatile, internal(12) {text,record} */
+ {2624, "pg_read_file", false}, /* volatile, internal(12)
+ * {int8,int8,text,text} */
+ {2626, "pg_sleep", false}, /* volatile, internal(12) {float8,void} */
+ {2647, "transaction_timestamp", false}, /* stable, internal(12)
+ * {timestamptz} */
+ {2648, "statement_timestamp", false}, /* stable, internal(12)
+ * {timestamptz} */
+ {2649, "clock_timestamp", true}, /* volatile, internal(12)
+ * {timestamptz} */
+ {2705, "pg_has_role", false}, /* stable, internal(12)
+ * {bool,name,name,text} */
+ {2709, "pg_has_role", false}, /* stable, internal(12)
+ * {bool,name,text} */
+ {2711, "justify_interval", true}, /* immutable, internal(12)
+ * {interval,interval} */
+ {2767, "regexp_split_to_array", false}, /* immutable, internal(12)
+ * {text,text,_text} */
+ {2768, "regexp_split_to_array", false}, /* immutable, internal(12)
+ * {text,text,text,_text} */
+ {2971, "text", true}, /* immutable, internal(12) {bool,text} */
+ {3030, "overlay", true}, /* immutable, internal(12)
+ * {int4,int4,bit,bit,bit} */
+ {3031, "overlay", true}, /* immutable, internal(12) {int4,bit,bit,bit} */
+ {3032, "get_bit", true}, /* immutable, internal(12) {int4,int4,bit} */
+ {3033, "set_bit", true}, /* immutable, internal(12) {int4,int4,bit,bit} */
+ {3036, "pg_notify", false}, /* volatile, internal(12) {text,text,void} */
+ {3051, "xml_is_well_formed", false}, /* stable, internal(12)
+ * {bool,text} */
+ {3052, "xml_is_well_formed_document", false}, /* immutable, internal(12)
+ * {bool,text} */
+ {3053, "xml_is_well_formed_content", false}, /* immutable, internal(12)
+ * {bool,text} */
+ {3058, "concat", true}, /* stable, internal(12) {text,any} */
+ {3059, "concat_ws", true}, /* stable, internal(12) {text,text,any} */
+ {3060, "left", true}, /* immutable, internal(12) {int4,text,text} */
+ {3061, "right", true}, /* immutable, internal(12) {int4,text,text} */
+ {3062, "reverse", true}, /* immutable, internal(12) {text,text} */
+ {3162, "pg_collation_for", false}, /* stable, internal(12) {text,any} */
+ {3166, "pg_size_pretty", false}, /* volatile, internal(12)
+ * {text,numeric} */
+ {3167, "array_remove", false}, /* immutable, internal(12)
+ * {anyarray,anyarray,anyelement} */
+ {3168, "array_replace", false}, /* immutable, internal(12)
+ * {anyarray,anyarray,anyelement,anyelement} */
+ {3179, "cardinality", false}, /* immutable, internal(12) {int4,anyarray} */
+ {3461, "make_timestamp", true}, /* immutable, internal(12)
+ * {int4,int4,int4,int4,int4,float8,timestamp} */
+ {3462, "make_timestamptz", true}, /* stable, internal(12)
+ * {int4,int4,int4,int4,int4,float8,timestamptz} */
+ {3463, "make_timestamptz", true}, /* stable, internal(12)
+ * {int4,int4,int4,int4,int4,text,float8,timestamptz} */
+ {3464, "make_interval", true}, /* immutable, internal(12)
+ * {int4,int4,int4,int4,int4,int4,float8,interval} */
+ {3528, "enum_first", false}, /* stable, internal(12)
+ * {anyenum,anyenum} */
+ {3529, "enum_last", false}, /* stable, internal(12) {anyenum,anyenum} */
+ {3530, "enum_range", false}, /* stable, internal(12)
+ * {anyarray,anyenum,anyenum} */
+ {3531, "enum_range", false}, /* stable, internal(12)
+ * {anyarray,anyenum} */
+ {3533, "enum_send", false}, /* stable, internal(12) {bytea,anyenum} */
+ {3539, "format", false}, /* stable, internal(12) {text,text,any} */
+ {3540, "format", true}, /* stable, internal(12) {text,text} */
+ {3811, "money", true}, /* stable, internal(12) {int4,money} */
+ {3812, "money", true}, /* stable, internal(12) {int8,money} */
+ {3823, "numeric", true}, /* stable, internal(12) {money,numeric} */
+ {3824, "money", true}, /* stable, internal(12) {money,numeric} */
+ {3846, "make_date", true}, /* immutable, internal(12)
+ * {int4,int4,int4,date} */
+ {3847, "make_time", true}, /* immutable, internal(12)
+ * {int4,int4,float8,time} */
+ {3935, "pg_sleep_for", false}, /* volatile, sql(14) {interval,void} */
+ {3936, "pg_sleep_until", false}, /* volatile, sql(14)
+ * {timestamptz,void} */
+ {4350, "normalize", true}, /* immutable, internal(12) {text,text,text} */
+ {4351, "is_normalized", true}, /* immutable, internal(12)
+ * {bool,text,text} */
+ {5044, "gcd", true}, /* immutable, internal(12) {int4,int4,int4} */
+ {5045, "gcd", true}, /* immutable, internal(12) {int8,int8,int8} */
+ {5046, "lcm", true}, /* immutable, internal(12) {int4,int4,int4} */
+ {5047, "lcm", true}, /* immutable, internal(12) {int8,int8,int8} */
+ {5048, "gcd", true}, /* immutable, internal(12)
+ * {numeric,numeric,numeric} */
+ {5049, "lcm", true}, /* immutable, internal(12)
+ * {numeric,numeric,numeric} */
+ {6162, "bit_count", true}, /* immutable, internal(12) {int8,bit} */
+ {6163, "bit_count", true}, /* immutable, internal(12) {bytea,int8} */
+ {6177, "date_bin", true}, /* immutable, internal(12)
+ * {timestamp,timestamp,timestamp,interval} */
+ {6178, "date_bin", true}, /* immutable, internal(12)
+ * {timestamptz,timestamptz,timestamptz,interval} */
+ {6195, "ltrim", true}, /* immutable, internal(12) {bytea,bytea,bytea} */
+ {6196, "rtrim", true}, /* immutable, internal(12) {bytea,bytea,bytea} */
+ {6198, "unistr", true}, /* immutable, internal(12) {text,text} */
+ {6199, "extract", true}, /* immutable, internal(12) {text,date,numeric} */
+ {6200, "extract", true}, /* immutable, internal(12) {text,time,numeric} */
+ {6201, "extract", true}, /* immutable, internal(12)
+ * {text,timetz,numeric} */
+ {6202, "extract", true}, /* immutable, internal(12)
+ * {text,timestamp,numeric} */
+ {6203, "extract", true}, /* stable, internal(12)
+ * {text,timestamptz,numeric} */
+ {6204, "extract", true}, /* immutable, internal(12)
+ * {text,interval,numeric} */
+};
+
+/** Maximum number of arguments for specially treated user defined function */
+#define VCI_MAX_APPLICABLE_UDF_NARGS (2)
+
+/**
+ * Template to specify a specially treated user defined function
+ */
+typedef struct
+{
+ const char *name; /* Function name */
+ Oid namespace; /* Namespace */
+ int16 nargs; /* Number of arguments */
+ Oid rettype; /* Function return type */
+ /** Function argument types. The number of elements is specified by nargs */
+ Oid argtypes[VCI_MAX_APPLICABLE_UDF_NARGS];
+} vci_applicable_udf_template;
+
+/*
+ * Index numbers that are treated specially among applicable_udf_table[]
+ */
+#define APPLICABLE_UDF_TABLE_VCI_RUNS_IN_PLAN_INDEX (0)
+#define APPLICABLE_UDF_TABLE_VCI_ALWAYS_RETURN_TRUE (1)
+
+/**
+ * Template table for specially treated user defined function
+ *
+ * However the top 2 functions are treated specially and have fixed array positions.
+ */
+static vci_applicable_udf_template applicable_udf_table[] = {
+
+ {"vci_runs_in_plan", PG_PUBLIC_NAMESPACE, 0, BOOLOID, {0, 0}},
+ {"vci_always_return_true", PG_PUBLIC_NAMESPACE, 0, BOOLOID, {0, 0}},
+
+ {"vci_runs_in_query", PG_PUBLIC_NAMESPACE, 0, BOOLOID, {0, 0}},
+ {"hamming_distance", PG_PUBLIC_NAMESPACE, 2, INT4OID, {BITOID, BITOID}}
+};
+
+static bool is_supported_udf(Oid oid);
+
+/**
+ * Determine if the given oid is a function that VCI can support
+ *
+ * @param[in] oid OID (pg_proc.oid) indicating the function to be determined
+ * @return true if supported, false otherwise
+ */
+bool
+vci_is_supported_function(Oid oid)
+{
+ int min,
+ max,
+ pivot;
+
+ if (FirstNormalObjectId <= oid)
+ return is_supported_udf(oid);
+
+ if ((oid < VCI_SUPPORTED_FUNC_MIN) || (VCI_SUPPORTED_FUNC_MAX < oid))
+ return false;
+
+ /* 2 minute search */
+
+ min = 0;
+ max = lengthof(vci_supported_func_table); /* exclusive */
+
+ while (max - min > 1)
+ {
+ Oid comp;
+
+ pivot = (min + max) / 2;
+
+ comp = vci_supported_func_table[pivot].oid;
+
+ if (comp == oid)
+ return vci_supported_func_table[pivot].is_support;
+ else if (oid < comp)
+ max = pivot;
+ else /* comp < oid */
+ min = pivot;
+ }
+
+ if (max - min == 1)
+ if (oid == vci_supported_func_table[min].oid)
+ return vci_supported_func_table[min].is_support;
+
+ return false;
+}
+
+/**
+ * Determine if the given user-defined functions indicated by the oid is treated specially by VCI
+ *
+ * @param[in] oid OID (pg_proc.oid) indicating the function to be determined
+ * @return true if supported, false otherwise
+ */
+static bool
+is_supported_udf(Oid oid)
+{
+ bool result;
+
+ result = false;
+
+ for (int i = 0; i < vci_special_udf_info.num_applicable_udfs; i++)
+ {
+ if (oid == vci_special_udf_info.applicable_udfs[i])
+ {
+ result = true;
+ break;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Register user defined function for special handling
+ *
+ * @param[in] snapshot Current snapshot
+ *
+ * This function is called every time before attempting to rewrite the VCI plan,
+ * but the actual registration process is only called once within the PostgreSQL instance.
+ */
+void
+vci_register_applicable_udf(Snapshot snapshot)
+{
+ bool already_registerd;
+ MemoryContext tmpcontext,
+ oldcontext;
+ Relation rel;
+ TableScanDesc scan;
+ HeapTuple tuple;
+
+ already_registerd = (vci_special_udf_info.num_applicable_udfs > 0);
+
+ if (already_registerd)
+ return;
+
+ /*
+ * To use fmgr_info, a temporary memory context is needed, but since
+ * CurrentMemoryContext is SMC here, create child memory context from
+ * MessageContext.
+ */
+ tmpcontext = AllocSetContextCreate(MessageContext,
+ "Register Applicable UDF",
+ ALLOCSET_DEFAULT_SIZES);
+
+ oldcontext = MemoryContextSwitchTo(tmpcontext);
+
+ rel = table_open(ProcedureRelationId, AccessShareLock);
+ scan = table_beginscan(rel, snapshot, 0, NULL);
+
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Oid funcoid;
+ Form_pg_proc procform;
+
+ funcoid = ((Form_pg_proc) GETSTRUCT(tuple))->oid;
+
+ /*
+ * UDF always takes an OID greater than or equal to
+ * FirstNormalObjectId
+ */
+ if (funcoid < FirstNormalObjectId)
+ continue;
+
+ procform = (Form_pg_proc) GETSTRUCT(tuple);
+
+ /*
+ * Check if tuple matches an entry in the template table
+ */
+ for (int i = 0; i < lengthof(applicable_udf_table); i++)
+ {
+ vci_applicable_udf_template *entry = &applicable_udf_table[i];
+
+ if ((procform->pronamespace != entry->namespace) ||
+ (procform->pronargs != entry->nargs) ||
+ (procform->prorettype != entry->rettype) ||
+ (strcmp(NameStr(procform->proname), entry->name) != 0))
+ goto next;
+
+ for (int j = 0; j < Min(entry->nargs, VCI_MAX_APPLICABLE_UDF_NARGS); j++)
+ if (procform->proargtypes.values[j] != entry->argtypes[j])
+ goto next;
+
+ vci_special_udf_info.applicable_udfs[vci_special_udf_info.num_applicable_udfs++]
+ = funcoid;
+
+ if (i == APPLICABLE_UDF_TABLE_VCI_RUNS_IN_PLAN_INDEX)
+ vci_special_udf_info.vci_runs_in_plan_funcoid = funcoid;
+ else if (i == APPLICABLE_UDF_TABLE_VCI_ALWAYS_RETURN_TRUE)
+ vci_special_udf_info.vci_always_return_true_funcoid = funcoid;
+
+ break;
+
+ next:
+ ;
+ }
+ }
+
+ table_endscan(scan);
+ table_close(rel, AccessShareLock);
+
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(tmpcontext);
+}
+
+/*==========================================================================*/
+/* Implementation of PG function to check supported functions at CREATE EXTENSION */
+/*==========================================================================*/
+
+PG_FUNCTION_INFO_V1(vci_check_supported_functions);
+
+Datum
+vci_check_supported_functions(PG_FUNCTION_ARGS)
+{
+ Relation rel;
+
+ rel = table_open(ProcedureRelationId, AccessShareLock);
+
+ for (int i = 0; i < lengthof(vci_supported_func_table); i++)
+ {
+ HeapTuple tuple;
+ Form_pg_proc procform;
+
+ if (!vci_supported_func_table[i].is_support)
+ continue;
+
+ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(vci_supported_func_table[i].oid));
+ if (!HeapTupleIsValid(tuple))
+ goto error;
+
+ procform = (Form_pg_proc) GETSTRUCT(tuple);
+
+ if (strcmp(vci_supported_func_table[i].name, NameStr(procform->proname)) != 0)
+ goto error;
+
+ ReleaseSysCache(tuple);
+ }
+
+ table_close(rel, AccessShareLock);
+
+ PG_RETURN_VOID();
+
+error:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("extension \"%s\" cannot be installed under this version of PostgreSQL", VCI_STRING)));
+
+ PG_RETURN_VOID();
+}
diff --git a/contrib/vci/vci_supported_funcs.sql b/contrib/vci/vci_supported_funcs.sql
new file mode 100644
index 0000000..cecebe4
--- /dev/null
+++ b/contrib/vci/vci_supported_funcs.sql
@@ -0,0 +1,114 @@
+-- sys_func_table A table that registers the OID of system-related functions from multiple system catalogs
+
+CREATE TEMPORARY TABLE test (funcoid oid);
+INSERT INTO test (funcoid) SELECT unnest(ARRAY[aggfnoid, aggtransfn, aggfinalfn, aggmtransfn, aggminvtransfn, aggmfinalfn]) FROM pg_aggregate;
+INSERT INTO test (funcoid) SELECT amhandler FROM pg_am;
+INSERT INTO test (funcoid) SELECT unnest(ARRAY[amproc]) FROM pg_amproc;
+INSERT INTO test (funcoid) SELECT unnest(ARRAY[castfunc]) FROM pg_cast;
+INSERT INTO test (funcoid) SELECT unnest(ARRAY[conproc]) FROM pg_conversion;
+INSERT INTO test (funcoid) SELECT evtfoid FROM pg_event_trigger;
+INSERT INTO test (funcoid) SELECT unnest(ARRAY[fdwhandler, fdwvalidator]) FROM pg_foreign_data_wrapper;
+INSERT INTO test (funcoid) SELECT unnest(ARRAY[lanplcallfoid, laninline, lanvalidator]) FROM pg_language;
+INSERT INTO test (funcoid) SELECT tgfoid FROM pg_trigger;
+INSERT INTO test (funcoid) SELECT unnest(ARRAY[oprcode, oprrest, oprjoin]) FROM pg_operator;
+INSERT INTO test (funcoid) SELECT unnest(ARRAY[rngcanonical, rngsubdiff]) FROM pg_range;
+INSERT INTO test (funcoid) SELECT unnest(ARRAY[prsstart, prstoken, prsend, prsheadline, prslextype]) FROM pg_ts_parser;
+INSERT INTO test (funcoid) SELECT unnest(ARRAY[tmplinit, tmpllexize]) FROM pg_ts_template;
+INSERT INTO test (funcoid) SELECT unnest(ARRAY[typinput, typoutput, typreceive, typsend, typmodin, typmodout, typanalyze]) FROM pg_type;
+
+DROP TABLE IF EXISTS sys_func_table;
+CREATE TABLE sys_func_table (funcoid oid UNIQUE);
+INSERT INTO sys_func_table SELECT distinct funcoid FROM test WHERE funcoid > 0 ORDER BY funcoid;
+
+DROP TABLE IF EXISTS safe_types;
+CREATE TABLE safe_types (typeoid oid UNIQUE);
+INSERT INTO safe_types (typeoid) VALUES
+ (16), -- bool
+ (17), -- bytea
+ (18), -- char
+ (19), -- name
+ (20), -- int8
+ (21), -- int2
+ (23), -- int4
+-- (24) -- regproc
+ (25), -- text
+-- (26) -- oid
+-- (27) -- tid
+-- (28) -- xid
+-- (30) -- oidvector
+-- (71) -- pg_type
+-- (75) -- pg_attribute
+-- (114) -- json [not supported]
+-- (142) -- xml [not supported]
+-- (143) -- _xml [not supported]
+-- (194) -- pg_node_tree
+-- (210) -- smgr
+-- (600), -- point [not supported]
+-- (601), -- lseg [not supported]
+-- (602), -- path [not supported]
+-- (603), -- box [not supported]
+-- (604), -- polygon [not supported]
+-- (628), -- line [not supported]
+ (700), -- float4
+ (701), -- float8
+-- (718), -- circle [not supported]
+ (790), -- money
+-- (829), -- macaddr [not supported]
+-- (869), -- inet [not supported]
+-- (650), -- cidr [not supported]
+ (1003), -- _name
+ (1005), -- _int2
+ (1007), -- _int4
+ (1009), -- _text
+ (1021), -- _float4
+-- (1033) -- aclitem
+-- (1034) -- _aclitem
+ (1042), -- bpchar
+ (1082), -- date
+ (1083), -- time
+ (1114), -- timestamp
+ (1184), -- timestamptz
+ (1186), -- interval
+ (1266), -- timetz
+ (1560), -- bit
+ (1700), -- numeric
+-- (1790), -- refcursor
+-- (2202), -- regprocedure
+-- (2203), -- regoper
+-- (2204), -- regoperator
+-- (2205), -- regclass
+-- (2206), -- regtype
+-- (3220), -- pg_lsn
+-- (3614), -- tsvector [not supported]
+-- (3615), -- tsquery [not supported]
+-- (3734), -- regconfig
+-- (3769), -- regdictionary
+-- (3802), -- jsonb [not supported]
+-- (2970), -- txid_snapshot
+-- (3904), -- int4range [not supported]
+-- (3906), -- numrange [not supported]
+-- (3908), -- tsrange [not supported]
+-- (3910), -- tstzrange [not supported]
+-- (3912), -- daterange [not supported]
+-- (3926), -- int8range [not supported]
+ (2249), -- record
+-- (2275) -- cstring
+ (2276), -- any
+ (2277), -- anyarray
+ (2278), -- void
+-- (2279) -- trigger
+-- (2281) -- internal
+-- (2282) -- opaque
+ (2283), -- anyelement
+ (3500); -- anyenum
+-- (3831) -- anyrange
+
+DROP FUNCTION IF EXISTS print_typename;
+CREATE FUNCTION print_typename(IN oids _oid) RETURNS _name AS $$
+ SELECT array_agg(pg_type.typname) FROM unnest(oids) AS t(i), pg_type WHERE i = pg_type.oid;
+$$ LANGUAGE SQL;
+
+SELECT oid, proname, provolatile, prolang, print_typename(array_prepend(prorettype, proargtypes)) FROM pg_proc WHERE prokind = 'f' AND NOT proretset
+ AND NOT EXISTS (SELECT funcoid FROM sys_func_table WHERE pg_proc.oid = sys_func_table.funcoid)
+ AND (SELECT bool_and(i IN (SELECT typeoid FROM safe_types)) FROM unnest(array_prepend(prorettype, proargtypes)) AS t(i))
+ AND oid < 16384 ORDER BY oid;
diff --git a/contrib/vci/vci_supported_types.c b/contrib/vci/vci_supported_types.c
new file mode 100644
index 0000000..30448df
--- /dev/null
+++ b/contrib/vci/vci_supported_types.c
@@ -0,0 +1,244 @@
+/*-------------------------------------------------------------------------
+ *
+ * vci_supported_types.c
+ * Types supported by VCI
+ *
+ * vci_supported_type_table[] is created with following SQL and then examined individually.
+ *
+ * SELECT oid, typname FROM pg_type WHERE typnamespace = 11 AND typrelid = 0 AND typelem = 0 ORDER BY oid;
+ *
+ * - 'typnamespace = 11' is to exclude types not related to table structure
+ * - 'typelem = 0' is to exclude array type
+ * - 'typrelid = 0' is to exclude composite type
+ *
+ * Portions Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/vci/vci_supported_types.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup.h"
+#include "access/htup_details.h"
+#include "c.h"
+#include "catalog/pg_type.h" /* for TypeRelationId, Form_pg_type */
+#include "fmgr.h"
+#include "utils/elog.h"
+#include "utils/relcache.h"
+#include "utils/syscache.h"
+
+#include "vci.h"
+#include "vci_supported_oid.h"
+
+/**
+ * Smallest OID among types supported by VCI
+ */
+#define VCI_SUPPORTED_TYPE_MIN (16)
+
+/**
+ * Biggest OID among types supported by VCI
+ */
+#define VCI_SUPPORTED_TYPE_MAX (2950)
+
+/**
+ * Array of information about types supported by VCI
+ */
+static const struct
+{
+ Oid oid;
+ const char *name;
+ bool is_support;
+} vci_supported_type_table[] = {
+ {16, "bool", true}, /* BOOLOID */
+ {17, "bytea", true}, /* BYTEAOID */
+ {18, "char", true}, /* CHAROID */
+ {20, "int8", true}, /* INT8OID */
+ {21, "int2", true}, /* INT2OID */
+ {23, "int4", true}, /* INT4OID */
+ {24, "regproc", false}, /* REGPROCOID */
+ {25, "text", true}, /* TEXTOID */
+ {26, "oid", false}, /* OIDOID */
+ {27, "tid", false}, /* TIDOID */
+ {28, "xid", false}, /* XIDOID */
+ {29, "cid", false}, /* CIDOID */
+ {32, "pg_ddl_command", false}, /* PG_DDL_COMMANDOID */
+ {114, "json", false}, /* JSONOID */
+ {142, "xml", false}, /* XMLOID */
+ {194, "pg_node_tree", false}, /* PG_NODE_TREEOID */
+ {269, "table_am_handler", false}, /* TABLE_AM_HANDLEROID */
+ {325, "index_am_handler", false}, /* INDEX_AM_HANDLEROID */
+ {602, "path", false}, /* PATHOID */
+ {604, "polygon", false}, /* POLYGONOID */
+ {650, "cidr", false}, /* CIDROID */
+ {700, "float4", true}, /* FLOAT4OID */
+ {701, "float8", true}, /* FLOAT8OID */
+ {705, "unknown", false}, /* UNKNOWNOID */
+ {718, "circle", false}, /* CIRCLEOID */
+ {774, "macaddr8", false}, /* MACADDR8OID */
+ {790, "money", true}, /* MONEYOID */
+ {829, "macaddr", false}, /* MACADDROID */
+ {869, "inet", false}, /* INETOID */
+ {1033, "aclitem", false}, /* ACLITEMOID */
+ {1042, "bpchar", true}, /* BPCHAROID */
+ {1043, "varchar", true}, /* VARCHAROID */
+ {1082, "date", true}, /* DATEOID */
+ {1083, "time", true}, /* TIMEOID */
+ {1114, "timestamp", true}, /* TIMESTAMPOID */
+ {1184, "timestamptz", true}, /* TIMESTAMPTZOID */
+ {1186, "interval", true}, /* INTERVALOID */
+ {1266, "timetz", true}, /* TIMETZOID */
+ {1560, "bit", true}, /* BITOID */
+ {1562, "varbit", true}, /* VARBITOID */
+ {1700, "numeric", true}, /* NUMERICOID */
+ {1790, "refcursor", false}, /* REFCURSOROID */
+ {2202, "regprocedure", false}, /* REGPROCEDUREOID */
+ {2203, "regoper", false}, /* REGOPEROID */
+ {2204, "regoperator", false}, /* REGOPERATOROID */
+ {2205, "regclass", false}, /* REGCLASSOID */
+ {2206, "regtype", false}, /* REGTYPEOID */
+ {2249, "record", false}, /* RECORDOID */
+ {2275, "cstring", false}, /* CSTRINGOID */
+ {2276, "any", false}, /* ANYOID */
+ {2277, "anyarray", false}, /* ANYARRAYOID */
+ {2278, "void", false}, /* VOIDOID */
+ {2279, "trigger", false}, /* TRIGGEROID */
+ {2280, "language_handler", false}, /* LANGUAGE_HANDLEROID */
+ {2281, "internal", false}, /* INTERNALOID */
+ {2283, "anyelement", false}, /* ANYELEMENTOID */
+ {2776, "anynonarray", false}, /* ANYNONARRAYOID */
+ {2950, "uuid", true}, /* UUIDOID */
+ {2970, "txid_snapshot", false},
+ {3115, "fdw_handler", false}, /* FDW_HANDLEROID */
+ {3220, "pg_lsn", false}, /* PG_LSNOID */
+ {3310, "tsm_handler", false}, /* TSM_HANDLEROID */
+ {3361, "pg_ndistinct", false}, /* PG_NDISTINCTOID */
+ {3402, "pg_dependencies", false}, /* PG_DEPENDENCIESOID */
+ {3500, "anyenum", false}, /* ANYENUMOID */
+ {3614, "tsvector", false}, /* TSVECTOROID */
+ {3615, "tsquery", false}, /* TSQUERYOID */
+ {3642, "gtsvector", false}, /* GTSVECTOROID */
+ {3734, "regconfig", false}, /* REGCONFIGOID */
+ {3769, "regdictionary", false}, /* REGDICTIONARYOID */
+ {3802, "jsonb", false}, /* JSONBOID */
+ {3831, "anyrange", false}, /* ANYRANGEOID */
+ {3838, "event_trigger", false}, /* EVENT_TRIGGEROID */
+ {3904, "int4range", false}, /* INT4RANGEOID */
+ {3906, "numrange", false},
+ {3908, "tsrange", false},
+ {3910, "tstzrange", false},
+ {3912, "daterange", false},
+ {3926, "int8range", false},
+ {4072, "jsonpath", false}, /* JSONPATHOID */
+ {4089, "regnamespace", false}, /* REGNAMESPACEOID */
+ {4096, "regrole", false}, /* REGROLEOID */
+ {4191, "regcollation", false}, /* REGCOLLATIONOID */
+ {4451, "int4multirange", false},
+ {4532, "nummultirange", false},
+ {4533, "tsmultirange", false},
+ {4534, "tstzmultirange", false},
+ {4535, "datemultirange", false},
+ {4536, "int8multirange", false},
+ {4537, "anymultirange", false},
+ {4538, "anycompatiblemultirange", false},
+ {4600, "pg_brin_bloom_summary", false}, /* PG_BRIN_BLOOM_SUMMARYOID */
+ {4601, "pg_brin_minmax_multi_summary", false}, /* PG_BRIN_MINMAX_MULTI_SUMMARYOID */
+ {5017, "pg_mcv_list", false}, /* PG_MCV_LISTOID */
+ {5038, "pg_snapshot", false}, /* PG_SNAPSHOTOID */
+ {5069, "xid8", false}, /* XID8OID */
+ {5077, "anycompatible", false}, /* ANYCOMPATIBLEOID */
+ {5078, "anycompatiblearray", false}, /* ANYCOMPATIBLEARRAYOID */
+ {5079, "anycompatiblenonarray", false}, /* ANYCOMPATIBLENONARRAYOID */
+ {5080, "anycompatiblerange", false}, /* ANYCOMPATIBLERANGEOID */
+};
+
+/**
+ * Determine if the given oid is a type that can be supported by VCI
+ *
+ * @param[in] oid OID (pg_proc.oid) indicating the type to be determined
+ * @return true if supported, false otherwise
+ */
+bool
+vci_is_supported_type(Oid oid)
+{
+ int min,
+ max,
+ pivot;
+
+ if ((oid < VCI_SUPPORTED_TYPE_MIN) || (VCI_SUPPORTED_TYPE_MAX < oid))
+ return false;
+
+ /* 2 minute search */
+
+ min = 0;
+ max = lengthof(vci_supported_type_table); /* exclusive */
+
+ while (max - min > 1)
+ {
+ Oid comp;
+
+ pivot = (min + max) / 2;
+
+ comp = vci_supported_type_table[pivot].oid;
+
+ if (comp == oid)
+ return vci_supported_type_table[pivot].is_support;
+ else if (oid < comp)
+ max = pivot;
+ else /* comp < oid */
+ min = pivot;
+ }
+
+ if (max - min == 1)
+ if (oid == vci_supported_type_table[min].oid)
+ return vci_supported_type_table[min].is_support;
+
+ return false;
+}
+
+/*==========================================================================*/
+/* Implementation of PG function to check supported types at CREATE EXTENSION */
+/*==========================================================================*/
+
+PG_FUNCTION_INFO_V1(vci_check_supported_types);
+
+Datum
+vci_check_supported_types(PG_FUNCTION_ARGS)
+{
+ Relation rel;
+
+ rel = table_open(TypeRelationId, AccessShareLock);
+
+ for (int i = 0; i < lengthof(vci_supported_type_table); i++)
+ {
+ HeapTuple tuple;
+ Form_pg_type typeform;
+
+ if (!vci_supported_type_table[i].is_support)
+ continue;
+
+ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(vci_supported_type_table[i].oid));
+ if (!HeapTupleIsValid(tuple))
+ goto error;
+
+ typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+ if (strcmp(vci_supported_type_table[i].name, NameStr(typeform->typname)) != 0)
+ goto error;
+
+ ReleaseSysCache(tuple);
+ }
+
+ table_close(rel, AccessShareLock);
+
+ PG_RETURN_VOID();
+
+error:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("extension \"%s\" cannot be installed under this version of PostgreSQL", VCI_STRING)));
+
+ PG_RETURN_VOID();
+}
--
1.8.3.1