]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
make immutability tests in to_json and to_jsonb complete
authorAndrew Dunstan <andrew@dunslane.net>
Tue, 10 Mar 2026 06:25:29 +0000 (14:25 +0800)
committerAndrew Dunstan <andrew@dunslane.net>
Tue, 17 Mar 2026 15:28:33 +0000 (11:28 -0400)
Complete the TODOs in to_json_is_immutable() and to_jsonb_is_immutable()
by recursing into container types (arrays, composites, ranges, multiranges,
domains) to check element/sub-type mutability, rather than conservatively
returning "mutable" for all arrays and composites.

The shared logic is factored into a single json_check_mutability() function
in jsonfuncs.c, with the existing exported functions as thin wrappers.
Composite type inspection uses lookup_rowtype_tupdesc() (typcache) instead
of relation_open() to avoid unnecessary lock acquisition in the optimizer.

Range and multirange types are now also checked recursively: if the
subtype's conversion is immutable, the range is considered immutable
for JSON purposes, even though range_out is generically marked STABLE.
This is a behavioral change: range types with immutable subtypes (e.g.,
int4range) can now appear in expression indexes via JSON_ARRAY/JSON_OBJECT,
whereas previously they were conservatively rejected.

Add regression tests for JSON_ARRAY and JSON_OBJECT mutability with
expression indexes and generated columns, covering arrays, composites,
domains, ranges, multiranges and combinations thereof.

Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Andrew Dunstan <andrew@dunslane.net>
Discussion: https://postgr.es/m/CACJufxFz=OsXQdsMJ-cqoqspD9aJrwntsQP-U2A-UaV_M+-S9g@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5759

src/backend/utils/adt/json.c
src/backend/utils/adt/jsonb.c
src/backend/utils/adt/jsonfuncs.c
src/include/utils/jsonfuncs.h
src/test/regress/expected/sqljson.out
src/test/regress/sql/sqljson.sql

index ab38ddb907927f6e34fddfa3d94b3be8ac766476..ae73c64fbb588c163b4c8c995f1bf6c491d6b235 100644 (file)
@@ -14,7 +14,6 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
-#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "common/hashfn.h"
 #include "funcapi.h"
