From 3800bca3c6be27f0077e5cb5232e56af1b40dc53 Mon Sep 17 00:00:00 2001
From: Alexander Nestorov <alexandernst@gmail.com>
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 <maxime.schoemans@enterprisedb.com>
---
 contrib/btree_gist/Makefile                  |   2 +-
 contrib/btree_gist/btree_gist--1.9--1.10.sql | 134 +++++++++++++++++++
 contrib/btree_gist/btree_gist.control        |   2 +-
 contrib/btree_gist/btree_int2.c              | 130 ++++++++++++++++--
 contrib/btree_gist/btree_int4.c              | 130 ++++++++++++++++--
 contrib/btree_gist/btree_int8.c              | 130 ++++++++++++++++--
 contrib/btree_gist/btree_utils_num.c         |  16 ++-
 contrib/btree_gist/btree_utils_num.h         |  39 +++++-
 contrib/btree_gist/meson.build               |   1 +
 src/tools/pgindent/typedefs.list             |   1 +
 10 files changed, 547 insertions(+), 38 deletions(-)
 create mode 100644 contrib/btree_gist/btree_gist--1.9--1.10.sql

diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile
index fbbbca95598..380c7642fde 100644
--- a/contrib/btree_gist/Makefile
+++ b/contrib/btree_gist/Makefile
@@ -32,13 +32,13 @@ OBJS =  \
 EXTENSION = btree_gist
 DATA = btree_gist--1.0--1.1.sql \
        btree_gist--1.1--1.2.sql btree_gist--1.2--1.3.sql \
        btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \
        btree_gist--1.5--1.6.sql btree_gist--1.6--1.7.sql \
        btree_gist--1.7--1.8.sql btree_gist--1.8--1.9.sql \
-       btree_gist--1.9.sql
+       btree_gist--1.9.sql btree_gist--1.9--1.10.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
         time timetz date interval macaddr macaddr8 inet cidr text varchar char \
         bytea bit varbit numeric uuid not_equal enum bool partitions \
         stratnum without_overlaps
