From 3800bca3c6be27f0077e5cb5232e56af1b40dc53 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 btree_gist's GiST opclasses were same-type only: the planner could use an index only when the query value's type exactly matched the indexed column. A common case like an int8 column compared against an int4 literal (WHERE bigint_col = 42) therefore could not use the index unless the query was written with an explicit cast, which is easy to forget and which ORMs and parameter binding routinely get wrong. Add cross-type query operator support among the integer trio (int2, int4, int8) to the gist_int2_ops, gist_int4_ops and gist_int8_ops families. Each family gains the six B-tree comparison strategies (<, <=, =, >=, >, <>) and the <-> distance operator against the other two integer types. The additions are deliberately pg_amop-only: GiST's amvalidate requires support functions in a family to have matching left/right input types, so no cross-type consistent/distance functions are registered. Instead the existing support functions dispatch on the operator's subtype OID. Same-type queries take the normal path; cross-type queries select a comparison/distance callback that reads the query and key sides at their own widths, so out-of-range query constants compare by normal integer semantics without being narrowed to the column type. To let the cross-type path reuse gbt_num_consistent(), all comparison callbacks are now invoked as f(query, key): query on the left, key on the right; one call site whose order differed is normalized. Note that the subtype is InvalidOid for exclusion-constraint and temporal PK/FK checks, which is handled as the same-type case. This is exposed as btree_gist version 1.10. Co-authored-by: Maxime Schoemans --- contrib/btree_gist/Makefile | 2 +- contrib/btree_gist/btree_gist--1.9--1.10.sql | 134 +++++++++++++++++++ contrib/btree_gist/btree_gist.control | 2 +- contrib/btree_gist/btree_int2.c | 130 ++++++++++++++++-- contrib/btree_gist/btree_int4.c | 130 ++++++++++++++++-- contrib/btree_gist/btree_int8.c | 130 ++++++++++++++++-- contrib/btree_gist/btree_utils_num.c | 16 ++- contrib/btree_gist/btree_utils_num.h | 39 +++++- contrib/btree_gist/meson.build | 1 + src/tools/pgindent/typedefs.list | 1 + 10 files changed, 547 insertions(+), 38 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..380c7642fde 100644 --- a/contrib/btree_gist/Makefile +++ b/contrib/btree_gist/Makefile @@ -32,13 +32,13 @@ 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 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..9cd57455086 --- /dev/null +++ b/contrib/btree_gist/btree_gist--1.9--1.10.sql @@ -0,0 +1,134 @@ +/* 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 existing consistent/distance support functions dispatch +-- on the subtype OID: same-type queries take the normal path, while mixed-width +-- integer queries select a cross-type comparison callback that reads the query +-- and key sides at their own widths (see btree_int{2,4,8}.c). + +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..8d644579b23 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,13 +74,12 @@ 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); } - static const gbtree_ninfo tinfo = { gbt_t_int2, sizeof(int16), 4, /* sizeof(gbtreekey4) */ gbt_int2gt, @@ -88,12 +88,80 @@ static const gbtree_ninfo tinfo = gbt_int2le, gbt_int2lt, gbt_int2key_cmp, gbt_int2_dist }; +/* + * Cross-type GiST callbacks: the indexed key is int2, the query is int4 or + * int8. Both reuse gbt_num_consistent()/gbt_num_distance() via a tinfo whose + * comparison/distance callbacks read the query (left) and key (right) sides at + * their own widths. f_cmp is unused on these paths and left NULL. + */ +GBT_INT_CMP_FNS(gbt_int2_q4_, int32, int16) +GBT_INT_CMP_FNS(gbt_int2_q8_, int64, int16) + +static const gbtree_ninfo tinfo_q4 = +{ + gbt_t_int2, + sizeof(int16), + 4, + gbt_int2_q4_gt, + gbt_int2_q4_ge, + gbt_int2_q4_eq, + gbt_int2_q4_le, + gbt_int2_q4_lt, + NULL, + gbt_int2_q4_dist +}; + +static const gbtree_ninfo tinfo_q8 = +{ + gbt_t_int2, + sizeof(int16), + 4, + gbt_int2_q8_gt, + gbt_int2_q8_ge, + gbt_int2_q8_eq, + gbt_int2_q8_le, + gbt_int2_q8_lt, + NULL, + gbt_int2_q8_dist +}; + +/* + * Cross-type dispatch shared by gbt_int2_consistent and gbt_int2_distance: + * select the tinfo for the query subtype and read the query value at its own + * width into caller-owned storage. + */ +static const gbtree_ninfo * +gbt_int2_crosstype(Oid subtype, Datum d, gbt_intkey *q, const void **qp) +{ + switch (subtype) + { + case InvalidOid: /* same-type: exclusion/temporal constraint + * checks pass the native type with subtype 0 */ + case INT2OID: + q->i2 = DatumGetInt16(d); + *qp = &q->i2; + return &tinfo; + case INT4OID: + q->i4 = DatumGetInt32(d); + *qp = &q->i4; + return &tinfo_q4; + case INT8OID: + q->i8 = DatumGetInt64(d); + *qp = &q->i8; + return &tinfo_q8; + default: + elog(ERROR, "unrecognized subtype %u for btree_gist int2 cross-type comparison", + subtype); + return NULL; /* keep compiler quiet */ + } +} + PG_FUNCTION_INFO_V1(int2_dist); Datum int2_dist(PG_FUNCTION_ARGS) { int16 a = PG_GETARG_INT16(0); @@ -109,12 +177,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 +236,53 @@ 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); + const gbtree_ninfo *ti; + gbt_intkey query; + const void *qp; GBT_NUMKEY_R key; /* All cases served by this function are exact */ *recheck = false; 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)); + ti = gbt_int2_crosstype(subtype, queryDatum, &query, &qp); + + PG_RETURN_BOOL(gbt_num_consistent(&key, qp, &strategy, GIST_LEAF(entry), + ti, 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); + const gbtree_ninfo *ti; + gbt_intkey query; + const void *qp; GBT_NUMKEY_R key; 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)); + ti = gbt_int2_crosstype(subtype, queryDatum, &query, &qp); + + PG_RETURN_FLOAT8(gbt_num_distance(&key, qp, GIST_LEAF(entry), + ti, 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..325ee78f2ab 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,13 +72,12 @@ 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); } - static const gbtree_ninfo tinfo = { gbt_t_int4, sizeof(int32), 8, /* sizeof(gbtreekey8) */ gbt_int4gt, @@ -86,12 +86,80 @@ static const gbtree_ninfo tinfo = gbt_int4le, gbt_int4lt, gbt_int4key_cmp, gbt_int4_dist }; +/* + * Cross-type GiST callbacks: the indexed key is int4, the query is int2 or + * int8. Both reuse gbt_num_consistent()/gbt_num_distance() via a tinfo whose + * comparison/distance callbacks read the query (left) and key (right) sides at + * their own widths. f_cmp is unused on these paths and left NULL. + */ +GBT_INT_CMP_FNS(gbt_int4_q2_, int16, int32) +GBT_INT_CMP_FNS(gbt_int4_q8_, int64, int32) + +static const gbtree_ninfo tinfo_q2 = +{ + gbt_t_int4, + sizeof(int32), + 8, + gbt_int4_q2_gt, + gbt_int4_q2_ge, + gbt_int4_q2_eq, + gbt_int4_q2_le, + gbt_int4_q2_lt, + NULL, + gbt_int4_q2_dist +}; + +static const gbtree_ninfo tinfo_q8 = +{ + gbt_t_int4, + sizeof(int32), + 8, + gbt_int4_q8_gt, + gbt_int4_q8_ge, + gbt_int4_q8_eq, + gbt_int4_q8_le, + gbt_int4_q8_lt, + NULL, + gbt_int4_q8_dist +}; + +/* + * Cross-type dispatch shared by gbt_int4_consistent and gbt_int4_distance: + * select the tinfo for the query subtype and read the query value at its own + * width into caller-owned storage. + */ +static const gbtree_ninfo * +gbt_int4_crosstype(Oid subtype, Datum d, gbt_intkey *q, const void **qp) +{ + switch (subtype) + { + case INT2OID: + q->i2 = DatumGetInt16(d); + *qp = &q->i2; + return &tinfo_q2; + case InvalidOid: /* same-type: exclusion/temporal constraint + * checks pass the native type with subtype 0 */ + case INT4OID: + q->i4 = DatumGetInt32(d); + *qp = &q->i4; + return &tinfo; + case INT8OID: + q->i8 = DatumGetInt64(d); + *qp = &q->i8; + return &tinfo_q8; + default: + elog(ERROR, "unrecognized subtype %u for btree_gist int4 cross-type comparison", + subtype); + return NULL; /* keep compiler quiet */ + } +} + PG_FUNCTION_INFO_V1(int4_dist); Datum int4_dist(PG_FUNCTION_ARGS) { int32 a = PG_GETARG_INT32(0); @@ -107,12 +175,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 +234,53 @@ 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); + const gbtree_ninfo *ti; + gbt_intkey query; + const void *qp; GBT_NUMKEY_R key; /* All cases served by this function are exact */ *recheck = false; 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)); + ti = gbt_int4_crosstype(subtype, queryDatum, &query, &qp); + + PG_RETURN_BOOL(gbt_num_consistent(&key, qp, &strategy, GIST_LEAF(entry), + ti, 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); + const gbtree_ninfo *ti; + gbt_intkey query; + const void *qp; GBT_NUMKEY_R key; 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)); + ti = gbt_int4_crosstype(subtype, queryDatum, &query, &qp); + + PG_RETURN_FLOAT8(gbt_num_distance(&key, qp, GIST_LEAF(entry), + ti, 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..62c06345e87 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,13 +74,12 @@ 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); } - static const gbtree_ninfo tinfo = { gbt_t_int8, sizeof(int64), 16, /* sizeof(gbtreekey16) */ gbt_int8gt, @@ -88,12 +88,80 @@ static const gbtree_ninfo tinfo = gbt_int8le, gbt_int8lt, gbt_int8key_cmp, gbt_int8_dist }; +/* + * Cross-type GiST callbacks: the indexed key is int8, the query is int2 or + * int4. Both reuse gbt_num_consistent()/gbt_num_distance() via a tinfo whose + * comparison/distance callbacks read the query (left) and key (right) sides at + * their own widths. f_cmp is unused on these paths and left NULL. + */ +GBT_INT_CMP_FNS(gbt_int8_q2_, int16, int64) +GBT_INT_CMP_FNS(gbt_int8_q4_, int32, int64) + +static const gbtree_ninfo tinfo_q2 = +{ + gbt_t_int8, + sizeof(int64), + 16, + gbt_int8_q2_gt, + gbt_int8_q2_ge, + gbt_int8_q2_eq, + gbt_int8_q2_le, + gbt_int8_q2_lt, + NULL, + gbt_int8_q2_dist +}; + +static const gbtree_ninfo tinfo_q4 = +{ + gbt_t_int8, + sizeof(int64), + 16, + gbt_int8_q4_gt, + gbt_int8_q4_ge, + gbt_int8_q4_eq, + gbt_int8_q4_le, + gbt_int8_q4_lt, + NULL, + gbt_int8_q4_dist +}; + +/* + * Cross-type dispatch shared by gbt_int8_consistent and gbt_int8_distance: + * select the tinfo for the query subtype and read the query value at its own + * width into caller-owned storage. + */ +static const gbtree_ninfo * +gbt_int8_crosstype(Oid subtype, Datum d, gbt_intkey *q, const void **qp) +{ + switch (subtype) + { + case INT2OID: + q->i2 = DatumGetInt16(d); + *qp = &q->i2; + return &tinfo_q2; + case INT4OID: + q->i4 = DatumGetInt32(d); + *qp = &q->i4; + return &tinfo_q4; + case InvalidOid: /* same-type: exclusion/temporal constraint + * checks pass the native type with subtype 0 */ + case INT8OID: + q->i8 = DatumGetInt64(d); + *qp = &q->i8; + return &tinfo; + default: + elog(ERROR, "unrecognized subtype %u for btree_gist int8 cross-type comparison", + subtype); + return NULL; /* keep compiler quiet */ + } +} + PG_FUNCTION_INFO_V1(int8_dist); Datum int8_dist(PG_FUNCTION_ARGS) { int64 a = PG_GETARG_INT64(0); @@ -109,12 +177,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 +236,53 @@ 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); + const gbtree_ninfo *ti; + gbt_intkey query; + const void *qp; GBT_NUMKEY_R key; /* All cases served by this function are exact */ *recheck = false; 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)); + ti = gbt_int8_crosstype(subtype, queryDatum, &query, &qp); + + PG_RETURN_BOOL(gbt_num_consistent(&key, qp, &strategy, GIST_LEAF(entry), + ti, 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); + const gbtree_ninfo *ti; + gbt_intkey query; + const void *qp; GBT_NUMKEY_R key; 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)); + ti = gbt_int8_crosstype(subtype, queryDatum, &query, &qp); + + PG_RETURN_FLOAT8(gbt_num_distance(&key, qp, GIST_LEAF(entry), + ti, 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..0d6d578062a 100644 --- a/contrib/btree_gist/btree_utils_num.c +++ b/contrib/btree_gist/btree_utils_num.c @@ -266,12 +266,18 @@ gbt_num_consistent(const GBT_NUMKEY_R *key, bool is_leaf, const gbtree_ninfo *tinfo, FmgrInfo *flinfo) { bool retval; + /* + * Every comparison callback is invoked as f_xx(query, key): the query + * value is always the left argument and the indexed key bound the right. + * The integer opclasses rely on this fixed order so their cross-type + * callbacks can read each side at its own width. + */ switch (*strategy) { case BTLessEqualStrategyNumber: retval = tinfo->f_ge(query, key->lower, flinfo); break; case BTLessStrategyNumber: @@ -281,13 +287,13 @@ gbt_num_consistent(const GBT_NUMKEY_R *key, retval = tinfo->f_ge(query, key->lower, flinfo); break; case BTEqualStrategyNumber: if (is_leaf) retval = tinfo->f_eq(query, key->lower, flinfo); else - retval = (tinfo->f_le(key->lower, query, flinfo) && + retval = (tinfo->f_ge(query, key->lower, flinfo) && tinfo->f_le(query, key->upper, flinfo)); break; case BTGreaterStrategyNumber: if (is_leaf) retval = tinfo->f_lt(query, key->upper, flinfo); else @@ -304,13 +310,12 @@ gbt_num_consistent(const GBT_NUMKEY_R *key, retval = false; } return retval; } - /* * The GiST distance method (for KNN-Gist) */ float8 gbt_num_distance(const GBT_NUMKEY_R *key, @@ -321,12 +326,19 @@ gbt_num_distance(const GBT_NUMKEY_R *key, { float8 retval; if (tinfo->f_dist == NULL) elog(ERROR, "KNN search is not supported for btree_gist type %d", (int) tinfo->t); + + /* + * As in gbt_num_consistent(), every callback is invoked as f_xx(query, key): + * the query value is the left argument and the indexed key bound the right. + * The integer opclasses' cross-type callbacks read each side at its own + * width, so keep this argument order if you ever touch the calls below. + */ if (tinfo->f_le(query, key->lower, flinfo)) retval = tinfo->f_dist(query, key->lower, flinfo); else if (tinfo->f_ge(query, key->upper, flinfo)) retval = tinfo->f_dist(query, key->upper, flinfo); else retval = 0.0; diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h index 53e477d8b1e..217b362c169 100644 --- a/contrib/btree_gist/btree_utils_num.h +++ b/contrib/btree_gist/btree_utils_num.h @@ -24,12 +24,24 @@ typedef struct typedef struct { int i; GBT_NUMKEY *t; } Nsrt; +/* + * Query-value storage for the integer opclasses' cross-type path. The caller + * owns one of these and passes its address to the per-type cross-type helper, + * which fills the right width and points the query pointer at it. + */ +typedef union +{ + int16 i2; + int32 i4; + int64 i8; +} gbt_intkey; + /* type description */ typedef struct { @@ -83,13 +95,38 @@ typedef struct */ #define INTERVAL_TO_SEC(ivp) \ (((double) (ivp)->time) / ((double) USECS_PER_SEC) + \ (ivp)->day * (24.0 * SECS_PER_HOUR) + \ (ivp)->month * (30.0 * SECS_PER_DAY)) -#define GET_FLOAT_DISTANCE(t, arg1, arg2) fabs( ((float8) *((const t *) (arg1))) - ((float8) *((const t *) (arg2))) ) +#define GET_FLOAT_DISTANCE2(t1, t2, arg1, arg2) fabs( ((float8) *((const t1 *) (arg1))) - ((float8) *((const t2 *) (arg2))) ) +#define GET_FLOAT_DISTANCE(t, arg1, arg2) GET_FLOAT_DISTANCE2(t, t, (arg1), (arg2)) + +/* + * Generate the comparison/distance callbacks for a gbtree_ninfo whose query + * and key sides may be different (integer) types. gbt_num_consistent() and + * gbt_num_distance() always invoke the callbacks as f_xx(query, key), so the + * first argument has the query type QT (the operator's right-hand subtype) and + * the second has the indexed key type KT. Integer widening is value-preserving, + * so the comparisons need no explicit cast; the distance widens to float8 to + * avoid overflow in the subtraction. Invoked with QT == KT this also generates + * the ordinary same-type callbacks. + */ +#define GBT_INT_CMP_FNS(prefix, QT, KT) \ +static bool prefix##gt(const void *a, const void *b, FmgrInfo *flinfo) \ +{ return *((const QT *) a) > *((const KT *) b); } \ +static bool prefix##ge(const void *a, const void *b, FmgrInfo *flinfo) \ +{ return *((const QT *) a) >= *((const KT *) b); } \ +static bool prefix##eq(const void *a, const void *b, FmgrInfo *flinfo) \ +{ return *((const QT *) a) == *((const KT *) b); } \ +static bool prefix##le(const void *a, const void *b, FmgrInfo *flinfo) \ +{ return *((const QT *) a) <= *((const KT *) b); } \ +static bool prefix##lt(const void *a, const void *b, FmgrInfo *flinfo) \ +{ return *((const QT *) a) < *((const KT *) b); } \ +static float8 prefix##dist(const void *a, const void *b, FmgrInfo *flinfo) \ +{ return GET_FLOAT_DISTANCE2(QT, KT, a, b); } 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, diff --git a/contrib/btree_gist/meson.build b/contrib/btree_gist/meson.build index 2b1a5463289..43ba130e8e4 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(), diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index c5db6ca6705..e29f76b6feb 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3808,12 +3808,13 @@ floating_decimal_32 floating_decimal_64 fmgr_hook_type foreign_glob_cxt foreign_loc_cxt freefunc fsec_t +gbt_intkey gbt_vsrt_arg gbtree_ninfo gbtree_vinfo generate_series_fctx generate_series_numeric_fctx generate_series_timestamp_fctx -- 2.51.0