From 347ed8923363a2ea0a7d4e348603825c6783cbf7 Mon Sep 17 00:00:00 2001 From: Maxime Schoemans Date: Mon, 22 Jun 2026 13:31:41 +0200 Subject: [PATCH v3 3/3] Reuse gbt_num_consistent for cross-type integer comparisons --- contrib/btree_gist/btree_gist--1.9--1.10.sql | 13 +- contrib/btree_gist/btree_int2.c | 98 +++++++--- contrib/btree_gist/btree_int4.c | 98 +++++++--- contrib/btree_gist/btree_int8.c | 98 +++++++--- contrib/btree_gist/btree_utils_num.c | 169 +----------------- contrib/btree_gist/btree_utils_num.h | 55 ++++-- contrib/btree_gist/expected/int_crosstype.out | 63 +------ contrib/btree_gist/sql/int_crosstype.sql | 60 +------ 8 files changed, 289 insertions(+), 365 deletions(-) diff --git a/contrib/btree_gist/btree_gist--1.9--1.10.sql b/contrib/btree_gist/btree_gist--1.9--1.10.sql index c9ff1955204..9cd57455086 100644 --- a/contrib/btree_gist/btree_gist--1.9--1.10.sql +++ b/contrib/btree_gist/btree_gist--1.9--1.10.sql @@ -10,8 +10,8 @@ -- 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 are promoted to int64 and compared by the integer cross-type --- helpers in btree_utils_num.c. +-- 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 @@ -43,15 +43,6 @@ RETURNS int8 AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; --- Introspection helper exposing the integer query subtypes understood by the C --- cross-type dispatch (gbt_int_crosstype_table in btree_utils_num.c). The --- regression tests use it to assert that the cross-type pg_amop entries below --- never drift from what the dispatch can handle. -CREATE FUNCTION gbt_int_crosstype_subtypes() -RETURNS SETOF oid -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE; - CREATE OPERATOR <-> ( LEFTARG = int2, RIGHTARG = int4, diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c index d00b24d9a57..ea9c206e552 100644 --- a/contrib/btree_gist/btree_int2.c +++ b/contrib/btree_gist/btree_int2.c @@ -91,6 +91,72 @@ static const gbtree_ninfo tinfo = 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 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 @@ -176,7 +242,9 @@ gbt_int2_consistent(PG_FUNCTION_ARGS) Oid subtype = PG_GETARG_OID(3); bool *recheck = (bool *) PG_GETARG_POINTER(4); int16KEY *kkk = (int16KEY *) DatumGetPointer(entry->key); - int16 query; + const gbtree_ninfo *ti; + gbt_intkey query; + const void *qp; GBT_NUMKEY_R key; /* All cases served by this function are exact */ @@ -185,17 +253,10 @@ gbt_int2_consistent(PG_FUNCTION_ARGS) key.lower = (GBT_NUMKEY *) &kkk->lower; key.upper = (GBT_NUMKEY *) &kkk->upper; - if (likely(subtype == InvalidOid || subtype == INT2OID)) - { - query = DatumGetInt16(queryDatum); - 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_int_consistent_x((int64) kkk->lower, (int64) kkk->upper, - queryDatum, subtype, &strategy, - GIST_LEAF(entry))); + PG_RETURN_BOOL(gbt_num_consistent(&key, qp, &strategy, GIST_LEAF(entry), + ti, fcinfo->flinfo)); } Datum @@ -205,21 +266,18 @@ gbt_int2_distance(PG_FUNCTION_ARGS) Datum queryDatum = PG_GETARG_DATUM(1); Oid subtype = PG_GETARG_OID(3); int16KEY *kkk = (int16KEY *) DatumGetPointer(entry->key); - int16 query; + 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; - if (likely(subtype == InvalidOid || subtype == INT2OID)) - { - query = DatumGetInt16(queryDatum); - 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_int_distance_x((int64) kkk->lower, (int64) kkk->upper, - queryDatum, subtype)); + PG_RETURN_FLOAT8(gbt_num_distance(&key, qp, GIST_LEAF(entry), + ti, fcinfo->flinfo)); } Datum diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c index 4df91f2057c..7e566890143 100644 --- a/contrib/btree_gist/btree_int4.c +++ b/contrib/btree_gist/btree_int4.c @@ -89,6 +89,72 @@ static const gbtree_ninfo tinfo = 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 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 @@ -174,7 +240,9 @@ gbt_int4_consistent(PG_FUNCTION_ARGS) Oid subtype = PG_GETARG_OID(3); bool *recheck = (bool *) PG_GETARG_POINTER(4); int32KEY *kkk = (int32KEY *) DatumGetPointer(entry->key); - int32 query; + const gbtree_ninfo *ti; + gbt_intkey query; + const void *qp; GBT_NUMKEY_R key; /* All cases served by this function are exact */ @@ -183,17 +251,10 @@ gbt_int4_consistent(PG_FUNCTION_ARGS) key.lower = (GBT_NUMKEY *) &kkk->lower; key.upper = (GBT_NUMKEY *) &kkk->upper; - if (likely(subtype == InvalidOid || subtype == INT4OID)) - { - query = DatumGetInt32(queryDatum); - 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_int_consistent_x((int64) kkk->lower, (int64) kkk->upper, - queryDatum, subtype, &strategy, - GIST_LEAF(entry))); + PG_RETURN_BOOL(gbt_num_consistent(&key, qp, &strategy, GIST_LEAF(entry), + ti, fcinfo->flinfo)); } Datum @@ -203,21 +264,18 @@ gbt_int4_distance(PG_FUNCTION_ARGS) Datum queryDatum = PG_GETARG_DATUM(1); Oid subtype = PG_GETARG_OID(3); int32KEY *kkk = (int32KEY *) DatumGetPointer(entry->key); - int32 query; + 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; - if (likely(subtype == InvalidOid || subtype == INT4OID)) - { - query = DatumGetInt32(queryDatum); - 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_int_distance_x((int64) kkk->lower, (int64) kkk->upper, - queryDatum, subtype)); + PG_RETURN_FLOAT8(gbt_num_distance(&key, qp, GIST_LEAF(entry), + ti, fcinfo->flinfo)); } Datum diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c index ae01273b709..81c8bb364cf 100644 --- a/contrib/btree_gist/btree_int8.c +++ b/contrib/btree_gist/btree_int8.c @@ -91,6 +91,72 @@ static const gbtree_ninfo tinfo = 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 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 @@ -176,7 +242,9 @@ gbt_int8_consistent(PG_FUNCTION_ARGS) Oid subtype = PG_GETARG_OID(3); bool *recheck = (bool *) PG_GETARG_POINTER(4); int64KEY *kkk = (int64KEY *) DatumGetPointer(entry->key); - int64 query; + const gbtree_ninfo *ti; + gbt_intkey query; + const void *qp; GBT_NUMKEY_R key; /* All cases served by this function are exact */ @@ -185,17 +253,10 @@ gbt_int8_consistent(PG_FUNCTION_ARGS) key.lower = (GBT_NUMKEY *) &kkk->lower; key.upper = (GBT_NUMKEY *) &kkk->upper; - if (likely(subtype == InvalidOid || subtype == INT8OID)) - { - query = DatumGetInt64(queryDatum); - 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_int_consistent_x(kkk->lower, kkk->upper, - queryDatum, subtype, &strategy, - GIST_LEAF(entry))); + PG_RETURN_BOOL(gbt_num_consistent(&key, qp, &strategy, GIST_LEAF(entry), + ti, fcinfo->flinfo)); } Datum @@ -205,21 +266,18 @@ gbt_int8_distance(PG_FUNCTION_ARGS) Datum queryDatum = PG_GETARG_DATUM(1); Oid subtype = PG_GETARG_OID(3); int64KEY *kkk = (int64KEY *) DatumGetPointer(entry->key); - int64 query; + 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; - if (likely(subtype == InvalidOid || subtype == INT8OID)) - { - query = DatumGetInt64(queryDatum); - 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_int_distance_x(kkk->lower, kkk->upper, - queryDatum, subtype)); + PG_RETURN_FLOAT8(gbt_num_distance(&key, qp, GIST_LEAF(entry), + ti, fcinfo->flinfo)); } Datum diff --git a/contrib/btree_gist/btree_utils_num.c b/contrib/btree_gist/btree_utils_num.c index cff47e2d876..91f253d880c 100644 --- a/contrib/btree_gist/btree_utils_num.c +++ b/contrib/btree_gist/btree_utils_num.c @@ -5,8 +5,6 @@ #include "btree_gist.h" #include "btree_utils_num.h" -#include "catalog/pg_type.h" -#include "funcapi.h" #include "utils/cash.h" #include "utils/date.h" #include "utils/timestamp.h" @@ -271,6 +269,12 @@ gbt_num_consistent(const GBT_NUMKEY_R *key, { 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: @@ -286,7 +290,7 @@ gbt_num_consistent(const GBT_NUMKEY_R *key, 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: @@ -309,139 +313,6 @@ gbt_num_consistent(const GBT_NUMKEY_R *key, return retval; } -/* - * Cross-type dispatch table for the integer opclasses. - * - * This is the single source of truth for which query subtypes the integer - * cross-type path understands. gbt_int_query_to_int64() promotes exactly the - * subtypes listed here, and the SQL-visible gbt_int_crosstype_subtypes() - * exposes the same list so the regression tests can assert that the cross-type - * pg_amop entries in gist_int{2,4,8}_ops never drift from what the C code can - * actually handle. - */ -typedef int64 (*gbt_int_promote_fn) (Datum query); - -static int64 -gbt_int2_to_int64(Datum query) -{ - return (int64) DatumGetInt16(query); -} - -static int64 -gbt_int4_to_int64(Datum query) -{ - return (int64) DatumGetInt32(query); -} - -static int64 -gbt_int8_to_int64(Datum query) -{ - return DatumGetInt64(query); -} - -typedef struct -{ - Oid subtype; - gbt_int_promote_fn promote; -} gbt_int_crosstype; - -static const gbt_int_crosstype gbt_int_crosstype_table[] = { - {INT2OID, gbt_int2_to_int64}, - {INT4OID, gbt_int4_to_int64}, - {INT8OID, gbt_int8_to_int64}, -}; - -/* - * Promote a cross-type integer query value to int64. - * - * A subtype outside gbt_int_crosstype_table means the C dispatch is out of sync - * with the operator-family registrations in pg_amop, so we treat it as an - * internal error. The regression tests assert this never happens. - */ -static int64 -gbt_int_query_to_int64(Datum query, Oid subtype) -{ - int i; - - for (i = 0; i < lengthof(gbt_int_crosstype_table); i++) - { - if (gbt_int_crosstype_table[i].subtype == subtype) - return gbt_int_crosstype_table[i].promote(query); - } - - elog(ERROR, "unrecognized subtype %u for btree_gist integer cross-type comparison", - subtype); - return 0; /* keep compiler quiet */ -} - -/* - * gbt_int_crosstype_subtypes - * - * Expose the contents of gbt_int_crosstype_table to SQL as a set of type OIDs. - * The btree_gist regression tests use this to check that the cross-type pg_amop - * entries in gist_int{2,4,8}_ops stay in agreement with the C dispatch. - */ -PG_FUNCTION_INFO_V1(gbt_int_crosstype_subtypes); -Datum -gbt_int_crosstype_subtypes(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - - if (SRF_IS_FIRSTCALL()) - funcctx = SRF_FIRSTCALL_INIT(); - - funcctx = SRF_PERCALL_SETUP(); - - if (funcctx->call_cntr < lengthof(gbt_int_crosstype_table)) - { - Oid subtype = gbt_int_crosstype_table[funcctx->call_cntr].subtype; - - SRF_RETURN_NEXT(funcctx, ObjectIdGetDatum(subtype)); - } - - SRF_RETURN_DONE(funcctx); -} - -/* - * Cross-type consistent method for the integer opclasses. - * - * The key range [lower, upper] and the query value are all compared as int64. - * The strategy logic mirrors the same-type path in gbt_num_consistent(): the - * query value keeps its own width (no narrowing to the indexed column type), - * so out-of-range constants behave according to normal integer comparison. - */ -bool -gbt_int_consistent_x(int64 lower, int64 upper, Datum query, Oid subtype, - const StrategyNumber *strategy, bool is_leaf) -{ - int64 q = gbt_int_query_to_int64(query, subtype); - - switch (*strategy) - { - case BTLessEqualStrategyNumber: - /* some k in [lower,upper] has k <= q iff lower <= q */ - return lower <= q; - case BTLessStrategyNumber: - /* leaf: key < q. internal: lower <= q (loose) */ - return is_leaf ? (lower < q) : (lower <= q); - case BTEqualStrategyNumber: - if (is_leaf) - return lower == q; - /* internal: lower <= q <= upper */ - return (lower <= q && q <= upper); - case BTGreaterStrategyNumber: - /* leaf: key > q. internal: upper >= q (loose) */ - return is_leaf ? (upper > q) : (upper >= q); - case BTGreaterEqualStrategyNumber: - return upper >= q; - case BtreeGistNotEqualStrategyNumber: - return !(lower == q && upper == q); - default: - return false; - } -} - - /* * The GiST distance method (for KNN-Gist) */ @@ -468,32 +339,6 @@ gbt_num_distance(const GBT_NUMKEY_R *key, return retval; } -/* - * Cross-type distance method for the integer opclasses. - * - * The distance from the query value to the key range is computed in int64 and - * returned as a float8, matching the same-type integer distance behaviour. - * - * Note that the subtraction is performed in float8, so when the key bound and - * the query value differ by more than 2^53 the returned distance loses - * precision. This only affects the ordering of KNN results that are nearly - * equidistant at that scale; it never changes which rows are returned. - */ -float8 -gbt_int_distance_x(int64 lower, int64 upper, Datum query, Oid subtype) -{ - int64 q = gbt_int_query_to_int64(query, subtype); - - /* query below the range: distance to lower bound */ - if (lower >= q) - return fabs((float8) lower - (float8) q); - /* query above the range: distance to upper bound */ - if (upper <= q) - return fabs((float8) upper - (float8) q); - /* query inside the range */ - return 0.0; -} - GIST_SPLITVEC * gbt_num_picksplit(const GistEntryVector *entryvec, GIST_SPLITVEC *v, diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h index d378b0df37e..be09bdb2581 100644 --- a/contrib/btree_gist/btree_utils_num.h +++ b/contrib/btree_gist/btree_utils_num.h @@ -27,6 +27,18 @@ typedef struct 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 */ @@ -86,7 +98,32 @@ typedef struct (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); @@ -98,22 +135,6 @@ extern bool gbt_num_consistent(const GBT_NUMKEY_R *key, const void *query, extern float8 gbt_num_distance(const GBT_NUMKEY_R *key, const void *query, bool is_leaf, const gbtree_ninfo *tinfo, FmgrInfo *flinfo); -/* - * Cross-type consistent/distance helpers for the integer opclasses - * (int2/int4/int8). All three integer widths promote losslessly to int64, so - * a cross-type query reduces to a plain int64 comparison and there is no need - * for the per-type callback machinery the same-type path uses. These helpers - * are deliberately integer-specific rather than a general cross-type framework; - * other type families should grow their own helpers shaped to their needs as - * the need arises. - */ -extern bool gbt_int_consistent_x(int64 lower, int64 upper, - Datum query, Oid subtype, - const StrategyNumber *strategy, bool is_leaf); - -extern float8 gbt_int_distance_x(int64 lower, int64 upper, - Datum query, Oid subtype); - extern GIST_SPLITVEC *gbt_num_picksplit(const GistEntryVector *entryvec, GIST_SPLITVEC *v, const gbtree_ninfo *tinfo, FmgrInfo *flinfo); diff --git a/contrib/btree_gist/expected/int_crosstype.out b/contrib/btree_gist/expected/int_crosstype.out index bd2b90bd3f6..af03cf84a40 100644 --- a/contrib/btree_gist/expected/int_crosstype.out +++ b/contrib/btree_gist/expected/int_crosstype.out @@ -6,66 +6,11 @@ -- index, and (c) values outside the smaller subtype's range are handled -- according to normal comparison semantics, without narrowing or erroring. -- --- Catalog invariant: the cross-type pg_amop entries in gist_int{2,4,8}_ops must --- agree, in both directions, with what the C cross-type dispatch can handle. --- The set of supported query subtypes is read from the C side via --- gbt_int_crosstype_subtypes(), so there is no hand-maintained second copy of --- the list here: registering a pg_amop row whose subtype the dispatch does not --- handle (or dropping a dispatch entry while its pg_amop rows remain, or vice --- versa) shows up as a diff below. Cross-type pg_amproc rows must also stay --- absent, since the dispatch reuses the same-type support functions. +-- The integer cross-type support is handled inside the existing +-- consistent/distance support functions (which dispatch on the subtype OID), +-- so the operator families must not contain any cross-type (different +-- left/right input type) support-function entries. -- -WITH dispatch_subtypes(typ) AS ( - SELECT s.subtype::regtype - FROM gbt_int_crosstype_subtypes() AS s(subtype) -), -gist_int_opclasses(opfamily, indextype) AS ( - SELECT opc.opcname::text, opc.opcintype::regtype - FROM pg_opclass opc - JOIN pg_am am ON am.oid = opc.opcmethod - WHERE am.amname = 'gist' - AND opc.opcname IN ('gist_int2_ops', 'gist_int4_ops', 'gist_int8_ops') -), -expected_pairs(opfamily, lefttype, righttype) AS ( - SELECT oc.opfamily, oc.indextype, ds.typ - FROM gist_int_opclasses oc - CROSS JOIN dispatch_subtypes ds - WHERE ds.typ <> oc.indextype -), -expected_amop(opfamily, lefttype, righttype, strategy, purpose) AS ( - SELECT opfamily, lefttype, righttype, strategy, purpose - FROM expected_pairs - CROSS JOIN (VALUES - (1, 's'), (2, 's'), (3, 's'), (4, 's'), - (5, 's'), (6, 's'), (15, 'o') - ) AS strategy_purposes(strategy, purpose) -), -actual_amop AS ( - SELECT opf.opfname::text AS opfamily, - amop.amoplefttype::regtype AS lefttype, - amop.amoprighttype::regtype AS righttype, - amop.amopstrategy::int AS strategy, - amop.amoppurpose::text AS purpose - FROM pg_amop amop - JOIN pg_opfamily opf ON opf.oid = amop.amopfamily - JOIN pg_am am ON am.oid = opf.opfmethod - WHERE am.amname = 'gist' - AND opf.opfname IN ('gist_int2_ops', 'gist_int4_ops', 'gist_int8_ops') - AND amop.amoplefttype <> amop.amoprighttype -) -SELECT * -FROM ( - SELECT 'missing from pg_amop' AS status, * - FROM (SELECT * FROM expected_amop EXCEPT SELECT * FROM actual_amop) missing - UNION ALL - SELECT 'unexpected in pg_amop' AS status, * - FROM (SELECT * FROM actual_amop EXCEPT SELECT * FROM expected_amop) unexpected -) diff -ORDER BY status, opfamily, lefttype::text, righttype::text, strategy, purpose; - status | opfamily | lefttype | righttype | strategy | purpose ---------+----------+----------+-----------+----------+--------- -(0 rows) - SELECT opf.opfname AS opfamily, amproc.amproclefttype::regtype AS lefttype, amproc.amprocrighttype::regtype AS righttype, diff --git a/contrib/btree_gist/sql/int_crosstype.sql b/contrib/btree_gist/sql/int_crosstype.sql index d38084f1941..b337e26a1af 100644 --- a/contrib/btree_gist/sql/int_crosstype.sql +++ b/contrib/btree_gist/sql/int_crosstype.sql @@ -7,63 +7,11 @@ -- according to normal comparison semantics, without narrowing or erroring. -- --- Catalog invariant: the cross-type pg_amop entries in gist_int{2,4,8}_ops must --- agree, in both directions, with what the C cross-type dispatch can handle. --- The set of supported query subtypes is read from the C side via --- gbt_int_crosstype_subtypes(), so there is no hand-maintained second copy of --- the list here: registering a pg_amop row whose subtype the dispatch does not --- handle (or dropping a dispatch entry while its pg_amop rows remain, or vice --- versa) shows up as a diff below. Cross-type pg_amproc rows must also stay --- absent, since the dispatch reuses the same-type support functions. +-- The integer cross-type support is handled inside the existing +-- consistent/distance support functions (which dispatch on the subtype OID), +-- so the operator families must not contain any cross-type (different +-- left/right input type) support-function entries. -- -WITH dispatch_subtypes(typ) AS ( - SELECT s.subtype::regtype - FROM gbt_int_crosstype_subtypes() AS s(subtype) -), -gist_int_opclasses(opfamily, indextype) AS ( - SELECT opc.opcname::text, opc.opcintype::regtype - FROM pg_opclass opc - JOIN pg_am am ON am.oid = opc.opcmethod - WHERE am.amname = 'gist' - AND opc.opcname IN ('gist_int2_ops', 'gist_int4_ops', 'gist_int8_ops') -), -expected_pairs(opfamily, lefttype, righttype) AS ( - SELECT oc.opfamily, oc.indextype, ds.typ - FROM gist_int_opclasses oc - CROSS JOIN dispatch_subtypes ds - WHERE ds.typ <> oc.indextype -), -expected_amop(opfamily, lefttype, righttype, strategy, purpose) AS ( - SELECT opfamily, lefttype, righttype, strategy, purpose - FROM expected_pairs - CROSS JOIN (VALUES - (1, 's'), (2, 's'), (3, 's'), (4, 's'), - (5, 's'), (6, 's'), (15, 'o') - ) AS strategy_purposes(strategy, purpose) -), -actual_amop AS ( - SELECT opf.opfname::text AS opfamily, - amop.amoplefttype::regtype AS lefttype, - amop.amoprighttype::regtype AS righttype, - amop.amopstrategy::int AS strategy, - amop.amoppurpose::text AS purpose - FROM pg_amop amop - JOIN pg_opfamily opf ON opf.oid = amop.amopfamily - JOIN pg_am am ON am.oid = opf.opfmethod - WHERE am.amname = 'gist' - AND opf.opfname IN ('gist_int2_ops', 'gist_int4_ops', 'gist_int8_ops') - AND amop.amoplefttype <> amop.amoprighttype -) -SELECT * -FROM ( - SELECT 'missing from pg_amop' AS status, * - FROM (SELECT * FROM expected_amop EXCEPT SELECT * FROM actual_amop) missing - UNION ALL - SELECT 'unexpected in pg_amop' AS status, * - FROM (SELECT * FROM actual_amop EXCEPT SELECT * FROM expected_amop) unexpected -) diff -ORDER BY status, opfamily, lefttype::text, righttype::text, strategy, purpose; - SELECT opf.opfname AS opfamily, amproc.amproclefttype::regtype AS lefttype, amproc.amprocrighttype::regtype AS righttype, -- 2.50.1 (Apple Git-155)