v20251223-0002-VCI-main-part1.patch

application/octet-stream

Filename: v20251223-0002-VCI-main-part1.patch
Type: application/octet-stream
Part: 2
Message: Re: [WIP]Vertical Clustered Index (columnar store extension) - take2
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