On 2026-03-10 Tu 2:31 AM, jian he wrote:
On Fri, Mar 6, 2026 at 6:09 AM Andrew Dunstan <[email protected]> wrote:
Here's a rework of this patch. It preserves the original signature of
to_json{b}_is_immutable, and fixes some code duplication. It also uses
the typecache to get composite info instead of calling relation_open,
supports MultiRange types, and exits early if we made a recursive call
(no need for json_categorize_type etc. in these cases).
In json_categorize_type, we have:
case INT2OID:
case INT4OID:
case INT8OID:
case FLOAT4OID:
case FLOAT8OID:
case NUMERICOID:
getTypeOutputInfo(typoid, outfuncoid, &typisvarlena);
*tcategory = JSONTYPE_NUMERIC;
select proname, provolatile from pg_Proc where proname in ('int8out',
'int2out', 'int4out', 'float4out', 'float8out', 'numeric_out');
proname | provolatile
-------------+-------------
int2out | i
int4out | i
float4out | i
float8out | i
int8out | i
numeric_out | i
(6 rows)
Therefore for JSONTYPE_NUMERIC, has_mutable in json_check_mutability
will be false.
I slightly changed the `switch (tcategory)` handling in `json_check_mutability`.
Make the handling order the same as JsonTypeCategory.
Cosmetic change in src/test/regress/sql/sqljson.sql
--error
To
-- error
OK, here's a v7.
. added a break in the loop if we found something mutable
. added test for JSON generated columns (was present for JSONB
expression indexes but missing for JSON).
. added test block demonstrating that range_int (subtype=int) and
multirange_int are now correctly treated as immutable, allowing
expression indexes via json_array()
and json_object()
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
From 916661fa2350c2aa8195983b663c0efb9868be33 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 10 Mar 2026 14:25:29 +0800
Subject: [v7] make to_json, to_jsonb immutability test complete
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.
Add comprehensive regression tests for JSON_ARRAY and JSON_OBJECT
mutability with expression indexes and generated columns, covering arrays,
composites, domains, ranges, multiranges and combinations thereof.
Discussion: https://postgr.es/m/CACJufxFz=osxqdsmj-cqoqspd9ajrwntsqp-u2a-uav_m+-...@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5759
---
src/backend/utils/adt/json.c | 38 +-----
src/backend/utils/adt/jsonb.c | 38 +-----
src/backend/utils/adt/jsonfuncs.c | 105 ++++++++++++++++
src/include/utils/jsonfuncs.h | 2 +
src/test/regress/expected/sqljson.out | 165 ++++++++++++++++++++++++++
src/test/regress/sql/sqljson.sql | 89 ++++++++++++++
6 files changed, 367 insertions(+), 70 deletions(-)
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 0b161398465..52f2ef2875b 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -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"
@@ -692,45 +691,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;
+ bool has_mutable = false;
- 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;
- }
-
- return false; /* not reached */
+ json_check_mutability(typoid, false, &has_mutable);
+ return !has_mutable;
}
/*
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 0a3a77ee786..1b1a8f301f2 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -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;
+ bool has_mutable = false;
- 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;
- }
-
- return false; /* not reached */
+ json_check_mutability(typoid, true, &has_mutable);
+ return !has_mutable;
}
/*
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index d5b64d7fca5..3ea8baee13b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -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"
@@ -6062,3 +6064,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;
+ }
+}
diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h
index 636f0f55840..27713be3aeb 100644
--- a/src/include/utils/jsonfuncs.h
+++ b/src/include/utils/jsonfuncs.h
@@ -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,
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index c7b9e575445..fccb37edca9 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -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 is aggregate function, can not 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 don't have 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 don't have 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?
diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index 343d344d270..c76de4a805c 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -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 is aggregate function, can not 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 don't have 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 don't have 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;
--
2.43.0