From 7c9306af289d1b232168129db9b248751f1164e1 Mon Sep 17 00:00:00 2001 From: Alexander Nestorov Date: Thu, 4 Jun 2026 00:06:54 +0200 Subject: [PATCH] Implement cross-type operators for GiST indexes --- contrib/btree_gist/Makefile | 4 +- contrib/btree_gist/btree_gist--1.9--1.10.sql | 133 +++++++++++++++++ contrib/btree_gist/btree_gist.control | 2 +- contrib/btree_gist/btree_int2.c | 87 +++++++++-- contrib/btree_gist/btree_int4.c | 86 +++++++++-- contrib/btree_gist/btree_int8.c | 86 +++++++++-- contrib/btree_gist/btree_utils_num.c | 148 +++++++++++++++++++ contrib/btree_gist/btree_utils_num.h | 85 +++++++++++ contrib/btree_gist/meson.build | 2 + 9 files changed, 597 insertions(+), 36 deletions(-) create mode 100644 contrib/btree_gist/btree_gist--1.9--1.10.sql diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile index fbbbca95598..1d0668d97ab 100644 --- a/contrib/btree_gist/Makefile +++ b/contrib/btree_gist/Makefile @@ -32,19 +32,19 @@ OBJS = \ EXTENSION = btree_gist DATA = btree_gist--1.0--1.1.sql \ btree_gist--1.1--1.2.sql btree_gist--1.2--1.3.sql \ btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \ btree_gist--1.5--1.6.sql btree_gist--1.6--1.7.sql \ btree_gist--1.7--1.8.sql btree_gist--1.8--1.9.sql \ - btree_gist--1.9.sql + btree_gist--1.9.sql btree_gist--1.9--1.10.sql PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes" REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \ time timetz date interval macaddr macaddr8 inet cidr text varchar char \ bytea bit varbit numeric uuid not_equal enum bool partitions \ - stratnum without_overlaps + stratnum int_crosstype without_overlaps SHLIB_LINK += $(filter -lm, $(LIBS)) ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) diff --git a/contrib/btree_gist/btree_gist--1.9--1.10.sql b/contrib/btree_gist/btree_gist--1.9--1.10.sql new file mode 100644 index 00000000000..f338b854b30 --- /dev/null +++ b/contrib/btree_gist/btree_gist--1.9--1.10.sql @@ -0,0 +1,133 @@ +/* contrib/btree_gist/btree_gist--1.9--1.10.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.10'" to load this file. \quit + +-- Add cross-type operator support for the integer trio (int2, int4, int8) +-- to the existing GiST operator families. +-- +-- GiST's amvalidate requires support functions in a family to have matching +-- left/right input types, so the catalog additions below are deliberately +-- pg_amop-only. The same-type consistent/distance support functions dispatch +-- on the subtype OID and route mixed-width integer comparisons through their +-- C-side dispatch tables. + +CREATE FUNCTION int2_int4_dist(int2, int4) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION int4_int2_dist(int4, int2) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION int2_int8_dist(int2, int8) +RETURNS int8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION int8_int2_dist(int8, int2) +RETURNS int8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION int4_int8_dist(int4, int8) +RETURNS int8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION int8_int4_dist(int8, int4) +RETURNS int8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR <-> ( + LEFTARG = int2, + RIGHTARG = int4, + PROCEDURE = int2_int4_dist, + COMMUTATOR = '<->' +); + +CREATE OPERATOR <-> ( + LEFTARG = int4, + RIGHTARG = int2, + PROCEDURE = int4_int2_dist, + COMMUTATOR = '<->' +); + +CREATE OPERATOR <-> ( + LEFTARG = int2, + RIGHTARG = int8, + PROCEDURE = int2_int8_dist, + COMMUTATOR = '<->' +); + +CREATE OPERATOR <-> ( + LEFTARG = int8, + RIGHTARG = int2, + PROCEDURE = int8_int2_dist, + COMMUTATOR = '<->' +); + +CREATE OPERATOR <-> ( + LEFTARG = int4, + RIGHTARG = int8, + PROCEDURE = int4_int8_dist, + COMMUTATOR = '<->' +); + +CREATE OPERATOR <-> ( + LEFTARG = int8, + RIGHTARG = int4, + PROCEDURE = int8_int4_dist, + COMMUTATOR = '<->' +); + +ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD + OPERATOR 1 < (int2, int4), + OPERATOR 2 <= (int2, int4), + OPERATOR 3 = (int2, int4), + OPERATOR 4 >= (int2, int4), + OPERATOR 5 > (int2, int4), + OPERATOR 6 <> (int2, int4), + OPERATOR 15 <-> (int2, int4) FOR ORDER BY pg_catalog.integer_ops, + OPERATOR 1 < (int2, int8), + OPERATOR 2 <= (int2, int8), + OPERATOR 3 = (int2, int8), + OPERATOR 4 >= (int2, int8), + OPERATOR 5 > (int2, int8), + OPERATOR 6 <> (int2, int8), + OPERATOR 15 <-> (int2, int8) FOR ORDER BY pg_catalog.integer_ops; + +ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD + OPERATOR 1 < (int4, int2), + OPERATOR 2 <= (int4, int2), + OPERATOR 3 = (int4, int2), + OPERATOR 4 >= (int4, int2), + OPERATOR 5 > (int4, int2), + OPERATOR 6 <> (int4, int2), + OPERATOR 15 <-> (int4, int2) FOR ORDER BY pg_catalog.integer_ops, + OPERATOR 1 < (int4, int8), + OPERATOR 2 <= (int4, int8), + OPERATOR 3 = (int4, int8), + OPERATOR 4 >= (int4, int8), + OPERATOR 5 > (int4, int8), + OPERATOR 6 <> (int4, int8), + OPERATOR 15 <-> (int4, int8) FOR ORDER BY pg_catalog.integer_ops; + +ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD + OPERATOR 1 < (int8, int2), + OPERATOR 2 <= (int8, int2), + OPERATOR 3 = (int8, int2), + OPERATOR 4 >= (int8, int2), + OPERATOR 5 > (int8, int2), + OPERATOR 6 <> (int8, int2), + OPERATOR 15 <-> (int8, int2) FOR ORDER BY pg_catalog.integer_ops, + OPERATOR 1 < (int8, int4), + OPERATOR 2 <= (int8, int4), + OPERATOR 3 = (int8, int4), + OPERATOR 4 >= (int8, int4), + OPERATOR 5 > (int8, int4), + OPERATOR 6 <> (int8, int4), + OPERATOR 15 <-> (int8, int4) FOR ORDER BY pg_catalog.integer_ops; diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control index 69d9341a0ad..e606fa6551d 100644 --- a/contrib/btree_gist/btree_gist.control +++ b/contrib/btree_gist/btree_gist.control @@ -1,6 +1,6 @@ # btree_gist extension comment = 'support for indexing common datatypes in GiST' -default_version = '1.9' +default_version = '1.10' module_pathname = '$libdir/btree_gist' relocatable = true trusted = true diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c index cc4b33177e3..1fdf55131b3 100644 --- a/contrib/btree_gist/btree_int2.c +++ b/contrib/btree_gist/btree_int2.c @@ -2,12 +2,13 @@ * contrib/btree_gist/btree_int2.c */ #include "postgres.h" #include "btree_gist.h" #include "btree_utils_num.h" +#include "catalog/pg_type.h" #include "common/int.h" #include "utils/rel.h" #include "utils/sortsupport.h" typedef struct int16key { @@ -73,25 +74,42 @@ gbt_int2key_cmp(const void *a, const void *b, FmgrInfo *flinfo) static float8 gbt_int2_dist(const void *a, const void *b, FmgrInfo *flinfo) { return GET_FLOAT_DISTANCE(int16, a, b); } +/* + * Cross-type callbacks + */ +GBT_DEFINE_INT_CROSSTYPE(gbt_int2_x_int4, int16, DatumGetInt32) +GBT_DEFINE_INT_CROSSTYPE(gbt_int2_x_int8, int16, DatumGetInt64) + +static const gbt_subtype_info gbt_int2_subtype_ops[] = { + {INT4OID, + gbt_int2_x_int4_lt, gbt_int2_x_int4_le, gbt_int2_x_int4_eq, + gbt_int2_x_int4_ge, gbt_int2_x_int4_gt, gbt_int2_x_int4_dist}, + {INT8OID, + gbt_int2_x_int8_lt, gbt_int2_x_int8_le, gbt_int2_x_int8_eq, + gbt_int2_x_int8_ge, gbt_int2_x_int8_gt, gbt_int2_x_int8_dist}, + {InvalidOid} +}; static const gbtree_ninfo tinfo = { gbt_t_int2, sizeof(int16), 4, /* sizeof(gbtreekey4) */ gbt_int2gt, gbt_int2ge, gbt_int2eq, gbt_int2le, gbt_int2lt, gbt_int2key_cmp, - gbt_int2_dist + gbt_int2_dist, + gbt_int2_subtype_ops, + INT2OID }; PG_FUNCTION_INFO_V1(int2_dist); Datum int2_dist(PG_FUNCTION_ARGS) @@ -109,12 +127,46 @@ int2_dist(PG_FUNCTION_ARGS) ra = abs(r); PG_RETURN_INT16(ra); } +PG_FUNCTION_INFO_V1(int2_int4_dist); +Datum +int2_int4_dist(PG_FUNCTION_ARGS) +{ + int32 a = (int32) PG_GETARG_INT16(0); + int32 b = PG_GETARG_INT32(1); + int32 r; + + if (pg_sub_s32_overflow(a, b, &r) || + r == PG_INT32_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + PG_RETURN_INT32(abs(r)); +} + +PG_FUNCTION_INFO_V1(int2_int8_dist); +Datum +int2_int8_dist(PG_FUNCTION_ARGS) +{ + int64 a = (int64) PG_GETARG_INT16(0); + int64 b = PG_GETARG_INT64(1); + int64 r; + + if (pg_sub_s64_overflow(a, b, &r) || + r == PG_INT64_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + PG_RETURN_INT64(i64abs(r)); +} + /************************************************** * GiST support functions **************************************************/ Datum @@ -134,47 +186,60 @@ gbt_int2_fetch(PG_FUNCTION_ARGS) } Datum gbt_int2_consistent(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); - int16 query = PG_GETARG_INT16(1); + Datum queryDatum = PG_GETARG_DATUM(1); StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); -#ifdef NOT_USED Oid subtype = PG_GETARG_OID(3); -#endif bool *recheck = (bool *) PG_GETARG_POINTER(4); int16KEY *kkk = (int16KEY *) DatumGetPointer(entry->key); + int16 query; GBT_NUMKEY_R key; /* All cases served by this function are exact */ *recheck = false; + /* Only decode as int16 on the same-type path to avoid silent truncation */ + if (subtype == InvalidOid || subtype == INT2OID) + query = DatumGetInt16(queryDatum); + else + query = 0; + key.lower = (GBT_NUMKEY *) &kkk->lower; key.upper = (GBT_NUMKEY *) &kkk->upper; - PG_RETURN_BOOL(gbt_num_consistent(&key, &query, &strategy, - GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); + PG_RETURN_BOOL(gbt_num_consistent_x(&key, &query, queryDatum, + subtype, PG_GET_COLLATION(), + &strategy, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); } Datum gbt_int2_distance(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); - int16 query = PG_GETARG_INT16(1); -#ifdef NOT_USED + Datum queryDatum = PG_GETARG_DATUM(1); Oid subtype = PG_GETARG_OID(3); -#endif int16KEY *kkk = (int16KEY *) DatumGetPointer(entry->key); + int16 query; GBT_NUMKEY_R key; + if (subtype == InvalidOid || subtype == INT2OID) + query = DatumGetInt16(queryDatum); + else + query = 0; + key.lower = (GBT_NUMKEY *) &kkk->lower; key.upper = (GBT_NUMKEY *) &kkk->upper; - PG_RETURN_FLOAT8(gbt_num_distance(&key, &query, GIST_LEAF(entry), - &tinfo, fcinfo->flinfo)); + PG_RETURN_FLOAT8(gbt_num_distance_x(&key, &query, queryDatum, + subtype, PG_GET_COLLATION(), + GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); } Datum gbt_int2_union(PG_FUNCTION_ARGS) { GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c index 47790578e6b..7c53ce9c56f 100644 --- a/contrib/btree_gist/btree_int4.c +++ b/contrib/btree_gist/btree_int4.c @@ -1,12 +1,13 @@ /* * contrib/btree_gist/btree_int4.c */ #include "postgres.h" #include "btree_gist.h" #include "btree_utils_num.h" +#include "catalog/pg_type.h" #include "common/int.h" #include "utils/rel.h" #include "utils/sortsupport.h" typedef struct int32key { @@ -71,25 +72,42 @@ gbt_int4key_cmp(const void *a, const void *b, FmgrInfo *flinfo) static float8 gbt_int4_dist(const void *a, const void *b, FmgrInfo *flinfo) { return GET_FLOAT_DISTANCE(int32, a, b); } +/* + * Cross-type callbacks + */ +GBT_DEFINE_INT_CROSSTYPE(gbt_int4_x_int2, int32, DatumGetInt16) +GBT_DEFINE_INT_CROSSTYPE(gbt_int4_x_int8, int32, DatumGetInt64) + +static const gbt_subtype_info gbt_int4_subtype_ops[] = { + {INT2OID, + gbt_int4_x_int2_lt, gbt_int4_x_int2_le, gbt_int4_x_int2_eq, + gbt_int4_x_int2_ge, gbt_int4_x_int2_gt, gbt_int4_x_int2_dist}, + {INT8OID, + gbt_int4_x_int8_lt, gbt_int4_x_int8_le, gbt_int4_x_int8_eq, + gbt_int4_x_int8_ge, gbt_int4_x_int8_gt, gbt_int4_x_int8_dist}, + {InvalidOid} +}; static const gbtree_ninfo tinfo = { gbt_t_int4, sizeof(int32), 8, /* sizeof(gbtreekey8) */ gbt_int4gt, gbt_int4ge, gbt_int4eq, gbt_int4le, gbt_int4lt, gbt_int4key_cmp, - gbt_int4_dist + gbt_int4_dist, + gbt_int4_subtype_ops, + INT4OID }; PG_FUNCTION_INFO_V1(int4_dist); Datum int4_dist(PG_FUNCTION_ARGS) @@ -107,12 +125,46 @@ int4_dist(PG_FUNCTION_ARGS) ra = abs(r); PG_RETURN_INT32(ra); } +PG_FUNCTION_INFO_V1(int4_int2_dist); +Datum +int4_int2_dist(PG_FUNCTION_ARGS) +{ + int32 a = PG_GETARG_INT32(0); + int32 b = (int32) PG_GETARG_INT16(1); + int32 r; + + if (pg_sub_s32_overflow(a, b, &r) || + r == PG_INT32_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + PG_RETURN_INT32(abs(r)); +} + +PG_FUNCTION_INFO_V1(int4_int8_dist); +Datum +int4_int8_dist(PG_FUNCTION_ARGS) +{ + int64 a = (int64) PG_GETARG_INT32(0); + int64 b = PG_GETARG_INT64(1); + int64 r; + + if (pg_sub_s64_overflow(a, b, &r) || + r == PG_INT64_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + PG_RETURN_INT64(i64abs(r)); +} + /************************************************** * GiST support functions **************************************************/ Datum @@ -132,47 +184,59 @@ gbt_int4_fetch(PG_FUNCTION_ARGS) } Datum gbt_int4_consistent(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); - int32 query = PG_GETARG_INT32(1); + Datum queryDatum = PG_GETARG_DATUM(1); StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); -#ifdef NOT_USED Oid subtype = PG_GETARG_OID(3); -#endif bool *recheck = (bool *) PG_GETARG_POINTER(4); int32KEY *kkk = (int32KEY *) DatumGetPointer(entry->key); + int32 query; GBT_NUMKEY_R key; /* All cases served by this function are exact */ *recheck = false; + if (subtype == InvalidOid || subtype == INT4OID) + query = DatumGetInt32(queryDatum); + else + query = 0; + key.lower = (GBT_NUMKEY *) &kkk->lower; key.upper = (GBT_NUMKEY *) &kkk->upper; - PG_RETURN_BOOL(gbt_num_consistent(&key, &query, &strategy, - GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); + PG_RETURN_BOOL(gbt_num_consistent_x(&key, &query, queryDatum, + subtype, PG_GET_COLLATION(), + &strategy, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); } Datum gbt_int4_distance(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); - int32 query = PG_GETARG_INT32(1); -#ifdef NOT_USED + Datum queryDatum = PG_GETARG_DATUM(1); Oid subtype = PG_GETARG_OID(3); -#endif int32KEY *kkk = (int32KEY *) DatumGetPointer(entry->key); + int32 query; GBT_NUMKEY_R key; + if (subtype == InvalidOid || subtype == INT4OID) + query = DatumGetInt32(queryDatum); + else + query = 0; + key.lower = (GBT_NUMKEY *) &kkk->lower; key.upper = (GBT_NUMKEY *) &kkk->upper; - PG_RETURN_FLOAT8(gbt_num_distance(&key, &query, GIST_LEAF(entry), - &tinfo, fcinfo->flinfo)); + PG_RETURN_FLOAT8(gbt_num_distance_x(&key, &query, queryDatum, + subtype, PG_GET_COLLATION(), + GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); } Datum gbt_int4_union(PG_FUNCTION_ARGS) { GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c index f48122c8d84..352acb4ff76 100644 --- a/contrib/btree_gist/btree_int8.c +++ b/contrib/btree_gist/btree_int8.c @@ -2,12 +2,13 @@ * contrib/btree_gist/btree_int8.c */ #include "postgres.h" #include "btree_gist.h" #include "btree_utils_num.h" +#include "catalog/pg_type.h" #include "common/int.h" #include "utils/rel.h" #include "utils/sortsupport.h" typedef struct int64key { @@ -73,25 +74,42 @@ gbt_int8key_cmp(const void *a, const void *b, FmgrInfo *flinfo) static float8 gbt_int8_dist(const void *a, const void *b, FmgrInfo *flinfo) { return GET_FLOAT_DISTANCE(int64, a, b); } +/* + * Cross-type callbacks + */ +GBT_DEFINE_INT_CROSSTYPE(gbt_int8_x_int2, int64, DatumGetInt16) +GBT_DEFINE_INT_CROSSTYPE(gbt_int8_x_int4, int64, DatumGetInt32) + +static const gbt_subtype_info gbt_int8_subtype_ops[] = { + {INT2OID, + gbt_int8_x_int2_lt, gbt_int8_x_int2_le, gbt_int8_x_int2_eq, + gbt_int8_x_int2_ge, gbt_int8_x_int2_gt, gbt_int8_x_int2_dist}, + {INT4OID, + gbt_int8_x_int4_lt, gbt_int8_x_int4_le, gbt_int8_x_int4_eq, + gbt_int8_x_int4_ge, gbt_int8_x_int4_gt, gbt_int8_x_int4_dist}, + {InvalidOid} +}; static const gbtree_ninfo tinfo = { gbt_t_int8, sizeof(int64), 16, /* sizeof(gbtreekey16) */ gbt_int8gt, gbt_int8ge, gbt_int8eq, gbt_int8le, gbt_int8lt, gbt_int8key_cmp, - gbt_int8_dist + gbt_int8_dist, + gbt_int8_subtype_ops, + INT8OID }; PG_FUNCTION_INFO_V1(int8_dist); Datum int8_dist(PG_FUNCTION_ARGS) @@ -109,12 +127,46 @@ int8_dist(PG_FUNCTION_ARGS) ra = i64abs(r); PG_RETURN_INT64(ra); } +PG_FUNCTION_INFO_V1(int8_int2_dist); +Datum +int8_int2_dist(PG_FUNCTION_ARGS) +{ + int64 a = PG_GETARG_INT64(0); + int64 b = (int64) PG_GETARG_INT16(1); + int64 r; + + if (pg_sub_s64_overflow(a, b, &r) || + r == PG_INT64_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + PG_RETURN_INT64(i64abs(r)); +} + +PG_FUNCTION_INFO_V1(int8_int4_dist); +Datum +int8_int4_dist(PG_FUNCTION_ARGS) +{ + int64 a = PG_GETARG_INT64(0); + int64 b = (int64) PG_GETARG_INT32(1); + int64 r; + + if (pg_sub_s64_overflow(a, b, &r) || + r == PG_INT64_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + PG_RETURN_INT64(i64abs(r)); +} + /************************************************** * GiST support functions **************************************************/ Datum @@ -134,47 +186,59 @@ gbt_int8_fetch(PG_FUNCTION_ARGS) } Datum gbt_int8_consistent(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); - int64 query = PG_GETARG_INT64(1); + Datum queryDatum = PG_GETARG_DATUM(1); StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); -#ifdef NOT_USED Oid subtype = PG_GETARG_OID(3); -#endif bool *recheck = (bool *) PG_GETARG_POINTER(4); int64KEY *kkk = (int64KEY *) DatumGetPointer(entry->key); + int64 query; GBT_NUMKEY_R key; /* All cases served by this function are exact */ *recheck = false; + if (subtype == InvalidOid || subtype == INT8OID) + query = DatumGetInt64(queryDatum); + else + query = 0; + key.lower = (GBT_NUMKEY *) &kkk->lower; key.upper = (GBT_NUMKEY *) &kkk->upper; - PG_RETURN_BOOL(gbt_num_consistent(&key, &query, &strategy, - GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); + PG_RETURN_BOOL(gbt_num_consistent_x(&key, &query, queryDatum, + subtype, PG_GET_COLLATION(), + &strategy, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); } Datum gbt_int8_distance(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); - int64 query = PG_GETARG_INT64(1); -#ifdef NOT_USED + Datum queryDatum = PG_GETARG_DATUM(1); Oid subtype = PG_GETARG_OID(3); -#endif int64KEY *kkk = (int64KEY *) DatumGetPointer(entry->key); + int64 query; GBT_NUMKEY_R key; + if (subtype == InvalidOid || subtype == INT8OID) + query = DatumGetInt64(queryDatum); + else + query = 0; + key.lower = (GBT_NUMKEY *) &kkk->lower; key.upper = (GBT_NUMKEY *) &kkk->upper; - PG_RETURN_FLOAT8(gbt_num_distance(&key, &query, GIST_LEAF(entry), - &tinfo, fcinfo->flinfo)); + PG_RETURN_FLOAT8(gbt_num_distance_x(&key, &query, queryDatum, + subtype, PG_GET_COLLATION(), + GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); } Datum gbt_int8_union(PG_FUNCTION_ARGS) { GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); diff --git a/contrib/btree_gist/btree_utils_num.c b/contrib/btree_gist/btree_utils_num.c index 3affe4c2c46..68574b895c5 100644 --- a/contrib/btree_gist/btree_utils_num.c +++ b/contrib/btree_gist/btree_utils_num.c @@ -250,12 +250,32 @@ gbt_num_bin_union(Datum *u, GBT_NUMKEY *e, const gbtree_ninfo *tinfo, FmgrInfo * memcpy(unconstify(GBT_NUMKEY *, ur.upper), rd.upper, tinfo->size); } } +/* + * Look up cross-type callbacks for a given query subtype. Returns NULL if + * the opclass doesn't advertise cross-type support for this subtype, in + * which case callers must fall back to the same-type path. + */ +static const gbt_subtype_info * +gbt_find_subtype_ops(const gbtree_ninfo *tinfo, Oid subtype) +{ + const gbt_subtype_info *p; + + if (subtype == InvalidOid || tinfo->subtype_ops == NULL) + return NULL; + for (p = tinfo->subtype_ops; p->subtype != InvalidOid; p++) + { + if (p->subtype == subtype) + return p; + } + return NULL; +} + /* * The GiST consistent method * * Note: we currently assume that no datatypes that use this routine are * collation-aware; so we don't bother passing collation through. */ @@ -304,12 +324,99 @@ gbt_num_consistent(const GBT_NUMKEY_R *key, retval = false; } return retval; } +/* + * Verify that "subtype" is one this opclass knows how to handle: it must + * either be InvalidOid / the opclass's native type, or be registered in + * the dispatch table. Anything else is an operator-family configuration + * error. + */ +static void +gbt_check_subtype(const gbtree_ninfo *tinfo, Oid subtype) +{ + if (subtype == InvalidOid) + return; + if (tinfo->type_oid == InvalidOid) + return; + if (subtype == tinfo->type_oid) + return; + if (gbt_find_subtype_ops(tinfo, subtype) != NULL) + return; + + elog(ERROR, + "btree_gist: cross-type query with subtype %u is not supported " + "by the opclass for type %u (operator-family dispatch table is " + "out of sync with pg_amop)", + subtype, tinfo->type_oid); +} + +/* + * Cross-type aware consistent method. + */ +bool +gbt_num_consistent_x(const GBT_NUMKEY_R *key, + const void *query, + Datum queryDatum, + Oid subtype, + Oid collation, + const StrategyNumber *strategy, + bool is_leaf, + const gbtree_ninfo *tinfo, + FmgrInfo *flinfo) +{ + const gbt_subtype_info *xt = gbt_find_subtype_ops(tinfo, subtype); + gbt_subtype_context cxt; + + if (xt == NULL) + { + gbt_check_subtype(tinfo, subtype); + return gbt_num_consistent(key, query, strategy, is_leaf, tinfo, flinfo); + } + + cxt.query = queryDatum; + cxt.subtype = subtype; + cxt.collation = collation; + cxt.flinfo = flinfo; + cxt.query_cache = NULL; + + switch (*strategy) + { + case BTLessEqualStrategyNumber: + /* some k in [lower,upper] has k <= q iff lower <= q */ + return xt->f_le(key->lower, &cxt); + case BTLessStrategyNumber: + /* leaf: key < q. internal: lower <= q (loose) */ + return is_leaf ? xt->f_lt(key->lower, &cxt) + : xt->f_le(key->lower, &cxt); + case BTEqualStrategyNumber: + if (is_leaf) + return xt->f_eq(key->lower, &cxt); + /* internal: lower <= q <= upper */ + return (xt->f_le(key->lower, &cxt) && + xt->f_ge(key->upper, &cxt)); + case BTGreaterStrategyNumber: + + /* + * leaf: key > q. internal: upper >= q (loose). Read upper on + * leaves to match the same-type path. + */ + return is_leaf ? xt->f_gt(key->upper, &cxt) + : xt->f_ge(key->upper, &cxt); + case BTGreaterEqualStrategyNumber: + return xt->f_ge(key->upper, &cxt); + case BtreeGistNotEqualStrategyNumber: + return !(xt->f_eq(key->lower, &cxt) && + xt->f_eq(key->upper, &cxt)); + default: + return false; + } +} + /* * The GiST distance method (for KNN-Gist) */ float8 @@ -331,12 +438,53 @@ gbt_num_distance(const GBT_NUMKEY_R *key, else retval = 0.0; return retval; } +/* + * Cross-type aware distance method. + */ +float8 +gbt_num_distance_x(const GBT_NUMKEY_R *key, + const void *query, + Datum queryDatum, + Oid subtype, + Oid collation, + bool is_leaf, + const gbtree_ninfo *tinfo, + FmgrInfo *flinfo) +{ + const gbt_subtype_info *xt = gbt_find_subtype_ops(tinfo, subtype); + gbt_subtype_context cxt; + + if (xt == NULL) + { + gbt_check_subtype(tinfo, subtype); + return gbt_num_distance(key, query, is_leaf, tinfo, flinfo); + } + + if (xt->f_dist == NULL) + elog(ERROR, "KNN search is not supported for btree_gist type %d with subtype %u", + (int) tinfo->t, subtype); + + cxt.query = queryDatum; + cxt.subtype = subtype; + cxt.collation = collation; + cxt.flinfo = flinfo; + cxt.query_cache = NULL; + + /* q <= lower <=> lower >= q */ + if (xt->f_ge(key->lower, &cxt)) + return xt->f_dist(key->lower, &cxt); + /* q >= upper <=> upper <= q */ + if (xt->f_le(key->upper, &cxt)) + return xt->f_dist(key->upper, &cxt); + return 0.0; +} + GIST_SPLITVEC * gbt_num_picksplit(const GistEntryVector *entryvec, GIST_SPLITVEC *v, const gbtree_ninfo *tinfo, FmgrInfo *flinfo) { OffsetNumber i, diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h index 53e477d8b1e..dfb0a3bb41f 100644 --- a/contrib/btree_gist/btree_utils_num.h +++ b/contrib/btree_gist/btree_utils_num.h @@ -27,12 +27,42 @@ typedef struct GBT_NUMKEY *t; } Nsrt; /* type description */ +/* + * Cross-type comparison support. + * + * For cross-type operator support an opclass may supply a NULL-terminated + * array of gbt_subtype_info entries, one per supported query subtype. Each + * callback receives the index key in its native C representation (pointer + * into the compressed key) and a context describing the query value. + */ +typedef struct gbt_subtype_context +{ + Datum query; /* Datum of cxt->subtype */ + Oid subtype; /* right-hand/query operand type */ + Oid collation; /* collation from the support call */ + FmgrInfo *flinfo; /* support-function call context */ + void *query_cache; /* callback-owned per-call state */ +} gbt_subtype_context; + +typedef struct gbt_subtype_info +{ + Oid subtype; /* InvalidOid terminates the array */ + bool (*f_lt) (const void *key, gbt_subtype_context *cxt); + bool (*f_le) (const void *key, gbt_subtype_context *cxt); + bool (*f_eq) (const void *key, gbt_subtype_context *cxt); + bool (*f_ge) (const void *key, gbt_subtype_context *cxt); + bool (*f_gt) (const void *key, gbt_subtype_context *cxt); + + /* NULL if no KNN */ + float8 (*f_dist) (const void *key, gbt_subtype_context *cxt); +} gbt_subtype_info; + typedef struct { /* Attribs */ enum gbtree_type t; /* data type */ @@ -45,14 +75,59 @@ typedef struct bool (*f_ge) (const void *, const void *, FmgrInfo *); /* greater or equal */ bool (*f_eq) (const void *, const void *, FmgrInfo *); /* equal */ bool (*f_le) (const void *, const void *, FmgrInfo *); /* less or equal */ bool (*f_lt) (const void *, const void *, FmgrInfo *); /* less than */ int (*f_cmp) (const void *, const void *, FmgrInfo *); /* key compare function */ float8 (*f_dist) (const void *, const void *, FmgrInfo *); /* key distance function */ + + /* + * Optional NULL-terminated array of cross-type comparison callbacks. NULL + * if the opclass only supports same-type comparisons. + */ + const gbt_subtype_info *subtype_ops; + + /* + * Native pg_type OID of the indexed type. Used by the _x APIs to validate + * the subtype passed in from the planner. InvalidOid disables that check, + * which is right for legacy opclasses that don't use _x. + */ + Oid type_oid; } gbtree_ninfo; +#define GBT_DEFINE_INT_CROSSTYPE(prefix, key_ctype, get_sub) \ +static bool \ +prefix##_lt(const void *k, gbt_subtype_context *cxt) \ +{ \ + return (int64) *(const key_ctype *) k < (int64) get_sub(cxt->query); \ +} \ +static bool \ +prefix##_le(const void *k, gbt_subtype_context *cxt) \ +{ \ + return (int64) *(const key_ctype *) k <= (int64) get_sub(cxt->query); \ +} \ +static bool \ +prefix##_eq(const void *k, gbt_subtype_context *cxt) \ +{ \ + return (int64) *(const key_ctype *) k == (int64) get_sub(cxt->query); \ +} \ +static bool \ +prefix##_ge(const void *k, gbt_subtype_context *cxt) \ +{ \ + return (int64) *(const key_ctype *) k >= (int64) get_sub(cxt->query); \ +} \ +static bool \ +prefix##_gt(const void *k, gbt_subtype_context *cxt) \ +{ \ + return (int64) *(const key_ctype *) k > (int64) get_sub(cxt->query); \ +} \ +static float8 \ +prefix##_dist(const void *k, gbt_subtype_context *cxt) \ +{ \ + return fabs((float8) *(const key_ctype *) k - (float8) get_sub(cxt->query)); \ +} + /* * Numeric btree functions */ @@ -92,15 +167,25 @@ typedef struct extern Interval *abs_interval(Interval *a); extern bool gbt_num_consistent(const GBT_NUMKEY_R *key, const void *query, const StrategyNumber *strategy, bool is_leaf, const gbtree_ninfo *tinfo, FmgrInfo *flinfo); +extern bool gbt_num_consistent_x(const GBT_NUMKEY_R *key, const void *query, + Datum queryDatum, Oid subtype, Oid collation, + const StrategyNumber *strategy, bool is_leaf, + const gbtree_ninfo *tinfo, FmgrInfo *flinfo); + extern float8 gbt_num_distance(const GBT_NUMKEY_R *key, const void *query, bool is_leaf, const gbtree_ninfo *tinfo, FmgrInfo *flinfo); +extern float8 gbt_num_distance_x(const GBT_NUMKEY_R *key, const void *query, + Datum queryDatum, Oid subtype, Oid collation, + bool is_leaf, + const gbtree_ninfo *tinfo, FmgrInfo *flinfo); + extern GIST_SPLITVEC *gbt_num_picksplit(const GistEntryVector *entryvec, GIST_SPLITVEC *v, const gbtree_ninfo *tinfo, FmgrInfo *flinfo); extern GISTENTRY *gbt_num_compress(GISTENTRY *entry, const gbtree_ninfo *tinfo); extern GISTENTRY *gbt_num_fetch(GISTENTRY *entry, const gbtree_ninfo *tinfo); diff --git a/contrib/btree_gist/meson.build b/contrib/btree_gist/meson.build index 2b1a5463289..c640cb7d8d2 100644 --- a/contrib/btree_gist/meson.build +++ b/contrib/btree_gist/meson.build @@ -49,12 +49,13 @@ install_data( 'btree_gist--1.4--1.5.sql', 'btree_gist--1.5--1.6.sql', 'btree_gist--1.6--1.7.sql', 'btree_gist--1.7--1.8.sql', 'btree_gist--1.8--1.9.sql', 'btree_gist--1.9.sql', + 'btree_gist--1.9--1.10.sql', kwargs: contrib_data_args, ) tests += { 'name': 'btree_gist', 'sd': meson.current_source_dir(), @@ -89,10 +90,11 @@ tests += { 'uuid', 'not_equal', 'enum', 'bool', 'partitions', 'stratnum', + 'int_crosstype', 'without_overlaps', ], }, } -- 2.51.0