public inbox for [email protected]  
help / color / mirror / Atom feed
From: Alexander Nestorov <[email protected]>
To: [email protected]
Subject: Re: [PATCH] btree_gist: add cross-type integer operator support for GiST
Date: Thu, 4 Jun 2026 00:16:16 +0200
Message-ID: <2d176c16-c148-4cfc-8576-2659117ec12c@Spark> (raw)
In-Reply-To: <6fb7f880-fd70-49e6-b9c1-549faadcde58@Spark>
References: <aac10ffa-a0ca-4c49-846b-3655cbc6b37e@Spark>
	<36b4f67d-5975-452c-a6b8-b6407f0924ee@Spark>
	<6fb7f880-fd70-49e6-b9c1-549faadcde58@Spark>

Hello hackers,

Following up on my previous message, I’m attaching a partial patch for the
btree_gist cross-type integer operator support.

This patch intentionally contains only the main code changes. It does not yet
include regression tests or documentation updates. I’m sending it this way to
make the core approach easier to review before adding the surrounding test and
documentation work.

I’d appreciate any feedback!

If the approach looks reasonable, I’ll send a more complete version with tests
and documentation.
Thank you


Attachments:

  [application/octet-stream] 0001-Implement-cross-type-operators-for-GiST-indexes.patch (30.2K, 3-0001-Implement-cross-type-operators-for-GiST-indexes.patch)
  download | inline diff:
From 7c9306af289d1b232168129db9b248751f1164e1 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 | 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



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]
  Subject: Re: [PATCH] btree_gist: add cross-type integer operator support for GiST
  In-Reply-To: <2d176c16-c148-4cfc-8576-2659117ec12c@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