Skip to content

Commit

Permalink
Process variadic arguments consistently in json functions
Browse files Browse the repository at this point in the history
json_build_object and json_build_array and the jsonb equivalents did not
correctly process explicit VARIADIC arguments. They are modified to use
the new extract_variadic_args() utility function which abstracts away
the details of the call method.

Michael Paquier, reviewed by Tom Lane and Dmitry Dolgov.

Backpatch to 9.5 for the jsonb fixes and 9.4 for the json fixes, as
that's where they originated.
  • Loading branch information
adunstan committed Oct 25, 2017
1 parent 7f89fc4 commit 9cb28e9
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 60 deletions.
84 changes: 24 additions & 60 deletions src/backend/utils/adt/json.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "catalog/pg_cast.h"
#include "catalog/pg_type.h"
#include "executor/spi.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
Expand Down Expand Up @@ -2035,10 +2036,17 @@ json_build_object(PG_FUNCTION_ARGS)
{
int nargs = PG_NARGS();
int i;
Datum arg;
const char *sep = "";
StringInfo result;
Oid val_type;
Datum *args;
bool *nulls;
Oid *types;

/* fetch argument values to build the object */
nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls);

if (nargs < 0)
PG_RETURN_NULL();

if (nargs % 2 != 0)
ereport(ERROR,
Expand All @@ -2052,52 +2060,22 @@ json_build_object(PG_FUNCTION_ARGS)

for (i = 0; i < nargs; i += 2)
{
/*
* 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.
*/
appendStringInfoString(result, sep);
sep = ", ";

/* process key */
val_type = get_fn_expr_argtype(fcinfo->flinfo, i);

if (val_type == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not determine data type for argument %d",
i + 1)));

if (PG_ARGISNULL(i))
if (nulls[i])
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("argument %d cannot be null", i + 1),
errhint("Object keys should be text.")));

arg = PG_GETARG_DATUM(i);

add_json(arg, false, result, val_type, true);
add_json(args[i], false, result, types[i], true);

appendStringInfoString(result, " : ");

/* process value */
val_type = get_fn_expr_argtype(fcinfo->flinfo, i + 1);

if (val_type == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not determine data type for argument %d",
i + 2)));

if (PG_ARGISNULL(i + 1))
arg = (Datum) 0;
else
arg = PG_GETARG_DATUM(i + 1);

add_json(arg, PG_ARGISNULL(i + 1), result, val_type, false);
add_json(args[i + 1], nulls[i + 1], result, types[i + 1], false);
}

appendStringInfoChar(result, '}');
Expand All @@ -2120,43 +2098,29 @@ json_build_object_noargs(PG_FUNCTION_ARGS)
Datum
json_build_array(PG_FUNCTION_ARGS)
{
int nargs = PG_NARGS();
int nargs;
int i;
Datum arg;
const char *sep = "";
StringInfo result;
Oid val_type;
Datum *args;
bool *nulls;
Oid *types;

/* fetch argument values to build the array */
nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls);

if (nargs < 0)
PG_RETURN_NULL();

result = makeStringInfo();

appendStringInfoChar(result, '[');

for (i = 0; i < nargs; i++)
{
/*
* 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.
*/
appendStringInfoString(result, sep);
sep = ", ";

val_type = get_fn_expr_argtype(fcinfo->flinfo, i);

if (val_type == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not determine data type for argument %d",
i + 1)));

if (PG_ARGISNULL(i))
arg = (Datum) 0;
else
arg = PG_GETARG_DATUM(i);

add_json(arg, PG_ARGISNULL(i), result, val_type, false);
add_json(args[i], nulls[i], result, types[i], false);
}

