From 3c152a27b06313fe27bd47079658f928e291986b Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Thu, 20 Jul 2023 16:19:56 +0900 Subject: [PATCH] Unify JSON categorize type API and export for external use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This essentially removes the JsonbTypeCategory enum and jsonb_categorize_type() and integrates any jsonb-specific logic that was in jsonb_categorize_type() into json_categorize_type(), now moved to jsonfuncs.c. The remaining JsonTypeCategory enum and json_categorize_type() cover the needs of the callers in both json.c and jsonb.c. json_categorize_type() has grown a new parameter named is_jsonb for callers to engage the jsonb-specific behavior of json_categorize_type(). One notable change in the now exported API of json_categorize_type() is that it now always returns *outfuncoid even though a caller may have no need currently to see one. This is in preparation of later commits to implement additional SQL/JSON functions. Co-authored-by: Álvaro Herrera Reviewed-by: Álvaro Herrera Discussion: https://postgr.es/m/CA+HiwqE4XTdfb1nW=Ojoy_tQSRhYt-q_kb6i5d4xcKyrLC1Nbg@mail.gmail.com --- src/backend/utils/adt/json.c | 137 ++--------------- src/backend/utils/adt/jsonb.c | 245 +++++++----------------------- src/backend/utils/adt/jsonfuncs.c | 111 ++++++++++++++ src/include/utils/jsonfuncs.h | 20 +++ src/tools/pgindent/typedefs.list | 1 - 5 files changed, 199 insertions(+), 315 deletions(-) diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 49080e5fbf..f6bef9c148 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -19,7 +19,6 @@ #include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" -#include "parser/parse_coerce.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/date.h" @@ -29,21 +28,6 @@ #include "utils/lsyscache.h" #include "utils/typcache.h" -typedef enum /* type categories for datum_to_json */ -{ - JSONTYPE_NULL, /* null, so we didn't bother to identify */ - JSONTYPE_BOOL, /* boolean (built-in types only) */ - JSONTYPE_NUMERIC, /* numeric (ditto) */ - JSONTYPE_DATE, /* we use special formatting for datetimes */ - JSONTYPE_TIMESTAMP, - JSONTYPE_TIMESTAMPTZ, - JSONTYPE_JSON, /* JSON itself (and JSONB) */ - JSONTYPE_ARRAY, /* array */ - JSONTYPE_COMPOSITE, /* composite */ - JSONTYPE_CAST, /* something with an explicit cast to JSON */ - JSONTYPE_OTHER /* all else */ -} JsonTypeCategory; - /* * Support for fast key uniqueness checking. @@ -107,9 +91,6 @@ static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, bool use_line_feeds); static void array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds); -static void json_categorize_type(Oid typoid, - JsonTypeCategory *tcategory, - Oid *outfuncoid); static void datum_to_json(Datum val, bool is_null, StringInfo result, JsonTypeCategory tcategory, Oid outfuncoid, bool key_scalar); @@ -182,106 +163,6 @@ json_recv(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text_with_len(str, nbytes)); } -/* - * Determine how we want to print values of a given type in datum_to_json. - * - * Given the datatype OID, return its JsonTypeCategory, as well as the type's - * output function OID. If the returned category is JSONTYPE_CAST, we - * return the OID of the type->JSON cast function instead. - */ -static void -json_categorize_type(Oid typoid, - JsonTypeCategory *tcategory, - Oid *outfuncoid) -{ - bool typisvarlena; - - /* Look through any domain */ - typoid = getBaseType(typoid); - - *outfuncoid = InvalidOid; - - /* - * We need to get the output function for everything except date and - * timestamp types, array and composite types, booleans, and non-builtin - * types where there's a cast to json. - */ - - switch (typoid) - { - case BOOLOID: - *tcategory = JSONTYPE_BOOL; - break; - - case INT2OID: - case INT4OID: - case INT8OID: - case FLOAT4OID: - case FLOAT8OID: - case NUMERICOID: - getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); - *tcategory = JSONTYPE_NUMERIC; - break; - - case DATEOID: - *tcategory = JSONTYPE_DATE; - break; - - case TIMESTAMPOID: - *tcategory = JSONTYPE_TIMESTAMP; - break; - - case TIMESTAMPTZOID: - *tcategory = JSONTYPE_TIMESTAMPTZ; - break; - - case JSONOID: - case JSONBOID: - getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); - *tcategory = JSONTYPE_JSON; - break; - - default: - /* Check for arrays and composites */ - if (OidIsValid(get_element_type(typoid)) || typoid == ANYARRAYOID - || typoid == ANYCOMPATIBLEARRAYOID || typoid == RECORDARRAYOID) - *tcategory = JSONTYPE_ARRAY; - else if (type_is_rowtype(typoid)) /* includes RECORDOID */ - *tcategory = JSONTYPE_COMPOSITE; - else - { - /* It's probably the general case ... */ - *tcategory = JSONTYPE_OTHER; - /* but let's look for a cast to json, if it's not built-in */ - if (typoid >= FirstNormalObjectId) - { - Oid castfunc; - CoercionPathType ctype; - - ctype = find_coercion_pathway(JSONOID, typoid, - COERCION_EXPLICIT, - &castfunc); - if (ctype == COERCION_PATH_FUNC && OidIsValid(castfunc)) - { - *tcategory = JSONTYPE_CAST; - *outfuncoid = castfunc; - } - else - { - /* non builtin type with no cast */ - getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); - } - } - else - { - /* any other builtin type */ - getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); - } - } - break; - } -} - /* * Turn a Datum into JSON text, appending the string to "result". * @@ -591,7 +472,7 @@ array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds) get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign); - json_categorize_type(element_type, + json_categorize_type(element_type, false, &tcategory, &outfuncoid); deconstruct_array(v, element_type, typlen, typbyval, @@ -665,7 +546,8 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) outfuncoid = InvalidOid; } else - json_categorize_type(att->atttypid, &tcategory, &outfuncoid); + json_categorize_type(att->atttypid, false, &tcategory, + &outfuncoid); datum_to_json(val, isnull, result, tcategory, outfuncoid, false); } @@ -699,7 +581,7 @@ add_json(Datum val, bool is_null, StringInfo result, outfuncoid = InvalidOid; } else - json_categorize_type(val_type, + json_categorize_type(val_type, false, &tcategory, &outfuncoid); datum_to_json(val, is_null, result, tcategory, outfuncoid, key_scalar); @@ -784,12 +666,13 @@ to_json_is_immutable(Oid typoid) JsonTypeCategory tcategory; Oid outfuncoid; - json_categorize_type(typoid, &tcategory, &outfuncoid); + json_categorize_type(typoid, false, &tcategory, &outfuncoid); switch (tcategory) { case JSONTYPE_BOOL: case JSONTYPE_JSON: + case JSONTYPE_JSONB: case JSONTYPE_NULL: return true; @@ -830,7 +713,7 @@ to_json(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); - json_categorize_type(val_type, + json_categorize_type(val_type, false, &tcategory, &outfuncoid); result = makeStringInfo(); @@ -880,7 +763,7 @@ json_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null) MemoryContextSwitchTo(oldcontext); appendStringInfoChar(state->str, '['); - json_categorize_type(arg_type, &state->val_category, + json_categorize_type(arg_type, false, &state->val_category, &state->val_output_func); } else @@ -1112,7 +995,7 @@ json_object_agg_transfn_worker(FunctionCallInfo fcinfo, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine data type for argument %d", 1))); - json_categorize_type(arg_type, &state->key_category, + json_categorize_type(arg_type, false, &state->key_category, &state->key_output_func); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 2); @@ -1122,7 +1005,7 @@ json_object_agg_transfn_worker(FunctionCallInfo fcinfo, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine data type for argument %d", 2))); - json_categorize_type(arg_type, &state->val_category, + json_categorize_type(arg_type, false, &state->val_category, &state->val_output_func); appendStringInfoString(state->str, "{ "); diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index cf43c3f2de..fc64f56868 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -19,7 +19,6 @@ #include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" -#include "parser/parse_coerce.h" #include "utils/builtins.h" #include "utils/date.h" #include "utils/datetime.h" @@ -37,29 +36,12 @@ typedef struct JsonbInState Node *escontext; } JsonbInState; -/* unlike with json categories, we need to treat json and jsonb differently */ -typedef enum /* type categories for datum_to_jsonb */ -{ - JSONBTYPE_NULL, /* null, so we didn't bother to identify */ - JSONBTYPE_BOOL, /* boolean (built-in types only) */ - JSONBTYPE_NUMERIC, /* numeric (ditto) */ - JSONBTYPE_DATE, /* we use special formatting for datetimes */ - JSONBTYPE_TIMESTAMP, /* we use special formatting for timestamp */ - JSONBTYPE_TIMESTAMPTZ, /* ... and timestamptz */ - JSONBTYPE_JSON, /* JSON */ - JSONBTYPE_JSONB, /* JSONB */ - JSONBTYPE_ARRAY, /* array */ - JSONBTYPE_COMPOSITE, /* composite */ - JSONBTYPE_JSONCAST, /* something with an explicit cast to JSON */ - JSONBTYPE_OTHER /* all else */ -} JsonbTypeCategory; - typedef struct JsonbAggState { JsonbInState *res; - JsonbTypeCategory key_category; + JsonTypeCategory key_category; Oid key_output_func; - JsonbTypeCategory val_category; + JsonTypeCategory val_category; Oid val_output_func; } JsonbAggState; @@ -72,19 +54,13 @@ static JsonParseErrorType jsonb_in_array_end(void *pstate); static JsonParseErrorType jsonb_in_object_field_start(void *pstate, char *fname, bool isnull); static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal); static JsonParseErrorType jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype); -static void jsonb_categorize_type(Oid typoid, - JsonbTypeCategory *tcategory, - Oid *outfuncoid); static void composite_to_jsonb(Datum composite, JsonbInState *result); static void array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims, Datum *vals, bool *nulls, int *valcount, - JsonbTypeCategory tcategory, Oid outfuncoid); + JsonTypeCategory tcategory, Oid outfuncoid); static void array_to_jsonb_internal(Datum array, JsonbInState *result); -static void jsonb_categorize_type(Oid typoid, - JsonbTypeCategory *tcategory, - Oid *outfuncoid); static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, - JsonbTypeCategory tcategory, Oid outfuncoid, + JsonTypeCategory tcategory, Oid outfuncoid, bool key_scalar); static void add_jsonb(Datum val, bool is_null, JsonbInState *result, Oid val_type, bool key_scalar); @@ -633,112 +609,6 @@ add_indent(StringInfo out, bool indent, int level) } -/* - * Determine how we want to render values of a given type in datum_to_jsonb. - * - * Given the datatype OID, return its JsonbTypeCategory, as well as the type's - * output function OID. If the returned category is JSONBTYPE_JSONCAST, - * we return the OID of the relevant cast function instead. - */ -static void -jsonb_categorize_type(Oid typoid, - JsonbTypeCategory *tcategory, - Oid *outfuncoid) -{ - bool typisvarlena; - - /* Look through any domain */ - typoid = getBaseType(typoid); - - *outfuncoid = InvalidOid; - - /* - * We need to get the output function for everything except date and - * timestamp types, booleans, array and composite types, json and jsonb, - * and non-builtin types where there's a cast to json. In this last case - * we return the oid of the cast function instead. - */ - - switch (typoid) - { - case BOOLOID: - *tcategory = JSONBTYPE_BOOL; - break; - - case INT2OID: - case INT4OID: - case INT8OID: - case FLOAT4OID: - case FLOAT8OID: - case NUMERICOID: - getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); - *tcategory = JSONBTYPE_NUMERIC; - break; - - case DATEOID: - *tcategory = JSONBTYPE_DATE; - break; - - case TIMESTAMPOID: - *tcategory = JSONBTYPE_TIMESTAMP; - break; - - case TIMESTAMPTZOID: - *tcategory = JSONBTYPE_TIMESTAMPTZ; - break; - - case JSONBOID: - *tcategory = JSONBTYPE_JSONB; - break; - - case JSONOID: - *tcategory = JSONBTYPE_JSON; - break; - - default: - /* Check for arrays and composites */ - if (OidIsValid(get_element_type(typoid)) || typoid == ANYARRAYOID - || typoid == ANYCOMPATIBLEARRAYOID || typoid == RECORDARRAYOID) - *tcategory = JSONBTYPE_ARRAY; - else if (type_is_rowtype(typoid)) /* includes RECORDOID */ - *tcategory = JSONBTYPE_COMPOSITE; - else - { - /* It's probably the general case ... */ - *tcategory = JSONBTYPE_OTHER; - - /* - * but first let's look for a cast to json (note: not to - * jsonb) if it's not built-in. - */ - if (typoid >= FirstNormalObjectId) - { - Oid castfunc; - CoercionPathType ctype; - - ctype = find_coercion_pathway(JSONOID, typoid, - COERCION_EXPLICIT, &castfunc); - if (ctype == COERCION_PATH_FUNC && OidIsValid(castfunc)) - { - *tcategory = JSONBTYPE_JSONCAST; - *outfuncoid = castfunc; - } - else - { - /* not a cast type, so just get the usual output func */ - getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); - } - } - else - { - /* any other builtin type */ - getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); - } - break; - } - } -} - /* * Turn a Datum into jsonb, adding it to the result JsonbInState. * @@ -753,7 +623,7 @@ jsonb_categorize_type(Oid typoid, */ static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, - JsonbTypeCategory tcategory, Oid outfuncoid, + JsonTypeCategory tcategory, Oid outfuncoid, bool key_scalar) { char *outputstr; @@ -770,11 +640,11 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, jb.type = jbvNull; } else if (key_scalar && - (tcategory == JSONBTYPE_ARRAY || - tcategory == JSONBTYPE_COMPOSITE || - tcategory == JSONBTYPE_JSON || - tcategory == JSONBTYPE_JSONB || - tcategory == JSONBTYPE_JSONCAST)) + (tcategory == JSONTYPE_ARRAY || + tcategory == JSONTYPE_COMPOSITE || + tcategory == JSONTYPE_JSON || + tcategory == JSONTYPE_JSONB || + tcategory == JSONTYPE_JSON)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -782,18 +652,18 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, } else { - if (tcategory == JSONBTYPE_JSONCAST) + if (tcategory == JSONTYPE_CAST) val = OidFunctionCall1(outfuncoid, val); switch (tcategory) { - case JSONBTYPE_ARRAY: + case JSONTYPE_ARRAY: array_to_jsonb_internal(val, result); break; - case JSONBTYPE_COMPOSITE: + case JSONTYPE_COMPOSITE: composite_to_jsonb(val, result); break; - case JSONBTYPE_BOOL: + case JSONTYPE_BOOL: if (key_scalar) { outputstr = DatumGetBool(val) ? "true" : "false"; @@ -807,7 +677,7 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, jb.val.boolean = DatumGetBool(val); } break; - case JSONBTYPE_NUMERIC: + case JSONTYPE_NUMERIC: outputstr = OidOutputFunctionCall(outfuncoid, val); if (key_scalar) { @@ -845,26 +715,26 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, } } break; - case JSONBTYPE_DATE: + case JSONTYPE_DATE: jb.type = jbvString; jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID, NULL); jb.val.string.len = strlen(jb.val.string.val); break; - case JSONBTYPE_TIMESTAMP: + case JSONTYPE_TIMESTAMP: jb.type = jbvString; jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID, NULL); jb.val.string.len = strlen(jb.val.string.val); break; - case JSONBTYPE_TIMESTAMPTZ: + case JSONTYPE_TIMESTAMPTZ: jb.type = jbvString; jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID, NULL); jb.val.string.len = strlen(jb.val.string.val); break; - case JSONBTYPE_JSONCAST: - case JSONBTYPE_JSON: + case JSONTYPE_CAST: + case JSONTYPE_JSON: { /* parse the json right into the existing result object */ JsonLexContext *lex; @@ -887,7 +757,7 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, pg_parse_json_or_ereport(lex, &sem); } break; - case JSONBTYPE_JSONB: + case JSONTYPE_JSONB: { Jsonb *jsonb = DatumGetJsonbP(val); JsonbIterator *it; @@ -931,7 +801,7 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, /* Now insert jb into result, unless we did it recursively */ if (!is_null && !scalar_jsonb && - tcategory >= JSONBTYPE_JSON && tcategory <= JSONBTYPE_JSONCAST) + tcategory >= JSONTYPE_JSON && tcategory <= JSONTYPE_CAST) { /* work has been done recursively */ return; @@ -976,7 +846,7 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, */ static void array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims, Datum *vals, - bool *nulls, int *valcount, JsonbTypeCategory tcategory, + bool *nulls, int *valcount, JsonTypeCategory tcategory, Oid outfuncoid) { int i; @@ -1020,7 +890,7 @@ array_to_jsonb_internal(Datum array, JsonbInState *result) int16 typlen; bool typbyval; char typalign; - JsonbTypeCategory tcategory; + JsonTypeCategory tcategory; Oid outfuncoid; ndim = ARR_NDIM(v); @@ -1037,8 +907,8 @@ array_to_jsonb_internal(Datum array, JsonbInState *result) get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign); - jsonb_categorize_type(element_type, - &tcategory, &outfuncoid); + json_categorize_type(element_type, true, + &tcategory, &outfuncoid); deconstruct_array(v, element_type, typlen, typbyval, typalign, &elements, &nulls, @@ -1084,7 +954,7 @@ composite_to_jsonb(Datum composite, JsonbInState *result) Datum val; bool isnull; char *attname; - JsonbTypeCategory tcategory; + JsonTypeCategory tcategory; Oid outfuncoid; JsonbValue v; Form_pg_attribute att = TupleDescAttr(tupdesc, i); @@ -1105,11 +975,12 @@ composite_to_jsonb(Datum composite, JsonbInState *result) if (isnull) { - tcategory = JSONBTYPE_NULL; + tcategory = JSONTYPE_NULL; outfuncoid = InvalidOid; } else - jsonb_categorize_type(att->atttypid, &tcategory, &outfuncoid); + json_categorize_type(att->atttypid, true, &tcategory, + &outfuncoid); datum_to_jsonb(val, isnull, result, tcategory, outfuncoid, false); } @@ -1122,7 +993,7 @@ composite_to_jsonb(Datum composite, JsonbInState *result) * 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 jsonb_categorize_type + * printed many times, avoid using this; better to do the json_categorize_type * lookups only once. */ @@ -1130,7 +1001,7 @@ static void add_jsonb(Datum val, bool is_null, JsonbInState *result, Oid val_type, bool key_scalar) { - JsonbTypeCategory tcategory; + JsonTypeCategory tcategory; Oid outfuncoid; if (val_type == InvalidOid) @@ -1140,12 +1011,12 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result, if (is_null) { - tcategory = JSONBTYPE_NULL; + tcategory = JSONTYPE_NULL; outfuncoid = InvalidOid; } else - jsonb_categorize_type(val_type, - &tcategory, &outfuncoid); + json_categorize_type(val_type, true, + &tcategory, &outfuncoid); datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar); } @@ -1160,33 +1031,33 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result, bool to_jsonb_is_immutable(Oid typoid) { - JsonbTypeCategory tcategory; + JsonTypeCategory tcategory; Oid outfuncoid; - jsonb_categorize_type(typoid, &tcategory, &outfuncoid); + json_categorize_type(typoid, true, &tcategory, &outfuncoid); switch (tcategory) { - case JSONBTYPE_NULL: - case JSONBTYPE_BOOL: - case JSONBTYPE_JSON: - case JSONBTYPE_JSONB: + case JSONTYPE_NULL: + case JSONTYPE_BOOL: + case JSONTYPE_JSON: + case JSONTYPE_JSONB: return true; - case JSONBTYPE_DATE: - case JSONBTYPE_TIMESTAMP: - case JSONBTYPE_TIMESTAMPTZ: + case JSONTYPE_DATE: + case JSONTYPE_TIMESTAMP: + case JSONTYPE_TIMESTAMPTZ: return false; - case JSONBTYPE_ARRAY: + case JSONTYPE_ARRAY: return false; /* TODO recurse into elements */ - case JSONBTYPE_COMPOSITE: + case JSONTYPE_COMPOSITE: return false; /* TODO recurse into fields */ - case JSONBTYPE_NUMERIC: - case JSONBTYPE_JSONCAST: - case JSONBTYPE_OTHER: + case JSONTYPE_NUMERIC: + case JSONTYPE_CAST: + case JSONTYPE_OTHER: return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE; } @@ -1202,7 +1073,7 @@ to_jsonb(PG_FUNCTION_ARGS) Datum val = PG_GETARG_DATUM(0); Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); JsonbInState result; - JsonbTypeCategory tcategory; + JsonTypeCategory tcategory; Oid outfuncoid; if (val_type == InvalidOid) @@ -1210,8 +1081,8 @@ to_jsonb(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); - jsonb_categorize_type(val_type, - &tcategory, &outfuncoid); + json_categorize_type(val_type, true, + &tcategory, &outfuncoid); memset(&result, 0, sizeof(JsonbInState)); @@ -1636,8 +1507,8 @@ jsonb_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null) WJB_BEGIN_ARRAY, NULL); MemoryContextSwitchTo(oldcontext); - jsonb_categorize_type(arg_type, &state->val_category, - &state->val_output_func); + json_categorize_type(arg_type, true, &state->val_category, + &state->val_output_func); } else { @@ -1816,8 +1687,8 @@ jsonb_object_agg_transfn_worker(FunctionCallInfo fcinfo, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); - jsonb_categorize_type(arg_type, &state->key_category, - &state->key_output_func); + json_categorize_type(arg_type, true, &state->key_category, + &state->key_output_func); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 2); @@ -1826,8 +1697,8 @@ jsonb_object_agg_transfn_worker(FunctionCallInfo fcinfo, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); - jsonb_categorize_type(arg_type, &state->val_category, - &state->val_output_func); + json_categorize_type(arg_type, true, &state->val_category, + &state->val_output_func); } else { diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 70cb922e6b..a4bfa5e404 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -26,6 +26,7 @@ #include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/miscnodes.h" +#include "parser/parse_coerce.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -5685,3 +5686,113 @@ json_get_first_token(text *json, bool throw_error) return JSON_TOKEN_INVALID; /* invalid json */ } + +/* + * Determine how we want to print values of a given type in datum_to_json(b). + * + * Given the datatype OID, return its JsonTypeCategory, as well as the type's + * output function OID. If the returned category is JSONTYPE_CAST, we return + * the OID of the type->JSON cast function instead. + */ +void +json_categorize_type(Oid typoid, bool is_jsonb, + JsonTypeCategory *tcategory, Oid *outfuncoid) +{ + bool typisvarlena; + + /* Look through any domain */ + typoid = getBaseType(typoid); + + *outfuncoid = InvalidOid; + + switch (typoid) + { + case BOOLOID: + *outfuncoid = F_BOOLOUT; + *tcategory = JSONTYPE_BOOL; + break; + + case INT2OID: + case INT4OID: + case INT8OID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + *tcategory = JSONTYPE_NUMERIC; + break; + + case DATEOID: + *outfuncoid = F_DATE_OUT; + *tcategory = JSONTYPE_DATE; + break; + + case TIMESTAMPOID: + *outfuncoid = F_TIMESTAMP_OUT; + *tcategory = JSONTYPE_TIMESTAMP; + break; + + case TIMESTAMPTZOID: + *outfuncoid = F_TIMESTAMPTZ_OUT; + *tcategory = JSONTYPE_TIMESTAMPTZ; + break; + + case JSONOID: + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + *tcategory = JSONTYPE_JSON; + break; + + case JSONBOID: + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + *tcategory = is_jsonb ? JSONTYPE_JSONB : JSONTYPE_JSON; + break; + + default: + /* Check for arrays and composites */ + if (OidIsValid(get_element_type(typoid)) || typoid == ANYARRAYOID + || typoid == ANYCOMPATIBLEARRAYOID || typoid == RECORDARRAYOID) + { + *outfuncoid = F_ARRAY_OUT; + *tcategory = JSONTYPE_ARRAY; + } + else if (type_is_rowtype(typoid)) /* includes RECORDOID */ + { + *outfuncoid = F_RECORD_OUT; + *tcategory = JSONTYPE_COMPOSITE; + } + else + { + /* + * It's probably the general case. But let's look for a cast + * to json (note: not to jsonb even if is_jsonb is true), if + * it's not built-in. + */ + *tcategory = JSONTYPE_OTHER; + if (typoid >= FirstNormalObjectId) + { + Oid castfunc; + CoercionPathType ctype; + + ctype = find_coercion_pathway(JSONOID, typoid, + COERCION_EXPLICIT, + &castfunc); + if (ctype == COERCION_PATH_FUNC && OidIsValid(castfunc)) + { + *outfuncoid = castfunc; + *tcategory = JSONTYPE_CAST; + } + else + { + /* non builtin type with no cast */ + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + } + } + else + { + /* any other builtin type */ + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + } + } + break; + } +} diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h index a85203d4a4..121dfd5d24 100644 --- a/src/include/utils/jsonfuncs.h +++ b/src/include/utils/jsonfuncs.h @@ -63,4 +63,24 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state, extern text *transform_json_string_values(text *json, void *action_state, JsonTransformStringValuesAction transform_action); +/* Type categories returned by json_categorize_type */ +typedef enum +{ + JSONTYPE_NULL, /* null, so we didn't bother to identify */ + JSONTYPE_BOOL, /* boolean (built-in types only) */ + JSONTYPE_NUMERIC, /* numeric (ditto) */ + JSONTYPE_DATE, /* we use special formatting for datetimes */ + JSONTYPE_TIMESTAMP, + JSONTYPE_TIMESTAMPTZ, + JSONTYPE_JSON, /* JSON (and JSONB, if not is_jsonb) */ + JSONTYPE_JSONB, /* JSONB (if is_jsonb) */ + JSONTYPE_ARRAY, /* array */ + JSONTYPE_COMPOSITE, /* composite */ + JSONTYPE_CAST, /* something with an explicit cast to JSON */ + JSONTYPE_OTHER, /* all else */ +} JsonTypeCategory; + +extern void json_categorize_type(Oid typoid, bool is_jsonb, + JsonTypeCategory *tcategory, Oid *outfuncoid); + #endif diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index a1cf01e38e..05814136c6 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1318,7 +1318,6 @@ JsonbIteratorToken JsonbPair JsonbParseState JsonbSubWorkspace -JsonbTypeCategory JsonbValue JumbleState JunkFilter