From 7c9306af289d1b232168129db9b248751f1164e1 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

---
 contrib/btree_gist/Makefile                  |   4 +-
 contrib/btree_gist/btree_gist--1.9--1.10.sql | 133 +++++++++++++++++
 contrib/btree_gist/btree_gist.control        |   2 +-
 contrib/btree_gist/btree_int2.c              |  87 +++++++++--
 contrib/btree_gist/btree_int4.c              |  86 +++++++++--
 contrib/btree_gist/btree_int8.c              |  86 +++++++++--
 contrib/btree_gist/btree_utils_num.c         | 148 +++++++++++++++++++
 contrib/btree_gist/btree_utils_num.h         |  85 +++++++++++
 contrib/btree_gist/meson.build               |   2 +
 9 files changed, 597 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..f338b854b30
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.9--1.10.sql
@@ -0,0 +1,133 @@
+/* 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 same-type consistent/distance support functions dispatch
+-- on the subtype OID and route mixed-width integer comparisons through their
+-- C-side dispatch tables.
+
+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..1fdf55131b3 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,25 +74,42 @@ 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);
 }
 
+/*
+ * Cross-type callbacks
+ */
+GBT_DEFINE_INT_CROSSTYPE(gbt_int2_x_int4, int16, DatumGetInt32)
+GBT_DEFINE_INT_CROSSTYPE(gbt_int2_x_int8, int16, DatumGetInt64)
+
+static const gbt_subtype_info gbt_int2_subtype_ops[] = {
+	{INT4OID,
+		gbt_int2_x_int4_lt, gbt_int2_x_int4_le, gbt_int2_x_int4_eq,
+	gbt_int2_x_int4_ge, gbt_int2_x_int4_gt, gbt_int2_x_int4_dist},
+	{INT8OID,
+		gbt_int2_x_int8_lt, gbt_int2_x_int8_le, gbt_int2_x_int8_eq,
+	gbt_int2_x_int8_ge, gbt_int2_x_int8_gt, gbt_int2_x_int8_dist},
+	{InvalidOid}
+};
 
 static const gbtree_ninfo tinfo =
 {
 	gbt_t_int2,
 	sizeof(int16),
 	4,							/* sizeof(gbtreekey4) */
 	gbt_int2gt,
 	gbt_int2ge,
 	gbt_int2eq,
 	gbt_int2le,
 	gbt_int2lt,
 	gbt_int2key_cmp,
-	gbt_int2_dist
+	gbt_int2_dist,
+	gbt_int2_subtype_ops,
+	INT2OID
 };
 
 
 PG_FUNCTION_INFO_V1(int2_dist);
 Datum
 int2_dist(PG_FUNCTION_ARGS)
@@ -109,12 +127,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 +186,60 @@ 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;
 
+	/* Only decode as int16 on the same-type path to avoid silent truncation */
+	if (subtype == InvalidOid || subtype == INT2OID)
+		query = DatumGetInt16(queryDatum);
+	else
+		query = 0;
+
 	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));
+	PG_RETURN_BOOL(gbt_num_consistent_x(&key, &query, queryDatum,
+										subtype, PG_GET_COLLATION(),
+										&strategy, GIST_LEAF(entry),
+										&tinfo, 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);
+	int16		query;
 	GBT_NUMKEY_R key;
 
+	if (subtype == InvalidOid || subtype == INT2OID)
+		query = DatumGetInt16(queryDatum);
+	else
+		query = 0;
+
 	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));
+	PG_RETURN_FLOAT8(gbt_num_distance_x(&key, &query, queryDatum,
+										subtype, PG_GET_COLLATION(),
+										GIST_LEAF(entry),
+										&tinfo, 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..7c53ce9c56f 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,25 +72,42 @@ 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);
 }
 