diff --git a/contrib/btree_gist/btree_gist--1.9--1.10.sql b/contrib/btree_gist/btree_gist--1.9--1.10.sql
new file mode 100644
index 00000000000..9cd57455086
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.9--1.10.sql
@@ -0,0 +1,134 @@
+/* contrib/btree_gist/btree_gist--1.9--1.10.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.10'" to load this file. \quit
+
+-- Add cross-type operator support for the integer trio (int2, int4, int8)
+-- to the existing GiST operator families.
+--
+-- GiST's amvalidate requires support functions in a family to have matching
+-- left/right input types, so the catalog additions below are deliberately
+-- pg_amop-only. The existing consistent/distance support functions dispatch
+-- on the subtype OID: same-type queries take the normal path, while mixed-width
+-- integer queries select a cross-type comparison callback that reads the query
+-- and key sides at their own widths (see btree_int{2,4,8}.c).
+
+CREATE FUNCTION int2_int4_dist(int2, int4)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION int4_int2_dist(int4, int2)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION int2_int8_dist(int2, int8)
+RETURNS int8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION int8_int2_dist(int8, int2)
+RETURNS int8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION int4_int8_dist(int4, int8)
+RETURNS int8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION int8_int4_dist(int8, int4)
+RETURNS int8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE OPERATOR <-> (
+	LEFTARG = int2,
+	RIGHTARG = int4,
+	PROCEDURE = int2_int4_dist,
+	COMMUTATOR = '<->'
+);
+
+CREATE OPERATOR <-> (
+	LEFTARG = int4,
+	RIGHTARG = int2,
+	PROCEDURE = int4_int2_dist,
+	COMMUTATOR = '<->'
+);
+
+CREATE OPERATOR <-> (
+	LEFTARG = int2,
+	RIGHTARG = int8,
+	PROCEDURE = int2_int8_dist,
+	COMMUTATOR = '<->'
+);
+
+CREATE OPERATOR <-> (
+	LEFTARG = int8,
+	RIGHTARG = int2,
+	PROCEDURE = int8_int2_dist,
+	COMMUTATOR = '<->'
+);
+
+CREATE OPERATOR <-> (
+	LEFTARG = int4,
+	RIGHTARG = int8,
+	PROCEDURE = int4_int8_dist,
+	COMMUTATOR = '<->'
+);
+
+CREATE OPERATOR <-> (
+	LEFTARG = int8,
+	RIGHTARG = int4,
+	PROCEDURE = int8_int4_dist,
+	COMMUTATOR = '<->'
+);
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
+	OPERATOR	1	<  (int2, int4),
+	OPERATOR	2	<= (int2, int4),
+	OPERATOR	3	=  (int2, int4),
+	OPERATOR	4	>= (int2, int4),
+	OPERATOR	5	>  (int2, int4),
+	OPERATOR	6	<> (int2, int4),
+	OPERATOR	15	<-> (int2, int4) FOR ORDER BY pg_catalog.integer_ops,
+	OPERATOR	1	<  (int2, int8),
+	OPERATOR	2	<= (int2, int8),
+	OPERATOR	3	=  (int2, int8),
+	OPERATOR	4	>= (int2, int8),
+	OPERATOR	5	>  (int2, int8),
+	OPERATOR	6	<> (int2, int8),
+	OPERATOR	15	<-> (int2, int8) FOR ORDER BY pg_catalog.integer_ops;
+
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
+	OPERATOR	1	<  (int4, int2),
+	OPERATOR	2	<= (int4, int2),
+	OPERATOR	3	=  (int4, int2),
+	OPERATOR	4	>= (int4, int2),
+	OPERATOR	5	>  (int4, int2),
+	OPERATOR	6	<> (int4, int2),
+	OPERATOR	15	<-> (int4, int2) FOR ORDER BY pg_catalog.integer_ops,
+	OPERATOR	1	<  (int4, int8),
+	OPERATOR	2	<= (int4, int8),
+	OPERATOR	3	=  (int4, int8),
+	OPERATOR	4	>= (int4, int8),
+	OPERATOR	5	>  (int4, int8),
+	OPERATOR	6	<> (int4, int8),
+	OPERATOR	15	<-> (int4, int8) FOR ORDER BY pg_catalog.integer_ops;
+
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
+	OPERATOR	1	<  (int8, int2),
+	OPERATOR	2	<= (int8, int2),
+	OPERATOR	3	=  (int8, int2),
+	OPERATOR	4	>= (int8, int2),
+	OPERATOR	5	>  (int8, int2),
+	OPERATOR	6	<> (int8, int2),
+	OPERATOR	15	<-> (int8, int2) FOR ORDER BY pg_catalog.integer_ops,
+	OPERATOR	1	<  (int8, int4),
+	OPERATOR	2	<= (int8, int4),
+	OPERATOR	3	=  (int8, int4),
+	OPERATOR	4	>= (int8, int4),
+	OPERATOR	5	>  (int8, int4),
+	OPERATOR	6	<> (int8, int4),
+	OPERATOR	15	<-> (int8, int4) FOR ORDER BY pg_catalog.integer_ops;
diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control
index 69d9341a0ad..e606fa6551d 100644
--- a/contrib/btree_gist/btree_gist.control
+++ b/contrib/btree_gist/btree_gist.control
@@ -1,6 +1,6 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.9'
+default_version = '1.10'
 module_pathname = '$libdir/btree_gist'
 relocatable = true
 trusted = true
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index cc4b33177e3..8d644579b23 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -2,12 +2,13 @@
  * contrib/btree_gist/btree_int2.c
  */
 #include "postgres.h"
 
 #include "btree_gist.h"
 #include "btree_utils_num.h"
