Fix finalization for json_objectagg and friends
authorAndrew Dunstan <[email protected]>
Wed, 13 Apr 2022 14:26:38 +0000 (10:26 -0400)
committerAndrew Dunstan <[email protected]>
Wed, 13 Apr 2022 14:37:43 +0000 (10:37 -0400)
Commit f4fb45d15c misguidedly tried to free some state during aggregate
finalization for json_objectagg. This resulted in attempts to access
freed memory, especially when the function is used as a window function.
Commit 4eb9798879 attempted to ameliorate that, but in fact it should
just be ripped out, which is done here. Also add some regression tests
for json_objectagg in various flavors as a window function.

Original report from Jaime Casanova, diagnosis by Andres Freund.

Discussion: https://postgr.es/m/YkfeMNYRCGhySKyg@ahch-to

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

index 4cf01300d8b7c1ae039175aed9ff384f1f3c86a9..da3f1b9700407268160e94398de5f22a5b228c29 100644 (file)
@@ -1002,13 +1002,6 @@ json_unique_builder_init(JsonUniqueBuilderState *cxt)
        cxt->skipped_keys.data = NULL;
 }
 
-static void
-json_unique_builder_clean(JsonUniqueBuilderState *cxt)
-{
-       if (cxt->skipped_keys.data)
-               resetStringInfo(&cxt->skipped_keys);
-}
-
 /* On-demand initialization of skipped_keys StringInfo structure */
 static StringInfo
 json_unique_builder_get_skipped_keys(JsonUniqueBuilderState *cxt)
@@ -1216,8 +1209,6 @@ json_object_agg_finalfn(PG_FUNCTION_ARGS)
        if (state == NULL)
                PG_RETURN_NULL();
 
-       json_unique_builder_clean(&state->unique_check);
-
        /* Else return state with appropriate object terminator added */
        PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, " }"));
 }
@@ -1324,9 +1315,6 @@ json_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types,
 
        appendStringInfoChar(result, '}');
 
-       if (unique_keys)
-               json_unique_builder_clean(&unique_check);
-
        return PointerGetDatum(cstring_to_text_with_len(result->data, result->len));
 }
 
index 6cadd87868a779091937173579457a4306986226..97a72be970c36d91aab69f5018b08675bd4fd198 100644 (file)
@@ -959,6 +959,41 @@ SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);
 CREATE OR REPLACE VIEW public.json_object_view AS
  SELECT JSON_OBJECT('foo' : '1'::text FORMAT JSON, 'bar' : 'baz'::text RETURNING json) AS "json_object"
 DROP VIEW json_object_view;
+SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v WITH UNIQUE KEYS) OVER (ORDER BY k)
+FROM (VALUES (1,1), (2,2)) a(k,v);
+       a       |    json_objectagg    
+---------------+----------------------
+ {"k":1,"v":1} | { "1" : 1 }
+ {"k":2,"v":2} | { "1" : 1, "2" : 2 }
+(2 rows)
+
+SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v WITH UNIQUE KEYS) OVER (ORDER BY k)
+FROM (VALUES (1,1), (1,2), (2,2)) a(k,v);
+ERROR:  duplicate JSON key "1"
+SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL WITH UNIQUE KEYS)
+   OVER (ORDER BY k)
+FROM (VALUES (1,1), (1,null), (2,2)) a(k,v);
+ERROR:  duplicate JSON key "1"
+SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL)
+OVER (ORDER BY k)
+FROM (VALUES (1,1), (1,null), (2,2)) a(k,v);
+        a         |    json_objectagg    
+------------------+----------------------
+ {"k":1,"v":1}    | { "1" : 1 }
+ {"k":1,"v":null} | { "1" : 1 }
+ {"k":2,"v":2}    | { "1" : 1, "2" : 2 }
+(3 rows)
+
+SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL)
+OVER (ORDER BY k RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
+FROM (VALUES (1,1), (1,null), (2,2)) a(k,v);
+        a         |    json_objectagg    
+------------------+----------------------
+ {"k":1,"v":1}    | { "1" : 1, "2" : 2 }
+ {"k":1,"v":null} | { "1" : 1, "2" : 2 }
+ {"k":2,"v":2}    | { "1" : 1, "2" : 2 }
+(3 rows)
+
 -- Test JSON_ARRAY deparsing
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
index 51fc659b58cd9590b02a357020476ef51660dcde..b422ded97800d6b7f45f577cfdf21d911422bf29 100644 (file)
@@ -292,6 +292,24 @@ SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);
 
 DROP VIEW json_object_view;
 
+SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v WITH UNIQUE KEYS) OVER (ORDER BY k)
+FROM (VALUES (1,1), (2,2)) a(k,v);
+
+SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v WITH UNIQUE KEYS) OVER (ORDER BY k)
+FROM (VALUES (1,1), (1,2), (2,2)) a(k,v);
+
+SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL WITH UNIQUE KEYS)
+   OVER (ORDER BY k)
+FROM (VALUES (1,1), (1,null), (2,2)) a(k,v);
+
+SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL)
+OVER (ORDER BY k)
+FROM (VALUES (1,1), (1,null), (2,2)) a(k,v);
+
+SELECT to_json(a) AS a, JSON_OBJECTAGG(k : v ABSENT ON NULL)
+OVER (ORDER BY k RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
+FROM (VALUES (1,1), (1,null), (2,2)) a(k,v);
+
 -- Test JSON_ARRAY deparsing
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);