From e6204108239a2458be11aa2286edd7bd029a3681 Mon Sep 17 00:00:00 2001 From: Alexander Nestorov Date: Thu, 4 Jun 2026 00:08:27 +0200 Subject: [PATCH] Add tests for cross-type operators for GiST indexes --- contrib/btree_gist/expected/int_crosstype.out | 568 ++++++++++++++++++ contrib/btree_gist/sql/int_crosstype.sql | 236 ++++++++ 2 files changed, 804 insertions(+) create mode 100644 contrib/btree_gist/expected/int_crosstype.out create mode 100644 contrib/btree_gist/sql/int_crosstype.sql diff --git a/contrib/btree_gist/expected/int_crosstype.out b/contrib/btree_gist/expected/int_crosstype.out new file mode 100644 index 00000000000..bd2b90bd3f6 --- /dev/null +++ b/contrib/btree_gist/expected/int_crosstype.out @@ -0,0 +1,568 @@ +-- Cross-type operator support for int2/int4/int8 in GiST. +-- +-- Verifies that (a) the cross-type B-tree-style operators registered in +-- gist_int{2,4,8}_ops match the results of seqscans using the same operator +-- expressions, (b) the KNN <-> operator works across types and uses the +-- index, and (c) values outside the smaller subtype's range are handled +-- according to normal comparison semantics, without narrowing or erroring. +-- +-- Catalog invariant: the cross-type pg_amop entries in gist_int{2,4,8}_ops must +-- agree, in both directions, with what the C cross-type dispatch can handle. +-- The set of supported query subtypes is read from the C side via +-- gbt_int_crosstype_subtypes(), so there is no hand-maintained second copy of +-- the list here: registering a pg_amop row whose subtype the dispatch does not +-- handle (or dropping a dispatch entry while its pg_amop rows remain, or vice +-- versa) shows up as a diff below. Cross-type pg_amproc rows must also stay +-- absent, since the dispatch reuses the same-type support functions. +-- +WITH dispatch_subtypes(typ) AS ( + SELECT s.subtype::regtype + FROM gbt_int_crosstype_subtypes() AS s(subtype) +), +gist_int_opclasses(opfamily, indextype) AS ( + SELECT opc.opcname::text, opc.opcintype::regtype + FROM pg_opclass opc + JOIN pg_am am ON am.oid = opc.opcmethod + WHERE am.amname = 'gist' + AND opc.opcname IN ('gist_int2_ops', 'gist_int4_ops', 'gist_int8_ops') +), +expected_pairs(opfamily, lefttype, righttype) AS ( + SELECT oc.opfamily, oc.indextype, ds.typ + FROM gist_int_opclasses oc + CROSS JOIN dispatch_subtypes ds + WHERE ds.typ <> oc.indextype +), +expected_amop(opfamily, lefttype, righttype, strategy, purpose) AS ( + SELECT opfamily, lefttype, righttype, strategy, purpose + FROM expected_pairs + CROSS JOIN (VALUES + (1, 's'), (2, 's'), (3, 's'), (4, 's'), + (5, 's'), (6, 's'), (15, 'o') + ) AS strategy_purposes(strategy, purpose) +), +actual_amop AS ( + SELECT opf.opfname::text AS opfamily, + amop.amoplefttype::regtype AS lefttype, + amop.amoprighttype::regtype AS righttype, + amop.amopstrategy::int AS strategy, + amop.amoppurpose::text AS purpose + FROM pg_amop amop + JOIN pg_opfamily opf ON opf.oid = amop.amopfamily + JOIN pg_am am ON am.oid = opf.opfmethod + WHERE am.amname = 'gist' + AND opf.opfname IN ('gist_int2_ops', 'gist_int4_ops', 'gist_int8_ops') + AND amop.amoplefttype <> amop.amoprighttype +) +SELECT * +FROM ( + SELECT 'missing from pg_amop' AS status, * + FROM (SELECT * FROM expected_amop EXCEPT SELECT * FROM actual_amop) missing + UNION ALL + SELECT 'unexpected in pg_amop' AS status, * + FROM (SELECT * FROM actual_amop EXCEPT SELECT * FROM expected_amop) unexpected +) diff +ORDER BY status, opfamily, lefttype::text, righttype::text, strategy, purpose; + status | opfamily | lefttype | righttype | strategy | purpose +--------+----------+----------+-----------+----------+--------- +(0 rows) + +SELECT opf.opfname AS opfamily, + amproc.amproclefttype::regtype AS lefttype, + amproc.amprocrighttype::regtype AS righttype, + amproc.amprocnum AS procnum, + amproc.amproc::regproc AS proc +FROM pg_amproc amproc + JOIN pg_opfamily opf ON opf.oid = amproc.amprocfamily + JOIN pg_am am ON am.oid = opf.opfmethod +WHERE am.amname = 'gist' + AND opf.opfname IN ('gist_int2_ops', 'gist_int4_ops', 'gist_int8_ops') + AND amproc.amproclefttype <> amproc.amprocrighttype +ORDER BY opf.opfname, + amproc.amproclefttype::regtype::text, + amproc.amprocrighttype::regtype::text, + amproc.amprocnum, + amproc.amproc::regproc::text; + opfamily | lefttype | righttype | procnum | proc +----------+----------+-----------+---------+------ +(0 rows) + +CREATE TABLE ct_i2 (a int2); +CREATE TABLE ct_i4 (a int4); +CREATE TABLE ct_i8 (a int8); +INSERT INTO ct_i2 SELECT g::int2 FROM generate_series(-100, 100) g; +INSERT INTO ct_i4 SELECT g FROM generate_series(-100, 100) g; +INSERT INTO ct_i8 SELECT g::int8 FROM generate_series(-100, 100) g; +-- Add some values that are representable only in wider types, to exercise +-- the path where the cross-type query constant is out of range of the key +-- type. +INSERT INTO ct_i4 VALUES (100000), (-100000); +INSERT INTO ct_i8 VALUES (5000000000), (-5000000000); +CREATE INDEX ct_i2_idx ON ct_i2 USING gist (a); +CREATE INDEX ct_i4_idx ON ct_i4 USING gist (a); +CREATE INDEX ct_i8_idx ON ct_i8 USING gist (a); +ANALYZE ct_i2; +ANALYZE ct_i4; +ANALYZE ct_i8; +SET enable_seqscan = off; +SET enable_bitmapscan = off; +-- int2 key x int4 query +SELECT count(*) FROM ct_i2 WHERE a < 50::int4; + count +------- + 150 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a <= 50::int4; + count +------- + 151 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a = 50::int4; + count +------- + 1 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a >= 50::int4; + count +------- + 51 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a > 50::int4; + count +------- + 50 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a <> 50::int4; + count +------- + 200 +(1 row) + +-- query out of int2 range: matches nothing for =, everything for <> +SELECT count(*) FROM ct_i2 WHERE a = 100000::int4; + count +------- + 0 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a <> 100000::int4; + count +------- + 201 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a < 100000::int4; + count +------- + 201 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a > 100000::int4; + count +------- + 0 +(1 row) + +-- int2 key x int8 query +SELECT count(*) FROM ct_i2 WHERE a < 50::int8; + count +------- + 150 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a <= 50::int8; + count +------- + 151 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a = 50::int8; + count +------- + 1 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a >= 50::int8; + count +------- + 51 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a > 50::int8; + count +------- + 50 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a <> 50::int8; + count +------- + 200 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a = 5000000000::int8; + count +------- + 0 +(1 row) + +SELECT count(*) FROM ct_i2 WHERE a < 5000000000::int8; + count +------- + 201 +(1 row) + +-- int4 key x int2 query +SELECT count(*) FROM ct_i4 WHERE a < 50::int2; + count +------- + 151 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a <= 50::int2; + count +------- + 152 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a = 50::int2; + count +------- + 1 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a >= 50::int2; + count +------- + 52 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a > 50::int2; + count +------- + 51 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a <> 50::int2; + count +------- + 202 +(1 row) + +-- int4 key x int8 query +SELECT count(*) FROM ct_i4 WHERE a < 50::int8; + count +------- + 151 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a <= 50::int8; + count +------- + 152 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a = 50::int8; + count +------- + 1 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a >= 50::int8; + count +------- + 52 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a > 50::int8; + count +------- + 51 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a <> 50::int8; + count +------- + 202 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a = 5000000000::int8; + count +------- + 0 +(1 row) + +SELECT count(*) FROM ct_i4 WHERE a < 5000000000::int8; + count +------- + 203 +(1 row) + +-- int8 key x int2 query +SELECT count(*) FROM ct_i8 WHERE a < 50::int2; + count +------- + 151 +(1 row) + +SELECT count(*) FROM ct_i8 WHERE a <= 50::int2; + count +------- + 152 +(1 row) + +SELECT count(*) FROM ct_i8 WHERE a = 50::int2; + count +------- + 1 +(1 row) + +SELECT count(*) FROM ct_i8 WHERE a >= 50::int2; + count +------- + 52 +(1 row) + +SELECT count(*) FROM ct_i8 WHERE a > 50::int2; + count +------- + 51 +(1 row) + +SELECT count(*) FROM ct_i8 WHERE a <> 50::int2; + count +------- + 202 +(1 row) + +-- int8 key x int4 query +SELECT count(*) FROM ct_i8 WHERE a < 50::int4; + count +------- + 151 +(1 row) + +SELECT count(*) FROM ct_i8 WHERE a <= 50::int4; + count +------- + 152 +(1 row) + +SELECT count(*) FROM ct_i8 WHERE a = 50::int4; + count +------- + 1 +(1 row) + +SELECT count(*) FROM ct_i8 WHERE a >= 50::int4; + count +------- + 52 +(1 row) + +SELECT count(*) FROM ct_i8 WHERE a > 50::int4; + count +------- + 51 +(1 row) + +SELECT count(*) FROM ct_i8 WHERE a <> 50::int4; + count +------- + 202 +(1 row) + +-- Confirm the index is actually used for a cross-type predicate. +EXPLAIN (COSTS OFF) +SELECT count(*) FROM ct_i4 WHERE a = 50::int8; + QUERY PLAN +------------------------------------------------ + Aggregate + -> Index Only Scan using ct_i4_idx on ct_i4 + Index Cond: (a = '50'::bigint) +(3 rows) + +-- Cross-type KNN: int4 key ordered by int2 / int8 queries. +EXPLAIN (COSTS OFF) +SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int2 LIMIT 3; + QUERY PLAN +------------------------------------------------ + Limit + -> Index Only Scan using ct_i4_idx on ct_i4 + Order By: (a <-> '-100'::smallint) +(3 rows) + +SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int2 LIMIT 3; + a +------ + -100 + -99 + -98 +(3 rows) + +EXPLAIN (COSTS OFF) +SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int8 LIMIT 3; + QUERY PLAN +------------------------------------------------ + Limit + -> Index Only Scan using ct_i4_idx on ct_i4 + Order By: (a <-> '-100'::bigint) +(3 rows) + +SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int8 LIMIT 3; + a +------ + -100 + -99 + -98 +(3 rows) + +-- Cross-type KNN: int2 key ordered by int4 / int8 queries. +SELECT a FROM ct_i2 ORDER BY a <-> '-100'::int4 LIMIT 3; + a +------ + -100 + -99 + -98 +(3 rows) + +SELECT a FROM ct_i2 ORDER BY a <-> '-100'::int8 LIMIT 3; + a +------ + -100 + -99 + -98 +(3 rows) + +-- Cross-type KNN: int8 key ordered by int2 / int4 queries. +SELECT a FROM ct_i8 ORDER BY a <-> '-100'::int2 LIMIT 3; + a +------ + -100 + -99 + -98 +(3 rows) + +SELECT a FROM ct_i8 ORDER BY a <-> '-100'::int4 LIMIT 3; + a +------ + -100 + -99 + -98 +(3 rows) + +-- Combined: cross-type WHERE + cross-type ORDER BY on the same index. +EXPLAIN (COSTS OFF) +SELECT a FROM ct_i4 WHERE a < 80::int8 ORDER BY a <-> '-100'::int8 LIMIT 3; + QUERY PLAN +------------------------------------------------ + Limit + -> Index Only Scan using ct_i4_idx on ct_i4 + Index Cond: (a < '80'::bigint) + Order By: (a <-> '-100'::bigint) +(4 rows) + +SELECT a FROM ct_i4 WHERE a < 80::int8 ORDER BY a <-> '-100'::int8 LIMIT 3; + a +------ + -100 + -99 + -98 +(3 rows) + +-- Standalone distance-function smoke tests (not going through the index), +-- including the overflow-detection paths. +SELECT int2_int4_dist(3::int2, 10::int4); + int2_int4_dist +---------------- + 7 +(1 row) + +SELECT int4_int2_dist(-5::int4, 5::int2); + int4_int2_dist +---------------- + 10 +(1 row) + +SELECT int2_int8_dist(3::int2, 10::int8); + int2_int8_dist +---------------- + 7 +(1 row) + +SELECT int8_int2_dist(100::int8, -5::int2); + int8_int2_dist +---------------- + 105 +(1 row) + +SELECT int4_int8_dist(100::int4, 5000000000::int8); + int4_int8_dist +---------------- + 4999999900 +(1 row) + +SELECT int8_int4_dist(5000000000::int8, 100::int4); + int8_int4_dist +---------------- + 4999999900 +(1 row) + +-- Overflow detection: INT32_MIN distance from a positive int2 can't fit +-- in int32, should error. +SELECT int2_int4_dist(1::int2, -2147483648::int4); +ERROR: integer out of range +-- Likewise INT64_MIN distance from a positive int4 can't fit in int64. +SELECT int4_int8_dist(1::int4, -9223372036854775808::int8); +ERROR: bigint out of range +-- +-- Multi-column GiST index with mixed-type predicates. This is the +-- original motivating case: without cross-type operator support the +-- planner can only use one column as an Index Cond and applies the +-- other(s) as a Filter post-scan. Here both columns should appear as +-- Index Cond. +-- +CREATE TABLE ct_multi (a int4, b int8); +INSERT INTO ct_multi + SELECT g, (g * 2)::int8 FROM generate_series(-50, 50) g; +CREATE INDEX ct_multi_idx ON ct_multi USING gist (a, b); +ANALYZE ct_multi; +EXPLAIN (COSTS OFF) +SELECT count(*) FROM ct_multi WHERE a = 25::int8 AND b = 50::int4; + QUERY PLAN +------------------------------------------------------- + Aggregate + -> Index Only Scan using ct_multi_idx on ct_multi + Index Cond: ((a = '25'::bigint) AND (b = 50)) +(3 rows) + +SELECT count(*) FROM ct_multi WHERE a = 25::int8 AND b = 50::int4; + count +------- + 1 +(1 row) + +-- Mixed cross-type ranges across both columns. +EXPLAIN (COSTS OFF) +SELECT count(*) FROM ct_multi WHERE a < 10::int8 AND b > 0::int2; + QUERY PLAN +------------------------------------------------------------------ + Aggregate + -> Index Only Scan using ct_multi_idx on ct_multi + Index Cond: ((a < '10'::bigint) AND (b > '0'::smallint)) +(3 rows) + +SELECT count(*) FROM ct_multi WHERE a < 10::int8 AND b > 0::int2; + count +------- + 9 +(1 row) + +DROP TABLE ct_multi; +DROP TABLE ct_i2; +DROP TABLE ct_i4; +DROP TABLE ct_i8; diff --git a/contrib/btree_gist/sql/int_crosstype.sql b/contrib/btree_gist/sql/int_crosstype.sql new file mode 100644 index 00000000000..d38084f1941 --- /dev/null +++ b/contrib/btree_gist/sql/int_crosstype.sql @@ -0,0 +1,236 @@ +-- Cross-type operator support for int2/int4/int8 in GiST. +-- +-- Verifies that (a) the cross-type B-tree-style operators registered in +-- gist_int{2,4,8}_ops match the results of seqscans using the same operator +-- expressions, (b) the KNN <-> operator works across types and uses the +-- index, and (c) values outside the smaller subtype's range are handled +-- according to normal comparison semantics, without narrowing or erroring. + +-- +-- Catalog invariant: the cross-type pg_amop entries in gist_int{2,4,8}_ops must +-- agree, in both directions, with what the C cross-type dispatch can handle. +-- The set of supported query subtypes is read from the C side via +-- gbt_int_crosstype_subtypes(), so there is no hand-maintained second copy of +-- the list here: registering a pg_amop row whose subtype the dispatch does not +-- handle (or dropping a dispatch entry while its pg_amop rows remain, or vice +-- versa) shows up as a diff below. Cross-type pg_amproc rows must also stay +-- absent, since the dispatch reuses the same-type support functions. +-- +WITH dispatch_subtypes(typ) AS ( + SELECT s.subtype::regtype + FROM gbt_int_crosstype_subtypes() AS s(subtype) +), +gist_int_opclasses(opfamily, indextype) AS ( + SELECT opc.opcname::text, opc.opcintype::regtype + FROM pg_opclass opc + JOIN pg_am am ON am.oid = opc.opcmethod + WHERE am.amname = 'gist' + AND opc.opcname IN ('gist_int2_ops', 'gist_int4_ops', 'gist_int8_ops') +), +expected_pairs(opfamily, lefttype, righttype) AS ( + SELECT oc.opfamily, oc.indextype, ds.typ + FROM gist_int_opclasses oc + CROSS JOIN dispatch_subtypes ds + WHERE ds.typ <> oc.indextype +), +expected_amop(opfamily, lefttype, righttype, strategy, purpose) AS ( + SELECT opfamily, lefttype, righttype, strategy, purpose + FROM expected_pairs + CROSS JOIN (VALUES + (1, 's'), (2, 's'), (3, 's'), (4, 's'), + (5, 's'), (6, 's'), (15, 'o') + ) AS strategy_purposes(strategy, purpose) +), +actual_amop AS ( + SELECT opf.opfname::text AS opfamily, + amop.amoplefttype::regtype AS lefttype, + amop.amoprighttype::regtype AS righttype, + amop.amopstrategy::int AS strategy, + amop.amoppurpose::text AS purpose + FROM pg_amop amop + JOIN pg_opfamily opf ON opf.oid = amop.amopfamily + JOIN pg_am am ON am.oid = opf.opfmethod + WHERE am.amname = 'gist' + AND opf.opfname IN ('gist_int2_ops', 'gist_int4_ops', 'gist_int8_ops') + AND amop.amoplefttype <> amop.amoprighttype +) +SELECT * +FROM ( + SELECT 'missing from pg_amop' AS status, * + FROM (SELECT * FROM expected_amop EXCEPT SELECT * FROM actual_amop) missing + UNION ALL + SELECT 'unexpected in pg_amop' AS status, * + FROM (SELECT * FROM actual_amop EXCEPT SELECT * FROM expected_amop) unexpected +) diff +ORDER BY status, opfamily, lefttype::text, righttype::text, strategy, purpose; + +SELECT opf.opfname AS opfamily, + amproc.amproclefttype::regtype AS lefttype, + amproc.amprocrighttype::regtype AS righttype, + amproc.amprocnum AS procnum, + amproc.amproc::regproc AS proc +FROM pg_amproc amproc + JOIN pg_opfamily opf ON opf.oid = amproc.amprocfamily + JOIN pg_am am ON am.oid = opf.opfmethod +WHERE am.amname = 'gist' + AND opf.opfname IN ('gist_int2_ops', 'gist_int4_ops', 'gist_int8_ops') + AND amproc.amproclefttype <> amproc.amprocrighttype +ORDER BY opf.opfname, + amproc.amproclefttype::regtype::text, + amproc.amprocrighttype::regtype::text, + amproc.amprocnum, + amproc.amproc::regproc::text; + +CREATE TABLE ct_i2 (a int2); +CREATE TABLE ct_i4 (a int4); +CREATE TABLE ct_i8 (a int8); + +INSERT INTO ct_i2 SELECT g::int2 FROM generate_series(-100, 100) g; +INSERT INTO ct_i4 SELECT g FROM generate_series(-100, 100) g; +INSERT INTO ct_i8 SELECT g::int8 FROM generate_series(-100, 100) g; + +-- Add some values that are representable only in wider types, to exercise +-- the path where the cross-type query constant is out of range of the key +-- type. +INSERT INTO ct_i4 VALUES (100000), (-100000); +INSERT INTO ct_i8 VALUES (5000000000), (-5000000000); + +CREATE INDEX ct_i2_idx ON ct_i2 USING gist (a); +CREATE INDEX ct_i4_idx ON ct_i4 USING gist (a); +CREATE INDEX ct_i8_idx ON ct_i8 USING gist (a); + +ANALYZE ct_i2; +ANALYZE ct_i4; +ANALYZE ct_i8; + +SET enable_seqscan = off; +SET enable_bitmapscan = off; + +-- int2 key x int4 query +SELECT count(*) FROM ct_i2 WHERE a < 50::int4; +SELECT count(*) FROM ct_i2 WHERE a <= 50::int4; +SELECT count(*) FROM ct_i2 WHERE a = 50::int4; +SELECT count(*) FROM ct_i2 WHERE a >= 50::int4; +SELECT count(*) FROM ct_i2 WHERE a > 50::int4; +SELECT count(*) FROM ct_i2 WHERE a <> 50::int4; + +-- query out of int2 range: matches nothing for =, everything for <> +SELECT count(*) FROM ct_i2 WHERE a = 100000::int4; +SELECT count(*) FROM ct_i2 WHERE a <> 100000::int4; +SELECT count(*) FROM ct_i2 WHERE a < 100000::int4; +SELECT count(*) FROM ct_i2 WHERE a > 100000::int4; + +-- int2 key x int8 query +SELECT count(*) FROM ct_i2 WHERE a < 50::int8; +SELECT count(*) FROM ct_i2 WHERE a <= 50::int8; +SELECT count(*) FROM ct_i2 WHERE a = 50::int8; +SELECT count(*) FROM ct_i2 WHERE a >= 50::int8; +SELECT count(*) FROM ct_i2 WHERE a > 50::int8; +SELECT count(*) FROM ct_i2 WHERE a <> 50::int8; +SELECT count(*) FROM ct_i2 WHERE a = 5000000000::int8; +SELECT count(*) FROM ct_i2 WHERE a < 5000000000::int8; + +-- int4 key x int2 query +SELECT count(*) FROM ct_i4 WHERE a < 50::int2; +SELECT count(*) FROM ct_i4 WHERE a <= 50::int2; +SELECT count(*) FROM ct_i4 WHERE a = 50::int2; +SELECT count(*) FROM ct_i4 WHERE a >= 50::int2; +SELECT count(*) FROM ct_i4 WHERE a > 50::int2; +SELECT count(*) FROM ct_i4 WHERE a <> 50::int2; + +-- int4 key x int8 query +SELECT count(*) FROM ct_i4 WHERE a < 50::int8; +SELECT count(*) FROM ct_i4 WHERE a <= 50::int8; +SELECT count(*) FROM ct_i4 WHERE a = 50::int8; +SELECT count(*) FROM ct_i4 WHERE a >= 50::int8; +SELECT count(*) FROM ct_i4 WHERE a > 50::int8; +SELECT count(*) FROM ct_i4 WHERE a <> 50::int8; +SELECT count(*) FROM ct_i4 WHERE a = 5000000000::int8; +SELECT count(*) FROM ct_i4 WHERE a < 5000000000::int8; + +-- int8 key x int2 query +SELECT count(*) FROM ct_i8 WHERE a < 50::int2; +SELECT count(*) FROM ct_i8 WHERE a <= 50::int2; +SELECT count(*) FROM ct_i8 WHERE a = 50::int2; +SELECT count(*) FROM ct_i8 WHERE a >= 50::int2; +SELECT count(*) FROM ct_i8 WHERE a > 50::int2; +SELECT count(*) FROM ct_i8 WHERE a <> 50::int2; + +-- int8 key x int4 query +SELECT count(*) FROM ct_i8 WHERE a < 50::int4; +SELECT count(*) FROM ct_i8 WHERE a <= 50::int4; +SELECT count(*) FROM ct_i8 WHERE a = 50::int4; +SELECT count(*) FROM ct_i8 WHERE a >= 50::int4; +SELECT count(*) FROM ct_i8 WHERE a > 50::int4; +SELECT count(*) FROM ct_i8 WHERE a <> 50::int4; + +-- Confirm the index is actually used for a cross-type predicate. +EXPLAIN (COSTS OFF) +SELECT count(*) FROM ct_i4 WHERE a = 50::int8; + +-- Cross-type KNN: int4 key ordered by int2 / int8 queries. +EXPLAIN (COSTS OFF) +SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int2 LIMIT 3; +SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int2 LIMIT 3; + +EXPLAIN (COSTS OFF) +SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int8 LIMIT 3; +SELECT a FROM ct_i4 ORDER BY a <-> '-100'::int8 LIMIT 3; + +-- Cross-type KNN: int2 key ordered by int4 / int8 queries. +SELECT a FROM ct_i2 ORDER BY a <-> '-100'::int4 LIMIT 3; +SELECT a FROM ct_i2 ORDER BY a <-> '-100'::int8 LIMIT 3; + +-- Cross-type KNN: int8 key ordered by int2 / int4 queries. +SELECT a FROM ct_i8 ORDER BY a <-> '-100'::int2 LIMIT 3; +SELECT a FROM ct_i8 ORDER BY a <-> '-100'::int4 LIMIT 3; + +-- Combined: cross-type WHERE + cross-type ORDER BY on the same index. +EXPLAIN (COSTS OFF) +SELECT a FROM ct_i4 WHERE a < 80::int8 ORDER BY a <-> '-100'::int8 LIMIT 3; +SELECT a FROM ct_i4 WHERE a < 80::int8 ORDER BY a <-> '-100'::int8 LIMIT 3; + +-- Standalone distance-function smoke tests (not going through the index), +-- including the overflow-detection paths. +SELECT int2_int4_dist(3::int2, 10::int4); +SELECT int4_int2_dist(-5::int4, 5::int2); +SELECT int2_int8_dist(3::int2, 10::int8); +SELECT int8_int2_dist(100::int8, -5::int2); +SELECT int4_int8_dist(100::int4, 5000000000::int8); +SELECT int8_int4_dist(5000000000::int8, 100::int4); + +-- Overflow detection: INT32_MIN distance from a positive int2 can't fit +-- in int32, should error. +SELECT int2_int4_dist(1::int2, -2147483648::int4); +-- Likewise INT64_MIN distance from a positive int4 can't fit in int64. +SELECT int4_int8_dist(1::int4, -9223372036854775808::int8); + +-- +-- Multi-column GiST index with mixed-type predicates. This is the +-- original motivating case: without cross-type operator support the +-- planner can only use one column as an Index Cond and applies the +-- other(s) as a Filter post-scan. Here both columns should appear as +-- Index Cond. +-- +CREATE TABLE ct_multi (a int4, b int8); +INSERT INTO ct_multi + SELECT g, (g * 2)::int8 FROM generate_series(-50, 50) g; +CREATE INDEX ct_multi_idx ON ct_multi USING gist (a, b); +ANALYZE ct_multi; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM ct_multi WHERE a = 25::int8 AND b = 50::int4; + +SELECT count(*) FROM ct_multi WHERE a = 25::int8 AND b = 50::int4; + +-- Mixed cross-type ranges across both columns. +EXPLAIN (COSTS OFF) +SELECT count(*) FROM ct_multi WHERE a < 10::int8 AND b > 0::int2; + +SELECT count(*) FROM ct_multi WHERE a < 10::int8 AND b > 0::int2; + +DROP TABLE ct_multi; + +DROP TABLE ct_i2; +DROP TABLE ct_i4; +DROP TABLE ct_i8; -- 2.51.0