Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wJErP-000C2U-1g for pgsql-hackers@arkaria.postgresql.org; Sat, 02 May 2026 18:14:39 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wJErO-001sHW-1E for pgsql-hackers@arkaria.postgresql.org; Sat, 02 May 2026 18:14:38 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wJE7F-001nYS-1b for pgsql-hackers@lists.postgresql.org; Sat, 02 May 2026 17:26:57 +0000 Received: from mail-wm1-x330.google.com ([2a00:1450:4864:20::330]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wJE7C-000000005GM-3B9X for pgsql-hackers@postgresql.org; Sat, 02 May 2026 17:26:56 +0000 Received: by mail-wm1-x330.google.com with SMTP id 5b1f17b1804b1-488d2079582so30464685e9.2 for ; Sat, 02 May 2026 10:26:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777742812; x=1778347612; darn=postgresql.org; h=mime-version:subject:references:in-reply-to:message-id:to:from:date :from:to:cc:subject:date:message-id:reply-to; bh=pX79VmjbyVsAs2CRVPcbCWaKGCqZOSKvk4RIqta33Vg=; b=q5AYFAvErZc54DgaIkocwpiFohg8Ms2jHZRQxFDRUfas8BtVc5msuiTnb5kAVhTcyI PyFqCvCef//s8gLne7wvT/fAvluLaQFTcsQQ+v0QpcDSq/Hhxwl9j92fn2/VB/RSGmBu 5zSb4h1Xi4g/yhVB/XyKqDFUsjpmL+VbnnBb1twXkww4HQyZMu5FkKbLjKZf8O44lSi0 O/MTV46yhZ5h2cIHnVE6IYNYB7b6zimUGryZcCeZ8od2S1hS7B+rVt/Xw/nIMHPeJfKX ceLFqSQ6m1BQIbWz+k4jRJ5Bzep2BiIVoBYDW/8gQ7RnvFDRLqy+gLZ26jpBIhCZzZ3E kfbg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777742812; x=1778347612; h=mime-version:subject:references:in-reply-to:message-id:to:from:date :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=pX79VmjbyVsAs2CRVPcbCWaKGCqZOSKvk4RIqta33Vg=; b=RWmEe19B9bjld1xAccasmefSCI4bRcLPHogSOtAefZANcw4YX7mFbRjgwxm4nGIsOF NGeJq2EA1kQJDrgbkH75pRA8LCcKi4loJR+Vj+RESDK8VsWN/GnrdRGyJ7kQv9Ww45LR k2gQXEOOIoKgWbkNvI5Dkt54+DenqVNhNB3Cn1e6kRZ2DczM2l67m4CqxGVpXbV1+jgq qJK2SBcjif9jq7zsoc7l0ezQq79PXLjYiUWJ0msnvX2cIwuVFgplL1vB7pclKueqKmgH TlJKovgNRjYolSNbRdxB004T1LdLUiiQFxc4coApjjk2sgtgyatwKbRSgzcdLq8BxsuJ 8x/A== X-Gm-Message-State: AOJu0YyZPvH/gJfW2yIluMz94v7B1f6bEY+ohn3BvyuFlOVZFJPJfthw 6zurki+zbsyUKhIYOzVZYNAKUEE6Sc8b4npmm98nhpnaWMMOhS3ZChdailPt9w== X-Gm-Gg: AeBDiestHb+TgfkQYSP3+gkz8P/ynsuByPVPLYMsNxHWAt+Vt3eKLgp+yKu9z6cBlkn oiwEMmk/CINHx1Q9690tZgXx8G9IJVvBvMVIjlffDFsTvkWDQzg/rU/QJ5iFUWJexN2GuBuA2Jz brEUhfbTvc1CvocUzPutKfDpkUhcS2JmlNM8q5kaFTAt+gBv3aSa+rLjJirB7RCilFrVBRYZUG5 M2sU/tjjA0NJQ/WcLXxYGaonkD1QqCBjBAGtiDkkk2Q7mdczwB/zN3mCHi/0CnZnAyoq8gF+TSm u7B73CjEYOjEEE4XY9uuUyYaa3nspkUptsD/dV607j6W3ZAdKiYiqI/lbRKBTpQ53b5m6x39Knl eesPr565KAQmy4PPKZ8FHExNmot+a9m4rPLuP1/9QyWgN461yvzUFqZ03/KVfVj2nU2pZYuloTs zQIJEvjM6dz0q3ZsGHIqqX1NvXyl8y/xV0/EDjc7wducdpnGxh+afjMgbhZEblV/iyNJyouWU= X-Received: by 2002:a05:600c:1385:b0:48a:5970:2005 with SMTP id 5b1f17b1804b1-48a9853c8camr59131705e9.2.1777742811322; Sat, 02 May 2026 10:26:51 -0700 (PDT) Received: from [192.168.1.235] ([109.227.129.105]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-44a986aaad6sm11515099f8f.28.2026.05.02.10.26.49 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Sat, 02 May 2026 10:26:50 -0700 (PDT) Date: Sat, 2 May 2026 19:26:43 +0200 From: Alexander Nestorov To: pgsql-hackers@postgresql.org Message-ID: <36b4f67d-5975-452c-a6b8-b6407f0924ee@Spark> In-Reply-To: References: Subject: [PATCH] btree_gist: add cross-type integer operator support for GiST X-Readdle-Message-ID: 36b4f67d-5975-452c-a6b8-b6407f0924ee@Spark MIME-Version: 1.0 Content-Type: multipart/alternative; boundary="69f633d8_ded7263_b38c" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --69f633d8_ded7263_b38c Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Content-Disposition: inline Hello hackers, I'd like to submit a patch that adds cross-type operator support for the three integer types (int2, int4, int8) to the btree=5Fgist GiST operator families. The patch also lays a general foundation for cross-type dispatch that other type families can adopt. Current problem: GiST indexes are currently limited to same-type operators: the planner can only match a query condition against an index column if the operator's left and right argument types exactly match the types registered in the index's operator family (pg=5Famop). This is true for every data type that backs a GiST opclass. When a query provides a value whose type is compatible but different from the column type, the planner cannot use the index for that column. The operator family lookup fails because no cross-type entry exists. The planner must then either fall back to a sequential scan, or in multi-column indexes use only the remaining column(s) and filter the rest as a post-filter. As a specific example, consider an int8 (bigint) column indexed with btree=5Fgist. The query: =C2=A0=C2=A0 =C2=A0SELECT * =46ROM t WHERE camera=5Fid =3D 1189; Here 1189 is evaluated as int4 (integer). The planner resolves the operator to =3D(int8,int4), which exists in pg=5Foperator but is not registered in the gist=5Fint8=5Fops family. Only =3D(int8,int8) is registered, so the column cannot be used as an index condition. The workaround is to write an explicit cast in every query: =C2=A0=C2=A0 =C2=A0WHERE camera=5Fid =3D 1189::int8 This is fragile as ORMs, application parameter binding, and even hand- written SQL queries produce values whose types do not exactly match the column type. A couple of self-contained reproduction scripts are included at the end of this email. They create a table with a GiST index, insert enough rows to make the plan difference visible, and run EXPLAIN ANALYZE. Proposed solution: I'm working on a patch that extends the three btree=5Fgist integer operator families (gist=5Fint2=5Fops, gist=5Fint4=5Fops, gist=5Fint8=5Fop= s) with cross-type comparison and KNN-distance operators covering the other two integer types.=C2=A0=C2=A0Concretely: =C2=A0=C2=A0 =C2=A0gist=5Fint2=5Fops=C2=A0 =C2=A0<-=C2=A0=C2=A0operators = for (int2, int4) and (int2, int8) =C2=A0=C2=A0 =C2=A0gist=5Fint4=5Fops=C2=A0 =C2=A0<-=C2=A0=C2=A0operators = for (int4, int2) and (int4, int8) =C2=A0=C2=A0 =C2=A0gist=5Fint8=5Fops=C2=A0 =C2=A0<-=C2=A0=C2=A0operators = for (int8, int2) and (int8, int4) =46or each pair I add all the standard btree=5Fgist strategies for the comparison operators (<, <=3D, =3D, >=3D, >, <>), plus the strategy for the KNN distance operator (<->) used by ORDER BY. I propose not to register separate cross-type support functions in the operator family. GiST's amvalidate requires every support function to have a matching left and right type, and registering 18 additional support functions (three families x two subtypes x three strategies) would be verbose and error-prone. Instead, I propose to dispatch cross-type queries directly inside the existing consistent and distance functions and use the existing subtype OID argument. I'm thinking of introducing a general-purpose cross-type dispatch table in btree=5Futils=5Fnum: =C2=A0=C2=A0 =C2=A0typedef struct gbt=5Fsubtype=5Finfo =C2=A0=C2=A0 =C2=A0=7B =C2=A0=C2=A0 =C2=A0 =C2=A0 =C2=A0Oid=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0sub= type;=C2=A0 =C2=A0 =C2=A0/* right-hand Oid, e.g. INT4OID */ =C2=A0=C2=A0 =C2=A0 =C2=A0 =C2=A0gbt=5Fcmp=5Ffn=C2=A0=C2=A0lt, le, eq, ge= , gt;=C2=A0 =C2=A0/* comparison callbacks */ =C2=A0=C2=A0 =C2=A0 =C2=A0 =C2=A0gbt=5Fdist=5Ffn dist;=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0/* KNN distance callback */ =C2=A0=C2=A0 =C2=A0=7D gbt=5Fsubtype=5Finfo; Each integer opclass defines a static array of these entries: =C2=A0=C2=A0 =C2=A0static const gbt=5Fsubtype=5Finfo gbt=5Fint2=5Fsubtype= =5Fops=5B=5D =3D =7B =C2=A0=C2=A0 =C2=A0 =C2=A0 =C2=A0=7BINT4OID,=C2=A0=C2=A0... 6 comparison = fns ... ,=C2=A0=C2=A0distance fn=7D, =C2=A0=C2=A0 =C2=A0 =C2=A0 =C2=A0=7BINT8OID,=C2=A0=C2=A0... 6 comparison = fns ... ,=C2=A0=C2=A0distance fn=7D, =C2=A0=C2=A0 =C2=A0 =C2=A0 =C2=A0=7BInvalidOid=7D =C2=A0=C2=A0 =C2=A0=7D; I'll replace the existing gbt=5Fnum=5Fconsistent() function with gbt=5Fnum=5Fconsistent=5Fx(), which takes a Datum query value and a subtype Oid. If the subtype is InvalidOid or matches the indexed type, the same-type path is used (backward compatible). Otherwise, the function walks the dispatch table, finds the matching subtype entry, and invokes the corresponding cross-type comparison or distance callback. Other btree=5Fgist opclasses (float4/float8, date, timestamp, ...) and even range-type GiST opfamilies in core can adopt the same pattern by defining their own subtype dispatch tables and registering cross-type operators via ALTER OPERATOR =46AMILY. I don't plan adding cross-type support for every data type as that would result in a very bulky patch difficult to review, but I think my proposal establishes the infrastructure so that follow-up work for additional types is straightforward. Is there interest in this patch=3F Should I proceed with polishing my patch and sending it here for a review=3F I appreciate any feedback=21 Thank you Simple repro example: DROP TABLE I=46 EXISTS camera=5Ffeeds=5Fsimple CASCADE; CREATE TABLE camera=5Ffeeds=5Fsimple ( =C2=A0=C2=A0 =C2=A0id=C2=A0=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0SERIAL PRIMA= RY KEY, =C2=A0=C2=A0 =C2=A0camera=5Fid=C2=A0 =C2=A0int8 NOT NULL ); CREATE EXTENSION I=46 NOT EXISTS btree=5Fgist; CREATE INDEX idx=5Fcamera=5Ffeeds=5Fgist =C2=A0=C2=A0 =C2=A0ON camera=5Ffeeds=5Fsimple =C2=A0=C2=A0 =C2=A0USING GIST (camera=5Fid); SET enable=5Fseqscan =3D O=46=46; SET enable=5Fbitmapscan =3D ON; INSERT INTO camera=5Ffeeds=5Fsimple (camera=5Fid) VALUES =C2=A0=C2=A0 =C2=A0(1), (1), (1), (1), (2), (2), (2), (2), (3), (3), (3),= =C2=A0=C2=A0 =C2=A0(4), (4), (4), (4), (4), (4), (4), (4), (5), (5), (6),= (6); ANALYZE camera=5Ffeeds=5Fsimple; =5Cecho 'Triggering current behaviour that fails to use the index' EXPLAIN (ANALYZE, COSTS, BU=46=46ERS, TIMING, SUMMARY) SELECT * =46ROM camera=5Ffeeds=5Fsimple WHERE camera=5Fid =3D 4; =5Cecho 'Triggering query with manual cast workaround' EXPLAIN (ANALYZE, COSTS, BU=46=46ERS, TIMING, SUMMARY) SELECT * =46ROM camera=5Ffeeds=5Fsimple WHERE camera=5Fid =3D 4::int8; RESET enable=5Fseqscan; RESET enable=5Fbitmapscan; Repro example with two columns: DROP TABLE I=46 EXISTS camera=5Ffeeds=5Fmulti CASCADE; CREATE TABLE camera=5Ffeeds=5Fmulti ( =C2=A0=C2=A0 =C2=A0id=C2=A0=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0SERIAL PRIMA= RY KEY, =C2=A0=C2=A0 =C2=A0camera=5Fid=C2=A0 =C2=A0int8 NOT NULL, =C2=A0=C2=A0 =C2=A0timerange=C2=A0 =C2=A0TSTZRANGE NOT NULL ); CREATE EXTENSION I=46 NOT EXISTS btree=5Fgist; CREATE INDEX idx=5Fcamera=5Ffeeds=5Fgist =C2=A0=C2=A0 =C2=A0ON camera=5Ffeeds=5Fmulti =C2=A0=C2=A0 =C2=A0USING GIST (camera=5Fid, timerange); SET enable=5Fseqscan =3D O=46=46; SET enable=5Fbitmapscan =3D ON; INSERT INTO camera=5Ffeeds=5Fmulti (camera=5Fid, timerange) VALUES =C2=A0=C2=A0 =C2=A0(1, '=5B2026-04-01 06:00:00+00, 2026-04-01 12:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(1, '=5B2026-04-01 12:00:00+00, 2026-04-01 18:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(1, '=5B2026-04-01 18:00:00+00, 2026-04-02 06:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(1, '=5B2026-04-02 06:00:00+00, 2026-04-02 12:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(2, '=5B2026-04-01 07:00:00+00, 2026-04-01 09:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(2, '=5B2026-04-01 08:30:00+00, 2026-04-01 11:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(2, '=5B2026-04-01 10:00:00+00, 2026-04-01 14:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(2, '=5B2026-04-01 13:00:00+00, 2026-04-01 17:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(3, '=5B2026-03-15 00:00:00+00, 2026-03-15 23:59:59+00= )'), =C2=A0=C2=A0 =C2=A0(3, '=5B2026-03-20 08:00:00+00, 2026-03-20 20:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(3, '=5B2026-04-01 00:00:00+00, 2026-04-01 23:59:59+00= )'), =C2=A0=C2=A0 =C2=A0(4, '=5B2026-04-01 00:00:00+00, 2026-04-01 06:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(4, '=5B2026-04-01 06:00:00+00, 2026-04-01 08:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(4, '=5B2026-04-01 08:00:00+00, 2026-04-01 12:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(4, '=5B2026-04-01 12:00:00+00, 2026-04-01 14:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(4, '=5B2026-04-01 14:00:00+00, 2026-04-01 18:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(4, '=5B2026-04-01 18:00:00+00, 2026-04-01 22:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(4, '=5B2026-04-01 22:00:00+00, 2026-04-02 00:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(4, '=5B2026-04-01 09:00:00+00, 2026-04-01 15:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(5, '=5B2026-03-30 00:00:00+00, 2026-04-02 00:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(5, '=5B2026-04-01 10:00:00+00, 2026-04-01 10:30:00+00= )'), =C2=A0=C2=A0 =C2=A0(6, '=5B2026-04-01 12:00:00+00, 2026-04-01 12:00:00+00= )'), =C2=A0=C2=A0 =C2=A0(6, '=5B2026-04-01 12:00:00+00, 2026-04-01 13:00:00+00= )'); ANALYZE camera=5Ffeeds=5Fmulti; =5Cecho 'Triggering current behaviour that fails to use the index' EXPLAIN (ANALYZE, COSTS, BU=46=46ERS, TIMING, SUMMARY) SELECT * =46ROM camera=5Ffeeds=5Fmulti WHERE camera=5Fid =3D 4 =C2=A0=C2=A0AND timerange && '=5B2026-04-01 10:00:00+00, 2026-04-01 16:00= :00+00)'::tstzrange ORDER BY timerange; =5Cecho 'Triggering query with manual cast workaround' EXPLAIN (ANALYZE, COSTS, BU=46=46ERS, TIMING, SUMMARY) SELECT * =46ROM camera=5Ffeeds=5Fmulti WHERE camera=5Fid =3D 4::int8 =C2=A0=C2=A0AND timerange && '=5B2026-04-01 10:00:00+00, 2026-04-01 16:00= :00+00)'::tstzrange ORDER BY timerange; RESET enable=5Fseqscan; RESET enable=5Fbitmapscan; --69f633d8_ded7263_b38c Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: quoted-printable Content-Disposition: inline
Hello hackers,

I'd like to submit a patch that adds cross-type operator support for the<= br /> three integer types (int2, int4, int8) to the btree=5Fgist GiST operator<= br /> families. The patch also lays a general foundation for cross-type
dispatch that other type families can adopt.


Current problem:

GiST indexes are currently limited to same-type operators: the planner can only match a query condition against an index column if the
operator's left and right argument types exactly match the types
registered in the index's operator family (pg=5Famop). This is true for every data type that backs a GiST opclass.

When a query provides a value whose type is compatible but different
from the column type, the planner cannot use the index for that column. The operator family lookup fails because no cross-type entry exists.
The planner must then either fall back to a sequential scan,
or in multi-column indexes use only the remaining column(s) and filter the rest as a post-filter.

As a specific example, consider an int8 (bigint) column indexed with
btree=5Fgist. The query:

&=23160;&=23160; &=23160;SELECT * =46ROM t WHERE camera=5Fid =3D 1189;
Here 1189 is evaluated as int4 (integer). The planner resolves the
operator to =3D(int8,int4), which exists in pg=5Foperator but is not
registered in the gist=5Fint8=5Fops family. Only =3D(int8,int8) is
registered, so the column cannot be used as an index condition.

The workaround is to write an explicit cast in every query:

&=23160;&=23160; &=23160;WHERE camera=5Fid =3D 1189::int8

This is fragile as ORMs, application parameter binding, and even hand- written SQL queries produce values whose types do not exactly match
= the column type.

A couple of self-contained reproduction scripts are included at the
= end of this email. They create a table with a GiST index, insert
enough rows to make the plan difference visible, and run
EXPLAIN ANALYZE.


Proposed solution:

I'm working on a patch that extends the three btree=5Fgist integer
operator families (gist=5Fint2=5Fops, gist=5Fint4=5Fops, gist=5Fint8=5Fop= s)
with cross-type comparison and KNN-distance operators covering the
other two integer types.&=23160;&=23160;Concretely:

&=23160;&=23160; &=23160;gist=5Fint2=5Fops&=23160; &=23160;<-&=23160;&= =23160;operators for (int2, int4) and (int2, int8)
&=23160;&=23160; &=23160;gist=5Fint4=5Fops&=23160; &=23160;<-&=23160;&= =23160;operators for (int4, int2) and (int4, int8)
&=23160;&=23160; &=23160;gist=5Fint8=5Fops&=23160; &=23160;<-&=23160;&= =23160;operators for (int8, int2) and (int8, int4)

=46or each pair I add all the standard btree=5Fgist strategies for the comparison operators (<, <=3D, =3D, >=3D, >, <>), plus = the strategy for
the KNN distance operator (<->) used by ORDER BY.

I propose not to register separate cross-type support functions in
the operator family. GiST's amvalidate requires every support
function to have a matching left and right type, and registering
18 additional support functions (three families x two subtypes x
three strategies) would be verbose and error-prone. Instead, I
propose to dispatch cross-type queries directly inside the existing
= consistent and distance functions and use the existing subtype
OID argument.

I'm thinking of introducing a general-purpose cross-type dispatch
table in btree=5Futils=5Fnum:

&=23160;&=23160; &=23160;typedef struct gbt=5Fsubtype=5Finfo
&=23160;&=23160; &=23160;=7B
&=23160;&=23160; &=23160; &=23160; &=23160;Oid&=23160; &=23160; &=23160; = &=23160; &=23160;subtype;&=23160; &=23160; &=23160;/* right-hand Oid, e.g= . INT4OID */
&=23160;&=23160; &=23160; &=23160; &=23160;gbt=5Fcmp=5Ffn&=23160;&=23160;= lt, le, eq, ge, gt;&=23160; &=23160;/* comparison callbacks */
&=23160;&=23160; &=23160; &=23160; &=23160;gbt=5Fdist=5Ffn dist;&=23160; = &=23160; &=23160; &=23160; &=23160; &=23160; &=23160; &=23160; &=23160;/*= KNN distance callback */
&=23160;&=23160; &=23160;=7D gbt=5Fsubtype=5Finfo;

Each integer opclass defines a static array of these entries:

&=23160;&=23160; &=23160;static const gbt=5Fsubtype=5Finfo gbt=5Fint2=5Fs= ubtype=5Fops=5B=5D =3D =7B
&=23160;&=23160; &=23160; &=23160; &=23160;=7BINT4OID,&=23160;&=23160;...= 6 comparison fns ... ,&=23160;&=23160;distance fn=7D,
&=23160;&=23160; &=23160; &=23160; &=23160;=7BINT8OID,&=23160;&=23160;...= 6 comparison fns ... ,&=23160;&=23160;distance fn=7D,
&=23160;&=23160; &=23160; &=23160; &=23160;=7BInvalidOid=7D
&=23160;&=23160; &=23160;=7D;

I'll replace the existing gbt=5Fnum=5Fconsistent() function with
gbt=5Fnum=5Fconsistent=5Fx(), which takes a Datum query value and a
= subtype Oid. If the subtype is InvalidOid or matches the indexed
type, the same-type path is used (backward compatible). Otherwise,
the function walks the dispatch table, finds the matching subtype
entry, and invokes the corresponding cross-type comparison or
distance callback.

Other btree=5Fgist opclasses (float4/float8, date, timestamp, ...) and even range-type GiST opfamilies in core can adopt the same pattern by
defining their own subtype dispatch tables and registering cross-type
operators via ALTER OPERATOR =46AMILY.

I don't plan adding cross-type support for every data type as that
would result in a very bulky patch difficult to review, but I think
= my proposal establishes the infrastructure so that follow-up work
for additional types is straightforward.

Is there interest in this patch=3F Should I proceed with polishing my
patch and sending it here for a review=3F I appreciate any feedback=21
Thank you





Simple repro example:


DROP TABLE I=46 EXISTS camera=5Ffeeds=5Fsimple CASCADE;

CREATE TABLE camera=5Ffeeds=5Fsimple (
&=23160;&=23160; &=23160;id&=23160;&=23160; &=23160; &=23160; &=23160; &=23= 160;SERIAL PRIMARY KEY,
&=23160;&=23160; &=23160;camera=5Fid&=23160; &=23160;int8 NOT NULL
);

CREATE EXTENSION I=46 NOT EXISTS btree=5Fgist;

CREATE INDEX idx=5Fcamera=5Ffeeds=5Fgist
&=23160;&=23160; &=23160;ON camera=5Ffeeds=5Fsimple
&=23160;&=23160; &=23160;USING GIST (camera=5Fid);

SET enable=5Fseqscan =3D O=46=46;
SET enable=5Fbitmapscan =3D ON;

INSERT INTO camera=5Ffeeds=5Fsimple (camera=5Fid) VALUES
&=23160;&=23160; &=23160;(1), (1), (1), (1), (2), (2), (2), (2), (3), (3)= , (3),
&=23160;&=23160; &=23160;(4), (4), (4), (4), (4), (4), (4), (4), (5), (5)= , (6), (6);

ANALYZE camera=5Ffeeds=5Fsimple;

=5Cecho 'Triggering current behaviour that fails to use the index'

EXPLAIN (ANALYZE, COSTS, BU=46=46ERS, TIMING, SUMMARY)
SELECT *
=46ROM camera=5Ffeeds=5Fsimple
WHERE camera=5Fid =3D 4;

=5Cecho 'Triggering query with manual cast workaround'

EXPLAIN (ANALYZE, COSTS, BU=46=46ERS, TIMING, SUMMARY)
SELECT *
=46ROM camera=5Ffeeds=5Fsimple
WHERE camera=5Fid =3D 4::int8;

RESET enable=5Fseqscan;
RESET enable=5Fbitmapscan;






Repro example with two columns:


DROP TABLE I=46 EXISTS camera=5Ffeeds=5Fmulti CASCADE;

CREATE TABLE camera=5Ffeeds=5Fmulti (
&=23160;&=23160; &=23160;id&=23160;&=23160; &=23160; &=23160; &=23160; &=23= 160;SERIAL PRIMARY KEY,
&=23160;&=23160; &=23160;camera=5Fid&=23160; &=23160;int8 NOT NULL,
= &=23160;&=23160; &=23160;timerange&=23160; &=23160;TSTZRANGE NOT NULL
);

CREATE EXTENSION I=46 NOT EXISTS btree=5Fgist;

CREATE INDEX idx=5Fcamera=5Ffeeds=5Fgist
&=23160;&=23160; &=23160;ON camera=5Ffeeds=5Fmulti
&=23160;&=23160; &=23160;USING GIST (camera=5Fid, timerange);

SET enable=5Fseqscan =3D O=46=46;
SET enable=5Fbitmapscan =3D ON;

INSERT INTO camera=5Ffeeds=5Fmulti (camera=5Fid, timerange) VALUES
&=23160;&=23160; &=23160;(1, '=5B2026-04-01 06:00:00+00, 2026-04-01 12:00= :00+00)'),
&=23160;&=23160; &=23160;(1, '=5B2026-04-01 12:00:00+00, 2026-04-01 18:00= :00+00)'),
&=23160;&=23160; &=23160;(1, '=5B2026-04-01 18:00:00+00, 2026-04-02 06:00= :00+00)'),
&=23160;&=23160; &=23160;(1, '=5B2026-04-02 06:00:00+00, 2026-04-02 12:00= :00+00)'),

&=23160;&=23160; &=23160;(2, '=5B2026-04-01 07:00:00+00, 2026-04-01 09:00= :00+00)'),
&=23160;&=23160; &=23160;(2, '=5B2026-04-01 08:30:00+00, 2026-04-01 11:00= :00+00)'),
&=23160;&=23160; &=23160;(2, '=5B2026-04-01 10:00:00+00, 2026-04-01 14:00= :00+00)'),
&=23160;&=23160; &=23160;(2, '=5B2026-04-01 13:00:00+00, 2026-04-01 17:00= :00+00)'),

&=23160;&=23160; &=23160;(3, '=5B2026-03-15 00:00:00+00, 2026-03-15 23:59= :59+00)'),
&=23160;&=23160; &=23160;(3, '=5B2026-03-20 08:00:00+00, 2026-03-20 20:00= :00+00)'),
&=23160;&=23160; &=23160;(3, '=5B2026-04-01 00:00:00+00, 2026-04-01 23:59= :59+00)'),

&=23160;&=23160; &=23160;(4, '=5B2026-04-01 00:00:00+00, 2026-04-01 06:00= :00+00)'),
&=23160;&=23160; &=23160;(4, '=5B2026-04-01 06:00:00+00, 2026-04-01 08:00= :00+00)'),
&=23160;&=23160; &=23160;(4, '=5B2026-04-01 08:00:00+00, 2026-04-01 12:00= :00+00)'),
&=23160;&=23160; &=23160;(4, '=5B2026-04-01 12:00:00+00, 2026-04-01 14:00= :00+00)'),
&=23160;&=23160; &=23160;(4, '=5B2026-04-01 14:00:00+00, 2026-04-01 18:00= :00+00)'),
&=23160;&=23160; &=23160;(4, '=5B2026-04-01 18:00:00+00, 2026-04-01 22:00= :00+00)'),
&=23160;&=23160; &=23160;(4, '=5B2026-04-01 22:00:00+00, 2026-04-02 00:00= :00+00)'),
&=23160;&=23160; &=23160;(4, '=5B2026-04-01 09:00:00+00, 2026-04-01 15:00= :00+00)'),

&=23160;&=23160; &=23160;(5, '=5B2026-03-30 00:00:00+00, 2026-04-02 00:00= :00+00)'),
&=23160;&=23160; &=23160;(5, '=5B2026-04-01 10:00:00+00, 2026-04-01 10:30= :00+00)'),

&=23160;&=23160; &=23160;(6, '=5B2026-04-01 12:00:00+00, 2026-04-01 12:00= :00+00)'),
&=23160;&=23160; &=23160;(6, '=5B2026-04-01 12:00:00+00, 2026-04-01 13:00= :00+00)');

ANALYZE camera=5Ffeeds=5Fmulti;

=5Cecho 'Triggering current behaviour that fails to use the index'

EXPLAIN (ANALYZE, COSTS, BU=46=46ERS, TIMING, SUMMARY)
SELECT *
=46ROM camera=5Ffeeds=5Fmulti
WHERE camera=5Fid =3D 4
&=23160;&=23160;AND timerange && '=5B2026-04-01 10:00:00+00, 2026= -04-01 16:00:00+00)'::tstzrange
ORDER BY timerange;

=5Cecho 'Triggering query with manual cast workaround'

EXPLAIN (ANALYZE, COSTS, BU=46=46ERS, TIMING, SUMMARY)
SELECT *
=46ROM camera=5Ffeeds=5Fmulti
WHERE camera=5Fid =3D 4::int8
&=23160;&=23160;AND timerange && '=5B2026-04-01 10:00:00+00, 2026= -04-01 16:00:00+00)'::tstzrange
ORDER BY timerange;

RESET enable=5Fseqscan;
RESET enable=5Fbitmapscan;
--69f633d8_ded7263_b38c--