From 25c933c5c9d6323271fe2fdc67b2fe748ce1bcd4 Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Fri, 9 May 2014 12:55:06 -0400 Subject: [PATCH] Get rid of bogus dependency on typcategory in to_json() and friends. These functions were relying on typcategory to identify arrays and composites, which is not reliable and not the normal way to do it. Using typcategory to identify boolean, numeric types, and json itself is also pretty questionable, though the code in those cases didn't seem to be at risk of anything worse than wrong output. Instead, use the standard lsyscache functions to identify arrays and composites, and rely on a direct check of the type OID for the other cases. In HEAD, also be sure to look through domains so that a domain is treated the same as its base type for conversions to JSON. However, this is a small behavioral change; given the lack of field complaints, we won't back-patch it. In passing, refactor so that there's only one copy of the code that decides which conversion strategy to apply, not multiple copies that could (and have) gotten out of sync. --- src/backend/utils/adt/json.c | 174 ++++++++++++++++++++++------------- 1 file changed, 108 insertions(+), 66 deletions(-) diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index aec50cb5006..d22aa131c54 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -70,6 +70,17 @@ typedef enum /* required operations on state stack */ JSON_STACKOP_POP /* pop, or expect end of input if no stack */ } JsonStackOp; +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_JSON, /* JSON itself */ + JSONTYPE_ARRAY, /* array */ + JSONTYPE_COMPOSITE, /* composite */ + JSONTYPE_OTHER /* all else */ +} JsonTypeCategory; + static void json_validate_cstring(char *input); static void json_lex(JsonLexContext *lex); static void json_lex_string(JsonLexContext *lex); @@ -82,13 +93,16 @@ static void composite_to_json(Datum composite, StringInfo result, bool use_line_feeds); static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals, bool *nulls, int *valcount, - TYPCATEGORY tcategory, Oid typoutputfunc, + JsonTypeCategory tcategory, Oid outfuncoid, bool use_line_feeds); static void array_to_json_internal(Datum array, StringInfo result, - bool use_line_feeds); + 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); -/* fake type category for JSON so we can distinguish it in datum_to_json */ -#define TYPCATEGORY_JSON 'j' /* chars to consider as part of an alphanumeric token */ #define JSON_ALPHANUMERIC_CHAR(c) \ (((c) >= 'a' && (c) <= 'z') || \ @@ -816,14 +830,67 @@ extract_mb_char(char *s) } /* - * Turn a scalar Datum into JSON, appending the string to "result". + * 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; + + /* + * We should look through domains here, but we'll wait till 9.4. + */ + + /* We'll usually need to return the type output function */ + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + + /* Check for known types */ + switch (typoid) + { + case BOOLOID: + *tcategory = JSONTYPE_BOOL; + break; + + case INT2OID: + case INT4OID: + case INT8OID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + *tcategory = JSONTYPE_NUMERIC; + break; + + case JSONOID: + *tcategory = JSONTYPE_JSON; + break; + + default: + /* Check for arrays and composites */ + if (OidIsValid(get_element_type(typoid))) + *tcategory = JSONTYPE_ARRAY; + else if (type_is_rowtype(typoid)) + *tcategory = JSONTYPE_COMPOSITE; + else + *tcategory = JSONTYPE_OTHER; + break; + } +} + +/* + * Turn a Datum into JSON text, appending the string to "result". * - * Hand off a non-scalar datum to composite_to_json or array_to_json_internal - * as appropriate. + * tcategory and outfuncoid are from a previous call to json_categorize_type, + * except that if is_null is true then they can be invalid. */ static void datum_to_json(Datum val, bool is_null, StringInfo result, - TYPCATEGORY tcategory, Oid typoutputfunc) + JsonTypeCategory tcategory, Oid outfuncoid) { char *outputstr; bool numeric_error; @@ -837,20 +904,20 @@ datum_to_json(Datum val, bool is_null, StringInfo result, switch (tcategory) { - case TYPCATEGORY_ARRAY: + case JSONTYPE_ARRAY: array_to_json_internal(val, result, false); break; - case TYPCATEGORY_COMPOSITE: + case JSONTYPE_COMPOSITE: composite_to_json(val, result, false); break; - case TYPCATEGORY_BOOLEAN: + case JSONTYPE_BOOL: if (DatumGetBool(val)) appendStringInfoString(result, "true"); else appendStringInfoString(result, "false"); break; - case TYPCATEGORY_NUMERIC: - outputstr = OidOutputFunctionCall(typoutputfunc, val); + case JSONTYPE_NUMERIC: + outputstr = OidOutputFunctionCall(outfuncoid, val); /* * Don't call escape_json here if it's a valid JSON number. @@ -863,14 +930,14 @@ datum_to_json(Datum val, bool is_null, StringInfo result, escape_json(result, outputstr); pfree(outputstr); break; - case TYPCATEGORY_JSON: + case JSONTYPE_JSON: /* JSON will already be escaped */ - outputstr = OidOutputFunctionCall(typoutputfunc, val); + outputstr = OidOutputFunctionCall(outfuncoid, val); appendStringInfoString(result, outputstr); pfree(outputstr); break; default: - outputstr = OidOutputFunctionCall(typoutputfunc, val); + outputstr = OidOutputFunctionCall(outfuncoid, val); escape_json(result, outputstr); pfree(outputstr); break; @@ -884,8 +951,8 @@ datum_to_json(Datum val, bool is_null, StringInfo result, */ static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals, - bool *nulls, int *valcount, TYPCATEGORY tcategory, - Oid typoutputfunc, bool use_line_feeds) + bool *nulls, int *valcount, JsonTypeCategory tcategory, + Oid outfuncoid, bool use_line_feeds) { int i; const char *sep; @@ -904,7 +971,7 @@ array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals, if (dim + 1 == ndims) { datum_to_json(vals[*valcount], nulls[*valcount], result, tcategory, - typoutputfunc); + outfuncoid); (*valcount)++; } else @@ -914,7 +981,7 @@ array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals, * we'll say no. */ array_dim_to_json(result, dim + 1, ndims, dims, vals, nulls, - valcount, tcategory, typoutputfunc, false); + valcount, tcategory, outfuncoid, false); } } @@ -937,11 +1004,9 @@ array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds) bool *nulls; int16 typlen; bool typbyval; - char typalign, - typdelim; - Oid typioparam; - Oid typoutputfunc; - TYPCATEGORY tcategory; + char typalign; + JsonTypeCategory tcategory; + Oid outfuncoid; ndim = ARR_NDIM(v); dim = ARR_DIMS(v); @@ -953,23 +1018,18 @@ array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds) return; } - get_type_io_data(element_type, IOFunc_output, - &typlen, &typbyval, &typalign, - &typdelim, &typioparam, &typoutputfunc); + get_typlenbyvalalign(element_type, + &typlen, &typbyval, &typalign); + + json_categorize_type(element_type, + &tcategory, &outfuncoid); deconstruct_array(v, element_type, typlen, typbyval, typalign, &elements, &nulls, &nitems); - if (element_type == RECORDOID) - tcategory = TYPCATEGORY_COMPOSITE; - else if (element_type == JSONOID) - tcategory = TYPCATEGORY_JSON; - else - tcategory = TypeCategory(element_type); - array_dim_to_json(result, 0, ndim, dim, elements, nulls, &count, tcategory, - typoutputfunc, use_line_feeds); + outfuncoid, use_line_feeds); pfree(elements); pfree(nulls); @@ -1009,13 +1069,11 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) for (i = 0; i < tupdesc->natts; i++) { - Datum val, - origval; + Datum val; bool isnull; char *attname; - TYPCATEGORY tcategory; - Oid typoutput; - bool typisvarlena; + JsonTypeCategory tcategory; + Oid outfuncoid; if (tupdesc->attrs[i]->attisdropped) continue; @@ -1028,34 +1086,18 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) escape_json(result, attname); appendStringInfoChar(result, ':'); - origval = heap_getattr(tuple, i + 1, tupdesc, &isnull); - - if (tupdesc->attrs[i]->atttypid == RECORDARRAYOID) - tcategory = TYPCATEGORY_ARRAY; - else if (tupdesc->attrs[i]->atttypid == RECORDOID) - tcategory = TYPCATEGORY_COMPOSITE; - else if (tupdesc->attrs[i]->atttypid == JSONOID) - tcategory = TYPCATEGORY_JSON; - else - tcategory = TypeCategory(tupdesc->attrs[i]->atttypid); + val = heap_getattr(tuple, i + 1, tupdesc, &isnull); - getTypeOutputInfo(tupdesc->attrs[i]->atttypid, - &typoutput, &typisvarlena); - - /* - * If we have a toasted datum, forcibly detoast it here to avoid - * memory leakage inside the type's output routine. - */ - if (typisvarlena && !isnull) - val = PointerGetDatum(PG_DETOAST_DATUM(origval)); + if (isnull) + { + tcategory = JSONTYPE_NULL; + outfuncoid = InvalidOid; + } else - val = origval; - - datum_to_json(val, isnull, result, tcategory, typoutput); + json_categorize_type(tupdesc->attrs[i]->atttypid, + &tcategory, &outfuncoid); - /* Clean up detoasted copy, if any */ - if (val != origval) - pfree(DatumGetPointer(val)); + datum_to_json(val, isnull, result, tcategory, outfuncoid); } appendStringInfoChar(result, '}'); -- GitLab