public inbox for [email protected]
help / color / mirror / Atom feedFrom: Alexander Nestorov <[email protected]>
To: Maxime Schoemans <[email protected]>
Cc: Andrey Borodin <[email protected]>
Cc: pgsql-hackers mailing list <[email protected]>
Subject: Re: [PATCH] btree_gist: add cross-type integer operator support for GiST
Date: Wed, 24 Jun 2026 00:53:47 +0200
Message-ID: <ac05a661-53a5-478a-9907-f56a960dab51@Spark> (raw)
In-Reply-To: <[email protected]>
References: <aac10ffa-a0ca-4c49-846b-3655cbc6b37e@Spark>
<36b4f67d-5975-452c-a6b8-b6407f0924ee@Spark>
<[email protected]>
<18e88767-6c31-402a-887e-37c38b366a6a@Spark>
<80ef3b41-1a71-47a4-a320-29e118d7092c@Spark>
<[email protected]>
Hey Maxime!
Thank you for reviewing my changes so far and for proposing the changes in your
patch! And thank you for the kind words!
1)
I think we still need to check `InvalidOid` because execIndexing.c:~800
initializes scan keys ('ScanKeyEntryInitialize') with `InvalidOid` as the
subtype. Running the 'not_equal', 'partitions' and 'without_overlaps' tests
confirms that 'InvalidOid' must be handled. I can add the check again in the
switch. (attaching patch)
2) and 3)
I completely agree, your patch makes it much cleaner.
I applied your patch (with the 'InvalidOid' fix) on top of mine and ran all the
tests and benchmarks that I prepared previously. These are the results before
the entire patchset and after:
duration=15s/run, reps=10, 1 client, interleaved, lower is better
------------------------------------------------------------------------
before consistent min/med/mean = 51.246 51.361 51.378 ms
after consistent min/med/mean = 51.849 51.958 51.967 ms
------------------------------------------------------------------------
before distance min/med/mean = 76.441 76.561 76.567 ms
after distance min/med/mean = 76.568 76.678 76.681 ms
------------------------------------------------------------------------
I added one more commit that adds a small description of this fix in the docs.
The current patchset has been rebased on top of ef01ca6dbca54e9bf3abea01c357b346847ebcf3
Best regards!
Attachments:
[application/octet-stream] v4-0001-Implement-cross-type-operators-for-GiST-indexes.patch (26.4K, 3-v4-0001-Implement-cross-type-operators-for-GiST-indexes.patch)
download | inline diff:
From 2c7258fdef6c91551996cb7e3f4184c5b4daa2f4 Mon Sep 17 00:00:00 2001
From: Alexander Nestorov <[email protected]>
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 | 143 +++++++++++++++++
contrib/btree_gist/btree_gist.control | 2 +-
contrib/btree_gist/btree_int2.c | 70 ++++++--
contrib/btree_gist/btree_int4.c | 70 ++++++--
contrib/btree_gist/btree_int8.c | 70 ++++++--
contrib/btree_gist/btree_utils_num.c | 160 +++++++++++++++++++
contrib/btree_gist/btree_utils_num.h | 16 ++
contrib/btree_gist/meson.build | 2 +
9 files changed, 501 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..c9ff1955204
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.9--1.10.sql
@@ -0,0 +1,143 @@
+/* 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 are promoted to int64 and compared by the integer cross-type
+-- helpers in btree_utils_num.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;
+
+-- 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,
+ 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..d00b24d9a57 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,
@@ -109,12 +109,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 +168,61 @@ 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;
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));
+ if (likely(subtype == InvalidOid || subtype == INT2OID))
+ {
+ query = DatumGetInt16(queryDatum);
+ PG_RETURN_BOOL(gbt_num_consistent(&key, &query, &strategy,
+ GIST_LEAF(entry), &tinfo,
+ fcinfo->flinfo));
+ }
+
+ PG_RETURN_BOOL(gbt_int_consistent_x((int64) kkk->lower, (int64) kkk->upper,
+ queryDatum, subtype, &strategy,
+ GIST_LEAF(entry)));
}
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;
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));
+ if (likely(subtype == InvalidOid || subtype == INT2OID))
+ {
+ query = DatumGetInt16(queryDatum);
+ PG_RETURN_FLOAT8(gbt_num_distance(&key, &query, GIST_LEAF(entry),
+ &tinfo, fcinfo->flinfo));
+ }
+
+ PG_RETURN_FLOAT8(gbt_int_distance_x((int64) kkk->lower, (int64) kkk->upper,
+ queryDatum, subtype));
}
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..4df91f2057c 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,
@@ -107,12 +107,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 +166,61 @@ 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;
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));
+ if (likely(subtype == InvalidOid || subtype == INT4OID))
+ {
+ query = DatumGetInt32(queryDatum);
+ PG_RETURN_BOOL(gbt_num_consistent(&key, &query, &strategy,
+ GIST_LEAF(entry), &tinfo,
+ fcinfo->flinfo));
+ }
+
+ PG_RETURN_BOOL(gbt_int_consistent_x((int64) kkk->lower, (int64) kkk->upper,
+ queryDatum, subtype, &strategy,
+ GIST_LEAF(entry)));
}
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;
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));
+ if (likely(subtype == InvalidOid || subtype == INT4OID))
+ {
+ query = DatumGetInt32(queryDatum);
+ PG_RETURN_FLOAT8(gbt_num_distance(&key, &query, GIST_LEAF(entry),
+ &tinfo, fcinfo->flinfo));
+ }
+
+ PG_RETURN_FLOAT8(gbt_int_distance_x((int64) kkk->lower, (int64) kkk->upper,
+ queryDatum, subtype));
}
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..ae01273b709 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,
@@ -109,12 +109,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 +168,61 @@ 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;
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));
+ if (likely(subtype == InvalidOid || subtype == INT8OID))
+ {
+ query = DatumGetInt64(queryDatum);
+ PG_RETURN_BOOL(gbt_num_consistent(&key, &query, &strategy,
+ GIST_LEAF(entry), &tinfo,
+ fcinfo->flinfo));
+ }
+
+ PG_RETURN_BOOL(gbt_int_consistent_x(kkk->lower, kkk->upper,
+ queryDatum, subtype, &strategy,
+ GIST_LEAF(entry)));
}
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;
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));
+ if (likely(subtype == InvalidOid || subtype == INT8OID))
+ {
+ query = DatumGetInt64(queryDatum);
+ PG_RETURN_FLOAT8(gbt_num_distance(&key, &query, GIST_LEAF(entry),
+ &tinfo, fcinfo->flinfo));
+ }
+
+ PG_RETURN_FLOAT8(gbt_int_distance_x(kkk->lower, kkk->upper,
+ queryDatum, subtype));
}
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..cff47e2d876 100644
--- a/contrib/btree_gist/btree_utils_num.c
+++ b/contrib/btree_gist/btree_utils_num.c
@@ -2,12 +2,14 @@
* contrib/btree_gist/btree_utils_num.c
*/
#include "postgres.h"
#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"
GISTENTRY *
@@ -304,12 +306,144 @@ gbt_num_consistent(const GBT_NUMKEY_R *key,
retval = false;
}
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)
*/
float8
@@ -331,12 +465,38 @@ gbt_num_distance(const GBT_NUMKEY_R *key,
else
retval = 0.0;
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,
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..d378b0df37e 100644
--- a/contrib/btree_gist/btree_utils_num.h
+++ b/contrib/btree_gist/btree_utils_num.h
@@ -95,12 +95,28 @@ 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 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);
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
[application/octet-stream] v4-0002-Add-tests-for-cross-type-operators-for-GiST-indexes.patch (23.9K, 4-v4-0002-Add-tests-for-cross-type-operators-for-GiST-indexes.patch)
download | inline diff:
From 138c09a5b13faac8edeb387eab26799e07ab1a07 Mon Sep 17 00:00:00 2001
From: Alexander Nestorov <[email protected]>
Date: Thu, 4 Jun 2026 00:08:27 +0200
Subject: [PATCH] Add tests for cross-type operators for GiST indexes
---
contrib/btree_gist/expected/int_crosstype.out | 568 ++++++++++++++++++
contrib/btree_gist/sql/int_crosstype.sql | 236 ++++++++
2 files changed, 804 insertions(+)
create mode 100644 contrib/btree_gist/expected/int_crosstype.out
create mode 100644 contrib/btree_gist/sql/int_crosstype.sql
diff --git a/contrib/btree_gist/expected/int_crosstype.out b/contrib/btree_gist/expected/int_crosstype.out
new file mode 100644
index 00000000000..bd2b90bd3f6
--- /dev/null
+++ b/contrib/btree_gist/expected/int_crosstype.out
@@ -0,0 +1,568 @@
+-- Cross-type operator support for int2/int4/int8 in GiST.
+--
+-- Verifies that (a) the cross-type B-tree-style operators registered in
+-- gist_int{2,4,8}_ops match the results of seqscans using the same operator
+-- expressions, (b) the KNN <-> operator works across types and uses the
+-- 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.
+--
+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,
+ amproc.amprocnum AS procnum,
+ amproc.amproc::regproc AS proc
+FROM pg_amproc amproc
+ JOIN pg_opfamily opf ON opf.oid = amproc.amprocfamily
+ 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 amproc.amproclefttype <> amproc.amprocrighttype
+ORDER BY opf.opfname,
+ amproc.amproclefttype::regtype::text,
+ amproc.amprocrighttype::regtype::text,
+ amproc.amprocnum,
+ amproc.amproc::regproc::text;
+ opfamily | lefttype | righttype | procnum | proc
+----------+----------+-----------+---------+------
+(0 rows)
+
+CREATE TABLE ct_i2 (a int2);
+CREATE TABLE ct_i4 (a int4);
+CREATE TABLE ct_i8 (a int8);
+INSERT INTO ct_i2 SELECT g::int2 FROM generate_series(-100, 100) g;
+INSERT INTO ct_i4 SELECT g FROM generate_series(-100, 100) g;
+INSERT INTO ct_i8 SELECT g::int8 FROM generate_series(-100, 100) g;
+-- Add some values that are representable only in wider types, to exercise
+-- the path where the cross-type query constant is out of range of the key
+-- type.
+INSERT INTO ct_i4 VALUES (100000), (-100000);
+INSERT INTO ct_i8 VALUES (5000000000), (-5000000000);
+CREATE INDEX ct_i2_idx ON ct_i2 USING gist (a);
+CREATE INDEX ct_i4_idx ON ct_i4 USING gist (a);
+CREATE INDEX ct_i8_idx ON ct_i8 USING gist (a);
+ANALYZE ct_i2;
+ANALYZE ct_i4;
+ANALYZE ct_i8;
+SET enable_seqscan = off;
+SET enable_bitmapscan = off;
+-- int2 key x int4 query
+SELECT count(*) FROM ct_i2 WHERE a < 50::int4;
+ count
+-------
+ 150
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a <= 50::int4;
+ count
+-------
+ 151
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a = 50::int4;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a >= 50::int4;
+ count
+-------
+ 51
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a > 50::int4;
+ count
+-------
+ 50
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a <> 50::int4;
+ count
+-------
+ 200
+(1 row)
+
+-- query out of int2 range: matches nothing for =, everything for <>
+SELECT count(*) FROM ct_i2 WHERE a = 100000::int4;
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a <> 100000::int4;
+ count
+-------
+ 201
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a < 100000::int4;
+ count
+-------
+ 201
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a > 100000::int4;
+ count
+-------
+ 0
+(1 row)
+
+-- int2 key x int8 query
+SELECT count(*) FROM ct_i2 WHERE a < 50::int8;
+ count
+-------
+ 150
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a <= 50::int8;
+ count
+-------
+ 151
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a = 50::int8;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a >= 50::int8;
+ count
+-------
+ 51
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a > 50::int8;
+ count
+-------
+ 50
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a <> 50::int8;
+ count
+-------
+ 200
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a = 5000000000::int8;
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM ct_i2 WHERE a < 5000000000::int8;
+ count
+-------
+ 201
+(1 row)
+
+-- int4 key x int2 query
+SELECT count(*) FROM ct_i4 WHERE a < 50::int2;
+ count
+-------
+ 151
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a <= 50::int2;
+ count
+-------
+ 152
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a = 50::int2;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a >= 50::int2;
+ count
+-------
+ 52
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a > 50::int2;
+ count
+-------
+ 51
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a <> 50::int2;
+ count
+-------
+ 202
+(1 row)
+
+-- int4 key x int8 query
+SELECT count(*) FROM ct_i4 WHERE a < 50::int8;
+ count
+-------
+ 151
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a <= 50::int8;
+ count
+-------
+ 152
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a = 50::int8;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a >= 50::int8;
+ count
+-------
+ 52
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a > 50::int8;
+ count
+-------
+ 51
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a <> 50::int8;
+ count
+-------
+ 202
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a = 5000000000::int8;
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM ct_i4 WHERE a < 5000000000::int8;
+ count
+-------
+ 203
+(1 row)
+
+-- int8 key x int2 query
+SELECT count(*) FROM ct_i8 WHERE a < 50::int2;
+ count
+-------
+ 151
+(1 row)
+
+SELECT count(*) FROM ct_i8 WHERE a <= 50::int2;
+ count
+-------
+ 152
+(1 row)
+
+SELECT count(*) FROM ct_i8 WHERE a = 50::int2;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM ct_i8 WHERE a >= 50::int2;
+ count
+-------
+ 52
+(1 row)
+
+SELECT count(*) FROM ct_i8 WHERE a > 50::int2;
+ count
+-------
+ 51
+(1 row)
+
+SELECT count(*) FROM ct_i8 WHERE a <> 50::int2;
+ count
+-------
+ 202
+(1 row)
+
+-- int8 key x int4 query
+SELECT count(*) FROM ct_i8 WHERE a < 50::int4;
+ count
+-------
+ 151
+(1 row)
+
+SELECT count(*) FROM ct_i8 WHERE a <= 50::int4;
+ count
+-------
+ 152
+(1 row)
+
+SELECT count(*) FROM ct_i8 WHERE a = 50::int4;
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM ct_i8 WHERE a >= 50::int4;
+ count
+-------
+ 52
+(1 row)
+
+SELECT count(*) FROM ct_i8 WHERE a > 50::int4;
+ count
+-------
+ 51
+(1 row)
+
+SELECT count(*) FROM ct_i8 WHERE a <> 50::int4;
+ count
+-------
+ 202
+(1 row)
+
+-- Confirm the index is actually used for a cross-type predicate.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM ct_i4 WHERE a = 50::int8;
+ QUERY PLAN
+------------------------------------------------
+ Aggregate
+ -> Index Only Scan using ct_i4_idx on ct_i4
+ Index Cond: (a = '50'::bigint)
+(3 rows)
+
+-- Cross-type KNN: int4 key ordered by int2 / int8 queries.
+EXPLAIN (COSTS OFF)
+SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int2 LIMIT 3;
+ QUERY PLAN
+------------------------------------------------
+ Limit
+ -> Index Only Scan using ct_i4_idx on ct_i4
+ Order By: (a <-> '-100'::smallint)
+(3 rows)
+
+SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int2 LIMIT 3;
+ a
+------
+ -100
+ -99
+ -98
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int8 LIMIT 3;
+ QUERY PLAN
+------------------------------------------------
+ Limit
+ -> Index Only Scan using ct_i4_idx on ct_i4
+ Order By: (a <-> '-100'::bigint)
+(3 rows)
+
+SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int8 LIMIT 3;
+ a
+------
+ -100
+ -99
+ -98
+(3 rows)
+
+-- Cross-type KNN: int2 key ordered by int4 / int8 queries.
+SELECT a FROM ct_i2 ORDER BY a <-> '-100'::int4 LIMIT 3;
+ a
+------
+ -100
+ -99
+ -98
+(3 rows)
+
+SELECT a FROM ct_i2 ORDER BY a <-> '-100'::int8 LIMIT 3;
+ a
+------
+ -100
+ -99
+ -98
+(3 rows)
+
+-- Cross-type KNN: int8 key ordered by int2 / int4 queries.
+SELECT a FROM ct_i8 ORDER BY a <-> '-100'::int2 LIMIT 3;
+ a
+------
+ -100
+ -99
+ -98
+(3 rows)
+
+SELECT a FROM ct_i8 ORDER BY a <-> '-100'::int4 LIMIT 3;
+ a
+------
+ -100
+ -99
+ -98
+(3 rows)
+
+-- Combined: cross-type WHERE + cross-type ORDER BY on the same index.
+EXPLAIN (COSTS OFF)
+SELECT a FROM ct_i4 WHERE a < 80::int8 ORDER BY a <-> '-100'::int8 LIMIT 3;
+ QUERY PLAN
+------------------------------------------------
+ Limit
+ -> Index Only Scan using ct_i4_idx on ct_i4
+ Index Cond: (a < '80'::bigint)
+ Order By: (a <-> '-100'::bigint)
+(4 rows)
+
+SELECT a FROM ct_i4 WHERE a < 80::int8 ORDER BY a <-> '-100'::int8 LIMIT 3;
+ a
+------
+ -100
+ -99
+ -98
+(3 rows)
+
+-- Standalone distance-function smoke tests (not going through the index),
+-- including the overflow-detection paths.
+SELECT int2_int4_dist(3::int2, 10::int4);
+ int2_int4_dist
+----------------
+ 7
+(1 row)
+
+SELECT int4_int2_dist(-5::int4, 5::int2);
+ int4_int2_dist
+----------------
+ 10
+(1 row)
+
+SELECT int2_int8_dist(3::int2, 10::int8);
+ int2_int8_dist
+----------------
+ 7
+(1 row)
+
+SELECT int8_int2_dist(100::int8, -5::int2);
+ int8_int2_dist
+----------------
+ 105
+(1 row)
+
+SELECT int4_int8_dist(100::int4, 5000000000::int8);
+ int4_int8_dist
+----------------
+ 4999999900
+(1 row)
+
+SELECT int8_int4_dist(5000000000::int8, 100::int4);
+ int8_int4_dist
+----------------
+ 4999999900
+(1 row)
+
+-- Overflow detection: INT32_MIN distance from a positive int2 can't fit
+-- in int32, should error.
+SELECT int2_int4_dist(1::int2, -2147483648::int4);
+ERROR: integer out of range
+-- Likewise INT64_MIN distance from a positive int4 can't fit in int64.
+SELECT int4_int8_dist(1::int4, -9223372036854775808::int8);
+ERROR: bigint out of range
+--
+-- Multi-column GiST index with mixed-type predicates. This is the
+-- original motivating case: without cross-type operator support the
+-- planner can only use one column as an Index Cond and applies the
+-- other(s) as a Filter post-scan. Here both columns should appear as
+-- Index Cond.
+--
+CREATE TABLE ct_multi (a int4, b int8);
+INSERT INTO ct_multi
+ SELECT g, (g * 2)::int8 FROM generate_series(-50, 50) g;
+CREATE INDEX ct_multi_idx ON ct_multi USING gist (a, b);
+ANALYZE ct_multi;
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM ct_multi WHERE a = 25::int8 AND b = 50::int4;
+ QUERY PLAN
+-------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using ct_multi_idx on ct_multi
+ Index Cond: ((a = '25'::bigint) AND (b = 50))
+(3 rows)
+
+SELECT count(*) FROM ct_multi WHERE a = 25::int8 AND b = 50::int4;
+ count
+-------
+ 1
+(1 row)
+
+-- Mixed cross-type ranges across both columns.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM ct_multi WHERE a < 10::int8 AND b > 0::int2;
+ QUERY PLAN
+------------------------------------------------------------------
+ Aggregate
+ -> Index Only Scan using ct_multi_idx on ct_multi
+ Index Cond: ((a < '10'::bigint) AND (b > '0'::smallint))
+(3 rows)
+
+SELECT count(*) FROM ct_multi WHERE a < 10::int8 AND b > 0::int2;
+ count
+-------
+ 9
+(1 row)
+
+DROP TABLE ct_multi;
+DROP TABLE ct_i2;
+DROP TABLE ct_i4;
+DROP TABLE ct_i8;
diff --git a/contrib/btree_gist/sql/int_crosstype.sql b/contrib/btree_gist/sql/int_crosstype.sql
new file mode 100644
index 00000000000..d38084f1941
--- /dev/null
+++ b/contrib/btree_gist/sql/int_crosstype.sql
@@ -0,0 +1,236 @@
+-- Cross-type operator support for int2/int4/int8 in GiST.
+--
+-- Verifies that (a) the cross-type B-tree-style operators registered in
+-- gist_int{2,4,8}_ops match the results of seqscans using the same operator
+-- expressions, (b) the KNN <-> operator works across types and uses the
+-- 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.
+--
+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,
+ amproc.amprocnum AS procnum,
+ amproc.amproc::regproc AS proc
+FROM pg_amproc amproc
+ JOIN pg_opfamily opf ON opf.oid = amproc.amprocfamily
+ 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 amproc.amproclefttype <> amproc.amprocrighttype
+ORDER BY opf.opfname,
+ amproc.amproclefttype::regtype::text,
+ amproc.amprocrighttype::regtype::text,
+ amproc.amprocnum,
+ amproc.amproc::regproc::text;
+
+CREATE TABLE ct_i2 (a int2);
+CREATE TABLE ct_i4 (a int4);
+CREATE TABLE ct_i8 (a int8);
+
+INSERT INTO ct_i2 SELECT g::int2 FROM generate_series(-100, 100) g;
+INSERT INTO ct_i4 SELECT g FROM generate_series(-100, 100) g;
+INSERT INTO ct_i8 SELECT g::int8 FROM generate_series(-100, 100) g;
+
+-- Add some values that are representable only in wider types, to exercise
+-- the path where the cross-type query constant is out of range of the key
+-- type.
+INSERT INTO ct_i4 VALUES (100000), (-100000);
+INSERT INTO ct_i8 VALUES (5000000000), (-5000000000);
+
+CREATE INDEX ct_i2_idx ON ct_i2 USING gist (a);
+CREATE INDEX ct_i4_idx ON ct_i4 USING gist (a);
+CREATE INDEX ct_i8_idx ON ct_i8 USING gist (a);
+
+ANALYZE ct_i2;
+ANALYZE ct_i4;
+ANALYZE ct_i8;
+
+SET enable_seqscan = off;
+SET enable_bitmapscan = off;
+
+-- int2 key x int4 query
+SELECT count(*) FROM ct_i2 WHERE a < 50::int4;
+SELECT count(*) FROM ct_i2 WHERE a <= 50::int4;
+SELECT count(*) FROM ct_i2 WHERE a = 50::int4;
+SELECT count(*) FROM ct_i2 WHERE a >= 50::int4;
+SELECT count(*) FROM ct_i2 WHERE a > 50::int4;
+SELECT count(*) FROM ct_i2 WHERE a <> 50::int4;
+
+-- query out of int2 range: matches nothing for =, everything for <>
+SELECT count(*) FROM ct_i2 WHERE a = 100000::int4;
+SELECT count(*) FROM ct_i2 WHERE a <> 100000::int4;
+SELECT count(*) FROM ct_i2 WHERE a < 100000::int4;
+SELECT count(*) FROM ct_i2 WHERE a > 100000::int4;
+
+-- int2 key x int8 query
+SELECT count(*) FROM ct_i2 WHERE a < 50::int8;
+SELECT count(*) FROM ct_i2 WHERE a <= 50::int8;
+SELECT count(*) FROM ct_i2 WHERE a = 50::int8;
+SELECT count(*) FROM ct_i2 WHERE a >= 50::int8;
+SELECT count(*) FROM ct_i2 WHERE a > 50::int8;
+SELECT count(*) FROM ct_i2 WHERE a <> 50::int8;
+SELECT count(*) FROM ct_i2 WHERE a = 5000000000::int8;
+SELECT count(*) FROM ct_i2 WHERE a < 5000000000::int8;
+
+-- int4 key x int2 query
+SELECT count(*) FROM ct_i4 WHERE a < 50::int2;
+SELECT count(*) FROM ct_i4 WHERE a <= 50::int2;
+SELECT count(*) FROM ct_i4 WHERE a = 50::int2;
+SELECT count(*) FROM ct_i4 WHERE a >= 50::int2;
+SELECT count(*) FROM ct_i4 WHERE a > 50::int2;
+SELECT count(*) FROM ct_i4 WHERE a <> 50::int2;
+
+-- int4 key x int8 query
+SELECT count(*) FROM ct_i4 WHERE a < 50::int8;
+SELECT count(*) FROM ct_i4 WHERE a <= 50::int8;
+SELECT count(*) FROM ct_i4 WHERE a = 50::int8;
+SELECT count(*) FROM ct_i4 WHERE a >= 50::int8;
+SELECT count(*) FROM ct_i4 WHERE a > 50::int8;
+SELECT count(*) FROM ct_i4 WHERE a <> 50::int8;
+SELECT count(*) FROM ct_i4 WHERE a = 5000000000::int8;
+SELECT count(*) FROM ct_i4 WHERE a < 5000000000::int8;
+
+-- int8 key x int2 query
+SELECT count(*) FROM ct_i8 WHERE a < 50::int2;
+SELECT count(*) FROM ct_i8 WHERE a <= 50::int2;
+SELECT count(*) FROM ct_i8 WHERE a = 50::int2;
+SELECT count(*) FROM ct_i8 WHERE a >= 50::int2;
+SELECT count(*) FROM ct_i8 WHERE a > 50::int2;
+SELECT count(*) FROM ct_i8 WHERE a <> 50::int2;
+
+-- int8 key x int4 query
+SELECT count(*) FROM ct_i8 WHERE a < 50::int4;
+SELECT count(*) FROM ct_i8 WHERE a <= 50::int4;
+SELECT count(*) FROM ct_i8 WHERE a = 50::int4;
+SELECT count(*) FROM ct_i8 WHERE a >= 50::int4;
+SELECT count(*) FROM ct_i8 WHERE a > 50::int4;
+SELECT count(*) FROM ct_i8 WHERE a <> 50::int4;
+
+-- Confirm the index is actually used for a cross-type predicate.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM ct_i4 WHERE a = 50::int8;
+
+-- Cross-type KNN: int4 key ordered by int2 / int8 queries.
+EXPLAIN (COSTS OFF)
+SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int2 LIMIT 3;
+SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int2 LIMIT 3;
+
+EXPLAIN (COSTS OFF)
+SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int8 LIMIT 3;
+SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int8 LIMIT 3;
+
+-- Cross-type KNN: int2 key ordered by int4 / int8 queries.
+SELECT a FROM ct_i2 ORDER BY a <-> '-100'::int4 LIMIT 3;
+SELECT a FROM ct_i2 ORDER BY a <-> '-100'::int8 LIMIT 3;
+
+-- Cross-type KNN: int8 key ordered by int2 / int4 queries.
+SELECT a FROM ct_i8 ORDER BY a <-> '-100'::int2 LIMIT 3;
+SELECT a FROM ct_i8 ORDER BY a <-> '-100'::int4 LIMIT 3;
+
+-- Combined: cross-type WHERE + cross-type ORDER BY on the same index.
+EXPLAIN (COSTS OFF)
+SELECT a FROM ct_i4 WHERE a < 80::int8 ORDER BY a <-> '-100'::int8 LIMIT 3;
+SELECT a FROM ct_i4 WHERE a < 80::int8 ORDER BY a <-> '-100'::int8 LIMIT 3;
+
+-- Standalone distance-function smoke tests (not going through the index),
+-- including the overflow-detection paths.
+SELECT int2_int4_dist(3::int2, 10::int4);
+SELECT int4_int2_dist(-5::int4, 5::int2);
+SELECT int2_int8_dist(3::int2, 10::int8);
+SELECT int8_int2_dist(100::int8, -5::int2);
+SELECT int4_int8_dist(100::int4, 5000000000::int8);
+SELECT int8_int4_dist(5000000000::int8, 100::int4);
+
+-- Overflow detection: INT32_MIN distance from a positive int2 can't fit
+-- in int32, should error.
+SELECT int2_int4_dist(1::int2, -2147483648::int4);
+-- Likewise INT64_MIN distance from a positive int4 can't fit in int64.
+SELECT int4_int8_dist(1::int4, -9223372036854775808::int8);
+
+--
+-- Multi-column GiST index with mixed-type predicates. This is the
+-- original motivating case: without cross-type operator support the
+-- planner can only use one column as an Index Cond and applies the
+-- other(s) as a Filter post-scan. Here both columns should appear as
+-- Index Cond.
+--
+CREATE TABLE ct_multi (a int4, b int8);
+INSERT INTO ct_multi
+ SELECT g, (g * 2)::int8 FROM generate_series(-50, 50) g;
+CREATE INDEX ct_multi_idx ON ct_multi USING gist (a, b);
+ANALYZE ct_multi;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM ct_multi WHERE a = 25::int8 AND b = 50::int4;
+
+SELECT count(*) FROM ct_multi WHERE a = 25::int8 AND b = 50::int4;
+
+-- Mixed cross-type ranges across both columns.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM ct_multi WHERE a < 10::int8 AND b > 0::int2;
+
+SELECT count(*) FROM ct_multi WHERE a < 10::int8 AND b > 0::int2;
+
+DROP TABLE ct_multi;
+
+DROP TABLE ct_i2;
+DROP TABLE ct_i4;
+DROP TABLE ct_i8;
--
2.51.0
[application/octet-stream] v4-0003-Add-docs-for-cross-type-operators-for-GiST-indexes.patch (2.4K, 5-v4-0003-Add-docs-for-cross-type-operators-for-GiST-indexes.patch)
download | inline diff:
From bf76a8c75ddff6253961523a9e2c03c3f71480ec Mon Sep 17 00:00:00 2001
From: Alexander Nestorov <[email protected]>
Date: Thu, 4 Jun 2026 00:09:08 +0200
Subject: [PATCH] Add docs for cross-type operators for GiST indexes
---
doc/src/sgml/btree-gist.sgml | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/doc/src/sgml/btree-gist.sgml b/doc/src/sgml/btree-gist.sgml
index cc09ec83733..1b1dce707cf 100644
--- a/doc/src/sgml/btree-gist.sgml
+++ b/doc/src/sgml/btree-gist.sgml
@@ -49,12 +49,36 @@
<type>float8</type>, <type>timestamp with time zone</type>,
<type>timestamp without time zone</type>,
<type>time without time zone</type>, <type>date</type>, <type>interval</type>,
<type>oid</type>, and <type>money</type>.
</para>
+ <para>
+ As of version 1.10, the operator classes for <type>int2</type>,
+ <type>int4</type> and <type>int8</type> additionally support cross-type
+ query operators. An index on any one of these integer types can be used
+ with a B-tree-style comparison (<literal><</literal>,
+ <literal><=</literal>, <literal>=</literal>, <literal>>=</literal>,
+ <literal>></literal>, <literal><></literal>) or with the
+ distance operator (<literal><-></literal>) against a value of any
+ of the other two integer types, without an explicit cast. Query
+ constants outside the indexed column's range are compared according to
+ normal integer comparison semantics, without narrowing the query value
+ to the column type first.
+ </para>
+
+ <para>
+ The integer distance operator (<literal><-></literal>) used for
+ nearest-neighbor (<literal>ORDER BY</literal>) searches computes the
+ distance in <type>float8</type>. For <type>int8</type> values whose
+ distance from the query exceeds 2^53, the result loses precision and
+ may reorder results that are nearly equidistant; it never changes which
+ rows match. This applies equally to same-type and cross-type
+ <type>int8</type> distance searches.
+ </para>
+
<para>
By default <filename>btree_gist</filename> builds <acronym>GiST</acronym> index with
<function>sortsupport</function> in <firstterm>sorted</firstterm> mode. This usually results in
much faster index built speed. It is still possible to revert to buffered built strategy
by using the <literal>buffering</literal> parameter when creating the index.
</para>
--
2.51.0
[application/octet-stream] v4-0004-Reuse-gbt_num_consistent-for-cross-type-integer-comp.patch (32.9K, 6-v4-0004-Reuse-gbt_num_consistent-for-cross-type-integer-comp.patch)
download | inline diff:
From 4499a0f846262d6bfc8f01d9ca61ce4c41857722 Mon Sep 17 00:00:00 2001
From: Maxime Schoemans <[email protected]>
Date: Mon, 22 Jun 2026 13:31:41 +0200
Subject: [PATCH] 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
@@ -7,14 +7,14 @@
-- 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 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
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
@@ -40,21 +40,12 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION int8_int4_dist(int8, int4)
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,
PROCEDURE = int2_int4_dist,
COMMUTATOR = '<->'
);
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
@@ -88,12 +88,78 @@ 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 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);
@@ -173,56 +239,48 @@ gbt_int2_consistent(PG_FUNCTION_ARGS)
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
Datum queryDatum = PG_GETARG_DATUM(1);
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
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 */
*recheck = false;
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
gbt_int2_distance(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
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
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 4df91f2057c..7e566890143 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -86,12 +86,78 @@ 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 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);
@@ -171,56 +237,48 @@ gbt_int4_consistent(PG_FUNCTION_ARGS)
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
Datum queryDatum = PG_GETARG_DATUM(1);
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
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 */
*recheck = false;
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
gbt_int4_distance(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
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
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 ae01273b709..81c8bb364cf 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -88,12 +88,78 @@ 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 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);
@@ -173,56 +239,48 @@ gbt_int8_consistent(PG_FUNCTION_ARGS)
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
Datum queryDatum = PG_GETARG_DATUM(1);
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
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 */
*recheck = false;
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
gbt_int8_distance(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
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
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 cff47e2d876..91f253d880c 100644
--- a/contrib/btree_gist/btree_utils_num.c
+++ b/contrib/btree_gist/btree_utils_num.c
@@ -2,14 +2,12 @@
* contrib/btree_gist/btree_utils_num.c
*/
#include "postgres.h"
#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"
GISTENTRY *
@@ -268,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:
@@ -283,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
@@ -306,145 +310,12 @@ gbt_num_consistent(const GBT_NUMKEY_R *key,
retval = false;
}
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)
*/
float8
gbt_num_distance(const GBT_NUMKEY_R *key,
@@ -465,38 +336,12 @@ gbt_num_distance(const GBT_NUMKEY_R *key,
else
retval = 0.0;
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,
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 d378b0df37e..be09bdb2581 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,40 +95,49 @@ 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,
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);
-/*
- * 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);
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/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
@@ -3,72 +3,17 @@
-- Verifies that (a) the cross-type B-tree-style operators registered in
-- gist_int{2,4,8}_ops match the results of seqscans using the same operator
-- expressions, (b) the KNN <-> operator works across types and uses the
-- 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,
amproc.amprocnum AS procnum,
amproc.amproc::regproc AS proc
FROM pg_amproc amproc
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
@@ -4,69 +4,17 @@
-- gist_int{2,4,8}_ops match the results of seqscans using the same operator
-- expressions, (b) the KNN <-> operator works across types and uses the
-- 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;
-
SELECT opf.opfname AS opfamily,
amproc.amproclefttype::regtype AS lefttype,
amproc.amprocrighttype::regtype AS righttype,
amproc.amprocnum AS procnum,
amproc.amproc::regproc AS proc
FROM pg_amproc amproc
--
2.51.0
[application/octet-stream] v4-0005-Safe-guard-InvalidOid.patch (4.4K, 7-v4-0005-Safe-guard-InvalidOid.patch)
download | inline diff:
From 5e1791ee0dae54a2e87eab0ae0832aa5cf19bbe5 Mon Sep 17 00:00:00 2001
From: Alexander Nestorov <[email protected]>
Date: Tue, 23 Jun 2026 23:07:21 +0200
Subject: [PATCH] Safe-guard InvalidOid
---
contrib/btree_gist/btree_int2.c | 2 ++
contrib/btree_gist/btree_int4.c | 2 ++
contrib/btree_gist/btree_int8.c | 2 ++
contrib/btree_gist/btree_utils_num.c | 8 ++++----
contrib/btree_gist/btree_utils_num.h | 2 +-
src/tools/pgindent/typedefs.list | 1 +
6 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index ea9c206e552..8d644579b23 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -135,12 +135,14 @@ static const gbtree_ninfo tinfo_q8 =
*/
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);
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index 7e566890143..325ee78f2ab 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -137,12 +137,14 @@ 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);
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index 81c8bb364cf..62c06345e87 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -143,12 +143,14 @@ gbt_int8_crosstype(Oid subtype, Datum d, gbt_intkey *q, const void **qp)
*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",
diff --git a/contrib/btree_gist/btree_utils_num.c b/contrib/btree_gist/btree_utils_num.c
index 91f253d880c..145db8154c5 100644
--- a/contrib/btree_gist/btree_utils_num.c
+++ b/contrib/btree_gist/btree_utils_num.c
@@ -267,16 +267,16 @@ gbt_num_consistent(const GBT_NUMKEY_R *key,
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.
+ * 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;
diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h
index be09bdb2581..217b362c169 100644
--- a/contrib/btree_gist/btree_utils_num.h
+++ b/contrib/btree_gist/btree_utils_num.h
@@ -34,13 +34,13 @@ typedef struct
*/
typedef union
{
int16 i2;
int32 i4;
int64 i8;
-} gbt_intkey;
+} gbt_intkey;
/* type description */
typedef struct
{
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
view thread (9+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected]
Subject: Re: [PATCH] btree_gist: add cross-type integer operator support for GiST
In-Reply-To: <ac05a661-53a5-478a-9907-f56a960dab51@Spark>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox