diff options
Diffstat (limited to 'src/backend/utils/adt/json.c')
-rw-r--r-- | src/backend/utils/adt/json.c | 577 |
1 files changed, 558 insertions, 19 deletions
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 481db16c6cc..f170661d721 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -68,6 +68,10 @@ 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 datum_to_json(Datum val, bool is_null, StringInfo result, + TYPCATEGORY tcategory, Oid typoutputfunc, bool key_scalar); +static void add_json(Datum val, bool is_null, StringInfo result, + Oid val_type, bool key_scalar); /* the null action object used for pure validation */ static JsonSemAction nullSemAction = @@ -1219,7 +1223,7 @@ extract_mb_char(char *s) */ static void datum_to_json(Datum val, bool is_null, StringInfo result, - TYPCATEGORY tcategory, Oid typoutputfunc) + TYPCATEGORY tcategory, Oid typoutputfunc, bool key_scalar) { char *outputstr; text *jsontext; @@ -1241,24 +1245,32 @@ datum_to_json(Datum val, bool is_null, StringInfo result, composite_to_json(val, result, false); break; case TYPCATEGORY_BOOLEAN: - if (DatumGetBool(val)) - appendStringInfoString(result, "true"); + if (!key_scalar) + appendStringInfoString(result, DatumGetBool(val) ? "true" : "false"); else - appendStringInfoString(result, "false"); + escape_json(result, DatumGetBool(val) ? "true" : "false"); break; case TYPCATEGORY_NUMERIC: outputstr = OidOutputFunctionCall(typoutputfunc, val); - - /* - * Don't call escape_json here if it's a valid JSON number. - */ - dummy_lex.input = *outputstr == '-' ? outputstr + 1 : outputstr; - dummy_lex.input_length = strlen(dummy_lex.input); - json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error); - if (!numeric_error) - appendStringInfoString(result, outputstr); - else + if (key_scalar) + { + /* always quote keys */ escape_json(result, outputstr); + } + else + { + /* + * Don't call escape_json for a non-key if it's a valid JSON + * number. + */ + dummy_lex.input = *outputstr == '-' ? outputstr + 1 : outputstr; + dummy_lex.input_length = strlen(dummy_lex.input); + json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error); + if (!numeric_error) + appendStringInfoString(result, outputstr); + else + escape_json(result, outputstr); + } pfree(outputstr); break; case TYPCATEGORY_JSON: @@ -1276,6 +1288,10 @@ datum_to_json(Datum val, bool is_null, StringInfo result, break; default: outputstr = OidOutputFunctionCall(typoutputfunc, val); + if (key_scalar && *outputstr == '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key value must not be empty"))); escape_json(result, outputstr); pfree(outputstr); break; @@ -1309,7 +1325,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); + typoutputfunc, false); (*valcount)++; } else @@ -1490,7 +1506,7 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) else tcategory = TypeCategory(tupdesc->attrs[i]->atttypid); - datum_to_json(val, isnull, result, tcategory, typoutput); + datum_to_json(val, isnull, result, tcategory, typoutput, false); } appendStringInfoChar(result, '}'); @@ -1498,6 +1514,68 @@ 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. + */ +static void +add_json(Datum val, bool is_null, StringInfo result, Oid val_type, bool key_scalar) +{ + TYPCATEGORY tcategory; + Oid typoutput; + bool typisvarlena; + Oid castfunc = InvalidOid; + + 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) + { + 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) + tcategory = TYPCATEGORY_JSON; + else + tcategory = TypeCategory(val_type); + + 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); +} + +/* * SQL function array_to_json(row) */ extern Datum @@ -1616,7 +1694,7 @@ to_json(PG_FUNCTION_ARGS) else tcategory = TypeCategory(val_type); - datum_to_json(val, false, result, tcategory, typoutput); + datum_to_json(val, false, result, tcategory, typoutput, false); PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } @@ -1672,7 +1750,7 @@ json_agg_transfn(PG_FUNCTION_ARGS) if (PG_ARGISNULL(1)) { val = (Datum) 0; - datum_to_json(val, true, state, 0, InvalidOid); + datum_to_json(val, true, state, 0, InvalidOid, false); PG_RETURN_POINTER(state); } @@ -1716,7 +1794,7 @@ json_agg_transfn(PG_FUNCTION_ARGS) appendStringInfoString(state, "\n "); } - datum_to_json(val, false, state, tcategory, typoutput); + datum_to_json(val, false, state, tcategory, typoutput, false); /* * The transition type for array_agg() is declared to be "internal", which @@ -1748,6 +1826,467 @@ json_agg_finalfn(PG_FUNCTION_ARGS) } /* + * json_object_agg transition function. + * + * aggregate two input columns as a single json value. + */ +Datum +json_object_agg_transfn(PG_FUNCTION_ARGS) +{ + Oid val_type; + MemoryContext aggcontext, + oldcontext; + StringInfo state; + Datum arg; + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "json_agg_transfn called in non-aggregate context"); + } + + if (PG_ARGISNULL(0)) + { + /* + * 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. + */ + oldcontext = MemoryContextSwitchTo(aggcontext); + state = makeStringInfo(); + MemoryContextSwitchTo(oldcontext); + + appendStringInfoString(state, "{ "); + } + else + { + state = (StringInfo) PG_GETARG_POINTER(0); + appendStringInfoString(state, ", "); + } + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("field name must not be null"))); + + + val_type = get_fn_expr_argtype(fcinfo->flinfo, 1); + + /* + * turn a constant (more or less literal) value that's of unknown type + * into text. Unknowns come in as a cstring pointer. + */ + if (val_type == UNKNOWNOID && get_fn_expr_arg_stable(fcinfo->flinfo, 1)) + { + val_type = TEXTOID; + arg = CStringGetTextDatum(PG_GETARG_POINTER(1)); + } + else + { + arg = PG_GETARG_DATUM(1); + } + + if (val_type == InvalidOid || val_type == UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("arg 1: could not determine data type"))); + + add_json(arg, false, state, val_type, true); + + appendStringInfoString(state, " : "); + + val_type = get_fn_expr_argtype(fcinfo->flinfo, 2); + /* see comments above */ + if (val_type == UNKNOWNOID && get_fn_expr_arg_stable(fcinfo->flinfo, 2)) + { + val_type = TEXTOID; + if (PG_ARGISNULL(2)) + arg = (Datum) 0; + else + arg = CStringGetTextDatum(PG_GETARG_POINTER(2)); + } + else + { + arg = PG_GETARG_DATUM(2); + } + + if (val_type == InvalidOid || val_type == UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("arg 2: could not determine data type"))); + + add_json(arg, PG_ARGISNULL(2), state, val_type, false); + + PG_RETURN_POINTER(state); +} + +/* + * json_object_agg final function. + * + */ +Datum +json_object_agg_finalfn(PG_FUNCTION_ARGS) +{ + StringInfo state; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); + + if (state == NULL) + PG_RETURN_TEXT_P(cstring_to_text("{}")); + + appendStringInfoString(state, " }"); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(state->data, state->len)); +} + +/* + * SQL function json_build_object(variadic "any") + */ +Datum +json_build_object(PG_FUNCTION_ARGS) +{ + int nargs = PG_NARGS(); + int i; + Datum arg; + char *sep = ""; + StringInfo result; + Oid val_type; + + + if (nargs % 2 != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid number or arguments: object must be matched key value pairs"))); + + result = makeStringInfo(); + + appendStringInfoChar(result, '{'); + + for (i = 0; i < nargs; i += 2) + { + + /* process key */ + + if (PG_ARGISNULL(i)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("arg %d: key cannot be null", i + 1))); + val_type = get_fn_expr_argtype(fcinfo->flinfo, i); + + /* + * turn a constant (more or less literal) value that's of unknown type + * into text. Unknowns come in as a cstring pointer. + */ + if (val_type == UNKNOWNOID && get_fn_expr_arg_stable(fcinfo->flinfo, i)) + { + val_type = TEXTOID; + if (PG_ARGISNULL(i)) + arg = (Datum) 0; + else + arg = CStringGetTextDatum(PG_GETARG_POINTER(i)); + } + else + { + arg = PG_GETARG_DATUM(i); + } + if (val_type == InvalidOid || val_type == UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("arg %d: could not determine data type", i + 1))); + appendStringInfoString(result, sep); + sep = ", "; + add_json(arg, false, result, val_type, true); + + appendStringInfoString(result, " : "); + + /* process value */ + + val_type = get_fn_expr_argtype(fcinfo->flinfo, i + 1); + /* see comments above */ + if (val_type == UNKNOWNOID && get_fn_expr_arg_stable(fcinfo->flinfo, i + 1)) + { + val_type = TEXTOID; + if (PG_ARGISNULL(i + 1)) + arg = (Datum) 0; + else + arg = CStringGetTextDatum(PG_GETARG_POINTER(i + 1)); + } + else + { + arg = PG_GETARG_DATUM(i + 1); + } + if (val_type == InvalidOid || val_type == UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("arg %d: could not determine data type", i + 2))); + add_json(arg, PG_ARGISNULL(i + 1), result, val_type, false); + + } + appendStringInfoChar(result, '}'); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); + +} + +/* + * degenerate case of json_build_object where it gets 0 arguments. + */ +Datum +json_build_object_noargs(PG_FUNCTION_ARGS) +{ + PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); +} + +/* + * SQL function json_build_array(variadic "any") + */ +Datum +json_build_array(PG_FUNCTION_ARGS) +{ + int nargs = PG_NARGS(); + int i; + Datum arg; + char *sep = ""; + StringInfo result; + Oid val_type; + + + result = makeStringInfo(); + + appendStringInfoChar(result, '['); + + for (i = 0; i < nargs; i++) + { + val_type = get_fn_expr_argtype(fcinfo->flinfo, i); + arg = PG_GETARG_DATUM(i + 1); + /* see comments in json_build_object above */ + if (val_type == UNKNOWNOID && get_fn_expr_arg_stable(fcinfo->flinfo, i)) + { + val_type = TEXTOID; + if (PG_ARGISNULL(i)) + arg = (Datum) 0; + else + arg = CStringGetTextDatum(PG_GETARG_POINTER(i)); + } + else + { + arg = PG_GETARG_DATUM(i); + } + if (val_type == InvalidOid || val_type == UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("arg %d: could not determine data type", i + 1))); + appendStringInfoString(result, sep); + sep = ", "; + add_json(arg, PG_ARGISNULL(i), result, val_type, false); + } + appendStringInfoChar(result, ']'); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); + +} + +/* + * degenerate case of json_build_array where it gets 0 arguments. + */ +Datum +json_build_array_noargs(PG_FUNCTION_ARGS) +{ + PG_RETURN_TEXT_P(cstring_to_text_with_len("[]", 2)); +} + +/* + * SQL function json_object(text[]) + * + * take a one or two dimensional array of text as name vale pairs + * for a json object. + * + */ +Datum +json_object(PG_FUNCTION_ARGS) +{ + ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0); + int ndims = ARR_NDIM(in_array); + StringInfoData result; + Datum *in_datums; + bool *in_nulls; + int in_count, + count, + i; + text *rval; + char *v; + + switch (ndims) + { + case 0: + PG_RETURN_DATUM(CStringGetTextDatum("{}")); + break; + + case 1: + if ((ARR_DIMS(in_array)[0]) % 2) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must have even number of elements"))); + break; + + case 2: + if ((ARR_DIMS(in_array)[1]) != 2) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must have two columns"))); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + } + + deconstruct_array(in_array, + TEXTOID, -1, false, 'i', + &in_datums, &in_nulls, &in_count); + + count = in_count / 2; + + initStringInfo(&result); + + appendStringInfoChar(&result, '{'); + + for (i = 0; i < count; ++i) + { + if (in_nulls[i * 2]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for object key"))); + + v = TextDatumGetCString(in_datums[i * 2]); + if (v[0] == '\0') + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("empty value not allowed for object key"))); + if (i > 0) + appendStringInfoString(&result, ", "); + escape_json(&result, v); + appendStringInfoString(&result, " : "); + pfree(v); + if (in_nulls[i * 2 + 1]) + appendStringInfoString(&result, "null"); + else + { + v = TextDatumGetCString(in_datums[i * 2 + 1]); + escape_json(&result, v); + pfree(v); + } + } + + appendStringInfoChar(&result, '}'); + + pfree(in_datums); + pfree(in_nulls); + + rval = cstring_to_text_with_len(result.data, result.len); + pfree(result.data); + + PG_RETURN_TEXT_P(rval); + +} + +/* + * SQL function json_object(text[], text[]) + * + * take separate name and value arrays of text to construct a json object + * pairwise. + */ +Datum +json_object_two_arg(PG_FUNCTION_ARGS) +{ + ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *val_array = PG_GETARG_ARRAYTYPE_P(1); + int nkdims = ARR_NDIM(key_array); + int nvdims = ARR_NDIM(val_array); + StringInfoData result; + Datum *key_datums, + *val_datums; + bool *key_nulls, + *val_nulls; + int key_count, + val_count, + i; + text *rval; + char *v; + + if (nkdims > 1 || nkdims != nvdims) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + if (nkdims == 0) + PG_RETURN_DATUM(CStringGetTextDatum("{}")); + + deconstruct_array(key_array, + TEXTOID, -1, false, 'i', + &key_datums, &key_nulls, &key_count); + + deconstruct_array(val_array, + TEXTOID, -1, false, 'i', + &val_datums, &val_nulls, &val_count); + + if (key_count != val_count) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("mismatched array dimensions"))); + + initStringInfo(&result); + + appendStringInfoChar(&result, '{'); + + for (i = 0; i < key_count; ++i) + { + if (key_nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for object key"))); + + v = TextDatumGetCString(key_datums[i]); + if (v[0] == '\0') + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("empty value not allowed for object key"))); + if (i > 0) + appendStringInfoString(&result, ", "); + escape_json(&result, v); + appendStringInfoString(&result, " : "); + pfree(v); + if (val_nulls[i]) + appendStringInfoString(&result, "null"); + else + { + v = TextDatumGetCString(val_datums[i]); + escape_json(&result, v); + pfree(v); + } + } + + appendStringInfoChar(&result, '}'); + + pfree(key_datums); + pfree(key_nulls); + pfree(val_datums); + pfree(val_nulls); + + rval = cstring_to_text_with_len(result.data, result.len); + pfree(result.data); + + PG_RETURN_TEXT_P(rval); + +} + + +/* * Produce a JSON string literal, properly escaping characters in the text. */ void |