From 742ba36d02d1c519d6a8d97cbb2f5356a53b2909 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 | 37 +++-- contrib/btree_gist/btree_utils_num.h | 41 +++++- contrib/btree_gist/meson.build | 1 + src/tools/pgindent/typedefs.list | 1 + 10 files changed, 558 insertions(+), 50 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 4d03fb2aa36..ff7d784e847 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 2db4baa0926..3a7eca08d3c 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 4b2333cb315..4c8f0d54832 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 971485dc8eb..640211ec0c8 100644 --- a/contrib/btree_gist/btree_utils_num.c +++ b/contrib/btree_gist/btree_utils_num.c @@ -267,82 +267,86 @@ gbt_num_consistent(const GBT_NUMKEY_R *key, const gbtree_ninfo *tinfo, FmgrInfo *flinfo) { bool retval; /* - * On leaf pages we directly apply the check "key->lower OP query"; we - * need not consider key->upper since it will be equal to key->lower. + * 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. + * + * On leaf pages we directly apply the check "query OP key->lower"; we need + * not consider key->upper since it will be equal to key->lower. * * On internal pages we mostly need to check "is lower bound below query?" * and/or "is upper bound above query?", where we must allow equality in * both cases. */ #define lower_is_below_query() \ - tinfo->f_le(key->lower, query, flinfo) + tinfo->f_ge(query, key->lower, flinfo) #define upper_is_above_query() \ - tinfo->f_ge(key->upper, query, flinfo) + tinfo->f_le(query, key->upper, flinfo) switch (strategy) { case BTLessEqualStrategyNumber: if (is_leaf) - retval = tinfo->f_le(key->lower, query, flinfo); + retval = tinfo->f_ge(query, key->lower, flinfo); else retval = lower_is_below_query(); break; case BTLessStrategyNumber: if (is_leaf) - retval = tinfo->f_lt(key->lower, query, flinfo); + retval = tinfo->f_gt(query, key->lower, flinfo); else retval = lower_is_below_query(); break; case BTEqualStrategyNumber: if (is_leaf) - retval = tinfo->f_eq(key->lower, query, flinfo); + retval = tinfo->f_eq(query, key->lower, flinfo); else retval = lower_is_below_query() && upper_is_above_query(); break; case BTGreaterStrategyNumber: if (is_leaf) - retval = tinfo->f_gt(key->lower, query, flinfo); + retval = tinfo->f_lt(query, key->lower, flinfo); else retval = upper_is_above_query(); break; case BTGreaterEqualStrategyNumber: if (is_leaf) - retval = tinfo->f_ge(key->lower, query, flinfo); + retval = tinfo->f_le(query, key->lower, flinfo); else retval = upper_is_above_query(); break; case BtreeGistNotEqualStrategyNumber: if (is_leaf) - retval = !(tinfo->f_eq(key->lower, query, flinfo)); + retval = !(tinfo->f_eq(query, key->lower, flinfo)); else { /* * If the upper/lower bounds are equal, then all entries below * this node must have exactly that value. So we can avoid * descending if the query equals both bounds. In all other * cases, we must descend. */ - retval = !(tinfo->f_eq(key->lower, query, flinfo) && - tinfo->f_eq(key->upper, query, flinfo)); + retval = !(tinfo->f_eq(query, key->lower, flinfo) && + tinfo->f_eq(query, key->upper, flinfo)); } break; default: retval = false; } #undef lower_is_below_query #undef upper_is_above_query return retval; } - /* * The GiST distance method (for KNN-Gist) */ float8 gbt_num_distance(const GBT_NUMKEY_R *key, @@ -353,12 +357,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 52f531f70e4..cbf011fc550 100644 --- a/contrib/btree_gist/btree_utils_num.h +++ b/contrib/btree_gist/btree_utils_num.h @@ -25,12 +25,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 { @@ -187,14 +199,39 @@ float_penalty_num_impl(double olower, double oupper, */ #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)) -/* This macro is not safe to use with actual float inputs, only integers */ -#define GET_FLOAT_DISTANCE(t, arg1, arg2) fabs( ((float8) *((const t *) (arg1))) - ((float8) *((const t *) (arg2))) ) +/* These macros are not safe to use with actual float inputs, only integers */ +#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, 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 117e7379f10..d2213c4080e 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3812,12 +3812,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