+/*
+ * Cross-type callbacks
+ */
+GBT_DEFINE_INT_CROSSTYPE(gbt_int4_x_int2, int32, DatumGetInt16)
+GBT_DEFINE_INT_CROSSTYPE(gbt_int4_x_int8, int32, DatumGetInt64)
+
+static const gbt_subtype_info gbt_int4_subtype_ops[] = {
+	{INT2OID,
+		gbt_int4_x_int2_lt, gbt_int4_x_int2_le, gbt_int4_x_int2_eq,
+	gbt_int4_x_int2_ge, gbt_int4_x_int2_gt, gbt_int4_x_int2_dist},
+	{INT8OID,
+		gbt_int4_x_int8_lt, gbt_int4_x_int8_le, gbt_int4_x_int8_eq,
+	gbt_int4_x_int8_ge, gbt_int4_x_int8_gt, gbt_int4_x_int8_dist},
+	{InvalidOid}
+};
 
 static const gbtree_ninfo tinfo =
 {
 	gbt_t_int4,
 	sizeof(int32),
 	8,							/* sizeof(gbtreekey8) */
 	gbt_int4gt,
 	gbt_int4ge,
 	gbt_int4eq,
 	gbt_int4le,
 	gbt_int4lt,
 	gbt_int4key_cmp,
-	gbt_int4_dist
+	gbt_int4_dist,
+	gbt_int4_subtype_ops,
+	INT4OID
 };
 
 
 PG_FUNCTION_INFO_V1(int4_dist);
 Datum
 int4_dist(PG_FUNCTION_ARGS)
@@ -107,12 +125,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 +184,59 @@ 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;
 
+	if (subtype == InvalidOid || subtype == INT4OID)
+		query = DatumGetInt32(queryDatum);
+	else
+		query = 0;
+
 	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));
+	PG_RETURN_BOOL(gbt_num_consistent_x(&key, &query, queryDatum,
+										subtype, PG_GET_COLLATION(),
+										&strategy, GIST_LEAF(entry),
+										&tinfo, 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);
+	int32		query;
 	GBT_NUMKEY_R key;
 
+	if (subtype == InvalidOid || subtype == INT4OID)
+		query = DatumGetInt32(queryDatum);
+	else
+		query = 0;
+
 	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));
+	PG_RETURN_FLOAT8(gbt_num_distance_x(&key, &query, queryDatum,
+										subtype, PG_GET_COLLATION(),
+										GIST_LEAF(entry),
+										&tinfo, 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..352acb4ff76 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,25 +74,42 @@ 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);
 }
 
+/*
+ * Cross-type callbacks
+ */
+GBT_DEFINE_INT_CROSSTYPE(gbt_int8_x_int2, int64, DatumGetInt16)
+GBT_DEFINE_INT_CROSSTYPE(gbt_int8_x_int4, int64, DatumGetInt32)
+
+static const gbt_subtype_info gbt_int8_subtype_ops[] = {
+	{INT2OID,
+		gbt_int8_x_int2_lt, gbt_int8_x_int2_le, gbt_int8_x_int2_eq,
+	gbt_int8_x_int2_ge, gbt_int8_x_int2_gt, gbt_int8_x_int2_dist},
+	{INT4OID,
+		gbt_int8_x_int4_lt, gbt_int8_x_int4_le, gbt_int8_x_int4_eq,
+	gbt_int8_x_int4_ge, gbt_int8_x_int4_gt, gbt_int8_x_int4_dist},
+	{InvalidOid}
+};
 
 static const gbtree_ninfo tinfo =
 {
 	gbt_t_int8,
 	sizeof(int64),
 	16,							/* sizeof(gbtreekey16) */
 	gbt_int8gt,
 	gbt_int8ge,
 	gbt_int8eq,
 	gbt_int8le,
 	gbt_int8lt,
 	gbt_int8key_cmp,
-	gbt_int8_dist
+	gbt_int8_dist,
+	gbt_int8_subtype_ops,
+	INT8OID
 };
 
 
 PG_FUNCTION_INFO_V1(int8_dist);
 Datum
 int8_dist(PG_FUNCTION_ARGS)
@@ -109,12 +127,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 +186,59 @@ 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;
 
+	if (subtype == InvalidOid || subtype == INT8OID)
+		query = DatumGetInt64(queryDatum);
+	else
+		query = 0;
+
 	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));
