From b7b25c5f168a9e79aad5897286f98b722b61cdbe Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
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)

