From b7b25c5f168a9e79aad5897286f98b722b61cdbe Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Sat, 4 Apr 2026 19:16:26 -0400 Subject: [PATCH v1 4/5] Allow JSON_OBJECT, JSON_ARRAY, JSON_SCALAR to cache JSON type information. This may significantly improve performance in queries which execute these functions repeatedly. --- src/backend/executor/execExpr.c | 28 ++++----- src/backend/executor/execExprInterp.c | 15 ++--- src/backend/utils/adt/json.c | 86 +++++++++++++------------- src/backend/utils/adt/jsonb.c | 88 +++++++++++++-------------- src/include/executor/execExpr.h | 9 +-- src/include/utils/json.h | 15 +++-- src/include/utils/jsonb.h | 15 +++-- 7 files changed, 129 insertions(+), 127 deletions(-) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 871fd17e1ca..766cd9448f6 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2399,15 +2399,12 @@ ExecInitExprRec(Expr *node, ExprState *state, jcstate->constructor = ctor; jcstate->arg_values = palloc_array(Datum, nargs); jcstate->arg_nulls = palloc_array(bool, nargs); - jcstate->arg_types = palloc_array(Oid, nargs); jcstate->nargs = nargs; foreach(lc, args) { Expr *arg = (Expr *) lfirst(lc); - jcstate->arg_types[argno] = exprType((Node *) arg); - if (IsA(arg, Const)) { /* Don't evaluate const arguments every round */ @@ -2425,25 +2422,28 @@ ExecInitExprRec(Expr *node, ExprState *state, argno++; } - /* prepare type cache for datum_to_json[b]() */ - if (ctor->type == JSCTOR_JSON_SCALAR) + /* + * Prepare type cache for json_build_*_worker and + * datum_to_json[b], which are used for SCALAR, OBJECT, + * and ARRAY constructors. + */ + if (ctor->type != JSCTOR_JSON_PARSE) { bool is_jsonb = ctor->returning->format->format_type == JS_FORMAT_JSONB; - jcstate->arg_type_cache = - palloc(sizeof(*jcstate->arg_type_cache) * nargs); + jcstate->arg_categories = + palloc_array(JsonTypeCategory, nargs); + jcstate->arg_outflinfos = + palloc0_array(FmgrInfo, nargs); for (int i = 0; i < nargs; i++) { - JsonTypeCategory category; - Oid typid = jcstate->arg_types[i]; - - json_categorize_type(typid, is_jsonb, - &category, - &jcstate->arg_type_cache[i].outflinfo); + Node *arg = (Node *) list_nth(args, i); - jcstate->arg_type_cache[i].category = (int) category; + json_categorize_type(exprType(arg), is_jsonb, + &jcstate->arg_categories[i], + &jcstate->arg_outflinfos[i]); } } diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 1ebfb190e67..a57be73be47 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4745,7 +4745,8 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, json_build_array_worker) (jcstate->nargs, jcstate->arg_values, jcstate->arg_nulls, - jcstate->arg_types, + jcstate->arg_categories, + jcstate->arg_outflinfos, jcstate->constructor->absent_on_null); else if (ctor->type == JSCTOR_JSON_OBJECT) res = (is_jsonb ? @@ -4753,7 +4754,8 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, json_build_object_worker) (jcstate->nargs, jcstate->arg_values, jcstate->arg_nulls, - jcstate->arg_types, + jcstate->arg_categories, + jcstate->arg_outflinfos, jcstate->constructor->absent_on_null, jcstate->constructor->unique); else if (ctor->type == JSCTOR_JSON_SCALAR) @@ -4766,14 +4768,13 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, else { Datum value = jcstate->arg_values[0]; - FmgrInfo *outflinfo = &jcstate->arg_type_cache[0].outflinfo; - JsonTypeCategory category = (JsonTypeCategory) - jcstate->arg_type_cache[0].category; if (is_jsonb) - res = datum_to_jsonb(value, category, outflinfo); + res = datum_to_jsonb(value, jcstate->arg_categories[0], + &jcstate->arg_outflinfos[0]); else - res = datum_to_json(value, category, outflinfo); + res = datum_to_json(value, jcstate->arg_categories[0], + &jcstate->arg_outflinfos[0]); } } else if (ctor->type == JSCTOR_JSON_PARSE) diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 788237f6b48..24b41f0a376 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -95,8 +95,6 @@ static void array_to_json_internal(Datum array, StringInfo result, static void datum_to_json_internal(Datum val, bool is_null, StringInfo result, JsonTypeCategory tcategory, FmgrInfo *outflinfo, bool key_scalar); -static void add_json(Datum val, bool is_null, StringInfo result, - Oid val_type, bool key_scalar); static text *catenate_stringinfo_string(StringInfo buffer, const char *addon); /* @@ -592,35 +590,6 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) ReleaseTupleDesc(tupdesc); } -/* - * Append JSON text for "val" to "result". - * - * This is just a thin wrapper around datum_to_json. If the same type will be - * printed many times, avoid using this; better to do the json_categorize_type - * lookups only once. - */ -static void -add_json(Datum val, bool is_null, StringInfo result, - Oid val_type, bool key_scalar) -{ - JsonTypeCategory tcategory; - FmgrInfo outflinfo; - - if (val_type == InvalidOid) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not determine input data type"))); - - if (is_null) - tcategory = JSONTYPE_NULL; - else - json_categorize_type(val_type, false, - &tcategory, &outflinfo); - - datum_to_json_internal(val, is_null, result, tcategory, &outflinfo, - key_scalar); -} - /* * SQL function array_to_json(row) */ @@ -1200,7 +1169,9 @@ catenate_stringinfo_string(StringInfo buffer, const char *addon) } Datum -json_build_object_worker(int nargs, const Datum *args, const bool *nulls, const Oid *types, +json_build_object_worker(int nargs, const Datum *args, const bool *nulls, + const JsonTypeCategory *categories, + FmgrInfo *outflinfos, bool absent_on_null, bool unique_keys) { int i; @@ -1256,7 +1227,8 @@ json_build_object_worker(int nargs, const Datum *args, const bool *nulls, const /* save key offset before appending it */ key_offset = out->len; - add_json(args[i], false, out, types[i], true); + datum_to_json_internal(args[i], false, out, + categories[i], &outflinfos[i], true); if (unique_keys) { @@ -1282,7 +1254,9 @@ json_build_object_worker(int nargs, const Datum *args, const bool *nulls, const appendStringInfoString(result, " : "); /* process value */ - add_json(args[i + 1], nulls[i + 1], result, types[i + 1], false); + datum_to_json_internal(args[i + 1], nulls[i + 1], result, + categories[i + 1], &outflinfos[i + 1], + false); } appendStringInfoChar(result, '}'); @@ -1299,15 +1273,26 @@ json_build_object(PG_FUNCTION_ARGS) Datum *args; bool *nulls; Oid *types; + int nargs; + JsonTypeCategory *categories; + FmgrInfo *outflinfos; /* build argument values to build the object */ - int nargs = extract_variadic_args(fcinfo, 0, true, - &args, &types, &nulls); + nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); - PG_RETURN_DATUM(json_build_object_worker(nargs, args, nulls, types, false, false)); + categories = palloc_array(JsonTypeCategory, nargs); + outflinfos = palloc0_array(FmgrInfo, nargs); + for (int i = 0; i < nargs; i++) + json_categorize_type(types[i], false, + &categories[i], &outflinfos[i]); + + PG_RETURN_DATUM(json_build_object_worker(nargs, args, nulls, + categories, outflinfos, + false, false)); } /* @@ -1320,8 +1305,9 @@ json_build_object_noargs(PG_FUNCTION_ARGS) } Datum -json_build_array_worker(int nargs, const Datum *args, const bool *nulls, const Oid *types, - bool absent_on_null) +json_build_array_worker(int nargs, const Datum *args, const bool *nulls, + const JsonTypeCategory *categories, + FmgrInfo *outflinfos, bool absent_on_null) { int i; const char *sep = ""; @@ -1338,7 +1324,8 @@ json_build_array_worker(int nargs, const Datum *args, const bool *nulls, const O appendStringInfoString(&result, sep); sep = ", "; - add_json(args[i], nulls[i], &result, types[i], false); + datum_to_json_internal(args[i], nulls[i], &result, + categories[i], &outflinfos[i], false); } appendStringInfoChar(&result, ']'); @@ -1355,15 +1342,26 @@ json_build_array(PG_FUNCTION_ARGS) Datum *args; bool *nulls; Oid *types; + int nargs; + JsonTypeCategory *categories; + FmgrInfo *outflinfos; - /* build argument values to build the object */ - int nargs = extract_variadic_args(fcinfo, 0, true, - &args, &types, &nulls); + /* build argument values to build the array */ + nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); - PG_RETURN_DATUM(json_build_array_worker(nargs, args, nulls, types, false)); + categories = palloc_array(JsonTypeCategory, nargs); + outflinfos = palloc0_array(FmgrInfo, nargs); + for (int i = 0; i < nargs; i++) + json_categorize_type(types[i], false, + &categories[i], &outflinfos[i]); + + PG_RETURN_DATUM(json_build_array_worker(nargs, args, nulls, + categories, outflinfos, + false)); } /* diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 94fc3d4ecfb..56e94acd2a8 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -52,8 +52,6 @@ static void array_to_jsonb_internal(Datum array, JsonbInState *result); static void datum_to_jsonb_internal(Datum val, bool is_null, JsonbInState *result, JsonTypeCategory tcategory, FmgrInfo *outflinfo, bool key_scalar); -static void add_jsonb(Datum val, bool is_null, JsonbInState *result, - Oid val_type, bool key_scalar); static char *JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent); static void add_indent(StringInfo out, bool indent, int level); @@ -1041,37 +1039,6 @@ composite_to_jsonb(Datum composite, JsonbInState *result) ReleaseTupleDesc(tupdesc); } -/* - * Append JSON text for "val" to "result". - * - * This is just a thin wrapper around datum_to_jsonb. If the same type will be - * printed many times, avoid using this; better to do the json_categorize_type - * lookups only once. - */ - -static void -add_jsonb(Datum val, bool is_null, JsonbInState *result, - Oid val_type, bool key_scalar) -{ - JsonTypeCategory tcategory; - FmgrInfo outflinfo; - - if (val_type == InvalidOid) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not determine input data type"))); - - if (is_null) - tcategory = JSONTYPE_NULL; - else - json_categorize_type(val_type, true, - &tcategory, &outflinfo); - - datum_to_jsonb_internal(val, is_null, result, tcategory, &outflinfo, - key_scalar); -} - - /* * Is the given type immutable when coming out of a JSONB context? */ @@ -1129,7 +1096,9 @@ datum_to_jsonb(Datum val, JsonTypeCategory tcategory, FmgrInfo *outflinfo) } Datum -jsonb_build_object_worker(int nargs, const Datum *args, const bool *nulls, const Oid *types, +jsonb_build_object_worker(int nargs, const Datum *args, const bool *nulls, + const JsonTypeCategory *categories, + FmgrInfo *outflinfos, bool absent_on_null, bool unique_keys) { int i; @@ -1166,10 +1135,13 @@ jsonb_build_object_worker(int nargs, const Datum *args, const bool *nulls, const if (skip && !unique_keys) continue; - add_jsonb(args[i], false, &result, types[i], true); + datum_to_jsonb_internal(args[i], false, &result, + categories[i], &outflinfos[i], true); /* process value */ - add_jsonb(args[i + 1], nulls[i + 1], &result, types[i + 1], false); + datum_to_jsonb_internal(args[i + 1], nulls[i + 1], &result, + categories[i + 1], &outflinfos[i + 1], + false); } pushJsonbValue(&result, WJB_END_OBJECT, NULL); @@ -1186,15 +1158,26 @@ jsonb_build_object(PG_FUNCTION_ARGS) Datum *args; bool *nulls; Oid *types; + int nargs; + JsonTypeCategory *categories; + FmgrInfo *outflinfos; /* build argument values to build the object */ - int nargs = extract_variadic_args(fcinfo, 0, true, - &args, &types, &nulls); + nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); - PG_RETURN_DATUM(jsonb_build_object_worker(nargs, args, nulls, types, false, false)); + categories = palloc_array(JsonTypeCategory, nargs); + outflinfos = palloc0_array(FmgrInfo, nargs); + for (int i = 0; i < nargs; i++) + json_categorize_type(types[i], true, + &categories[i], &outflinfos[i]); + + PG_RETURN_DATUM(jsonb_build_object_worker(nargs, args, nulls, + categories, outflinfos, + false, false)); } /* @@ -1214,8 +1197,9 @@ jsonb_build_object_noargs(PG_FUNCTION_ARGS) } Datum -jsonb_build_array_worker(int nargs, const Datum *args, const bool *nulls, const Oid *types, - bool absent_on_null) +jsonb_build_array_worker(int nargs, const Datum *args, const bool *nulls, + const JsonTypeCategory *categories, + FmgrInfo *outflinfos, bool absent_on_null) { int i; JsonbInState result; @@ -1229,7 +1213,8 @@ jsonb_build_array_worker(int nargs, const Datum *args, const bool *nulls, const if (absent_on_null && nulls[i]) continue; - add_jsonb(args[i], nulls[i], &result, types[i], false); + datum_to_jsonb_internal(args[i], nulls[i], &result, + categories[i], &outflinfos[i], false); } pushJsonbValue(&result, WJB_END_ARRAY, NULL); @@ -1246,15 +1231,26 @@ jsonb_build_array(PG_FUNCTION_ARGS) Datum *args; bool *nulls; Oid *types; + int nargs; + JsonTypeCategory *categories; + FmgrInfo *outflinfos; - /* build argument values to build the object */ - int nargs = extract_variadic_args(fcinfo, 0, true, - &args, &types, &nulls); + /* build argument values to build the array */ + nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); - PG_RETURN_DATUM(jsonb_build_array_worker(nargs, args, nulls, types, false)); + categories = palloc_array(JsonTypeCategory, nargs); + outflinfos = palloc0_array(FmgrInfo, nargs); + for (int i = 0; i < nargs; i++) + json_categorize_type(types[i], true, + &categories[i], &outflinfos[i]); + + PG_RETURN_DATUM(jsonb_build_array_worker(nargs, args, nulls, + categories, outflinfos, + false)); } diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 8650ff57952..168a90876fd 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -17,6 +17,7 @@ #include "executor/nodeAgg.h" #include "nodes/execnodes.h" #include "nodes/miscnodes.h" +#include "utils/jsontypes.h" /* forward references to avoid circularity */ struct ExprEvalStep; @@ -827,12 +828,8 @@ typedef struct JsonConstructorExprState JsonConstructorExpr *constructor; Datum *arg_values; bool *arg_nulls; - Oid *arg_types; - struct - { - int category; - FmgrInfo outflinfo; - } *arg_type_cache; /* cache for datum_to_json[b]() */ + JsonTypeCategory *arg_categories; + FmgrInfo *arg_outflinfos; int nargs; } JsonConstructorExprState; diff --git a/src/include/utils/json.h b/src/include/utils/json.h index 31f88fbbff6..3f9c5bd7ea3 100644 --- a/src/include/utils/json.h +++ b/src/include/utils/json.h @@ -28,11 +28,16 @@ extern void escape_json_text(StringInfo buf, const text *txt); extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp); extern bool to_json_is_immutable(Oid typoid); -extern Datum json_build_object_worker(int nargs, const Datum *args, const bool *nulls, - const Oid *types, bool absent_on_null, - bool unique_keys); -extern Datum json_build_array_worker(int nargs, const Datum *args, const bool *nulls, - const Oid *types, bool absent_on_null); +extern Datum json_build_object_worker(int nargs, const Datum *args, + const bool *nulls, + const JsonTypeCategory *categories, + FmgrInfo *outflinfos, + bool absent_on_null, bool unique_keys); +extern Datum json_build_array_worker(int nargs, const Datum *args, + const bool *nulls, + const JsonTypeCategory *categories, + FmgrInfo *outflinfos, + bool absent_on_null); extern bool json_validate(text *json, bool check_unique_keys, bool throw_error); #endif /* JSON_H */ diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 67e4675a908..33f0e716420 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -461,10 +461,15 @@ extern Datum jsonb_get_element(Jsonb *jb, const Datum *path, int npath, extern Datum datum_to_jsonb(Datum val, JsonTypeCategory tcategory, FmgrInfo *outflinfo); extern bool to_jsonb_is_immutable(Oid typoid); -extern Datum jsonb_build_object_worker(int nargs, const Datum *args, const bool *nulls, - const Oid *types, bool absent_on_null, - bool unique_keys); -extern Datum jsonb_build_array_worker(int nargs, const Datum *args, const bool *nulls, - const Oid *types, bool absent_on_null); +extern Datum jsonb_build_object_worker(int nargs, const Datum *args, + const bool *nulls, + const JsonTypeCategory *categories, + FmgrInfo *outflinfos, + bool absent_on_null, bool unique_keys); +extern Datum jsonb_build_array_worker(int nargs, const Datum *args, + const bool *nulls, + const JsonTypeCategory *categories, + FmgrInfo *outflinfos, + bool absent_on_null); #endif /* __JSONB_H__ */ -- 2.50.1 (Apple Git-155)