Repository navigation
Operator class support
This document describes the implementation of operator class (opclass) support in Springtail, enabling the system to handle GIN and GiST secondary indexes in addition to the existing B-tree indexes.
Status: This feature is currently in development on branch
SPR-1090-gin-gist-base-3and has not been merged tomain.
PostgreSQL uses operator classes to define the behavior of indexes for different data types. Each index type (B-tree, GIN, GiST) requires specific support functions identified by support numbers. For example:
-
GIN indexes use functions like
extractValue,extractQuery, andconsistent -
GiST indexes use functions like
consistent,union,compress,decompress, andpenalty
Previously, Springtail only supported B-tree secondary indexes. This implementation extends the system to:
- Capture and store operator class metadata from PostgreSQL
- Route index operations to the appropriate opclass-specific functions
- Provide the foundation for building and maintaining GIN/GiST indexes
- Store
opclass(operator class name) for each index column andindex_type(btree, gin, gist) for each index - Enable dynamic invocation of opclass support functions via
OpClassHandler - Prepare the indexer infrastructure to handle non-B-tree index types
struct OpClassHandler {
using OpClassFunc = uintptr_t (*)(const std::string& opclass_name,
int support_number,
uintptr_t /*Datum*/ datum);
OpClassFunc opclass_func = nullptr;
ExtensionContext context = {};
};This handler encapsulates a callback for invoking opclass-specific functions. The support_number parameter identifies which support function to call (e.g., GIST_CONSISTENT = 1, GIN_COMPARE = 1).
static constexpr std::string_view INDEX_TYPE_GIN = "gin";
static constexpr std::string_view INDEX_TYPE_GIST = "gist";
static constexpr std::string_view INDEX_TYPE_BTREE = "btree";Extended PgMsgSchemaIndexColumn with:
std::string opclass; // operator class name (e.g., "tsvector_ops", "int4_ops")Extended PgMsgIndex with:
std::string index_type; // "gin", "gist", or "btree"Extended Index::Column with:
std::string opclass;Extended Index with:
std::string index_type;Indexes table - Added column:
| Column | Position | Type |
|---|---|---|
| OPCLASS | 6 | TEXT |
IndexNames table - Added column:
| Column | Position | Type |
|---|---|---|
| INDEX_TYPE | 8 | TEXT |
Modified the index creation trigger to extract opclass and index type from PostgreSQL system catalogs:
SELECT
i.indexrelid AS index_oid,
i.indclass AS indclass,
am.amname AS index_type
FROM pg_index i
JOIN pg_class ic ON ic.oid = i.indexrelid
JOIN pg_am am ON am.oid = ic.relam
...
-- Extract opclass for each column
SELECT
opc.opcname AS opclass
FROM unnest(ind_obj.indkey, ind_obj.indclass)
WITH ORDINALITY AS u(attnum, opclass_oid, ord)
JOIN pg_opclass opc ON opc.oid = u.opclass_oidThis captures:
-
am.amname: The access method name (btree, gin, gist, brin) -
opc.opcname: The operator class name for each index column
Extended constructor to accept opclass handler and index type:
MutableBTree(uint64_t database_id,
const std::filesystem::path &file,
const std::vector<uint32_t> &keys,
ExtentSchemaPtr schema,
uint64_t xid,
uint64_t max_extent_size,
const ExtensionCallback &extension_callback = {},
const OpClassHandler &opclass_handler = {},
const std::string_view index_type = constant::INDEX_TYPE_BTREE);New member variables:
OpClassHandler _opclass_handler;
std::string_view _index_type;Extended create_index_root signature:
MutableBTreePtr create_index_root(
uint64_t index_id,
const std::vector<uint32_t>& index_columns,
const ExtensionCallback& extension_callback = {},
const OpClassHandler& opclass_handler = {},
const std::string_view index_type = constant::INDEX_TYPE_BTREE);Extended get_snapshot_table to accept OpClassHandler:
MutableTablePtr get_snapshot_table(
uint64_t db_id,
uint64_t table_id,
uint64_t snapshot_xid,
ExtentSchemaPtr schema,
const std::vector<Index>& secondary_keys,
const ExtensionCallback &extension_callback = {},
const OpClassHandler &opclass_handler = {});The indexer now branches based on index type:
if (idx._index_request.index().index_type() == constant::INDEX_TYPE_GIN) {
//XXX: Build GIN INDEX
} else {
// Default - btree index builder
root = mutable_table->create_index_root(index_id, idx_cols,
{PgExtnRegistry::get_instance()->comparator_func});
// ... existing B-tree build logic
}Similar branching exists for:
- Index invalidation during updates
- Index population during reconciliation
message IndexColumn {
string name = 1;
int32 position = 2;
int32 idx_position = 3;
string opclass = 4; // NEW
}
message IndexInfo {
...
string index_type = 10; // NEW
}PostgreSQL CREATE INDEX
↓
DDL Trigger (triggers.sql)
↓
Extract: opclass, index_type from pg_opclass, pg_am
↓
Replication Message (PgMsgIndex)
↓
sys_tbl_mgr::Server::_create_index()
↓
Store in IndexNames (index_type) and Indexes (opclass) system tables
↓
Indexer loads Index metadata (includes index_type, opclass per column)
↓
Indexer builds index based on index_type
↓
Create MutableBTree with OpClassHandler
↓
For GIN/GiST: invoke opclass methods via OpClassHandler
The core implementation is complete. The remaining blocker is a build failure in the unit test src/pg_fdw/test/where_test.cc.
Issue: The test file imports both table and mutable_table headers simultaneously. These headers have conflicting dependencies—one pulls in custom Springtail extension-related imports while the other includes default PostgreSQL imports, causing symbol conflicts during compilation.
Resolution: Avoid using mutable_table in the test. Instead, load the table data directly and use only the Table class for scan operations during testing.