From 1c0427b9e83944e5afea92ef8774b38884f52acc Mon Sep 17 00:00:00 2001 From: reshke Date: Mon, 21 Sep 2026 10:44:47 +0300 Subject: [PATCH v3] Fix SP-GiST inet opclass for mixed IP address families inet_spg_picksplit() used to lose an entry of the other address family, so IPv6 entries were placed into IPv4-prefixed inner tuples which is a corruption. Fix by following the same conventions as other SP-GiST opclasses. Existing mixed-family indexes need a REINDEX after updating. Reported-by: Ke Han Bug: #19700 --- src/backend/utils/adt/network_spgist.c | 39 +++++++++++++++++--------- src/test/regress/expected/inet.out | 18 ++++++++++++ src/test/regress/sql/inet.sql | 9 ++++++ 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/backend/utils/adt/network_spgist.c b/src/backend/utils/adt/network_spgist.c index 52e3c666d4f..a467b2f29d8 100644 --- a/src/backend/utils/adt/network_spgist.c +++ b/src/backend/utils/adt/network_spgist.c @@ -10,6 +10,10 @@ * * An inner tuple that has both IPv4 and IPv6 children has a null prefix * and exactly two nodes, the first being for IPv4 and the second for IPv6. + * There is one exception: allTheSame tuples, which the SP-GiST core can + * force here (see checkAllTheSame() in spgdoinsert.c) even though the + * entries span both families, in which case the nodes are duplicates of + * each other rather than family-specific. * * Otherwise, the prefix is a CIDR value representing the common prefix, * and there are exactly four nodes. Node numbers 0 and 1 are for addresses @@ -81,8 +85,19 @@ inet_spg_choose(PG_FUNCTION_ARGS) */ if (!in->hasPrefix) { - /* allTheSame isn't possible for such a tuple */ - Assert(!in->allTheSame); + /* + * The core can force allTheSame mode on this tuple (see + * checkAllTheSame() in spgdoinsert.c). + */ + if (in->allTheSame) + { + out->resultType = spgMatchNode; + out->result.matchNode.nodeN = 0 /* Doesn't matter, will bee overwritten */; + out->result.matchNode.restDatum = InetPGetDatum(val); + + PG_RETURN_VOID(); + } + Assert(in->nNodes == 2); out->resultType = spgMatchNode; @@ -191,9 +206,8 @@ inet_spg_picksplit(PG_FUNCTION_ARGS) if (ip_bits(tmp) < commonbits) commonbits = ip_bits(tmp); - commonbits = bitncommon(ip_addr(prefix), ip_addr(tmp), commonbits); - if (commonbits == 0) - break; + if (commonbits != 0) + commonbits = bitncommon(ip_addr(prefix), ip_addr(tmp), commonbits); } /* Don't need labels; allocate output arrays */ @@ -245,9 +259,13 @@ inet_spg_inner_consistent(PG_FUNCTION_ARGS) int i; int which; - if (!in->hasPrefix) + if (in->allTheSame) + { + /* Must visit all nodes; we assume there are less than 32 of 'em */ + which = ~0; + } + else if (!in->hasPrefix) { - Assert(!in->allTheSame); Assert(in->nNodes == 2); /* Identify which child nodes need to be visited */ @@ -285,7 +303,7 @@ inet_spg_inner_consistent(PG_FUNCTION_ARGS) } } } - else if (!in->allTheSame) + else { Assert(in->nNodes == 4); @@ -293,11 +311,6 @@ inet_spg_inner_consistent(PG_FUNCTION_ARGS) which = inet_spg_consistent_bitmap(DatumGetInetPP(in->prefixDatum), in->nkeys, in->scankeys, false); } - else - { - /* Must visit all nodes; we assume there are less than 32 of 'em */ - which = ~0; - } out->nNodes = 0; diff --git a/src/test/regress/expected/inet.out b/src/test/regress/expected/inet.out index 1705bff4dd3..51b031d51bf 100644 --- a/src/test/regress/expected/inet.out +++ b/src/test/regress/expected/inet.out @@ -709,6 +709,24 @@ SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY i; 192.168.1.226 (3 rows) +CREATE TABLE inet_tbl_mixedfamily (i inet); +INSERT INTO inet_tbl_mixedfamily SELECT '10.0.0.1/32' FROM generate_series(1, 100); +INSERT INTO inet_tbl_mixedfamily SELECT '0.0.0.0/0' FROM generate_series(1, 100); +INSERT INTO inet_tbl_mixedfamily SELECT '::1' FROM generate_series(1, 100); +CREATE INDEX inet_idx_mixedfamily ON inet_tbl_mixedfamily USING spgist (i); +SELECT count(*) FROM inet_tbl_mixedfamily WHERE i = '::1'; + count +------- + 100 +(1 row) + +SELECT count(*) FROM inet_tbl_mixedfamily WHERE i = '10.0.0.1'; + count +------- + 100 +(1 row) + +DROP TABLE inet_tbl_mixedfamily; SET enable_seqscan TO on; DROP INDEX inet_idx3; -- simple tests of inet boolean and arithmetic operators diff --git a/src/test/regress/sql/inet.sql b/src/test/regress/sql/inet.sql index 8f276856df9..6b6a852e794 100644 --- a/src/test/regress/sql/inet.sql +++ b/src/test/regress/sql/inet.sql @@ -135,6 +135,15 @@ EXPLAIN (COSTS OFF) SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY i; SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY i; +CREATE TABLE inet_tbl_mixedfamily (i inet); +INSERT INTO inet_tbl_mixedfamily SELECT '10.0.0.1/32' FROM generate_series(1, 100); +INSERT INTO inet_tbl_mixedfamily SELECT '0.0.0.0/0' FROM generate_series(1, 100); +INSERT INTO inet_tbl_mixedfamily SELECT '::1' FROM generate_series(1, 100); +CREATE INDEX inet_idx_mixedfamily ON inet_tbl_mixedfamily USING spgist (i); +SELECT count(*) FROM inet_tbl_mixedfamily WHERE i = '::1'; +SELECT count(*) FROM inet_tbl_mixedfamily WHERE i = '10.0.0.1'; +DROP TABLE inet_tbl_mixedfamily; + SET enable_seqscan TO on; DROP INDEX inet_idx3; -- 2.43.0