@@ -693,45 +692,14 @@ row_to_json_pretty(PG_FUNCTION_ARGS)
 
 /*
  * Is the given type immutable when coming out of a JSON context?
- *
- * At present, datetimes are all considered mutable, because they
- * depend on timezone.  XXX we should also drill down into objects
- * and arrays, but do not.
  */
 bool
 to_json_is_immutable(Oid typoid)
 {
-       JsonTypeCategory tcategory;
-       Oid                     outfuncoid;
-
-       json_categorize_type(typoid, false, &tcategory, &outfuncoid);
-
-       switch (tcategory)
-       {
-               case JSONTYPE_BOOL:
-               case JSONTYPE_JSON:
-               case JSONTYPE_JSONB:
-               case JSONTYPE_NULL:
-                       return true;
-
-               case JSONTYPE_DATE:
-               case JSONTYPE_TIMESTAMP:
-               case JSONTYPE_TIMESTAMPTZ:
-                       return false;
-
-               case JSONTYPE_ARRAY:
-                       return false;           /* TODO recurse into elements */
-
-               case JSONTYPE_COMPOSITE:
-                       return false;           /* TODO recurse into fields */
-
-               case JSONTYPE_NUMERIC:
-               case JSONTYPE_CAST:
-               case JSONTYPE_OTHER:
-                       return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE;
-       }
+       bool            has_mutable = false;
 
-       return false;                           /* not reached */
+       json_check_mutability(typoid, false, &has_mutable);
+       return !has_mutable;
 }
 
 /*
index 0a3a77ee786a5582e6805be1581e86cc2765746c..1b1a8f301f24628cdb2608af90d1306604ba9903 100644 (file)
@@ -13,7 +13,6 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
-#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
@@ -1077,45 +1076,14 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result,
 
 /*
  * Is the given type immutable when coming out of a JSONB context?
- *
- * At present, datetimes are all considered mutable, because they
- * depend on timezone.  XXX we should also drill down into objects and
- * arrays, but do not.
  */
 bool
 to_jsonb_is_immutable(Oid typoid)
 {
-       JsonTypeCategory tcategory;
-       Oid                     outfuncoid;
-
-       json_categorize_type(typoid, true, &tcategory, &outfuncoid);
-
-       switch (tcategory)
-       {
-               case JSONTYPE_NULL:
-               case JSONTYPE_BOOL:
-               case JSONTYPE_JSON:
-               case JSONTYPE_JSONB:
-                       return true;
-
-               case JSONTYPE_DATE:
-               case JSONTYPE_TIMESTAMP:
-               case JSONTYPE_TIMESTAMPTZ:
-                       return false;
-
-               case JSONTYPE_ARRAY:
-                       return false;           /* TODO recurse into elements */
-
-               case JSONTYPE_COMPOSITE:
-                       return false;           /* TODO recurse into fields */
-
-               case JSONTYPE_NUMERIC:
-               case JSONTYPE_CAST:
-               case JSONTYPE_OTHER:
-                       return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE;
-       }
+       bool            has_mutable = false;
 
-       return false;                           /* not reached */
+       json_check_mutability(typoid, true, &has_mutable);
+       return !has_mutable;
 }
 
 /*
index efdf60ca5a54d0fdab21d97eafc5d3326d484376..97cc3d603409257e8942913fe193745a37a0337c 100644 (file)
@@ -17,6 +17,8 @@
 #include <limits.h>
 
 #include "access/htup_details.h"
+#include "access/tupdesc.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "common/int.h"
 #include "common/jsonapi.h"
@@ -6063,3 +6065,106 @@ json_categorize_type(Oid typoid, bool is_jsonb,
                        break;
        }
 }
+
+/*
+ * Check whether a type conversion to JSON or JSONB involves any mutable
+ * functions.  This recurses into container types (arrays, composites,
+ * ranges, multiranges, domains) to check their element/sub types.
+ *
+ * The caller must initialize *has_mutable to false before calling.
+ * If any mutable function is found, *has_mutable is set to true.
+ */
+void
+json_check_mutability(Oid typoid, bool is_jsonb, bool *has_mutable)
+{
+       char            att_typtype = get_typtype(typoid);
+       JsonTypeCategory tcategory;
+       Oid                     outfuncoid;
+
+       /* since this function recurses, it could be driven to stack overflow */
+       check_stack_depth();
+
+       Assert(has_mutable != NULL);
+
+       if (*has_mutable)
+               return;
+
+       if (att_typtype == TYPTYPE_DOMAIN)
+       {
+               json_check_mutability(getBaseType(typoid), is_jsonb, has_mutable);
+               return;
+       }
+       else if (att_typtype == TYPTYPE_COMPOSITE)
+       {
+               /*
+                * For a composite type, recurse into its attributes.  Use the
+                * typcache to avoid opening the relation directly.
+                */
+               TupleDesc       tupdesc = lookup_rowtype_tupdesc(typoid, -1);
+
+               for (int i = 0; i < tupdesc->natts; i++)
+               {
+                       Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+                       if (attr->attisdropped)
+                               continue;
+
+                       json_check_mutability(attr->atttypid, is_jsonb, has_mutable);
+                       if (*has_mutable)
+                               break;
+               }
+               ReleaseTupleDesc(tupdesc);
+               return;
+       }
+       else if (att_typtype == TYPTYPE_RANGE)
+       {
+               json_check_mutability(get_range_subtype(typoid), is_jsonb,
+                                                         has_mutable);
+               return;
+       }
+       else if (att_typtype == TYPTYPE_MULTIRANGE)
+       {
+               json_check_mutability(get_multirange_range(typoid), is_jsonb,
+                                                         has_mutable);
+               return;
+       }
+       else
+       {
+               Oid                     att_typelem = get_element_type(typoid);
+
+               if (OidIsValid(att_typelem))
+               {
+                       /* recurse into array element type */
+                       json_check_mutability(att_typelem, is_jsonb, has_mutable);
+                       return;
+               }
+       }
+
+       json_categorize_type(typoid, is_jsonb, &tcategory, &outfuncoid);
+
+       switch (tcategory)
+       {
+               case JSONTYPE_NULL:
+               case JSONTYPE_BOOL:
+               case JSONTYPE_NUMERIC:
+                       break;
+
+               case JSONTYPE_DATE:
+               case JSONTYPE_TIMESTAMP:
+               case JSONTYPE_TIMESTAMPTZ:
+                       *has_mutable = true;
+                       break;
+
+               case JSONTYPE_JSON:
+               case JSONTYPE_JSONB:
+               case JSONTYPE_ARRAY:
+               case JSONTYPE_COMPOSITE:
+                       break;
+
+               case JSONTYPE_CAST:
+               case JSONTYPE_OTHER:
+                       if (func_volatile(outfuncoid) != PROVOLATILE_IMMUTABLE)
+                               *has_mutable = true;
+                       break;
+       }
+}
index 636f0f558403f3657b79f72491935a4c53973a8c..27713be3aeb33b5b5b1d5fad831e09a0200ec65b 100644 (file)
@@ -83,6 +83,8 @@ typedef enum
 
 extern void json_categorize_type(Oid typoid, bool is_jsonb,
                                                                 JsonTypeCategory *tcategory, Oid *outfuncoid);