+#include "catalog/pg_type.h"
 #include "common/int.h"
 #include "utils/rel.h"
 #include "utils/sortsupport.h"
 
 typedef struct int16key
 {
@@ -73,13 +74,12 @@ gbt_int2key_cmp(const void *a, const void *b, FmgrInfo *flinfo)
 static float8
 gbt_int2_dist(const void *a, const void *b, FmgrInfo *flinfo)
 {
 	return GET_FLOAT_DISTANCE(int16, a, b);
 }
 
-
 static const gbtree_ninfo tinfo =
 {
 	gbt_t_int2,
 	sizeof(int16),
 	4,							/* sizeof(gbtreekey4) */
 	gbt_int2gt,
@@ -88,12 +88,80 @@ static const gbtree_ninfo tinfo =
 	gbt_int2le,
 	gbt_int2lt,
 	gbt_int2key_cmp,
 	gbt_int2_dist
 };
 
+/*
+ * Cross-type GiST callbacks: the indexed key is int2, the query is int4 or
+ * int8.  Both reuse gbt_num_consistent()/gbt_num_distance() via a tinfo whose
+ * comparison/distance callbacks read the query (left) and key (right) sides at
+ * their own widths.  f_cmp is unused on these paths and left NULL.
+ */
+GBT_INT_CMP_FNS(gbt_int2_q4_, int32, int16)
+GBT_INT_CMP_FNS(gbt_int2_q8_, int64, int16)
+
+static const gbtree_ninfo tinfo_q4 =
+{
+	gbt_t_int2,
+	sizeof(int16),
+	4,
+	gbt_int2_q4_gt,
+	gbt_int2_q4_ge,
+	gbt_int2_q4_eq,
+	gbt_int2_q4_le,
+	gbt_int2_q4_lt,
+	NULL,
+	gbt_int2_q4_dist
+};
+
+static const gbtree_ninfo tinfo_q8 =
+{
+	gbt_t_int2,
+	sizeof(int16),
+	4,
+	gbt_int2_q8_gt,
+	gbt_int2_q8_ge,
+	gbt_int2_q8_eq,
+	gbt_int2_q8_le,
+	gbt_int2_q8_lt,
+	NULL,
+	gbt_int2_q8_dist
+};
+
+/*
+ * Cross-type dispatch shared by gbt_int2_consistent and gbt_int2_distance:
+ * select the tinfo for the query subtype and read the query value at its own
+ * width into caller-owned storage.
+ */
+static const gbtree_ninfo *
+gbt_int2_crosstype(Oid subtype, Datum d, gbt_intkey *q, const void **qp)
+{
+	switch (subtype)
+	{
+		case InvalidOid:		/* same-type: exclusion/temporal constraint
+								 * checks pass the native type with subtype 0 */
+		case INT2OID:
+			q->i2 = DatumGetInt16(d);
+			*qp = &q->i2;
+			return &tinfo;
+		case INT4OID:
+			q->i4 = DatumGetInt32(d);
+			*qp = &q->i4;
+			return &tinfo_q4;
+		case INT8OID:
+			q->i8 = DatumGetInt64(d);
+			*qp = &q->i8;
+			return &tinfo_q8;
+		default:
+			elog(ERROR, "unrecognized subtype %u for btree_gist int2 cross-type comparison",
+				 subtype);
+			return NULL;		/* keep compiler quiet */
+	}
+}
+
 
 PG_FUNCTION_INFO_V1(int2_dist);
 Datum
 int2_dist(PG_FUNCTION_ARGS)
 {
 	int16		a = PG_GETARG_INT16(0);
@@ -109,12 +177,46 @@ int2_dist(PG_FUNCTION_ARGS)
 
 	ra = abs(r);
 
 	PG_RETURN_INT16(ra);
 }
 
+PG_FUNCTION_INFO_V1(int2_int4_dist);
+Datum
+int2_int4_dist(PG_FUNCTION_ARGS)
+{
+	int32		a = (int32) PG_GETARG_INT16(0);
+	int32		b = PG_GETARG_INT32(1);
+	int32		r;
+
+	if (pg_sub_s32_overflow(a, b, &r) ||
+		r == PG_INT32_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	PG_RETURN_INT32(abs(r));
+}
+
+PG_FUNCTION_INFO_V1(int2_int8_dist);
+Datum
+int2_int8_dist(PG_FUNCTION_ARGS)
+{
+	int64		a = (int64) PG_GETARG_INT16(0);
+	int64		b = PG_GETARG_INT64(1);
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	PG_RETURN_INT64(i64abs(r));
+}
+
 
 /**************************************************
  * GiST support functions
  **************************************************/
 
 Datum
@@ -134,47 +236,53 @@ gbt_int2_fetch(PG_FUNCTION_ARGS)
 }
 
 Datum
 gbt_int2_consistent(PG_FUNCTION_ARGS)
 {
 	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
-	int16		query = PG_GETARG_INT16(1);
+	Datum		queryDatum = PG_GETARG_DATUM(1);
 	StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
-#ifdef NOT_USED
 	Oid			subtype = PG_GETARG_OID(3);
-#endif
 	bool	   *recheck = (bool *) PG_GETARG_POINTER(4);
 	int16KEY   *kkk = (int16KEY *) DatumGetPointer(entry->key);
+	const gbtree_ninfo *ti;
+	gbt_intkey	query;
+	const void *qp;
 	GBT_NUMKEY_R key;
 
 	/* All cases served by this function are exact */
 	*recheck = false;
 
 	key.lower = (GBT_NUMKEY *) &kkk->lower;
 	key.upper = (GBT_NUMKEY *) &kkk->upper;
 
-	PG_RETURN_BOOL(gbt_num_consistent(&key, &query, &strategy,
-									  GIST_LEAF(entry), &tinfo, fcinfo->flinfo));
+	ti = gbt_int2_crosstype(subtype, queryDatum, &query, &qp);
+
+	PG_RETURN_BOOL(gbt_num_consistent(&key, qp, &strategy, GIST_LEAF(entry),
+									  ti, fcinfo->flinfo));
 }
 
 Datum
 gbt_int2_distance(PG_FUNCTION_ARGS)
 {
 	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
-	int16		query = PG_GETARG_INT16(1);
-#ifdef NOT_USED
+	Datum		queryDatum = PG_GETARG_DATUM(1);
 	Oid			subtype = PG_GETARG_OID(3);
-#endif
 	int16KEY   *kkk = (int16KEY *) DatumGetPointer(entry->key);
+	const gbtree_ninfo *ti;
+	gbt_intkey	query;
+	const void *qp;
 	GBT_NUMKEY_R key;
 
 	key.lower = (GBT_NUMKEY *) &kkk->lower;
 	key.upper = (GBT_NUMKEY *) &kkk->upper;
 
-	PG_RETURN_FLOAT8(gbt_num_distance(&key, &query, GIST_LEAF(entry),
-									  &tinfo, fcinfo->flinfo));
+	ti = gbt_int2_crosstype(subtype, queryDatum, &query, &qp);
+
+	PG_RETURN_FLOAT8(gbt_num_distance(&key, qp, GIST_LEAF(entry),
+									  ti, fcinfo->flinfo));
 }
 
 Datum
 gbt_int2_union(PG_FUNCTION_ARGS)
 {
 	GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index 47790578e6b..325ee78f2ab 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -1,12 +1,13 @@
 /*
  * contrib/btree_gist/btree_int4.c
  */
 #include "postgres.h"
 #include "btree_gist.h"
 #include "btree_utils_num.h"
+#include "catalog/pg_type.h"
 #include "common/int.h"
 #include "utils/rel.h"
 #include "utils/sortsupport.h"
 
 typedef struct int32key
 {
@@ -71,13 +72,12 @@ gbt_int4key_cmp(const void *a, const void *b, FmgrInfo *flinfo)
 static float8
 gbt_int4_dist(const void *a, const void *b, FmgrInfo *flinfo)
 {
 	return GET_FLOAT_DISTANCE(int32, a, b);
 }
 
-
 static const gbtree_ninfo tinfo =
 {
 	gbt_t_int4,
 	sizeof(int32),
 	8,							/* sizeof(gbtreekey8) */
 	gbt_int4gt,
@@ -86,12 +86,80 @@ static const gbtree_ninfo tinfo =
 	gbt_int4le,
 	gbt_int4lt,
 	gbt_int4key_cmp,
 	gbt_int4_dist
 };
 
+/*
+ * Cross-type GiST callbacks: the indexed key is int4, the query is int2 or
+ * int8.  Both reuse gbt_num_consistent()/gbt_num_distance() via a tinfo whose
+ * comparison/distance callbacks read the query (left) and key (right) sides at
+ * their own widths.  f_cmp is unused on these paths and left NULL.
+ */
+GBT_INT_CMP_FNS(gbt_int4_q2_, int16, int32)
+GBT_INT_CMP_FNS(gbt_int4_q8_, int64, int32)
+
+static const gbtree_ninfo tinfo_q2 =
+{
+	gbt_t_int4,
+	sizeof(int32),
+	8,
+	gbt_int4_q2_gt,
+	gbt_int4_q2_ge,
+	gbt_int4_q2_eq,
+	gbt_int4_q2_le,
+	gbt_int4_q2_lt,
+	NULL,
+	gbt_int4_q2_dist
+};
+
+static const gbtree_ninfo tinfo_q8 =
+{
+	gbt_t_int4,
+	sizeof(int32),
+	8,
+	gbt_int4_q8_gt,
+	gbt_int4_q8_ge,
+	gbt_int4_q8_eq,
+	gbt_int4_q8_le,
+	gbt_int4_q8_lt,
+	NULL,
+	gbt_int4_q8_dist
+};
+
+/*
+ * Cross-type dispatch shared by gbt_int4_consistent and gbt_int4_distance:
+ * select the tinfo for the query subtype and read the query value at its own
+ * width into caller-owned storage.
+ */
+static const gbtree_ninfo *
+gbt_int4_crosstype(Oid subtype, Datum d, gbt_intkey *q, const void **qp)
+{
+	switch (subtype)
+	{
+		case INT2OID:
+			q->i2 = DatumGetInt16(d);
+			*qp = &q->i2;
+			return &tinfo_q2;
+		case InvalidOid:		/* same-type: exclusion/temporal constraint
+								 * checks pass the native type with subtype 0 */
+		case INT4OID:
+			q->i4 = DatumGetInt32(d);
+			*qp = &q->i4;
+			return &tinfo;
+		case INT8OID:
+			q->i8 = DatumGetInt64(d);
+			*qp = &q->i8;
+			return &tinfo_q8;
+		default:
+			elog(ERROR, "unrecognized subtype %u for btree_gist int4 cross-type comparison",
+				 subtype);
+			return NULL;		/* keep compiler quiet */
+	}
+}
+
 
 PG_FUNCTION_INFO_V1(int4_dist);
 Datum
 int4_dist(PG_FUNCTION_ARGS)
 {
 	int32		a = PG_GETARG_INT32(0);
@@ -107,12 +175,46 @@ int4_dist(PG_FUNCTION_ARGS)
 
 	ra = abs(r);
 
 	PG_RETURN_INT32(ra);
 }
 
+PG_FUNCTION_INFO_V1(int4_int2_dist);
+Datum
+int4_int2_dist(PG_FUNCTION_ARGS)
+{
+	int32		a = PG_GETARG_INT32(0);
+	int32		b = (int32) PG_GETARG_INT16(1);
+	int32		r;
+
+	if (pg_sub_s32_overflow(a, b, &r) ||
+		r == PG_INT32_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	PG_RETURN_INT32(abs(r));
+}
+
+PG_FUNCTION_INFO_V1(int4_int8_dist);
+Datum
+int4_int8_dist(PG_FUNCTION_ARGS)
+{
+	int64		a = (int64) PG_GETARG_INT32(0);
+	int64		b = PG_GETARG_INT64(1);
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	PG_RETURN_INT64(i64abs(r));
+}
+
 
 /**************************************************
  * GiST support functions
  **************************************************/
 
 Datum
@@ -132,47 +234,53 @@ gbt_int4_fetch(PG_FUNCTION_ARGS)
 }
 
 Datum
 gbt_int4_consistent(PG_FUNCTION_ARGS)
 {
 	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
-	int32		query = PG_GETARG_INT32(1);
+	Datum		queryDatum = PG_GETARG_DATUM(1);
 	StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
-#ifdef NOT_USED
 	Oid			subtype = PG_GETARG_OID(3);
-#endif
 	bool	   *recheck = (bool *) PG_GETARG_POINTER(4);
 	int32KEY   *kkk = (int32KEY *) DatumGetPointer(entry->key);
+	const gbtree_ninfo *ti;
+	gbt_intkey	query;
+	const void *qp;
 	GBT_NUMKEY_R key;
 
 	/* All cases served by this function are exact */
 	*recheck = false;
 
 	key.lower = (GBT_NUMKEY *) &kkk->lower;
 	key.upper = (GBT_NUMKEY *) &kkk->upper;
 
-	PG_RETURN_BOOL(gbt_num_consistent(&key, &query, &strategy,
-									  GIST_LEAF(entry), &tinfo, fcinfo->flinfo));
+	ti = gbt_int4_crosstype(subtype, queryDatum, &query, &qp);
+
+	PG_RETURN_BOOL(gbt_num_consistent(&key, qp, &strategy, GIST_LEAF(entry),
+									  ti, fcinfo->flinfo));
 }
 
 Datum
 gbt_int4_distance(PG_FUNCTION_ARGS)
 {
 	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
-	int32		query = PG_GETARG_INT32(1);
-#ifdef NOT_USED
+	Datum		queryDatum = PG_GETARG_DATUM(1);
 	Oid			subtype = PG_GETARG_OID(3);
-#endif
 	int32KEY   *kkk = (int32KEY *) DatumGetPointer(entry->key);
+	const gbtree_ninfo *ti;
+	gbt_intkey	query;
+	const void *qp;
 	GBT_NUMKEY_R key;
 
 	key.lower = (GBT_NUMKEY *) &kkk->lower;
 	key.upper = (GBT_NUMKEY *) &kkk->upper;
 
-	PG_RETURN_FLOAT8(gbt_num_distance(&key, &query, GIST_LEAF(entry),
-									  &tinfo, fcinfo->flinfo));
+	ti = gbt_int4_crosstype(subtype, queryDatum, &query, &qp);
+
+	PG_RETURN_FLOAT8(gbt_num_distance(&key, qp, GIST_LEAF(entry),
+									  ti, fcinfo->flinfo));
 }
 
 Datum
 gbt_int4_union(PG_FUNCTION_ARGS)
 {
 	GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index f48122c8d84..62c06345e87 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -2,12 +2,13 @@
  * contrib/btree_gist/btree_int8.c
  */
 #include "postgres.h"
 
 #include "btree_gist.h"
 #include "btree_utils_num.h"
+#include "catalog/pg_type.h"
 #include "common/int.h"
 #include "utils/rel.h"
 #include "utils/sortsupport.h"
 
 typedef struct int64key
 {
@@ -73,13 +74,12 @@ gbt_int8key_cmp(const void *a, const void *b, FmgrInfo *flinfo)
 static float8
 gbt_int8_dist(const void *a, const void *b, FmgrInfo *flinfo)
 {
 	return GET_FLOAT_DISTANCE(int64, a, b);
 }
 
-
 static const gbtree_ninfo tinfo =
 {
 	gbt_t_int8,
 	sizeof(int64),
 	16,							/* sizeof(gbtreekey16) */
 	gbt_int8gt,
@@ -88,12 +88,80 @@ static const gbtree_ninfo tinfo =
 	gbt_int8le,
 	gbt_int8lt,
 	gbt_int8key_cmp,
 	gbt_int8_dist
 };
 
+/*
+ * Cross-type GiST callbacks: the indexed key is int8, the query is int2 or
+ * int4.  Both reuse gbt_num_consistent()/gbt_num_distance() via a tinfo whose
+ * comparison/distance callbacks read the query (left) and key (right) sides at
+ * their own widths.  f_cmp is unused on these paths and left NULL.
+ */
+GBT_INT_CMP_FNS(gbt_int8_q2_, int16, int64)
+GBT_INT_CMP_FNS(gbt_int8_q4_, int32, int64)
+
+static const gbtree_ninfo tinfo_q2 =
+{
+	gbt_t_int8,
+	sizeof(int64),
+	16,
+	gbt_int8_q2_gt,
+	gbt_int8_q2_ge,
+	gbt_int8_q2_eq,
+	gbt_int8_q2_le,
+	gbt_int8_q2_lt,
+	NULL,
+	gbt_int8_q2_dist
+};
+
+static const gbtree_ninfo tinfo_q4 =
+{
+	gbt_t_int8,
+	sizeof(int64),
+	16,
+	gbt_int8_q4_gt,
+	gbt_int8_q4_ge,
+	gbt_int8_q4_eq,
+	gbt_int8_q4_le,
+	gbt_int8_q4_lt,
+	NULL,
+	gbt_int8_q4_dist
+};
+
+/*
+ * Cross-type dispatch shared by gbt_int8_consistent and gbt_int8_distance:
+ * select the tinfo for the query subtype and read the query value at its own
+ * width into caller-owned storage.
+ */
+static const gbtree_ninfo *
+gbt_int8_crosstype(Oid subtype, Datum d, gbt_intkey *q, const void **qp)
+{
+	switch (subtype)
+	{
+		case INT2OID:
+			q->i2 = DatumGetInt16(d);
+			*qp = &q->i2;
+			return &tinfo_q2;
+		case INT4OID:
+			q->i4 = DatumGetInt32(d);
+			*qp = &q->i4;
+			return &tinfo_q4;
+		case InvalidOid:		/* same-type: exclusion/temporal constraint
+								 * checks pass the native type with subtype 0 */
+		case INT8OID:
+			q->i8 = DatumGetInt64(d);
+			*qp = &q->i8;
+			return &tinfo;
+		default:
+			elog(ERROR, "unrecognized subtype %u for btree_gist int8 cross-type comparison",
+				 subtype);
+			return NULL;		/* keep compiler quiet */
+	}
+}
+
 
 PG_FUNCTION_INFO_V1(int8_dist);
 Datum
 int8_dist(PG_FUNCTION_ARGS)
 {
 	int64		a = PG_GETARG_INT64(0);
@@ -109,12 +177,46 @@ int8_dist(PG_FUNCTION_ARGS)
 
 	ra = i64abs(r);
 
 	PG_RETURN_INT64(ra);
 }
 
+PG_FUNCTION_INFO_V1(int8_int2_dist);
+Datum
+int8_int2_dist(PG_FUNCTION_ARGS)
+{
+	int64		a = PG_GETARG_INT64(0);
+	int64		b = (int64) PG_GETARG_INT16(1);
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	PG_RETURN_INT64(i64abs(r));
+}
+
+PG_FUNCTION_INFO_V1(int8_int4_dist);
+Datum
+int8_int4_dist(PG_FUNCTION_ARGS)
+{
+	int64		a = PG_GETARG_INT64(0);
+	int64		b = (int64) PG_GETARG_INT32(1);
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	PG_RETURN_INT64(i64abs(r));
+}
+
 
 /**************************************************
  * GiST support functions
  **************************************************/
 
 Datum
@@ -134,47 +236,53 @@ gbt_int8_fetch(PG_FUNCTION_ARGS)
 }
 
 Datum
 gbt_int8_consistent(PG_FUNCTION_ARGS)
 {
 	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
-	int64		query = PG_GETARG_INT64(1);
+	Datum		queryDatum = PG_GETARG_DATUM(1);
 	StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
-#ifdef NOT_USED
 	Oid			subtype = PG_GETARG_OID(3);
-#endif
 	bool	   *recheck = (bool *) PG_GETARG_POINTER(4);
 	int64KEY   *kkk = (int64KEY *) DatumGetPointer(entry->key);
+	const gbtree_ninfo *ti;
+	gbt_intkey	query;
+	const void *qp;
 	GBT_NUMKEY_R key;
 
 	/* All cases served by this function are exact */
 	*recheck = false;
 
 	key.lower = (GBT_NUMKEY *) &kkk->lower;
 	key.upper = (GBT_NUMKEY *) &kkk->upper;
 
-	PG_RETURN_BOOL(gbt_num_consistent(&key, &query, &strategy,
-									  GIST_LEAF(entry), &tinfo, fcinfo->flinfo));
+	ti = gbt_int8_crosstype(subtype, queryDatum, &query, &qp);
+
+	PG_RETURN_BOOL(gbt_num_consistent(&key, qp, &strategy, GIST_LEAF(entry),
+									  ti, fcinfo->flinfo));
 }
 
 Datum
 gbt_int8_distance(PG_FUNCTION_ARGS)
 {
 	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
-	int64		query = PG_GETARG_INT64(1);
-#ifdef NOT_USED
+	Datum		queryDatum = PG_GETARG_DATUM(1);
 	Oid			subtype = PG_GETARG_OID(3);
-#endif
 	int64KEY   *kkk = (int64KEY *) DatumGetPointer(entry->key);
+	const gbtree_ninfo *ti;
+	gbt_intkey	query;
+	const void *qp;
 	GBT_NUMKEY_R key;
 
 	key.lower = (GBT_NUMKEY *) &kkk->lower;
 	key.upper = (GBT_NUMKEY *) &kkk->upper;
 
-	PG_RETURN_FLOAT8(gbt_num_distance(&key, &query, GIST_LEAF(entry),
-									  &tinfo, fcinfo->flinfo));
+	ti = gbt_int8_crosstype(subtype, queryDatum, &query, &qp);
+
+	PG_RETURN_FLOAT8(gbt_num_distance(&key, qp, GIST_LEAF(entry),
+									  ti, fcinfo->flinfo));
 }
 
 Datum
 gbt_int8_union(PG_FUNCTION_ARGS)
 {
 	GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
diff --git a/contrib/btree_gist/btree_utils_num.c b/contrib/btree_gist/btree_utils_num.c
index 3affe4c2c46..0d6d578062a 100644
--- a/contrib/btree_gist/btree_utils_num.c
+++ b/contrib/btree_gist/btree_utils_num.c
@@ -266,12 +266,18 @@ gbt_num_consistent(const GBT_NUMKEY_R *key,
 				   bool is_leaf,
 				   const gbtree_ninfo *tinfo,
 				   FmgrInfo *flinfo)
 {
 	bool		retval;
 
+	/*
+	 * Every comparison callback is invoked as f_xx(query, key): the query
+	 * value is always the left argument and the indexed key bound the right.
+	 * The integer opclasses rely on this fixed order so their cross-type
+	 * callbacks can read each side at its own width.
+	 */
 	switch (*strategy)
 	{
 		case BTLessEqualStrategyNumber:
 			retval = tinfo->f_ge(query, key->lower, flinfo);
 			break;
 		case BTLessStrategyNumber:
@@ -281,13 +287,13 @@ gbt_num_consistent(const GBT_NUMKEY_R *key,
 				retval = tinfo->f_ge(query, key->lower, flinfo);
 			break;
 		case BTEqualStrategyNumber:
 			if (is_leaf)
 				retval = tinfo->f_eq(query, key->lower, flinfo);
 			else
-				retval = (tinfo->f_le(key->lower, query, flinfo) &&
+				retval = (tinfo->f_ge(query, key->lower, flinfo) &&
 						  tinfo->f_le(query, key->upper, flinfo));
 			break;
 		case BTGreaterStrategyNumber:
 			if (is_leaf)
 				retval = tinfo->f_lt(query, key->upper, flinfo);
 			else
@@ -304,13 +310,12 @@ gbt_num_consistent(const GBT_NUMKEY_R *key,
 			retval = false;
 	}
 
 	return retval;
 }
 
-
 /*
  * The GiST distance method (for KNN-Gist)
  */
 
 float8
 gbt_num_distance(const GBT_NUMKEY_R *key,
@@ -321,12 +326,19 @@ gbt_num_distance(const GBT_NUMKEY_R *key,
 {
 	float8		retval;
 
 	if (tinfo->f_dist == NULL)
 		elog(ERROR, "KNN search is not supported for btree_gist type %d",
 			 (int) tinfo->t);
+
+	/*
+	 * As in gbt_num_consistent(), every callback is invoked as f_xx(query, key):
+	 * the query value is the left argument and the indexed key bound the right.
+	 * The integer opclasses' cross-type callbacks read each side at its own
+	 * width, so keep this argument order if you ever touch the calls below.
+	 */
 	if (tinfo->f_le(query, key->lower, flinfo))
 		retval = tinfo->f_dist(query, key->lower, flinfo);
 	else if (tinfo->f_ge(query, key->upper, flinfo))
 		retval = tinfo->f_dist(query, key->upper, flinfo);
 	else
 		retval = 0.0;
diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h
index 53e477d8b1e..217b362c169 100644
--- a/contrib/btree_gist/btree_utils_num.h
+++ b/contrib/btree_gist/btree_utils_num.h
@@ -24,12 +24,24 @@ typedef struct
 typedef struct
 {
 	int			i;
 	GBT_NUMKEY *t;
 } Nsrt;
 
+/*
+ * Query-value storage for the integer opclasses' cross-type path.  The caller
+ * owns one of these and passes its address to the per-type cross-type helper,
+ * which fills the right width and points the query pointer at it.
+ */
+typedef union
+{
+	int16		i2;
+	int32		i4;
+	int64		i8;
+} gbt_intkey;
+
 
 /* type description */
 
 typedef struct
 {
 
@@ -83,13 +95,38 @@ typedef struct
  */
 #define INTERVAL_TO_SEC(ivp) \
 	(((double) (ivp)->time) / ((double) USECS_PER_SEC) + \
 	 (ivp)->day * (24.0 * SECS_PER_HOUR) + \
 	 (ivp)->month * (30.0 * SECS_PER_DAY))
 
-#define GET_FLOAT_DISTANCE(t, arg1, arg2)	fabs( ((float8) *((const t *) (arg1))) - ((float8) *((const t *) (arg2))) )
+#define GET_FLOAT_DISTANCE2(t1, t2, arg1, arg2)	fabs( ((float8) *((const t1 *) (arg1))) - ((float8) *((const t2 *) (arg2))) )
+#define GET_FLOAT_DISTANCE(t, arg1, arg2)	GET_FLOAT_DISTANCE2(t, t, (arg1), (arg2))
+
+/*
+ * Generate the comparison/distance callbacks for a gbtree_ninfo whose query
+ * and key sides may be different (integer) types.  gbt_num_consistent() and
+ * gbt_num_distance() always invoke the callbacks as f_xx(query, key), so the
+ * first argument has the query type QT (the operator's right-hand subtype) and
+ * the second has the indexed key type KT.  Integer widening is value-preserving,
+ * so the comparisons need no explicit cast; the distance widens to float8 to
+ * avoid overflow in the subtraction.  Invoked with QT == KT this also generates
+ * the ordinary same-type callbacks.
+ */
+#define GBT_INT_CMP_FNS(prefix, QT, KT) \
+static bool prefix##gt(const void *a, const void *b, FmgrInfo *flinfo) \
+{ return *((const QT *) a) > *((const KT *) b); } \
+static bool prefix##ge(const void *a, const void *b, FmgrInfo *flinfo) \
+{ return *((const QT *) a) >= *((const KT *) b); } \
+static bool prefix##eq(const void *a, const void *b, FmgrInfo *flinfo) \
+{ return *((const QT *) a) == *((const KT *) b); } \
+static bool prefix##le(const void *a, const void *b, FmgrInfo *flinfo) \
+{ return *((const QT *) a) <= *((const KT *) b); } \
+static bool prefix##lt(const void *a, const void *b, FmgrInfo *flinfo) \
+{ return *((const QT *) a) < *((const KT *) b); } \
+static float8 prefix##dist(const void *a, const void *b, FmgrInfo *flinfo) \
+{ return GET_FLOAT_DISTANCE2(QT, KT, a, b); }
 
 
 extern Interval *abs_interval(Interval *a);
 
 extern bool gbt_num_consistent(const GBT_NUMKEY_R *key, const void *query,
 							   const StrategyNumber *strategy, bool is_leaf,
diff --git a/contrib/btree_gist/meson.build b/contrib/btree_gist/meson.build
index 2b1a5463289..43ba130e8e4 100644
--- a/contrib/btree_gist/meson.build
+++ b/contrib/btree_gist/meson.build
@@ -49,12 +49,13 @@ install_data(
   'btree_gist--1.4--1.5.sql',
   'btree_gist--1.5--1.6.sql',
   'btree_gist--1.6--1.7.sql',
   'btree_gist--1.7--1.8.sql',
   'btree_gist--1.8--1.9.sql',
   'btree_gist--1.9.sql',
+  'btree_gist--1.9--1.10.sql',
   kwargs: contrib_data_args,
 )
 
 tests += {
   'name': 'btree_gist',
   'sd': meson.current_source_dir(),
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c5db6ca6705..e29f76b6feb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3808,12 +3808,13 @@ floating_decimal_32
 floating_decimal_64
 fmgr_hook_type
 foreign_glob_cxt
 foreign_loc_cxt
 freefunc
 fsec_t
+gbt_intkey
 gbt_vsrt_arg
 gbtree_ninfo
 gbtree_vinfo
 generate_series_fctx
 generate_series_numeric_fctx
 generate_series_timestamp_fctx
-- 
2.51.0

