diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index fe351edb2b..667f9d9563 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -225,13 +225,13 @@ struct RecordIOData ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER]; }; -/* per-query cache for populate_recordset */ -typedef struct PopulateRecordsetCache +/* per-query cache for populate_record_worker and populate_recordset_worker */ +typedef struct PopulateRecordCache { Oid argtype; /* declared type of the record argument */ ColumnIOData c; /* metadata cache for populate_composite() */ MemoryContext fn_mcxt; /* where this is stored */ -} PopulateRecordsetCache; +} PopulateRecordCache; /* per-call state for populate_recordset */ typedef struct PopulateRecordsetState @@ -244,16 +244,9 @@ typedef struct PopulateRecordsetState JsonTokenType saved_token_type; Tuplestorestate *tuple_store; HeapTupleHeader rec; - PopulateRecordsetCache *cache; + PopulateRecordCache *cache; } PopulateRecordsetState; -/* structure to cache metadata needed for populate_record_worker() */ -typedef struct PopulateRecordCache -{ - Oid argtype; /* declared type of the record argument */ - ColumnIOData c; /* metadata cache for populate_composite() */ -} PopulateRecordCache; - /* common data for populate_array_json() and populate_array_dim_jsonb() */ typedef struct PopulateArrayContext { @@ -429,6 +422,12 @@ static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcnam static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p, HeapTupleHeader defaultval, MemoryContext mcxt, JsObject *obj); +static void get_record_type_from_argument(FunctionCallInfo fcinfo, + const char *funcname, + PopulateRecordCache *cache); +static void get_record_type_from_query(FunctionCallInfo fcinfo, + const char *funcname, + PopulateRecordCache *cache); static void JsValueToJsObject(JsValue *jsv, JsObject *jso); static Datum populate_composite(CompositeIOData *io, Oid typid, const char *colname, MemoryContext mcxt, @@ -3202,6 +3201,70 @@ populate_record(TupleDesc tupdesc, return res->t_data; } +/* + * Setup for json{b}_populate_record{set}: result type will be same as first + * argument's type --- unless first argument is "null::record", which we can't + * extract type info from; we handle that later. + */ +static void +get_record_type_from_argument(FunctionCallInfo fcinfo, + const char *funcname, + PopulateRecordCache *cache) +{ + cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + prepare_column_cache(&cache->c, + cache->argtype, -1, + cache->fn_mcxt, false); + if (cache->c.typcat != TYPECAT_COMPOSITE && + cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + /* translator: %s is a function name, eg json_to_record */ + errmsg("first argument of %s must be a row type", + funcname))); +} + +/* + * Setup for json{b}_to_record{set}: result type is specified by calling + * query. We'll also use this code for json{b}_populate_record{set}, + * if we discover that the first argument is a null of type RECORD. + * + * Here it is syntactically impossible to specify the target type + * as domain-over-composite. + */ +static void +get_record_type_from_query(FunctionCallInfo fcinfo, + const char *funcname, + PopulateRecordCache *cache) +{ + TupleDesc tupdesc; + MemoryContext old_cxt; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + /* translator: %s is a function name, eg json_to_record */ + errmsg("could not determine row type for result of %s", + funcname), + errhint("Provide a non-null record argument, " + "or call the function in the FROM clause " + "using a column definition list."))); + + Assert(tupdesc); + cache->argtype = tupdesc->tdtypeid; + + /* If we go through this more than once, avoid memory leak */ + if (cache->c.io.composite.tupdesc) + FreeTupleDesc(cache->c.io.composite.tupdesc); + + /* Save identified tupdesc */ + old_cxt = MemoryContextSwitchTo(cache->fn_mcxt); + cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc); + cache->c.io.composite.base_typid = tupdesc->tdtypeid; + cache->c.io.composite.base_typmod = tupdesc->tdtypmod; + MemoryContextSwitchTo(old_cxt); +} + /* * common worker for json{b}_populate_record() and json{b}_to_record() * is_json and have_record_arg identify the specific function @@ -3227,63 +3290,24 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, { fcinfo->flinfo->fn_extra = cache = MemoryContextAllocZero(fnmcxt, sizeof(*cache)); + cache->fn_mcxt = fnmcxt; if (have_record_arg) - { - /* - * json{b}_populate_record case: result type will be same as first - * argument's. - */ - cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); - prepare_column_cache(&cache->c, - cache->argtype, -1, - fnmcxt, false); - if (cache->c.typcat != TYPECAT_COMPOSITE && - cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("first argument of %s must be a row type", - funcname))); - } + get_record_type_from_argument(fcinfo, funcname, cache); else - { - /* - * json{b}_to_record case: result type is specified by calling - * query. Here it is syntactically impossible to specify the - * target type as domain-over-composite. - */ - TupleDesc tupdesc; - MemoryContext old_cxt; - - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("function returning record called in context " - "that cannot accept type record"), - errhint("Try calling the function in the FROM clause " - "using a column definition list."))); - - Assert(tupdesc); - cache->argtype = tupdesc->tdtypeid; - - /* Save identified tupdesc */ - old_cxt = MemoryContextSwitchTo(fnmcxt); - cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc); - cache->c.io.composite.base_typid = tupdesc->tdtypeid; - cache->c.io.composite.base_typmod = tupdesc->tdtypmod; - MemoryContextSwitchTo(old_cxt); - } + get_record_type_from_query(fcinfo, funcname, cache); } /* Collect record arg if we have one */ - if (have_record_arg && !PG_ARGISNULL(0)) + if (!have_record_arg) + rec = NULL; /* it's json{b}_to_record() */ + else if (!PG_ARGISNULL(0)) { rec = PG_GETARG_HEAPTUPLEHEADER(0); /* * When declared arg type is RECORD, identify actual record type from - * the tuple itself. Note the lookup_rowtype_tupdesc call in - * update_cached_tupdesc will fail if we're unable to do this. + * the tuple itself. */ if (cache->argtype == RECORDOID) { @@ -3292,8 +3316,21 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, } } else + { rec = NULL; + /* + * When declared arg type is RECORD, identify actual record type from + * calling query, or fail if we can't. + */ + if (cache->argtype == RECORDOID) + { + get_record_type_from_query(fcinfo, funcname, cache); + /* This can't change argtype, which is important for next time */ + Assert(cache->argtype == RECORDOID); + } + } + /* If no JSON argument, just return the record (if any) unchanged */ if (PG_ARGISNULL(json_arg_num)) { @@ -3517,7 +3554,7 @@ json_to_recordset(PG_FUNCTION_ARGS) static void populate_recordset_record(PopulateRecordsetState *state, JsObject *obj) { - PopulateRecordsetCache *cache = state->cache; + PopulateRecordCache *cache = state->cache; HeapTupleHeader tuphead; HeapTupleData tuple; @@ -3559,7 +3596,7 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, ReturnSetInfo *rsi; MemoryContext old_cxt; HeapTupleHeader rec; - PopulateRecordsetCache *cache = fcinfo->flinfo->fn_extra; + PopulateRecordCache *cache = fcinfo->flinfo->fn_extra; PopulateRecordsetState *state; rsi = (ReturnSetInfo *) fcinfo->resultinfo; @@ -3585,60 +3622,21 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, cache->fn_mcxt = fcinfo->flinfo->fn_mcxt; if (have_record_arg) - { - /* - * json{b}_populate_recordset case: result type will be same as - * first argument's. - */ - cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); - prepare_column_cache(&cache->c, - cache->argtype, -1, - cache->fn_mcxt, false); - if (cache->c.typcat != TYPECAT_COMPOSITE && - cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("first argument of %s must be a row type", - funcname))); - } + get_record_type_from_argument(fcinfo, funcname, cache); else - { - /* - * json{b}_to_recordset case: result type is specified by calling - * query. Here it is syntactically impossible to specify the - * target type as domain-over-composite. - */ - TupleDesc tupdesc; - - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("function returning record called in context " - "that cannot accept type record"), - errhint("Try calling the function in the FROM clause " - "using a column definition list."))); - - Assert(tupdesc); - cache->argtype = tupdesc->tdtypeid; - - /* Save identified tupdesc */ - old_cxt = MemoryContextSwitchTo(cache->fn_mcxt); - cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc); - cache->c.io.composite.base_typid = tupdesc->tdtypeid; - cache->c.io.composite.base_typmod = tupdesc->tdtypmod; - MemoryContextSwitchTo(old_cxt); - } + get_record_type_from_query(fcinfo, funcname, cache); } /* Collect record arg if we have one */ - if (have_record_arg && !PG_ARGISNULL(0)) + if (!have_record_arg) + rec = NULL; /* it's json{b}_to_recordset() */ + else if (!PG_ARGISNULL(0)) { rec = PG_GETARG_HEAPTUPLEHEADER(0); /* * When declared arg type is RECORD, identify actual record type from - * the tuple itself. Note the lookup_rowtype_tupdesc call in - * update_cached_tupdesc will fail if we're unable to do this. + * the tuple itself. */ if (cache->argtype == RECORDOID) { @@ -3647,8 +3645,21 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, } } else + { rec = NULL; + /* + * When declared arg type is RECORD, identify actual record type from + * calling query, or fail if we can't. + */ + if (cache->argtype == RECORDOID) + { + get_record_type_from_query(fcinfo, funcname, cache); + /* This can't change argtype, which is important for next time */ + Assert(cache->argtype == RECORDOID); + } + } + /* if the json is null send back an empty set */ if (PG_ARGISNULL(json_arg_num)) PG_RETURN_NULL(); diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index 5b8e67784f..69b0a6ee6d 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -1744,13 +1744,21 @@ SELECT rec FROM json_populate_record( -- anonymous record type SELECT json_populate_record(null::record, '{"x": 0, "y": 1}'); -ERROR: record type has not been registered +ERROR: could not determine row type for result of json_populate_record +HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list. SELECT json_populate_record(row(1,2), '{"f1": 0, "f2": 1}'); json_populate_record ---------------------- (0,1) (1 row) +SELECT * FROM + json_populate_record(null::record, '{"x": 776}') AS (x int, y int); + x | y +-----+--- + 776 | +(1 row) + -- composite domain SELECT json_populate_record(null::j_ordered_pair, '{"x": 0, "y": 1}'); json_populate_record @@ -1834,7 +1842,8 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3 -- anonymous record type SELECT json_populate_recordset(null::record, '[{"x": 0, "y": 1}]'); -ERROR: record type has not been registered +ERROR: could not determine row type for result of json_populate_recordset +HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list. SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]'); json_populate_recordset ------------------------- @@ -1851,9 +1860,17 @@ FROM (VALUES (1),(2)) v(i); 2 | (2,43) (4 rows) +SELECT * FROM + json_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int); + x | y +-----+--- + 776 | +(1 row) + -- empty array is a corner case SELECT json_populate_recordset(null::record, '[]'); -ERROR: record type has not been registered +ERROR: could not determine row type for result of json_populate_recordset +HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list. SELECT json_populate_recordset(row(1,2), '[]'); json_populate_recordset ------------------------- @@ -1864,6 +1881,12 @@ SELECT * FROM json_populate_recordset(NULL::jpop,'[]') q; ---+---+--- (0 rows) +SELECT * FROM + json_populate_recordset(null::record, '[]') AS (x int, y int); + x | y +---+--- +(0 rows) + -- composite domain SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]'); json_populate_recordset diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index 469079c5d8..0f91755794 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -2433,13 +2433,21 @@ SELECT rec FROM jsonb_populate_record( -- anonymous record type SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}'); -ERROR: record type has not been registered +ERROR: could not determine row type for result of jsonb_populate_record +HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list. SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}'); jsonb_populate_record ----------------------- (0,1) (1 row) +SELECT * FROM + jsonb_populate_record(null::record, '{"x": 776}') AS (x int, y int); + x | y +-----+--- + 776 | +(1 row) + -- composite domain SELECT jsonb_populate_record(null::jb_ordered_pair, '{"x": 0, "y": 1}'); jsonb_populate_record @@ -2516,7 +2524,8 @@ SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200 -- anonymous record type SELECT jsonb_populate_recordset(null::record, '[{"x": 0, "y": 1}]'); -ERROR: record type has not been registered +ERROR: could not determine row type for result of jsonb_populate_recordset +HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list. SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]'); jsonb_populate_recordset -------------------------- @@ -2533,9 +2542,17 @@ FROM (VALUES (1),(2)) v(i); 2 | (2,43) (4 rows) +SELECT * FROM + jsonb_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int); + x | y +-----+--- + 776 | +(1 row) + -- empty array is a corner case SELECT jsonb_populate_recordset(null::record, '[]'); -ERROR: record type has not been registered +ERROR: could not determine row type for result of jsonb_populate_recordset +HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list. SELECT jsonb_populate_recordset(row(1,2), '[]'); jsonb_populate_recordset -------------------------- @@ -2546,6 +2563,12 @@ SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[]') q; ---+---+--- (0 rows) +SELECT * FROM + jsonb_populate_recordset(null::record, '[]') AS (x int, y int); + x | y +---+--- +(0 rows) + -- composite domain SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]'); jsonb_populate_recordset diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index a52beaa27a..e021664592 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -522,6 +522,8 @@ SELECT rec FROM json_populate_record( -- anonymous record type SELECT json_populate_record(null::record, '{"x": 0, "y": 1}'); SELECT json_populate_record(row(1,2), '{"f1": 0, "f2": 1}'); +SELECT * FROM + json_populate_record(null::record, '{"x": 776}') AS (x int, y int); -- composite domain SELECT json_populate_record(null::j_ordered_pair, '{"x": 0, "y": 1}'); @@ -549,11 +551,15 @@ SELECT json_populate_recordset(null::record, '[{"x": 0, "y": 1}]'); SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]'); SELECT i, json_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]') FROM (VALUES (1),(2)) v(i); +SELECT * FROM + json_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int); -- empty array is a corner case SELECT json_populate_recordset(null::record, '[]'); SELECT json_populate_recordset(row(1,2), '[]'); SELECT * FROM json_populate_recordset(NULL::jpop,'[]') q; +SELECT * FROM + json_populate_recordset(null::record, '[]') AS (x int, y int); -- composite domain SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]'); diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index ba870872e8..58703a90b7 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -642,6 +642,8 @@ SELECT rec FROM jsonb_populate_record( -- anonymous record type SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}'); SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}'); +SELECT * FROM + jsonb_populate_record(null::record, '{"x": 776}') AS (x int, y int); -- composite domain SELECT jsonb_populate_record(null::jb_ordered_pair, '{"x": 0, "y": 1}'); @@ -665,11 +667,15 @@ SELECT jsonb_populate_recordset(null::record, '[{"x": 0, "y": 1}]'); SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]'); SELECT i, jsonb_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]') FROM (VALUES (1),(2)) v(i); +SELECT * FROM + jsonb_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int); -- empty array is a corner case SELECT jsonb_populate_recordset(null::record, '[]'); SELECT jsonb_populate_recordset(row(1,2), '[]'); SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[]') q; +SELECT * FROM + jsonb_populate_recordset(null::record, '[]') AS (x int, y int); -- composite domain SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]');