From 907eec874f7612c7e019afe4912ac7f9d14ab4d0 Mon Sep 17 00:00:00 2001 From: Peter Smith 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 +#include +#include + +#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