appendStringInfoChar(result, ']');
Expand Down
107 changes: 107 additions & 0 deletions src/test/regress/expected/json.out
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,54 @@ SELECT json_build_array('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y":
["a", 1, "b", 1.2, "c", true, "d", null, "e", {"x": 3, "y": [1,2,3]}]
(1 row)

SELECT json_build_array('a', NULL); -- ok
json_build_array
------------------
["a", null]
(1 row)

SELECT json_build_array(VARIADIC NULL::text[]); -- ok
json_build_array
------------------

(1 row)

SELECT json_build_array(VARIADIC '{}'::text[]); -- ok
json_build_array
------------------
[]
(1 row)

SELECT json_build_array(VARIADIC '{a,b,c}'::text[]); -- ok
json_build_array
------------------
["a", "b", "c"]
(1 row)

SELECT json_build_array(VARIADIC ARRAY['a', NULL]::text[]); -- ok
json_build_array
------------------
["a", null]
(1 row)

SELECT json_build_array(VARIADIC '{1,2,3,4}'::text[]); -- ok
json_build_array
----------------------
["1", "2", "3", "4"]
(1 row)

SELECT json_build_array(VARIADIC '{1,2,3,4}'::int[]); -- ok
json_build_array
------------------
[1, 2, 3, 4]
(1 row)

SELECT json_build_array(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok
json_build_array
--------------------
[1, 4, 2, 5, 3, 6]
(1 row)

SELECT json_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}');
json_build_object
----------------------------------------------------------------------------
Expand All @@ -1435,6 +1483,65 @@ SELECT json_build_object(
{"a" : {"b" : false, "c" : 99}, "d" : {"e" : [9,8,7], "f" : {"relkind":"r","name":"pg_class"}}}
(1 row)

SELECT json_build_object('{a,b,c}'::text[]); -- error
ERROR: argument list must have even number of elements
HINT: The arguments of json_build_object() must consist of alternating keys and values.
SELECT json_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 json_build_object('a', 'b', 'c'); -- error
ERROR: argument list must have even number of elements
HINT: The arguments of json_build_object() must consist of alternating keys and values.
SELECT json_build_object(NULL, 'a'); -- error, key cannot be NULL
ERROR: argument 1 cannot be null
HINT: Object keys should be text.
SELECT json_build_object('a', NULL); -- ok
json_build_object
-------------------
{"a" : null}
(1 row)

SELECT json_build_object(VARIADIC NULL::text[]); -- ok
json_build_object
-------------------

(1 row)

SELECT json_build_object(VARIADIC '{}'::text[]); -- ok
json_build_object
-------------------
{}
(1 row)

SELECT json_build_object(VARIADIC '{a,b,c}'::text[]); -- error
ERROR: argument list must have even number of elements
HINT: The arguments of json_build_object() must consist of alternating keys and values.
SELECT json_build_object(VARIADIC ARRAY['a', NULL]::text[]); -- ok
json_build_object
-------------------
{"a" : null}
(1 row)

SELECT json_build_object(VARIADIC ARRAY[NULL, 'a']::text[]); -- error, key cannot be NULL
ERROR: argument 1 cannot be null
HINT: Object keys should be text.
SELECT json_build_object(VARIADIC '{1,2,3,4}'::text[]); -- ok
json_build_object
------------------------
{"1" : "2", "3" : "4"}
(1 row)

SELECT json_build_object(VARIADIC '{1,2,3,4}'::int[]); -- ok
json_build_object
--------------------
{"1" : 2, "3" : 4}
(1 row)

SELECT json_build_object(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok
json_build_object
-----------------------------
{"1" : 4, "2" : 5, "3" : 6}
(1 row)

-- empty objects/arrays
SELECT json_build_array();
json_build_array
Expand Down
21 changes: 21 additions & 0 deletions src/test/regress/sql/json.sql
Original file line number Diff line number Diff line change
Expand Up @@ -412,13 +412,34 @@ select value, json_typeof(value)
-- json_build_array, json_build_object, json_object_agg

SELECT json_build_array('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}');
SELECT json_build_array('a', NULL); -- ok
SELECT json_build_array(VARIADIC NULL::text[]); -- ok
SELECT json_build_array(VARIADIC '{}'::text[]); -- ok
SELECT json_build_array(VARIADIC '{a,b,c}'::text[]); -- ok
SELECT json_build_array(VARIADIC ARRAY['a', NULL]::text[]); -- ok
SELECT json_build_array(VARIADIC '{1,2,3,4}'::text[]); -- ok
SELECT json_build_array(VARIADIC '{1,2,3,4}'::int[]); -- ok
SELECT json_build_array(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok

SELECT json_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}');

SELECT json_build_object(
'a', json_build_object('b',false,'c',99),
'd', json_build_object('e',array[9,8,7]::int[],
'f', (select row_to_json(r) from ( select relkind, oid::regclass as name from pg_class where relname = 'pg_class') r)));
SELECT json_build_object('{a,b,c}'::text[]); -- error
SELECT json_build_object('{a,b,c}'::text[], '{d,e,f}'::text[]); -- error, key cannot be array
SELECT json_build_object('a', 'b', 'c'); -- error
SELECT json_build_object(NULL, 'a'); -- error, key cannot be NULL
SELECT json_build_object('a', NULL); -- ok
SELECT json_build_object(VARIADIC NULL::text[]); -- ok
SELECT json_build_object(VARIADIC '{}'::text[]); -- ok
SELECT json_build_object(VARIADIC '{a,b,c}'::text[]); -- error
SELECT json_build_object(VARIADIC ARRAY['a', NULL]::text[]); -- ok
SELECT json_build_object(VARIADIC ARRAY[NULL, 'a']::text[]); -- error, key cannot be NULL
SELECT json_build_object(VARIADIC '{1,2,3,4}'::text[]); -- ok
SELECT json_build_object(VARIADIC '{1,2,3,4}'::int[]); -- ok
SELECT json_build_object(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok


-- empty objects/arrays
Expand Down

0 comments on commit 9cb28e9

Please sign in to comment.