+	PG_RETURN_BOOL(gbt_num_consistent_x(&key, &query, queryDatum,
+										subtype, PG_GET_COLLATION(),
+										&strategy, GIST_LEAF(entry),
+										&tinfo, 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);
+	int64		query;
 	GBT_NUMKEY_R key;
 
+	if (subtype == InvalidOid || subtype == INT8OID)
+		query = DatumGetInt64(queryDatum);
+	else
+		query = 0;
+
 	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));
+	PG_RETURN_FLOAT8(gbt_num_distance_x(&key, &query, queryDatum,
+										subtype, PG_GET_COLLATION(),
+										GIST_LEAF(entry),
+										&tinfo, 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..68574b895c5 100644
--- a/contrib/btree_gist/btree_utils_num.c
+++ b/contrib/btree_gist/btree_utils_num.c
@@ -250,12 +250,32 @@ gbt_num_bin_union(Datum *u, GBT_NUMKEY *e, const gbtree_ninfo *tinfo, FmgrInfo *
 			memcpy(unconstify(GBT_NUMKEY *, ur.upper), rd.upper, tinfo->size);
 	}
 }
 
 
 
+/*
+ * Look up cross-type callbacks for a given query subtype. Returns NULL if
+ * the opclass doesn't advertise cross-type support for this subtype, in
+ * which case callers must fall back to the same-type path.
+ */
+static const gbt_subtype_info *
+gbt_find_subtype_ops(const gbtree_ninfo *tinfo, Oid subtype)
+{
+	const gbt_subtype_info *p;
+
+	if (subtype == InvalidOid || tinfo->subtype_ops == NULL)
+		return NULL;
+	for (p = tinfo->subtype_ops; p->subtype != InvalidOid; p++)
+	{
+		if (p->subtype == subtype)
+			return p;
+	}
+	return NULL;
+}
+
 /*
  * The GiST consistent method
  *
  * Note: we currently assume that no datatypes that use this routine are
  * collation-aware; so we don't bother passing collation through.
  */
@@ -304,12 +324,99 @@ gbt_num_consistent(const GBT_NUMKEY_R *key,
 			retval = false;
 	}
 
 	return retval;
 }
 
+/*
+ * Verify that "subtype" is one this opclass knows how to handle: it must
+ * either be InvalidOid / the opclass's native type, or be registered in
+ * the dispatch table. Anything else is an operator-family configuration
+ * error.
+ */
+static void
+gbt_check_subtype(const gbtree_ninfo *tinfo, Oid subtype)
+{
+	if (subtype == InvalidOid)
+		return;
+	if (tinfo->type_oid == InvalidOid)
+		return;
+	if (subtype == tinfo->type_oid)
+		return;
+	if (gbt_find_subtype_ops(tinfo, subtype) != NULL)
+		return;
+
+	elog(ERROR,
+		 "btree_gist: cross-type query with subtype %u is not supported "
+		 "by the opclass for type %u (operator-family dispatch table is "
+		 "out of sync with pg_amop)",
+		 subtype, tinfo->type_oid);
+}
+
+/*
+ * Cross-type aware consistent method.
+ */
+bool
+gbt_num_consistent_x(const GBT_NUMKEY_R *key,
+					 const void *query,
+					 Datum queryDatum,
+					 Oid subtype,
+					 Oid collation,
+					 const StrategyNumber *strategy,
+					 bool is_leaf,
+					 const gbtree_ninfo *tinfo,
+					 FmgrInfo *flinfo)
+{
+	const gbt_subtype_info *xt = gbt_find_subtype_ops(tinfo, subtype);
+	gbt_subtype_context cxt;
+
+	if (xt == NULL)
+	{
+		gbt_check_subtype(tinfo, subtype);
+		return gbt_num_consistent(key, query, strategy, is_leaf, tinfo, flinfo);
+	}
+
+	cxt.query = queryDatum;
+	cxt.subtype = subtype;
+	cxt.collation = collation;
+	cxt.flinfo = flinfo;
+	cxt.query_cache = NULL;
+
+	switch (*strategy)
+	{
+		case BTLessEqualStrategyNumber:
+			/* some k in [lower,upper] has k <= q iff lower <= q */
+			return xt->f_le(key->lower, &cxt);
+		case BTLessStrategyNumber:
+			/* leaf: key < q. internal: lower <= q (loose) */
+			return is_leaf ? xt->f_lt(key->lower, &cxt)
+				: xt->f_le(key->lower, &cxt);
+		case BTEqualStrategyNumber:
+			if (is_leaf)
+				return xt->f_eq(key->lower, &cxt);
+			/* internal: lower <= q <= upper */
+			return (xt->f_le(key->lower, &cxt) &&
+					xt->f_ge(key->upper, &cxt));
+		case BTGreaterStrategyNumber:
+
+			/*
+			 * leaf: key > q. internal: upper >= q (loose). Read upper on
+			 * leaves to match the same-type path.
+			 */
+			return is_leaf ? xt->f_gt(key->upper, &cxt)
+				: xt->f_ge(key->upper, &cxt);
+		case BTGreaterEqualStrategyNumber:
+			return xt->f_ge(key->upper, &cxt);
+		case BtreeGistNotEqualStrategyNumber:
+			return !(xt->f_eq(key->lower, &cxt) &&
+					 xt->f_eq(key->upper, &cxt));
+		default:
+			return false;
+	}
+}
+
 
 /*
  * The GiST distance method (for KNN-Gist)
  */
 
 float8
