PG19 regression in ecd9288624a: JSON_ARRAY() indexes that go stale

From: Manu <manuelreyesbravo(at)gmail(dot)com>
To: Andrew Dunstan <andrew(at)dunslane(dot)net>
Cc: jian he <jian(dot)universality(at)gmail(dot)com>, Robert Haas <robertmhaas(at)gmail(dot)com>, shihao zhong <zhong950419(at)gmail(dot)com>, pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: PG19 regression in ecd9288624a: JSON_ARRAY() indexes that go stale
Date: 2026-11-03 16:58:54
Message-ID: 179372513496.2500379.6217138376932454060@gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi Andrew,

Commit ecd9288624a (patch by Jian, CF 5759) makes 19 accept
JSON_ARRAY() and JSON_OBJECT() expression indexes that 18 rejects, and
several of them return wrong results after a later ALTER TYPE or
CREATE/DROP CAST. The attached v5 is Jian's fix plus a small one of
mine for arrays; it passes check-world on master and REL_19_STABLE.
Robert asked us to move this here from his thread; history at the end.

The shortest repro (repro.sql.txt):

CREATE TYPE c AS (x int);
CREATE TABLE t (v c);
INSERT INTO t VALUES (ROW(1));
CREATE INDEX t_i ON t ((json_array(v RETURNING jsonb)));
ALTER TYPE c ADD ATTRIBUTE y int;
SET enable_seqscan = off;
SELECT count(*) FROM t
WHERE json_array(v RETURNING jsonb) = '[{"x": 1, "y": null}]';

On 19 and master the index scan returns 0 and a seq scan returns 1;
after REINDEX the index returns 1 too. 18.6 rejects the CREATE INDEX.

contain_mutable_functions() asks to_json_is_immutable() about each
argument of these constructors. In 18 every array and composite was
mutable; ecd9288624a recurses into them, but the JSON text of some
types changes while the stored values don't. Case by case, on 18.6 /
19 / 19 with v5 ("stale": index and seq scan disagree afterwards):

- composite, ALTER TYPE ADD/RENAME/DROP ATTRIBUTE (shihao's case):
rejected / stale / rejected; unique and partial indexes, stored
generated columns and partition keys break the same way
- user-defined range, CREATE or DROP CAST: rejected / stale / rejected
- range over an enum with a cast, RENAME VALUE: rejected / stale /
rejected
- array of a user-defined base type, CREATE CAST: rejected / stale /
rejected (by 0002)
- float4, float8, then extra_float_digits = 0: stale / stale / stale,
since float8out is still IMMUTABLE in 18 and 19; on master it is
STABLE now, and there v5 rejects them
- int4range, int4multirange: rejected / accepted / accepted, which is
right: json casts are looked up only for user-defined types
(jsonfuncs.c:6060 on master); a cast on int4range is never used.

v5-0001 is Jian's v4 code unchanged, with a new commit message that
describes the problem and the fix (v4 reused Robert's v1-0002 one). It
makes composites, record, enums, user-defined ranges and multiranges,
anyarray and anycompatiblearray mutable, and stops treating float4 and
float8 as always immutable. v5-0002, mine, does the same for arrays of
a user-defined base type or a domain over one, with a test. Both apply
with git am on master eaa5961efc6 and REL_19_STABLE e33700c7670, build
with assertions and no warnings, and pass check-world on both.

Of the 342 types in matrix.txt, the only ones v5 rejects while 18.6
and the unpatched branch accept them are an enum or a user-defined
range with an IMMUTABLE cast to json (which can be dropped later), and
on master float4, float8 and a domain over float8.

Out of scope: a user-defined base type used directly or through a
domain goes stale the same way on 18.6; it is older than 19.

History (2026, UTC):
- Jul 2, Robert's v1-0002 drops the record special case, not knowing
it was new in 19:
https://postgr.es/m/CA+TgmoaiothgQrw9OtgsMzBUCnqJ2jdaGTbS6o3fkjCd+LfzWw@mail.gmail.com
- Oct 13, Jian proposes that fix alone for 19 (v2):
https://postgr.es/m/CACJufxF4yYQsTavPHkfyHRFvh1rO7xWH39u9pRJ9FGi5Wb_j9Q@mail.gmail.com
- Oct 18, shihao: a named composite plus ADD ATTRIBUTE still goes stale:
https://postgr.es/m/CAGRkXqTgwNKmZVK=3CpYZAdcatHEajOysyb072vwNuYUtX18eA@mail.gmail.com
- Oct 18, me: any structural change does, in every index form:
https://postgr.es/m/179229793051.2378320.14800418190474533566@gmail.com
- Oct 22, Jian's v3 makes every composite mutable:
https://postgr.es/m/CACJufxHQJ=5ee1FMhKqNjZ4TAX4q6dWsGiEEJJGgb1AhCF1cgA@mail.gmail.com
- Oct 22, me: a range over an enum with a cast still goes stale:
https://postgr.es/m/179268425559.902958.6506662009494214541@gmail.com
- Oct 23, shihao: v3 still treats float4/float8 as immutable on master:
https://postgr.es/m/CAGRkXqQz500QNGZrF36xu+VvoL9E3+FgyVvx8zW0f6jFnKM7iA@mail.gmail.com
- Oct 23, Jian's v4 adds enums and user-defined ranges:
https://postgr.es/m/CACJufxER-VJzaH33HYzoU-Z4ZOd_iJCX9+3r5TLhcEEN=NS=fQ@mail.gmail.com
- Oct 23, me: arrays of a user-defined base type still go stale:
https://postgr.es/m/179279809045.1942440.2176959044031147982@gmail.com
- Nov 3, Robert asks for this thread and the open item:
https://postgr.es/m/CA+TgmoYJMxeY2XoQ88qHgi2NCA2LbQd=aoVwcOAPLq3KZ22DKQ@mail.gmail.com

I'll add it to the PG19 open items, with Andrew as owner, as soon as
my wiki editor access comes through.

regards,
Manu

Attachment Content-Type Size
v5-0001-Fix-immutability-check-of-SQL-JSON-constructors-f.patch text/x-patch 22.8 KB
v5-0002-Treat-arrays-of-user-defined-base-types-as-mutabl.patch text/x-patch 5.4 KB
repro.sql.txt text/plain 854 bytes
matrix.txt text/plain 38.8 KB

Browse pgsql-hackers by date

  From Date Subject
Next Message Manu 2026-11-03 17:04:10 Re: json/jsonb cleanup + FmgrInfo caching
Previous Message Jeff Davis 2026-11-03 16:28:31 Re: [RFC] Property-based testing for Postgres