diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 7588eaa96f..d1f5618691 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -104,6 +104,8 @@ static void datum_to_json(Datum val, bool is_null, StringInfo result, static void add_json(Datum val, bool is_null, StringInfo result, Oid val_type, bool key_scalar); static text *catenate_stringinfo_string(StringInfo buffer, const char *addon); +static int extract_variadic_args(FunctionCallInfo fcinfo, Datum **values, + Oid **types, bool **nulls); /* the null action object used for pure validation */ static JsonSemAction nullSemAction = @@ -2104,27 +2106,33 @@ catenate_stringinfo_string(StringInfo buffer, const char *addon) } /* - * SQL function json_build_object(variadic "any") + * Extract a set of argument values, types and NULL markers for a given + * input function. This is used by json_build_object() and json_build_array() + * which make use of (VARIADIC "any") whose argument list depends on the + * caller context. When doing a VARIADIC call, the caller has provided one + * argument made of an array of keys, so deconstruct the array data before + * using it for the next processing. If no VARIADIC call is used, just fill + * in the status data based on all the arguments given by the caller. + * This function returns the number of arguments generated. In the event + * where the caller provided a NULL input, then the caller of this function + * ought to generate a NULL object as final result, so in this case, a + * result value of -1 is used to be able to make the difference between an + * empty array or object. */ -Datum -json_build_object(PG_FUNCTION_ARGS) +static int +extract_variadic_args(FunctionCallInfo fcinfo, Datum **values, + Oid **types, bool **nulls) { - int nargs = PG_NARGS(); - int i; - const char *sep = ""; - StringInfo result; bool variadic = get_fn_expr_variadic(fcinfo->flinfo); - Datum *elements; - bool *nulls; - Oid *val_type; + Datum *values_res; + bool *nulls_res; + Oid *types_res; + int nargs, i; + + *values = NULL; + *types = NULL; + *nulls = NULL; - /* - * When doing a VARIADIC call, the caller has provided one argument - * made of an array of keys, so deconstruct the array data before - * using it for the next processing. If no VARIADIC call is used, - * just fill in the status data based on all the arguments given by - * the caller. - */ if (variadic) { ArrayType *array_in; @@ -2136,7 +2144,7 @@ json_build_object(PG_FUNCTION_ARGS) Assert(PG_NARGS() == 1); if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); + return -1; array_in = PG_GETARG_ARRAYTYPE_P(0); element_type = ARR_ELEMTYPE(array_in); @@ -2144,34 +2152,35 @@ json_build_object(PG_FUNCTION_ARGS) get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign); deconstruct_array(array_in, element_type, typlen, typbyval, - typalign, &elements, &nulls, + typalign, &values_res, &nulls_res, &nargs); /* All the elements of the array have the same type */ - val_type = (Oid *) palloc0(nargs * sizeof(Oid)); + types_res = (Oid *) palloc0(nargs * sizeof(Oid)); for (i = 0; i < nargs; i++) - val_type[i] = element_type; + types_res[i] = element_type; } else { nargs = PG_NARGS(); - nulls = (bool *) palloc0(nargs * sizeof(bool)); - elements = (Datum *) palloc0(nargs * sizeof(Datum)); - val_type = (Oid *) palloc0(nargs * sizeof(Oid)); + nulls_res = (bool *) palloc0(nargs * sizeof(bool)); + values_res = (Datum *) palloc0(nargs * sizeof(Datum)); + types_res = (Oid *) palloc0(nargs * sizeof(Oid)); for (i = 0; i < nargs; i++) { /* - * Note: since json_build_object() is declared as taking type - * "any", the parser will not do any type conversion on - * unknown-type literals (that is, undecorated strings or NULLs). - * Such values will arrive here as type UNKNOWN, which fortunately - * does not matter to us, since unknownout() works fine. + * Note: since json_build_object() and json_build_array() are + * declared as taking type "any", the parser will not do any type + * conversion on unknown-type literals (that is, undecorated + * strings or NULLs). Such values will arrive here as type + * UNKNOWN, which fortunately does not matter to us, since + * unknownout() works fine. */ - nulls[i] = PG_ARGISNULL(i); - val_type[i] = get_fn_expr_argtype(fcinfo->flinfo, i); + nulls_res[i] = PG_ARGISNULL(i); + types_res[i] = get_fn_expr_argtype(fcinfo->flinfo, i); - if (!OidIsValid(val_type[i])) + if (!OidIsValid(types_res[i])) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine data type for argument %d", @@ -2179,12 +2188,41 @@ json_build_object(PG_FUNCTION_ARGS) /* important for value, key cannot being NULL */ if (PG_ARGISNULL(i)) - elements[i] = (Datum) 0; + values_res[i] = (Datum) 0; else - elements[i] = PG_GETARG_DATUM(i); + values_res[i] = PG_GETARG_DATUM(i); } } + /* Fill in results */ + *values = values_res; + *nulls = nulls_res; + *types = types_res; + + return nargs; +} + + +/* + * SQL function json_build_object(variadic "any") + */ +Datum +json_build_object(PG_FUNCTION_ARGS) +{ + int nargs = PG_NARGS(); + int i; + const char *sep = ""; + StringInfo result; + Datum *args; + bool *nulls; + Oid *types; + + /* build argument values to build the object */ + nargs = extract_variadic_args(fcinfo, &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + if (nargs % 2 != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -2207,12 +2245,12 @@ json_build_object(PG_FUNCTION_ARGS) errmsg("argument %d cannot be null", i + 1), errhint("Object keys should be text."))); - add_json(elements[i], false, result, val_type[i], true); + add_json(args[i], false, result, types[i], true); appendStringInfoString(result, " : "); /* process value */ - add_json(elements[i + 1], nulls[i + 1], result, val_type[i + 1], false); + add_json(args[i + 1], nulls[i + 1], result, types[i + 1], false); } appendStringInfoChar(result, '}'); @@ -2239,76 +2277,15 @@ json_build_array(PG_FUNCTION_ARGS) int i; const char *sep = ""; StringInfo result; - bool variadic = get_fn_expr_variadic(fcinfo->flinfo); - Datum *elements; + Datum *args; bool *nulls; - Oid *val_type; + Oid *types; - /* - * When doing a VARIADIC call, the caller has provided one argument - * made of an array of keys, so deconstruct the array data before - * using it for the next processing. If no VARIADIC call is used, - * just fill in the status data based on all the arguments given by - * the caller. - */ - if (variadic) - { - ArrayType *array_in; - Oid element_type; - bool typbyval; - char typalign; - int16 typlen; - - Assert(PG_NARGS() == 1); - - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - - array_in = PG_GETARG_ARRAYTYPE_P(0); - element_type = ARR_ELEMTYPE(array_in); - - get_typlenbyvalalign(element_type, - &typlen, &typbyval, &typalign); - deconstruct_array(array_in, element_type, typlen, typbyval, - typalign, &elements, &nulls, - &nargs); - - /* All the elements of the array have the same type */ - val_type = (Oid *) palloc0(nargs * sizeof(Oid)); - for (i = 0; i < nargs; i++) - val_type[i] = element_type; - } - else - { - nargs = PG_NARGS(); - nulls = (bool *) palloc0(nargs * sizeof(bool)); - elements = (Datum *) palloc0(nargs * sizeof(Datum)); - val_type = (Oid *) palloc0(nargs * sizeof(Oid)); - - for (i = 0; i < nargs; i++) - { - nulls[i] = PG_ARGISNULL(i); - val_type[i] = get_fn_expr_argtype(fcinfo->flinfo, i); + /* build argument values to build the array */ + nargs = extract_variadic_args(fcinfo, &args, &types, &nulls); - /* - * Note: since json_build_array() is declared as taking type "any", - * the parser will not do any type conversion on unknown-type literals - * (that is, undecorated strings or NULLs). Such values will arrive - * here as type UNKNOWN, which fortunately does not matter to us, - * since unknownout() works fine. - */ - if (!OidIsValid(val_type[i])) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not determine data type for argument %d", - i + 1))); - - if (PG_ARGISNULL(i)) - elements[i] = (Datum) 0; - else - elements[i] = PG_GETARG_DATUM(i); - } - } + if (nargs < 0) + PG_RETURN_NULL(); result = makeStringInfo(); @@ -2318,7 +2295,7 @@ json_build_array(PG_FUNCTION_ARGS) { appendStringInfoString(result, sep); sep = ", "; - add_json(elements[i], nulls[i], result, val_type[i], false); + add_json(args[i], nulls[i], result, types[i], false); } appendStringInfoChar(result, ']'); diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 8d598ca77e..106870b369 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -88,6 +88,8 @@ static void add_jsonb(Datum val, bool is_null, JsonbInState *result, static JsonbParseState *clone_parse_state(JsonbParseState *state); static char *JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent); static void add_indent(StringInfo out, bool indent, int level); +static int extract_variadic_args(FunctionCallInfo fcinfo, Datum **values, + Oid **types, bool **nulls); /* * jsonb type input function @@ -580,6 +582,114 @@ add_indent(StringInfo out, bool indent, int level) } +/* + * Extract a set of argument values, types and NULL markers for a given + * input function. This is used by jsonb_build_object() and jsonb_build_array() + * which make use of (VARIADIC "any") whose argument list depends on the + * caller context. When doing a VARIADIC call, the caller has provided one + * argument made of an array of keys, so deconstruct the array data before + * using it for the next processing. If no VARIADIC call is used, just fill + * in the status data based on all the arguments given by the caller. + * This function returns the number of arguments generated. In the event + * where the caller provided a NULL input, then the caller of this function + * ought to generate a NULL object as final result, so in this case, a + * result value of -1 is used to be able to make the difference between an + * empty array or object.. + */ +static int +extract_variadic_args(FunctionCallInfo fcinfo, Datum **args, + Oid **types, bool **nulls) +{ + bool variadic = get_fn_expr_variadic(fcinfo->flinfo); + Datum *args_res; + bool *nulls_res; + Oid *types_res; + int nargs, i; + + *args = NULL; + *types = NULL; + *nulls = NULL; + + if (variadic) + { + ArrayType *array_in; + Oid element_type; + bool typbyval; + char typalign; + int16 typlen; + + Assert(PG_NARGS() == 1); + + if (PG_ARGISNULL(0)) + return -1; + + array_in = PG_GETARG_ARRAYTYPE_P(0); + element_type = ARR_ELEMTYPE(array_in); + + get_typlenbyvalalign(element_type, + &typlen, &typbyval, &typalign); + deconstruct_array(array_in, element_type, typlen, typbyval, + typalign, &args_res, &nulls_res, + &nargs); + + /* All the elements of the array have the same type */ + types_res = (Oid *) palloc0(nargs * sizeof(Oid)); + for (i = 0; i < nargs; i++) + types_res[i] = element_type; + } + else + { + nargs = PG_NARGS(); + nulls_res = (bool *) palloc0(nargs * sizeof(bool)); + args_res = (Datum *) palloc0(nargs * sizeof(Datum)); + types_res = (Oid *) palloc0(nargs * sizeof(Oid)); + + for (i = 0; i < nargs; i++) + { + nulls_res[i] = PG_ARGISNULL(i); + types_res[i] = get_fn_expr_argtype(fcinfo->flinfo, i); + + if (!OidIsValid(types_res[i])) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine data type for argument %d", + i + 1))); + + /* + * Turn a constant (more or less literal) value that's of unknown + * type into text. Unknowns come in as a cstring pointer. + */ + if (types_res[i] == UNKNOWNOID && + get_fn_expr_arg_stable(fcinfo->flinfo, i)) + { + types_res[i] = TEXTOID; + + /* important for value, key cannot being NULL */ + if (PG_ARGISNULL(i)) + args_res[i] = (Datum) 0; + else + args_res[i] = CStringGetTextDatum(PG_GETARG_POINTER(i)); + } + else + args_res[i] = PG_GETARG_DATUM(i); + + if (!OidIsValid(types_res[i]) || types_res[i] == UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine data type for argument %d", + i + 1))); + } + } + + /* Fill in results */ + *args = args_res; + *nulls = nulls_res; + *types = types_res; + + return nargs; +} + + /* * Determine how we want to render values of a given type in datum_to_jsonb. * @@ -1174,87 +1284,21 @@ jsonb_build_object(PG_FUNCTION_ARGS) int nargs; int i; JsonbInState result; - bool variadic = get_fn_expr_variadic(fcinfo->flinfo); - Datum *elements; + Datum *args; bool *nulls; - Oid *val_type; - - /* - * When doing a VARIADIC call, the caller has provided one argument - * made of an array of keys, so deconstruct the array data before - * using it for the next processing. If no VARIADIC call is used, - * just fill in the status data based on all the arguments given by - * the caller. - */ - if (variadic) - { - ArrayType *array_in; - Oid element_type; - bool typbyval; - char typalign; - int16 typlen; - - Assert(PG_NARGS() == 1); - - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - - array_in = PG_GETARG_ARRAYTYPE_P(0); - element_type = ARR_ELEMTYPE(array_in); + Oid *types; - get_typlenbyvalalign(element_type, - &typlen, &typbyval, &typalign); - deconstruct_array(array_in, element_type, typlen, typbyval, - typalign, &elements, &nulls, - &nargs); + /* build argument values to build the object */ + nargs = extract_variadic_args(fcinfo, &args, &types, &nulls); - /* All the elements of the array have the same type */ - val_type = (Oid *) palloc0(nargs * sizeof(Oid)); - for (i = 0; i < nargs; i++) - val_type[i] = element_type; - } - else - { - nargs = PG_NARGS(); - nulls = (bool *) palloc0(nargs * sizeof(bool)); - elements = (Datum *) palloc0(nargs * sizeof(Datum)); - val_type = (Oid *) palloc0(nargs * sizeof(Oid)); - - for (i = 0; i < nargs; i++) - { - nulls[i] = PG_ARGISNULL(i); - val_type[i] = 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[i] == UNKNOWNOID && - get_fn_expr_arg_stable(fcinfo->flinfo, i)) - { - val_type[i] = TEXTOID; - - /* important for value, key cannot being NULL */ - if (PG_ARGISNULL(i)) - elements[i] = (Datum) 0; - else - elements[i] = CStringGetTextDatum(PG_GETARG_POINTER(i)); - } - else - elements[i] = PG_GETARG_DATUM(i); - - if (!OidIsValid(val_type[i]) || val_type[i] == UNKNOWNOID) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not determine data type for argument %d", - i + 1))); - } - } + if (nargs < 0) + PG_RETURN_NULL(); if (nargs % 2 != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid number of arguments: object must be matched key value pairs"))); + errmsg("argument list must have even number of elements"), + errhint("The arguments of jsonb_build_object() must consist of alternating keys and values."))); memset(&result, 0, sizeof(JsonbInState)); @@ -1268,10 +1312,10 @@ jsonb_build_object(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument %d: key must not be null", i + 1))); - add_jsonb(elements[i], false, &result, val_type[i], true); + add_jsonb(args[i], false, &result, types[i], true); /* process value */ - add_jsonb(elements[i + 1], nulls[i + 1], &result, val_type[i + 1], false); + add_jsonb(args[i + 1], nulls[i + 1], &result, types[i + 1], false); } result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); @@ -1304,85 +1348,22 @@ jsonb_build_array(PG_FUNCTION_ARGS) int nargs; int i; JsonbInState result; - bool variadic = get_fn_expr_variadic(fcinfo->flinfo); - Datum *elements; + Datum *args; bool *nulls; - Oid *val_type; - - /* - * When doing a VARIADIC call, the caller has provided one argument - * made of an array of keys, so deconstruct the array data before - * using it for the next processing. If no VARIADIC call is used, - * just fill in the status data based on all the arguments given by - * the caller. - */ - if (variadic) - { - ArrayType *array_in; - Oid element_type; - bool typbyval; - char typalign; - int16 typlen; + Oid *types; - Assert(PG_NARGS() == 1); - - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); + /* build argument values to build the array */ + nargs = extract_variadic_args(fcinfo, &args, &types, &nulls); - array_in = PG_GETARG_ARRAYTYPE_P(0); - element_type = ARR_ELEMTYPE(array_in); - - get_typlenbyvalalign(element_type, - &typlen, &typbyval, &typalign); - deconstruct_array(array_in, element_type, typlen, typbyval, - typalign, &elements, &nulls, - &nargs); - - /* All the elements of the array have the same type */ - val_type = (Oid *) palloc0(nargs * sizeof(Oid)); - for (i = 0; i < nargs; i++) - val_type[i] = element_type; - } - else - { - nargs = PG_NARGS(); - nulls = (bool *) palloc0(nargs * sizeof(bool)); - elements = (Datum *) palloc0(nargs * sizeof(Datum)); - val_type = (Oid *) palloc0(nargs * sizeof(Oid)); - - for (i = 0; i < nargs; i++) - { - nulls[i] = PG_ARGISNULL(i); - val_type[i] = get_fn_expr_argtype(fcinfo->flinfo, i); - - /* see comments in jsonb_build_object above */ - if (val_type[i] == UNKNOWNOID && - get_fn_expr_arg_stable(fcinfo->flinfo, i)) - { - val_type[i] = TEXTOID; - - if (PG_ARGISNULL(i)) - elements[i] = (Datum) 0; - else - elements[i] = CStringGetTextDatum(PG_GETARG_POINTER(i)); - } - else - elements[i] = PG_GETARG_DATUM(i); - - if (!OidIsValid(val_type[i]) || val_type[i] == UNKNOWNOID) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not determine data type for argument %d", - i + 1))); - } - } + if (nargs < 0) + PG_RETURN_NULL(); memset(&result, 0, sizeof(JsonbInState)); result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL); for (i = 0; i < nargs; i++) - add_jsonb(elements[i], nulls[i], &result, val_type[i], false); + add_jsonb(args[i], nulls[i], &result, types[i], false); result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index 923492b4de..eeac2a13c7 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -1409,11 +1409,13 @@ SELECT jsonb_build_object( (1 row) SELECT jsonb_build_object('{a,b,c}'::text[]); -- error -ERROR: invalid number of arguments: object must be matched key value pairs +ERROR: argument list must have even number of elements +HINT: The arguments of jsonb_build_object() must consist of alternating keys and values. SELECT jsonb_build_object('{a,b,c}'::text[], '{d,e,f}'::text[]); -- error, key cannot be array ERROR: key value must be scalar, not array, composite, or json SELECT jsonb_build_object('a', 'b', 'c'); -- error -ERROR: invalid number of arguments: object must be matched key value pairs +ERROR: argument list must have even number of elements +HINT: The arguments of jsonb_build_object() must consist of alternating keys and values. SELECT jsonb_build_object(NULL, 'a'); -- error, key cannot be NULL ERROR: argument 1: key must not be null SELECT jsonb_build_object('a', NULL); -- ok @@ -1435,7 +1437,8 @@ SELECT jsonb_build_object(VARIADIC '{}'::text[]); -- ok (1 row) SELECT jsonb_build_object(VARIADIC '{a,b,c}'::text[]); -- error -ERROR: invalid number of arguments: object must be matched key value pairs +ERROR: argument list must have even number of elements +HINT: The arguments of jsonb_build_object() must consist of alternating keys and values. SELECT jsonb_build_object(VARIADIC ARRAY['a', NULL]::text[]); -- ok jsonb_build_object --------------------