@@ -331,12 +438,53 @@ gbt_num_distance(const GBT_NUMKEY_R *key,
 	else
 		retval = 0.0;
 
 	return retval;
 }
 
+/*
+ * Cross-type aware distance method.
+ */
+float8
+gbt_num_distance_x(const GBT_NUMKEY_R *key,
+				   const void *query,
+				   Datum queryDatum,
+				   Oid subtype,
+				   Oid collation,
+				   bool is_leaf,
+				   const gbtree_ninfo *tinfo,
+				   FmgrInfo *flinfo)
+{
+	const gbt_subtype_info *xt = gbt_find_subtype_ops(tinfo, subtype);
+	gbt_subtype_context cxt;
+
+	if (xt == NULL)
+	{
+		gbt_check_subtype(tinfo, subtype);
+		return gbt_num_distance(key, query, is_leaf, tinfo, flinfo);
+	}
+
+	if (xt->f_dist == NULL)
+		elog(ERROR, "KNN search is not supported for btree_gist type %d with subtype %u",
+			 (int) tinfo->t, subtype);
+
+	cxt.query = queryDatum;
+	cxt.subtype = subtype;
+	cxt.collation = collation;
+	cxt.flinfo = flinfo;
+	cxt.query_cache = NULL;
+
+	/* q <= lower <=> lower >= q */
+	if (xt->f_ge(key->lower, &cxt))
+		return xt->f_dist(key->lower, &cxt);
+	/* q >= upper <=> upper <= q */
+	if (xt->f_le(key->upper, &cxt))
+		return xt->f_dist(key->upper, &cxt);
+	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..dfb0a3bb41f 100644
--- a/contrib/btree_gist/btree_utils_num.h
+++ b/contrib/btree_gist/btree_utils_num.h
@@ -27,12 +27,42 @@ typedef struct
 	GBT_NUMKEY *t;
 } Nsrt;
 
 
 /* type description */
 
