diff options
Diffstat (limited to 'src/backend/utils/adt/json.c')
-rw-r--r-- | src/backend/utils/adt/json.c | 410 |
1 files changed, 181 insertions, 229 deletions
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 22ef4026b7e..a7364f30f84 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -48,6 +48,18 @@ typedef enum /* contexts of JSON parser */ JSON_PARSE_END /* saw the end of a document, expect nothing */ } JsonParseContext; +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 (and JSONB) */ + JSONTYPE_ARRAY, /* array */ + JSONTYPE_COMPOSITE, /* composite */ + JSONTYPE_CAST, /* something with an explicit cast to JSON */ + JSONTYPE_OTHER /* all else */ +} JsonTypeCategory; + static inline void json_lex(JsonLexContext *lex); static inline void json_lex_string(JsonLexContext *lex); static inline void json_lex_number(JsonLexContext *lex, char *s, bool *num_err); @@ -64,12 +76,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); +static void json_categorize_type(Oid typoid, + JsonTypeCategory *tcategory, + Oid *outfuncoid); static void datum_to_json(Datum val, bool is_null, StringInfo result, - TYPCATEGORY tcategory, Oid typoutputfunc, bool key_scalar); + JsonTypeCategory tcategory, Oid outfuncoid, + bool key_scalar); static void add_json(Datum val, bool is_null, StringInfo result, Oid val_type, bool key_scalar); @@ -143,14 +159,6 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token) report_parse_error(ctx, lex);; } -/* - * All the defined type categories are upper case , so use lower case here - * so we avoid any possible clash. - */ -/* fake type category for JSON so we can distinguish it in datum_to_json */ -#define TYPCATEGORY_JSON 'j' -/* fake category for types that have a cast to json */ -#define TYPCATEGORY_JSON_CAST 'c' /* chars to consider as part of an alphanumeric token */ #define JSON_ALPHANUMERIC_CHAR(c) \ (((c) >= 'a' && (c) <= 'z') || \ @@ -1219,14 +1227,95 @@ 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. * - * Hand off a non-scalar datum to composite_to_json or array_to_json_internal - * as appropriate. + * 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); + + /* 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: + case JSONBOID: + *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 + { + /* 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) + { + HeapTuple tuple; + + tuple = SearchSysCache2(CASTSOURCETARGET, + ObjectIdGetDatum(typoid), + ObjectIdGetDatum(JSONOID)); + if (HeapTupleIsValid(tuple)) + { + Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple); + + if (castForm->castmethod == COERCION_METHOD_FUNCTION) + { + *tcategory = JSONTYPE_CAST; + *outfuncoid = castForm->castfunc; + } + + ReleaseSysCache(tuple); + } + } + } + break; + } +} + +/* + * Turn a Datum into JSON text, appending the string to "result". + * + * tcategory and outfuncoid are from a previous call to json_categorize_type, + * except that if is_null is true then they can be invalid. + * + * If key_scalar is true, the value is being printed as a key, so insist + * it's of an acceptable type, and force it to be quoted. */ static void datum_to_json(Datum val, bool is_null, StringInfo result, - TYPCATEGORY tcategory, Oid typoutputfunc, bool key_scalar) + JsonTypeCategory tcategory, Oid outfuncoid, + bool key_scalar) { char *outputstr; text *jsontext; @@ -1239,22 +1328,32 @@ datum_to_json(Datum val, bool is_null, StringInfo result, return; } + if (key_scalar && + (tcategory == JSONTYPE_ARRAY || + tcategory == JSONTYPE_COMPOSITE || + tcategory == JSONTYPE_JSON || + tcategory == JSONTYPE_CAST)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key value must be scalar, not array, composite or json"))); + 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: - if (!key_scalar) - appendStringInfoString(result, DatumGetBool(val) ? "true" : "false"); + case JSONTYPE_BOOL: + outputstr = DatumGetBool(val) ? "true" : "false"; + if (key_scalar) + escape_json(result, outputstr); else - escape_json(result, DatumGetBool(val) ? "true" : "false"); + appendStringInfoString(result, outputstr); break; - case TYPCATEGORY_NUMERIC: - outputstr = OidOutputFunctionCall(typoutputfunc, val); + case JSONTYPE_NUMERIC: + outputstr = OidOutputFunctionCall(outfuncoid, val); if (key_scalar) { /* always quote keys */ @@ -1276,21 +1375,22 @@ datum_to_json(Datum val, bool is_null, StringInfo result, } pfree(outputstr); break; - case TYPCATEGORY_JSON: - /* JSON and JSONB will already be escaped */ - outputstr = OidOutputFunctionCall(typoutputfunc, val); + case JSONTYPE_JSON: + /* JSON and JSONB output will already be escaped */ + outputstr = OidOutputFunctionCall(outfuncoid, val); appendStringInfoString(result, outputstr); pfree(outputstr); break; - case TYPCATEGORY_JSON_CAST: - jsontext = DatumGetTextP(OidFunctionCall1(typoutputfunc, val)); + case JSONTYPE_CAST: + /* outfuncoid refers to a cast function, not an output function */ + jsontext = DatumGetTextP(OidFunctionCall1(outfuncoid, val)); outputstr = text_to_cstring(jsontext); appendStringInfoString(result, outputstr); pfree(outputstr); pfree(jsontext); break; default: - outputstr = OidOutputFunctionCall(typoutputfunc, val); + outputstr = OidOutputFunctionCall(outfuncoid, val); if (key_scalar && *outputstr == '\0') ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -1308,8 +1408,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; @@ -1328,7 +1428,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, false); + outfuncoid, false); (*valcount)++; } else @@ -1338,7 +1438,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); } } @@ -1361,12 +1461,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; - Oid castfunc = InvalidOid; + char typalign; + JsonTypeCategory tcategory; + Oid outfuncoid; ndim = ARR_NDIM(v); dim = ARR_DIMS(v); @@ -1378,44 +1475,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); - - if (element_type > FirstNormalObjectId) - { - HeapTuple tuple; - Form_pg_cast castForm; - - tuple = SearchSysCache2(CASTSOURCETARGET, - ObjectIdGetDatum(element_type), - ObjectIdGetDatum(JSONOID)); - if (HeapTupleIsValid(tuple)) - { - castForm = (Form_pg_cast) GETSTRUCT(tuple); - - if (castForm->castmethod == COERCION_METHOD_FUNCTION) - castfunc = typoutputfunc = castForm->castfunc; + get_typlenbyvalalign(element_type, + &typlen, &typbyval, &typalign); - ReleaseSysCache(tuple); - } - } + json_categorize_type(element_type, + &tcategory, &outfuncoid); deconstruct_array(v, element_type, typlen, typbyval, typalign, &elements, &nulls, &nitems); - if (castfunc != InvalidOid) - tcategory = TYPCATEGORY_JSON_CAST; - else if (element_type == RECORDOID) - tcategory = TYPCATEGORY_COMPOSITE; - else if (element_type == JSONOID || element_type == JSONBOID) - 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); @@ -1458,10 +1529,8 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) Datum val; bool isnull; char *attname; - TYPCATEGORY tcategory; - Oid typoutput; - bool typisvarlena; - Oid castfunc = InvalidOid; + JsonTypeCategory tcategory; + Oid outfuncoid; if (tupdesc->attrs[i]->attisdropped) continue; @@ -1476,41 +1545,16 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) val = heap_getattr(tuple, i + 1, tupdesc, &isnull); - getTypeOutputInfo(tupdesc->attrs[i]->atttypid, - &typoutput, &typisvarlena); - - if (tupdesc->attrs[i]->atttypid > FirstNormalObjectId) + if (isnull) { - HeapTuple cast_tuple; - Form_pg_cast castForm; - - cast_tuple = SearchSysCache2(CASTSOURCETARGET, - ObjectIdGetDatum(tupdesc->attrs[i]->atttypid), - ObjectIdGetDatum(JSONOID)); - if (HeapTupleIsValid(cast_tuple)) - { - castForm = (Form_pg_cast) GETSTRUCT(cast_tuple); - - if (castForm->castmethod == COERCION_METHOD_FUNCTION) - castfunc = typoutput = castForm->castfunc; - - ReleaseSysCache(cast_tuple); - } + tcategory = JSONTYPE_NULL; + outfuncoid = InvalidOid; } - - if (castfunc != InvalidOid) - tcategory = TYPCATEGORY_JSON_CAST; - else 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 || - tupdesc->attrs[i]->atttypid == JSONBOID) - tcategory = TYPCATEGORY_JSON; else - tcategory = TypeCategory(tupdesc->attrs[i]->atttypid); + json_categorize_type(tupdesc->attrs[i]->atttypid, + &tcategory, &outfuncoid); - datum_to_json(val, isnull, result, tcategory, typoutput, false); + datum_to_json(val, isnull, result, tcategory, outfuncoid, false); } appendStringInfoChar(result, '}'); @@ -1518,65 +1562,34 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) } /* - * append Json for orig_val to result. If it's a field key, make sure it's - * of an acceptable type and is quoted. + * 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) +add_json(Datum val, bool is_null, StringInfo result, + Oid val_type, bool key_scalar) { - TYPCATEGORY tcategory; - Oid typoutput; - bool typisvarlena; - Oid castfunc = InvalidOid; + JsonTypeCategory tcategory; + Oid outfuncoid; if (val_type == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); - - getTypeOutputInfo(val_type, &typoutput, &typisvarlena); - - if (val_type > FirstNormalObjectId) + if (is_null) { - HeapTuple tuple; - Form_pg_cast castForm; - - tuple = SearchSysCache2(CASTSOURCETARGET, - ObjectIdGetDatum(val_type), - ObjectIdGetDatum(JSONOID)); - if (HeapTupleIsValid(tuple)) - { - castForm = (Form_pg_cast) GETSTRUCT(tuple); - - if (castForm->castmethod == COERCION_METHOD_FUNCTION) - castfunc = typoutput = castForm->castfunc; - - ReleaseSysCache(tuple); - } + tcategory = JSONTYPE_NULL; + outfuncoid = InvalidOid; } - - if (castfunc != InvalidOid) - tcategory = TYPCATEGORY_JSON_CAST; - else if (val_type == RECORDARRAYOID) - tcategory = TYPCATEGORY_ARRAY; - else if (val_type == RECORDOID) - tcategory = TYPCATEGORY_COMPOSITE; - else if (val_type == JSONOID || val_type == JSONBOID) - tcategory = TYPCATEGORY_JSON; else - tcategory = TypeCategory(val_type); + json_categorize_type(val_type, + &tcategory, &outfuncoid); - if (key_scalar && - (tcategory == TYPCATEGORY_ARRAY || - tcategory == TYPCATEGORY_COMPOSITE || - tcategory == TYPCATEGORY_JSON || - tcategory == TYPCATEGORY_JSON_CAST)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("key value must be scalar, not array, composite or json"))); - - datum_to_json(val, is_null, result, tcategory, typoutput, key_scalar); + datum_to_json(val, is_null, result, tcategory, outfuncoid, key_scalar); } /* @@ -1654,51 +1667,20 @@ to_json(PG_FUNCTION_ARGS) Datum val = PG_GETARG_DATUM(0); Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); StringInfo result; - TYPCATEGORY tcategory; - Oid typoutput; - bool typisvarlena; - Oid castfunc = InvalidOid; + JsonTypeCategory tcategory; + Oid outfuncoid; if (val_type == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); - result = makeStringInfo(); + json_categorize_type(val_type, + &tcategory, &outfuncoid); - getTypeOutputInfo(val_type, &typoutput, &typisvarlena); - - if (val_type > FirstNormalObjectId) - { - HeapTuple tuple; - Form_pg_cast castForm; - - tuple = SearchSysCache2(CASTSOURCETARGET, - ObjectIdGetDatum(val_type), - ObjectIdGetDatum(JSONOID)); - if (HeapTupleIsValid(tuple)) - { - castForm = (Form_pg_cast) GETSTRUCT(tuple); - - if (castForm->castmethod == COERCION_METHOD_FUNCTION) - castfunc = typoutput = castForm->castfunc; - - ReleaseSysCache(tuple); - } - } - - if (castfunc != InvalidOid) - tcategory = TYPCATEGORY_JSON_CAST; - else if (val_type == RECORDARRAYOID) - tcategory = TYPCATEGORY_ARRAY; - else if (val_type == RECORDOID) - tcategory = TYPCATEGORY_COMPOSITE; - else if (val_type == JSONOID || val_type == JSONBOID) - tcategory = TYPCATEGORY_JSON; - else - tcategory = TypeCategory(val_type); + result = makeStringInfo(); - datum_to_json(val, false, result, tcategory, typoutput, false); + datum_to_json(val, false, result, tcategory, outfuncoid, false); PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } @@ -1714,10 +1696,8 @@ json_agg_transfn(PG_FUNCTION_ARGS) oldcontext; StringInfo state; Datum val; - TYPCATEGORY tcategory; - Oid typoutput; - bool typisvarlena; - Oid castfunc = InvalidOid; + JsonTypeCategory tcategory; + Oid outfuncoid; if (val_type == InvalidOid) ereport(ERROR, @@ -1734,9 +1714,9 @@ json_agg_transfn(PG_FUNCTION_ARGS) { /* * Make this StringInfo in a context where it will persist for the - * duration off the aggregate call. It's only needed for this initial - * piece, as the StringInfo routines make sure they use the right - * context to enlarge the object if necessary. + * duration of the aggregate call. MemoryContextSwitchTo is only + * needed the first time, as the StringInfo routines make sure they + * use the right context to enlarge the object if necessary. */ oldcontext = MemoryContextSwitchTo(aggcontext); state = makeStringInfo(); @@ -1753,52 +1733,24 @@ json_agg_transfn(PG_FUNCTION_ARGS) /* fast path for NULLs */ if (PG_ARGISNULL(1)) { - val = (Datum) 0; - datum_to_json(val, true, state, 0, InvalidOid, false); + datum_to_json((Datum) 0, true, state, JSONTYPE_NULL, InvalidOid, false); PG_RETURN_POINTER(state); } val = PG_GETARG_DATUM(1); - getTypeOutputInfo(val_type, &typoutput, &typisvarlena); - - if (val_type > FirstNormalObjectId) - { - HeapTuple tuple; - Form_pg_cast castForm; - - tuple = SearchSysCache2(CASTSOURCETARGET, - ObjectIdGetDatum(val_type), - ObjectIdGetDatum(JSONOID)); - if (HeapTupleIsValid(tuple)) - { - castForm = (Form_pg_cast) GETSTRUCT(tuple); - - if (castForm->castmethod == COERCION_METHOD_FUNCTION) - castfunc = typoutput = castForm->castfunc; - - ReleaseSysCache(tuple); - } - } - - if (castfunc != InvalidOid) - tcategory = TYPCATEGORY_JSON_CAST; - else if (val_type == RECORDARRAYOID) - tcategory = TYPCATEGORY_ARRAY; - else if (val_type == RECORDOID) - tcategory = TYPCATEGORY_COMPOSITE; - else if (val_type == JSONOID || val_type == JSONBOID) - tcategory = TYPCATEGORY_JSON; - else - tcategory = TypeCategory(val_type); + /* XXX we do this every time?? */ + json_categorize_type(val_type, + &tcategory, &outfuncoid); + /* add some whitespace if structured type and not first item */ if (!PG_ARGISNULL(0) && - (tcategory == TYPCATEGORY_ARRAY || tcategory == TYPCATEGORY_COMPOSITE)) + (tcategory == JSONTYPE_ARRAY || tcategory == JSONTYPE_COMPOSITE)) { appendStringInfoString(state, "\n "); } - datum_to_json(val, false, state, tcategory, typoutput, false); + datum_to_json(val, false, state, tcategory, outfuncoid, false); /* * The transition type for array_agg() is declared to be "internal", which |