Fix NULL handling in datum_to_jsonb().
authorTom Lane <[email protected]>
Thu, 15 Oct 2015 17:46:09 +0000 (13:46 -0400)
committerTom Lane <[email protected]>
Thu, 15 Oct 2015 17:46:09 +0000 (13:46 -0400)
The function failed to adhere to its specification that the "tcategory"
argument should not be examined when the input value is NULL.  This
resulted in a crash in some cases.  Per bug #13680 from Boyko Yordanov.

In passing, re-pgindent some recent changes in jsonb.c, and fix a rather
ungrammatical comment.

Diagnosis and patch by Michael Paquier, cosmetic changes by me

src/backend/utils/adt/jsonb.c
src/test/regress/expected/jsonb.out
src/test/regress/sql/jsonb.sql

index 8b1cab488bb80bbce8e2c99aef4aa29a9df76f34..aa156c432c6c7073df265ec8be8762672fddbca3 100644 (file)
@@ -61,11 +61,11 @@ typedef enum                    /* type categories for datum_to_jsonb */
 
 typedef struct JsonbAggState
 {
-   JsonbInState      *res;
-   JsonbTypeCategory  key_category;
-   Oid                key_output_func;
-   JsonbTypeCategory  val_category;
-   Oid                val_output_func;
+   JsonbInState *res;
+   JsonbTypeCategory key_category;
+   Oid         key_output_func;
+   JsonbTypeCategory val_category;
+   Oid         val_output_func;
 } JsonbAggState;
 
 static inline Datum jsonb_from_cstring(char *json, int len);
@@ -714,6 +714,7 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
 
    check_stack_depth();
 
+   /* Convert val to a JsonbValue in jb (in most cases) */
    if (is_null)
    {
        Assert(!key_scalar);
@@ -936,8 +937,10 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
                break;
        }
    }
-   if (tcategory >= JSONBTYPE_JSON && tcategory <= JSONBTYPE_JSONCAST &&
-       !scalar_jsonb)
+
+   /* Now insert jb into result, unless we did it recursively */
+   if (!is_null && !scalar_jsonb &&
+       tcategory >= JSONBTYPE_JSON && tcategory <= JSONBTYPE_JSONCAST)
    {
        /* work has been done recursively */
        return;
@@ -1607,8 +1610,7 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS)
 
    if (PG_ARGISNULL(0))
    {
-
-       Oid         arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
+       Oid         arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
 
        if (arg_type == InvalidOid)
            ereport(ERROR,
@@ -1762,7 +1764,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
 
    if (PG_ARGISNULL(0))
    {
-       Oid         arg_type;
+       Oid         arg_type;
 
        oldcontext = MemoryContextSwitchTo(aggcontext);
        state = palloc(sizeof(JsonbAggState));
@@ -1950,8 +1952,9 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
    /*
     * We need to do a shallow clone of the argument's res field in case the
     * final function is called more than once, so we avoid changing the
-    * it. A shallow clone is sufficient as we aren't going to change any of
-    * the values, just add the final object end marker.
+    * aggregate state value.  A shallow clone is sufficient as we aren't
+    * going to change any of the values, just add the final object end
+    * marker.
     */
 
    result.parseState = clone_parse_state(arg->res->parseState);
index 07091a9bfb4c3d38ebdf29bc8f9401606e8376a8..bee95a3a9f17190fddfac4011be98f86548fd48e 100644 (file)
@@ -1362,6 +1362,15 @@ SELECT jsonb_build_object(json '{"a":1,"b":2}', 3);
 ERROR:  key value must be scalar, not array, composite or json
 SELECT jsonb_build_object('{1,2,3}'::int[], 3);
 ERROR:  key value must be scalar, not array, composite or json
+-- handling of NULL values
+SELECT jsonb_object_agg(1, NULL::jsonb);
+ jsonb_object_agg 
+------------------
+ {"1": null}
+(1 row)
+
+SELECT jsonb_object_agg(NULL, '{"a":1}');
+ERROR:  field name must not be null
 CREATE TEMP TABLE foo (serial_num int, name text, type text);
 INSERT INTO foo VALUES (847001,'t15','GE1043');
 INSERT INTO foo VALUES (847002,'t16','GE1043');
index 9700b7ca7e0e7bd3aa3a7626ff857d2355d3e9fd..d93b8c3de88f221d8504e629cb522dc109f5aceb 100644 (file)
@@ -330,6 +330,10 @@ SELECT jsonb_build_object(json '{"a":1,"b":2}', 3);
 
 SELECT jsonb_build_object('{1,2,3}'::int[], 3);
 
+-- handling of NULL values
+SELECT jsonb_object_agg(1, NULL::jsonb);
+SELECT jsonb_object_agg(NULL, '{"a":1}');
+
 CREATE TEMP TABLE foo (serial_num int, name text, type text);
 INSERT INTO foo VALUES (847001,'t15','GE1043');
 INSERT INTO foo VALUES (847002,'t16','GE1043');