+/*
+ * Cross-type comparison support.
+ *
+ * For cross-type operator support an opclass may supply a NULL-terminated
+ * array of gbt_subtype_info entries, one per supported query subtype. Each
+ * callback receives the index key in its native C representation (pointer
+ * into the compressed key) and a context describing the query value.
+ */
+typedef struct gbt_subtype_context
+{
+	Datum		query;			/* Datum of cxt->subtype */
+	Oid			subtype;		/* right-hand/query operand type */
+	Oid			collation;		/* collation from the support call */
+	FmgrInfo   *flinfo;			/* support-function call context */
+	void	   *query_cache;	/* callback-owned per-call state */
+} gbt_subtype_context;
+
+typedef struct gbt_subtype_info
+{
+	Oid			subtype;		/* InvalidOid terminates the array */
+	bool		(*f_lt) (const void *key, gbt_subtype_context *cxt);
+	bool		(*f_le) (const void *key, gbt_subtype_context *cxt);
+	bool		(*f_eq) (const void *key, gbt_subtype_context *cxt);
+	bool		(*f_ge) (const void *key, gbt_subtype_context *cxt);
+	bool		(*f_gt) (const void *key, gbt_subtype_context *cxt);
+
+	/* NULL if no KNN */
+	float8		(*f_dist) (const void *key, gbt_subtype_context *cxt);
+} gbt_subtype_info;
+
 typedef struct
 {
 
 	/* Attribs */
 
 	enum gbtree_type t;			/* data type */
@@ -45,14 +75,59 @@ typedef struct
 	bool		(*f_ge) (const void *, const void *, FmgrInfo *);	/* greater or equal */
 	bool		(*f_eq) (const void *, const void *, FmgrInfo *);	/* equal */
 	bool		(*f_le) (const void *, const void *, FmgrInfo *);	/* less or equal */
 	bool		(*f_lt) (const void *, const void *, FmgrInfo *);	/* less than */
 	int			(*f_cmp) (const void *, const void *, FmgrInfo *);	/* key compare function */
 	float8		(*f_dist) (const void *, const void *, FmgrInfo *); /* key distance function */
+
+	/*
+	 * Optional NULL-terminated array of cross-type comparison callbacks. NULL
+	 * if the opclass only supports same-type comparisons.
+	 */
+	const gbt_subtype_info *subtype_ops;
+
+	/*
+	 * Native pg_type OID of the indexed type. Used by the _x APIs to validate
+	 * the subtype passed in from the planner. InvalidOid disables that check,
+	 * which is right for legacy opclasses that don't use _x.
+	 */
+	Oid			type_oid;
 } gbtree_ninfo;
 
+#define GBT_DEFINE_INT_CROSSTYPE(prefix, key_ctype, get_sub) \
+static bool \
+prefix##_lt(const void *k, gbt_subtype_context *cxt) \
+{ \
+	return (int64) *(const key_ctype *) k < (int64) get_sub(cxt->query); \
+} \
+static bool \
+prefix##_le(const void *k, gbt_subtype_context *cxt) \
+{ \
+	return (int64) *(const key_ctype *) k <= (int64) get_sub(cxt->query); \
+} \
+static bool \
+prefix##_eq(const void *k, gbt_subtype_context *cxt) \
+{ \
+	return (int64) *(const key_ctype *) k == (int64) get_sub(cxt->query); \
+} \
+static bool \
+prefix##_ge(const void *k, gbt_subtype_context *cxt) \
+{ \
+	return (int64) *(const key_ctype *) k >= (int64) get_sub(cxt->query); \
+} \
+static bool \
+prefix##_gt(const void *k, gbt_subtype_context *cxt) \
+{ \
+	return (int64) *(const key_ctype *) k > (int64) get_sub(cxt->query); \
+} \
+static float8 \
+prefix##_dist(const void *k, gbt_subtype_context *cxt) \
+{ \
+	return fabs((float8) *(const key_ctype *) k - (float8) get_sub(cxt->query)); \
+}
+
 
 /*
  *	Numeric btree functions
  */
 
 
@@ -92,15 +167,25 @@ typedef struct
 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 bool gbt_num_consistent_x(const GBT_NUMKEY_R *key, const void *query,
+								 Datum queryDatum, Oid subtype, Oid collation,
+								 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);
 
+extern float8 gbt_num_distance_x(const GBT_NUMKEY_R *key, const void *query,
+								 Datum queryDatum, Oid subtype, Oid collation,
+								 bool is_leaf,
+								 const gbtree_ninfo *tinfo, FmgrInfo *flinfo);
+
 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

