#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"
/*
* 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;
}
/*
#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"
/*
* 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;
}
/*
#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"
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;
+ }
+}
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,
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?
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;