]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix handling of nested JSON objects in json_populate_recordset and friends.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 25 Jun 2014 04:22:47 +0000 (21:22 -0700)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 25 Jun 2014 04:22:47 +0000 (21:22 -0700)
populate_recordset_object_start() improperly created a new hash table
(overwriting the link to the existing one) if called at nest levels
greater than one.  This resulted in previous fields not appearing in
the final output, as reported by Matti Hameister in bug #10728.
In 9.4 the problem also affects json_to_recordset.

This perhaps missed detection earlier because the default behavior is to
throw an error for nested objects: you have to pass use_json_as_text = true
to see the problem.

In addition, fix query-lifespan leakage of the hashtable created by
json_populate_record().  This is pretty much the same problem recently
fixed in dblink: creating an intended-to-be-temporary context underneath
the executor's per-tuple context isn't enough to make it go away at the
end of the tuple cycle, because MemoryContextReset is not
MemoryContextResetAndDeleteChildren.

Michael Paquier and Tom Lane

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

index cf66a28cf2c044b102d9cebf81b2f0f1c52f1b05..6d1fed7c063b16b9583d5e5ad61a83809974dbc7 100644 (file)
@@ -1282,8 +1282,10 @@ json_populate_record(PG_FUNCTION_ARGS)
         * nulls.
         */
        if (hash_get_num_entries(json_hash) == 0 && rec)
+       {
+               hash_destroy(json_hash);
                PG_RETURN_POINTER(rec);
-
+       }
 
        tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
        ncolumns = tupdesc->natts;
@@ -1408,6 +1410,8 @@ json_populate_record(PG_FUNCTION_ARGS)
 
        ReleaseTupleDesc(tupdesc);
 
+       hash_destroy(json_hash);
+
        PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
 }
 
@@ -1698,16 +1702,23 @@ populate_recordset_object_start(void *state)
        int                     lex_level = _state->lex->lex_level;
        HASHCTL         ctl;
 
+       /* Reject object at top level: we must have an array at level 0 */
        if (lex_level == 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                 errmsg("cannot call json_populate_recordset on an object")));
-       else if (lex_level > 1 && !_state->use_json_as_text)
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("cannot call json_populate_recordset with nested objects")));
 
-       /* set up a new hash for this entry */
+       /* Nested objects, if allowed, require no special processing */
+       if (lex_level > 1)
+       {
+               if (!_state->use_json_as_text)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("cannot call json_populate_recordset with nested objects")));
+               return;
+       }
+
+       /* Object at level 1: set up a new hash table for this object */
        memset(&ctl, 0, sizeof(ctl));
        ctl.keysize = NAMEDATALEN;
        ctl.entrysize = sizeof(JsonHashEntry);
@@ -1734,9 +1745,11 @@ populate_recordset_object_end(void *state)
        HeapTupleHeader rec = _state->rec;
        HeapTuple       rettuple;
 
+       /* Nested objects require no special processing */
        if (_state->lex->lex_level > 1)
                return;
 
+       /* Otherwise, construct and return a tuple based on this level-1 object */
        values = (Datum *) palloc(ncolumns * sizeof(Datum));
        nulls = (bool *) palloc(ncolumns * sizeof(bool));
 
@@ -1828,7 +1841,9 @@ populate_recordset_object_end(void *state)
 
        tuplestore_puttuple(_state->tuple_store, rettuple);
 
+       /* Done with hash for this object */
        hash_destroy(json_hash);
+       _state->json_hash = NULL;
 }
 
 static void
index 1a357988e479994a1415c301f74c920a57673e45..0e1d79121f97c0893705e03ab7f642721248c348 100644 (file)
@@ -901,6 +901,13 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
 
 select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
 ERROR:  invalid input syntax for type timestamp: "[100,200,300]"
+create type jpop2 as (a int, b json, c int, d int);
+select * from json_populate_recordset(null::jpop2, '[{"a":2,"c":3,"b":{"z":4},"d":6}]',true) q;
+ a |    b    | c | d 
+---+---------+---+---
+ 2 | {"z":4} | 3 | 6
+(1 row)
+
 -- using the default use_json_as_text argument
 select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
    a    | b |            c             
index 201fcb2d2049fc75d2fee0d18490f8895fb92017..f19acf755a26207cd1b8ff39c2f4b516bf15f90e 100644 (file)
@@ -901,6 +901,13 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
 
 select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
 ERROR:  invalid input syntax for type timestamp: "[100,200,300]"
+create type jpop2 as (a int, b json, c int, d int);
+select * from json_populate_recordset(null::jpop2, '[{"a":2,"c":3,"b":{"z":4},"d":6}]',true) q;
+ a |    b    | c | d 
+---+---------+---+---
+ 2 | {"z":4} | 3 | 6
+(1 row)
+
 -- using the default use_json_as_text argument
 select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
    a    | b |            c             
index 4c4c6958bbd5205f49e461d9bacb2b3b894fac67..2de144bbaf7be7e78cfc1ce8cdd8a8e7d6f16583 100644 (file)
@@ -290,6 +290,9 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","
 select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
 select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q;
 
+create type jpop2 as (a int, b json, c int, d int);
+select * from json_populate_recordset(null::jpop2, '[{"a":2,"c":3,"b":{"z":4},"d":6}]',true) q;
+
 -- using the default use_json_as_text argument
 
 select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;