diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index aec50cb50061a8611942b1245dbd95410fd48bd7..d22aa131c54af444a72ba6c9b54cf184bb4016af 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, '}');