+extern void json_check_mutability(Oid typoid, bool is_jsonb,
+                                                                 bool *has_mutable);
 extern Datum datum_to_json(Datum val, JsonTypeCategory tcategory,
                                                   Oid outfuncoid);
 extern Datum datum_to_jsonb(Datum val, JsonTypeCategory tcategory,
index c7b9e575445e6255a1de238d3d199c50628bc684..4e3b4540d42c7c74cde910223b0386e4a6c49386 100644 (file)
@@ -1109,6 +1109,171 @@ CREATE OR REPLACE VIEW public.json_array_subquery_view AS
            FROM ( SELECT foo.i
                    FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i)) q(a)) AS "json_array"
 DROP VIEW json_array_subquery_view;
+-- Test mutability of JSON_OBJECTAGG, JSON_ARRAYAGG, JSON_ARRAY, JSON_OBJECT
+create type comp1 as (a int, b date);
+create domain d_comp1 as comp1;
+create domain mydomain as timestamptz;
+create type mydomainrange as range(subtype=mydomain);
+create type comp3 as (a int, b mydomainrange);
+create table test_mutability(
+       a text[], b timestamp, c timestamptz,
+       d date, f1 comp1[], f2 timestamp[],
+       f3 d_comp1[],
+       f4 mydomainrange[],
+       f5 comp3,
+       f6 mydomainmultirange);
+-- JSON_OBJECTAGG, JSON_ARRAYAGG are aggregate functions, cannot be used in index
+create index xx on test_mutability(json_objectagg(a: b absent on null with unique keys returning jsonb));
+ERROR:  aggregate functions are not allowed in index expressions
+LINE 1: create index xx on test_mutability(json_objectagg(a: b absen...
+                                           ^
+create index xx on test_mutability(json_objectagg(a: b absent on null with unique keys returning json));
+ERROR:  aggregate functions are not allowed in index expressions
+LINE 1: create index xx on test_mutability(json_objectagg(a: b absen...
+                                           ^
+create index xx on test_mutability(json_arrayagg(a returning jsonb));
+ERROR:  aggregate functions are not allowed in index expressions
+LINE 1: create index xx on test_mutability(json_arrayagg(a returning...
+                                           ^
+create index xx on test_mutability(json_arrayagg(a returning json));
+ERROR:  aggregate functions are not allowed in index expressions
+LINE 1: create index xx on test_mutability(json_arrayagg(a returning...
+                                           ^
+-- jsonb: create expression index via json_array
+create index on test_mutability(json_array(a returning jsonb)); -- ok
+create index on test_mutability(json_array(b returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_array(b returning jsonb...
+                                        ^
+create index on test_mutability(json_array(c returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_array(c returning jsonb...
+                                        ^
+create index on test_mutability(json_array(d returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_array(d returning jsonb...
+                                        ^
+create index on test_mutability(json_array(f1 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_array(f1 returning json...
+                                        ^
+create index on test_mutability(json_array(f2 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_array(f2 returning json...
+                                        ^
+create index on test_mutability(json_array(f3 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_array(f3 returning json...
+                                        ^
+create index on test_mutability(json_array(f4 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_array(f4 returning json...
+                                        ^
+create index on test_mutability(json_array(f5 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_array(f5 returning json...
+                                        ^
+create index on test_mutability(json_array(f6 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_array(f6 returning json...
+                                        ^
+-- jsonb: create expression index via json_object
+create index on test_mutability(json_object('hello' value a returning jsonb)); -- ok
+create index on test_mutability(json_object('hello' value b returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_object('hello' value b ...
+                                        ^
+create index on test_mutability(json_object('hello' value c returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_object('hello' value c ...
+                                        ^
+create index on test_mutability(json_object('hello' value d returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_object('hello' value d ...
+                                        ^
+create index on test_mutability(json_object('hello' value f1 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_object('hello' value f1...
+                                        ^
+create index on test_mutability(json_object('hello' value f2 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_object('hello' value f2...
+                                        ^
+create index on test_mutability(json_object('hello' value f3 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_object('hello' value f3...
+                                        ^
+create index on test_mutability(json_object('hello' value f4 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_object('hello' value f4...
+                                        ^
+create index on test_mutability(json_object('hello' value f5 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_object('hello' value f5...
+                                        ^
+create index on test_mutability(json_object('hello' value f6 returning jsonb)); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index on test_mutability(json_object('hello' value f6...
+                                        ^
+-- data type json doesn't have a default operator class for access method "btree" so
+-- we use a generated column to test whether the JSON_ARRAY expression is
+-- immutable
+alter table test_mutability add column f10 json generated always as (json_array(a returning json)); -- ok
+alter table test_mutability add column f11 json generated always as (json_array(b returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f11 json generated always as (json_array(c returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f11 json generated always as (json_array(d returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f11 json generated always as (json_array(f1 returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f11 json generated always as (json_array(f2 returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f11 json generated always as (json_array(f3 returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f11 json generated always as (json_array(f4 returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f11 json generated always as (json_array(f5 returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f11 json generated always as (json_array(f6 returning json)); -- error
+ERROR:  generation expression is not immutable
+-- data type json doesn't have a default operator class for access method "btree" so
+-- we use a generated column to test whether the JSON_OBJECT expression is
+-- immutable
+alter table test_mutability add column f11 json generated always as (json_object('hello' value a returning json)); -- ok
+alter table test_mutability add column f12 json generated always as (json_object('hello' value b returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f12 json generated always as (json_object('hello' value c returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f12 json generated always as (json_object('hello' value d returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f1 returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f2 returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f3 returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f4 returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f5 returning json)); -- error
+ERROR:  generation expression is not immutable
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f6 returning json)); -- error
+ERROR:  generation expression is not immutable
+drop table test_mutability;
+drop domain d_comp1;
+drop type comp3;
+drop type mydomainrange;
+drop domain mydomain;
+drop type comp1;
+-- Range/multirange with immutable subtype should be considered immutable
+create type range_int as range(subtype=int);
+create table test_range_immutable(r range_int, m multirange_int);
+create index on test_range_immutable(json_array(r returning jsonb)); -- ok
+create index on test_range_immutable(json_array(m returning jsonb)); -- ok
+create index on test_range_immutable(json_object('key' value r returning jsonb)); -- ok
+create index on test_range_immutable(json_object('key' value m returning jsonb)); -- ok
+drop table test_range_immutable;
+drop type range_int;
 -- IS JSON predicate
 SELECT NULL IS JSON;
  ?column? 
index 343d344d2707f40bbb4f9acd17b0ec5e0772b545..0ad7fb14e7d07d274c7543f13a2b939cd4b69bfe 100644 (file)
@@ -386,6 +386,95 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING
 
 DROP VIEW json_array_subquery_view;
 
+-- Test mutability of JSON_OBJECTAGG, JSON_ARRAYAGG, JSON_ARRAY, JSON_OBJECT
+create type comp1 as (a int, b date);
+create domain d_comp1 as comp1;
+create domain mydomain as timestamptz;
+create type mydomainrange as range(subtype=mydomain);
+create type comp3 as (a int, b mydomainrange);
+create table test_mutability(
+       a text[], b timestamp, c timestamptz,
+       d date, f1 comp1[], f2 timestamp[],
+       f3 d_comp1[],
+       f4 mydomainrange[],
+       f5 comp3,
+       f6 mydomainmultirange);
+
+-- JSON_OBJECTAGG, JSON_ARRAYAGG are aggregate functions, cannot be used in index
+create index xx on test_mutability(json_objectagg(a: b absent on null with unique keys returning jsonb));
+create index xx on test_mutability(json_objectagg(a: b absent on null with unique keys returning json));
+create index xx on test_mutability(json_arrayagg(a returning jsonb));
+create index xx on test_mutability(json_arrayagg(a returning json));
+
+-- jsonb: create expression index via json_array
+create index on test_mutability(json_array(a returning jsonb)); -- ok
+create index on test_mutability(json_array(b returning jsonb)); -- error
+create index on test_mutability(json_array(c returning jsonb)); -- error
+create index on test_mutability(json_array(d returning jsonb)); -- error
+create index on test_mutability(json_array(f1 returning jsonb)); -- error
+create index on test_mutability(json_array(f2 returning jsonb)); -- error
+create index on test_mutability(json_array(f3 returning jsonb)); -- error
+create index on test_mutability(json_array(f4 returning jsonb)); -- error
+create index on test_mutability(json_array(f5 returning jsonb)); -- error
+create index on test_mutability(json_array(f6 returning jsonb)); -- error
+
+-- jsonb: create expression index via json_object
+create index on test_mutability(json_object('hello' value a returning jsonb)); -- ok
+create index on test_mutability(json_object('hello' value b returning jsonb)); -- error
+create index on test_mutability(json_object('hello' value c returning jsonb)); -- error
+create index on test_mutability(json_object('hello' value d returning jsonb)); -- error
+create index on test_mutability(json_object('hello' value f1 returning jsonb)); -- error
+create index on test_mutability(json_object('hello' value f2 returning jsonb)); -- error
+create index on test_mutability(json_object('hello' value f3 returning jsonb)); -- error
+create index on test_mutability(json_object('hello' value f4 returning jsonb)); -- error
+create index on test_mutability(json_object('hello' value f5 returning jsonb)); -- error
+create index on test_mutability(json_object('hello' value f6 returning jsonb)); -- error
+
+-- data type json doesn't have a default operator class for access method "btree" so
+-- we use a generated column to test whether the JSON_ARRAY expression is
+-- immutable
+alter table test_mutability add column f10 json generated always as (json_array(a returning json)); -- ok
+alter table test_mutability add column f11 json generated always as (json_array(b returning json)); -- error
+alter table test_mutability add column f11 json generated always as (json_array(c returning json)); -- error
+alter table test_mutability add column f11 json generated always as (json_array(d returning json)); -- error
+alter table test_mutability add column f11 json generated always as (json_array(f1 returning json)); -- error
+alter table test_mutability add column f11 json generated always as (json_array(f2 returning json)); -- error
+alter table test_mutability add column f11 json generated always as (json_array(f3 returning json)); -- error
+alter table test_mutability add column f11 json generated always as (json_array(f4 returning json)); -- error
+alter table test_mutability add column f11 json generated always as (json_array(f5 returning json)); -- error
+alter table test_mutability add column f11 json generated always as (json_array(f6 returning json)); -- error
+
+-- data type json doesn't have a default operator class for access method "btree" so
+-- we use a generated column to test whether the JSON_OBJECT expression is
+-- immutable
+alter table test_mutability add column f11 json generated always as (json_object('hello' value a returning json)); -- ok
+alter table test_mutability add column f12 json generated always as (json_object('hello' value b returning json)); -- error
+alter table test_mutability add column f12 json generated always as (json_object('hello' value c returning json)); -- error
+alter table test_mutability add column f12 json generated always as (json_object('hello' value d returning json)); -- error
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f1 returning json)); -- error
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f2 returning json)); -- error
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f3 returning json)); -- error
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f4 returning json)); -- error
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f5 returning json)); -- error
+alter table test_mutability add column f12 json generated always as (json_object('hello' value f6 returning json)); -- error
+
+drop table test_mutability;
+drop domain d_comp1;
+drop type comp3;
+drop type mydomainrange;
+drop domain mydomain;
+drop type comp1;
+
+-- Range/multirange with immutable subtype should be considered immutable
+create type range_int as range(subtype=int);
+create table test_range_immutable(r range_int, m multirange_int);
+create index on test_range_immutable(json_array(r returning jsonb)); -- ok
+create index on test_range_immutable(json_array(m returning jsonb)); -- ok
+create index on test_range_immutable(json_object('key' value r returning jsonb)); -- ok
+create index on test_range_immutable(json_object('key' value m returning jsonb)); -- ok
+drop table test_range_immutable;
+drop type range_int;
+
 -- IS JSON predicate
 SELECT NULL IS JSON;
 SELECT NULL IS NOT JSON;