* jsonfuncs.c
* Functions to process JSON data types.
*
- * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
int sent_count;
} OkeysState;
-/* state for iterate_json_string_values function */
+/* state for iterate_json_values function */
typedef struct IterateJsonStringValuesState
{
JsonLexContext *lex;
JsonIterateStringValuesAction action; /* an action that will be applied
* to each json value */
void *action_state; /* any necessary context for iteration */
- uint32 flags; /* what kind of elements from a json we want to iterate */
+ uint32 flags; /* what kind of elements from a json we want
+ * to iterate */
} IterateJsonStringValuesState;
/* state for transform_json_string_values function */
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
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
{
/* common worker function for json getter functions */
static Datum get_path_all(FunctionCallInfo fcinfo, bool as_text);
static text *get_worker(text *json, char **tpath, int *ipath, int npath,
- bool normalize_results);
+ bool normalize_results);
static Datum get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text);
+static text *JsonbValueAsText(JsonbValue *v);
/* semantic action functions for json_array_length */
static void alen_object_start(void *state);
/* common workers for json{b}_each* functions */
static Datum each_worker(FunctionCallInfo fcinfo, bool as_text);
static Datum each_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname,
- bool as_text);
+ bool as_text);
/* semantic action functions for json_each */
static void each_object_field_start(void *state, char *fname, bool isnull);
/* common workers for json{b}_array_elements_* functions */
static Datum elements_worker(FunctionCallInfo fcinfo, const char *funcname,
- bool as_text);
+ bool as_text);
static Datum elements_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname,
- bool as_text);
+ bool as_text);
/* semantic action functions for json_array_elements */
static void elements_object_start(void *state);
/* worker functions for populate_record, to_record, populate_recordset and to_recordset */
static Datum populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
- bool have_record_arg);
+ bool is_json, bool have_record_arg);
static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
- bool have_record_arg);
+ bool is_json, bool have_record_arg);
/* helper functions for populate_record[set] */
static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p,
- HeapTupleHeader defaultval, MemoryContext mcxt,
- JsObject *obj);
+ 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,
- HeapTupleHeader defaultval, JsValue *jsv, bool isnull);
+ const char *colname, MemoryContext mcxt,
+ HeapTupleHeader defaultval, JsValue *jsv, bool isnull);
static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv);
static void prepare_column_cache(ColumnIOData *column, Oid typid, int32 typmod,
- MemoryContext mcxt, bool need_scalar);
+ MemoryContext mcxt, bool need_scalar);
static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod,
- const char *colname, MemoryContext mcxt, Datum defaultval,
- JsValue *jsv, bool *isnull);
+ const char *colname, MemoryContext mcxt, Datum defaultval,
+ JsValue *jsv, bool *isnull);
static RecordIOData *allocate_record_info(MemoryContext mcxt, int ncolumns);
static bool JsObjectGetField(JsObject *obj, char *field, JsValue *jsv);
static void populate_recordset_record(PopulateRecordsetState *state, JsObject *obj);
static void populate_array_json(PopulateArrayContext *ctx, char *json, int len);
static void populate_array_dim_jsonb(PopulateArrayContext *ctx, JsonbValue *jbv,
- int ndim);
+ int ndim);
static void populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim);
static void populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims);
static void populate_array_check_dimension(PopulateArrayContext *ctx, int ndim);
static void populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv);
static Datum populate_array(ArrayIOData *aio, const char *colname,
- MemoryContext mcxt, JsValue *jsv);
+ MemoryContext mcxt, JsValue *jsv);
static Datum populate_domain(DomainIOData *io, Oid typid, const char *colname,
- MemoryContext mcxt, JsValue *jsv, bool isnull);
-
-/* Worker that takes care of common setup for us */
-static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container,
- uint32 flags,
- char *key,
- uint32 keylen);
+ MemoryContext mcxt, JsValue *jsv, bool isnull);
/* functions supporting jsonb_delete, jsonb_set and jsonb_concat */
static JsonbValue *IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
- JsonbParseState **state);
+ JsonbParseState **state);
static JsonbValue *setPath(JsonbIterator **it, Datum *path_elems,
- bool *path_nulls, int path_len,
- JsonbParseState **st, int level, Jsonb *newval,
- int op_type);
+ bool *path_nulls, int path_len,
+ JsonbParseState **st, int level, Jsonb *newval,
+ int op_type);
static void setPathObject(JsonbIterator **it, Datum *path_elems,
- bool *path_nulls, int path_len, JsonbParseState **st,
- int level,
- Jsonb *newval, uint32 npairs, int op_type);
+ bool *path_nulls, int path_len, JsonbParseState **st,
+ int level,
+ Jsonb *newval, uint32 npairs, int op_type);
static void setPathArray(JsonbIterator **it, Datum *path_elems,
- bool *path_nulls, int path_len, JsonbParseState **st,
- int level, Jsonb *newval, uint32 nelems, int op_type);
+ bool *path_nulls, int path_len, JsonbParseState **st,
+ int level, Jsonb *newval, uint32 nelems, int op_type);
static void addJsonbToParseState(JsonbParseState **jbps, Jsonb *jb);
/* function supporting iterate_json_values */
Jsonb *jb = PG_GETARG_JSONB_P(0);
text *key = PG_GETARG_TEXT_PP(1);
JsonbValue *v;
+ JsonbValue vbuf;
if (!JB_ROOT_IS_OBJECT(jb))
PG_RETURN_NULL();
- v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT,
- VARDATA_ANY(key),
- VARSIZE_ANY_EXHDR(key));
+ v = getKeyJsonValueFromContainer(&jb->root,
+ VARDATA_ANY(key),
+ VARSIZE_ANY_EXHDR(key),
+ &vbuf);
if (v != NULL)
PG_RETURN_JSONB_P(JsonbValueToJsonb(v));
Jsonb *jb = PG_GETARG_JSONB_P(0);
text *key = PG_GETARG_TEXT_PP(1);
JsonbValue *v;
+ JsonbValue vbuf;
if (!JB_ROOT_IS_OBJECT(jb))
PG_RETURN_NULL();
- v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT,
- VARDATA_ANY(key),
- VARSIZE_ANY_EXHDR(key));
-
- if (v != NULL)
- {
- text *result = NULL;
-
- switch (v->type)
- {
- case jbvNull:
- break;
- case jbvBool:
- result = cstring_to_text(v->val.boolean ? "true" : "false");
- break;
- case jbvString:
- result = cstring_to_text_with_len(v->val.string.val, v->val.string.len);
- break;
- case jbvNumeric:
- result = cstring_to_text(DatumGetCString(DirectFunctionCall1(numeric_out,
- PointerGetDatum(v->val.numeric))));
- break;
- case jbvBinary:
- {
- StringInfo jtext = makeStringInfo();
-
- (void) JsonbToCString(jtext, v->val.binary.data, -1);
- result = cstring_to_text_with_len(jtext->data, jtext->len);
- }
- break;
- default:
- elog(ERROR, "unrecognized jsonb type: %d", (int) v->type);
- }
+ v = getKeyJsonValueFromContainer(&jb->root,
+ VARDATA_ANY(key),
+ VARSIZE_ANY_EXHDR(key),
+ &vbuf);
- if (result)
- PG_RETURN_TEXT_P(result);
- }
+ if (v != NULL && v->type != jbvNull)
+ PG_RETURN_TEXT_P(JsonbValueAsText(v));
PG_RETURN_NULL();
}
}
v = getIthJsonbValueFromContainer(&jb->root, element);
- if (v != NULL)
- {
- text *result = NULL;
- switch (v->type)
- {
- case jbvNull:
- break;
- case jbvBool:
- result = cstring_to_text(v->val.boolean ? "true" : "false");
- break;
- case jbvString:
- result = cstring_to_text_with_len(v->val.string.val, v->val.string.len);
- break;
- case jbvNumeric:
- result = cstring_to_text(DatumGetCString(DirectFunctionCall1(numeric_out,
- PointerGetDatum(v->val.numeric))));
- break;
- case jbvBinary:
- {
- StringInfo jtext = makeStringInfo();
-
- (void) JsonbToCString(jtext, v->val.binary.data, -1);
- result = cstring_to_text_with_len(jtext->data, jtext->len);
- }
- break;
- default:
- elog(ERROR, "unrecognized jsonb type: %d", (int) v->type);
- }
-
- if (result)
- PG_RETURN_TEXT_P(result);
- }
+ if (v != NULL && v->type != jbvNull)
+ PG_RETURN_TEXT_P(JsonbValueAsText(v));
PG_RETURN_NULL();
}
{
Jsonb *jb = PG_GETARG_JSONB_P(0);
ArrayType *path = PG_GETARG_ARRAYTYPE_P(1);
- Jsonb *res;
Datum *pathtext;
bool *pathnulls;
int npath;
bool have_object = false,
have_array = false;
JsonbValue *jbvp = NULL;
- JsonbValue tv;
+ JsonbValue jbvbuf;
JsonbContainer *container;
/*
{
if (have_object)
{
- jbvp = findJsonbValueFromContainerLen(container,
- JB_FOBJECT,
- VARDATA(pathtext[i]),
- VARSIZE(pathtext[i]) - VARHDRSZ);
+ jbvp = getKeyJsonValueFromContainer(container,
+ VARDATA(pathtext[i]),
+ VARSIZE(pathtext[i]) - VARHDRSZ,
+ &jbvbuf);
}
else if (have_array)
{
if (jbvp->type == jbvBinary)
{
- JsonbIterator *it = JsonbIteratorInit((JsonbContainer *) jbvp->val.binary.data);
- JsonbIteratorToken r;
-
- r = JsonbIteratorNext(&it, &tv, true);
- container = (JsonbContainer *) jbvp->val.binary.data;
- have_object = r == WJB_BEGIN_OBJECT;
- have_array = r == WJB_BEGIN_ARRAY;
+ container = jbvp->val.binary.data;
+ have_object = JsonContainerIsObject(container);
+ have_array = JsonContainerIsArray(container);
+ Assert(!JsonContainerIsScalar(container));
}
else
{
- have_object = jbvp->type == jbvObject;
- have_array = jbvp->type == jbvArray;
+ Assert(IsAJsonbScalar(jbvp));
+ have_object = false;
+ have_array = false;
}
}
if (as_text)
{
- /* special-case outputs for string and null values */
- if (jbvp->type == jbvString)
- PG_RETURN_TEXT_P(cstring_to_text_with_len(jbvp->val.string.val,
- jbvp->val.string.len));
if (jbvp->type == jbvNull)
PG_RETURN_NULL();
- }
-
- res = JsonbValueToJsonb(jbvp);
- if (as_text)
- {
- PG_RETURN_TEXT_P(cstring_to_text(JsonbToCString(NULL,
- &res->root,
- VARSIZE(res))));
+ PG_RETURN_TEXT_P(JsonbValueAsText(jbvp));
}
else
{
+ Jsonb *res = JsonbValueToJsonb(jbvp);
+
/* not text mode - just hand back the jsonb */
PG_RETURN_JSONB_P(res);
}
}
+/*
+ * Return the text representation of the given JsonbValue.
+ */
+static text *
+JsonbValueAsText(JsonbValue *v)
+{
+ switch (v->type)
+ {
+ case jbvNull:
+ return NULL;
+
+ case jbvBool:
+ return v->val.boolean ?
+ cstring_to_text_with_len("true", 4) :
+ cstring_to_text_with_len("false", 5);
+
+ case jbvString:
+ return cstring_to_text_with_len(v->val.string.val,
+ v->val.string.len);
+
+ case jbvNumeric:
+ {
+ Datum cstr;
+
+ cstr = DirectFunctionCall1(numeric_out,
+ PointerGetDatum(v->val.numeric));
+
+ return cstring_to_text(DatumGetCString(cstr));
+ }
+
+ case jbvBinary:
+ {
+ StringInfoData jtext;
+
+ initStringInfo(&jtext);
+ (void) JsonbToCString(&jtext, v->val.binary.data,
+ v->val.binary.len);
+
+ return cstring_to_text_with_len(jtext.data, jtext.len);
+ }
+
+ default:
+ elog(ERROR, "unrecognized jsonb type: %d", (int) v->type);
+ return NULL;
+ }
+}
+
/*
* SQL function json_array_length(json) -> int
*/
* matter what shape it is.
*/
r = JsonbIteratorNext(&it, &v, skipNested);
+ Assert(r != WJB_DONE);
values[0] = PointerGetDatum(key);
values[1] = (Datum) NULL;
}
else
- {
- text *sv;
-
- if (v.type == jbvString)
- {
- /* In text mode, scalar strings should be dequoted */
- sv = cstring_to_text_with_len(v.val.string.val, v.val.string.len);
- }
- else
- {
- /* Turn anything else into a json string */
- StringInfo jtext = makeStringInfo();
- Jsonb *jb = JsonbValueToJsonb(&v);
-
- (void) JsonbToCString(jtext, &jb->root, 0);
- sv = cstring_to_text_with_len(jtext->data, jtext->len);
- }
-
- values[1] = PointerGetDatum(sv);
- }
+ values[1] = PointerGetDatum(JsonbValueAsText(&v));
}
else
{
/* use the tmp context so we can clean up after each tuple is done */
old_cxt = MemoryContextSwitchTo(tmp_cxt);
- if (!as_text)
- {
- Jsonb *val = JsonbValueToJsonb(&v);
-
- values[0] = PointerGetDatum(val);
- }
- else
+ if (as_text)
{
if (v.type == jbvNull)
{
values[0] = (Datum) NULL;
}
else
- {
- text *sv;
-
- if (v.type == jbvString)
- {
- /* in text mode scalar strings should be dequoted */
- sv = cstring_to_text_with_len(v.val.string.val, v.val.string.len);
- }
- else
- {
- /* turn anything else into a json string */
- StringInfo jtext = makeStringInfo();
- Jsonb *jb = JsonbValueToJsonb(&v);
-
- (void) JsonbToCString(jtext, &jb->root, 0);
- sv = cstring_to_text_with_len(jtext->data, jtext->len);
- }
+ values[0] = PointerGetDatum(JsonbValueAsText(&v));
+ }
+ else
+ {
+ /* Not in text mode, just return the Jsonb */
+ Jsonb *val = JsonbValueToJsonb(&v);
- values[0] = PointerGetDatum(sv);
- }
+ values[0] = PointerGetDatum(val);
}
tuple = heap_form_tuple(ret_tdesc, values, nulls);
Datum
jsonb_populate_record(PG_FUNCTION_ARGS)
{
- return populate_record_worker(fcinfo, "jsonb_populate_record", true);
+ return populate_record_worker(fcinfo, "jsonb_populate_record",
+ false, true);
}
Datum
jsonb_to_record(PG_FUNCTION_ARGS)
{
- return populate_record_worker(fcinfo, "jsonb_to_record", false);
+ return populate_record_worker(fcinfo, "jsonb_to_record",
+ false, false);
}
Datum
json_populate_record(PG_FUNCTION_ARGS)
{
- return populate_record_worker(fcinfo, "json_populate_record", true);
+ return populate_record_worker(fcinfo, "json_populate_record",
+ true, true);
}
Datum
json_to_record(PG_FUNCTION_ARGS)
{
- return populate_record_worker(fcinfo, "json_to_record", false);
+ return populate_record_worker(fcinfo, "json_to_record",
+ true, false);
}
/* helper function for diagnostics */
if (ctx->colname)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("expected json array"),
+ errmsg("expected JSON array"),
errhint("See the value of key \"%s\".", ctx->colname)));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("expected json array")));
+ errmsg("expected JSON array")));
}
else
{
if (ctx->colname)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("expected json array"),
+ errmsg("expected JSON array"),
errhint("See the array element %s of key \"%s\".",
indices.data, ctx->colname)));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("expected json array"),
+ errmsg("expected JSON array"),
errhint("See the array element %s.",
indices.data)));
}
else if (ctx->dims[ndim] != dim)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("malformed json array"),
+ errmsg("malformed JSON array"),
errdetail("Multidimensional arrays must have "
"sub-arrays with matching dimensions.")));
json = jsv->val.json.str;
Assert(json);
-
- /* already done the hard work in the json case */
- if ((typid == JSONOID || typid == JSONBOID) &&
- jsv->val.json.type == JSON_TOKEN_STRING)
- {
- /*
- * Add quotes around string value (should be already escaped) if
- * converting to json/jsonb.
- */
-
- if (len < 0)
- len = strlen(json);
-
- str = palloc(len + sizeof(char) * 3);
- str[0] = '"';
- memcpy(&str[1], json, len);
- str[len + 1] = '"';
- str[len + 2] = '\0';
- }
- else if (len >= 0)
+ if (len >= 0)
{
/* Need to copy non-null-terminated string */
str = palloc(len + 1 * sizeof(char));
str[len] = '\0';
}
else
- str = json; /* null-terminated string */
+ str = json; /* string is already null-terminated */
+
+ /* If converting to json/jsonb, make string into valid JSON literal */
+ if ((typid == JSONOID || typid == JSONBOID) &&
+ jsv->val.json.type == JSON_TOKEN_STRING)
+ {
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ escape_json(&buf, str);
+ /* free temporary buffer */
+ if (str != json)
+ pfree(str);
+ str = buf.data;
+ }
}
else
{
else
{
jsv->val.jsonb = !obj->val.jsonb_cont ? NULL :
- findJsonbValueFromContainerLen(obj->val.jsonb_cont, JB_FOBJECT,
- field, strlen(field));
+ getKeyJsonValueFromContainer(obj->val.jsonb_cont, field, strlen(field),
+ NULL);
return jsv->val.jsonb != NULL;
}
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
+ */
static Datum
populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
- bool have_record_arg)
+ bool is_json, bool have_record_arg)
{
int json_arg_num = have_record_arg ? 1 : 0;
- Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num);
JsValue jsv = {0};
HeapTupleHeader rec;
Datum rettuple;
MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt;
PopulateRecordCache *cache = fcinfo->flinfo->fn_extra;
- Assert(jtype == JSONOID || jtype == JSONBOID);
-
/*
* If first time through, identify input/result record type. Note that
* this stanza looks only at fcinfo context, which can't change during the
{
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)
{
}
}
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))
{
PG_RETURN_NULL();
}
- jsv.is_json = jtype == JSONOID;
+ jsv.is_json = is_json;
- if (jsv.is_json)
+ if (is_json)
{
text *json = PG_GETARG_TEXT_PP(json_arg_num);
Datum
jsonb_populate_recordset(PG_FUNCTION_ARGS)
{
- return populate_recordset_worker(fcinfo, "jsonb_populate_recordset", true);
+ return populate_recordset_worker(fcinfo, "jsonb_populate_recordset",
+ false, true);
}
Datum
jsonb_to_recordset(PG_FUNCTION_ARGS)
{
- return populate_recordset_worker(fcinfo, "jsonb_to_recordset", false);
+ return populate_recordset_worker(fcinfo, "jsonb_to_recordset",
+ false, false);
}
Datum
json_populate_recordset(PG_FUNCTION_ARGS)
{
- return populate_recordset_worker(fcinfo, "json_populate_recordset", true);
+ return populate_recordset_worker(fcinfo, "json_populate_recordset",
+ true, true);
}
Datum
json_to_recordset(PG_FUNCTION_ARGS)
{
- return populate_recordset_worker(fcinfo, "json_to_recordset", false);
+ return populate_recordset_worker(fcinfo, "json_to_recordset",
+ true, false);
}
static void
populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
{
- PopulateRecordsetCache *cache = state->cache;
+ PopulateRecordCache *cache = state->cache;
HeapTupleHeader tuphead;
HeapTupleData tuple;
}
/*
- * common worker for json_populate_recordset() and json_to_recordset()
+ * common worker for json{b}_populate_recordset() and json{b}_to_recordset()
+ * is_json and have_record_arg identify the specific function
*/
static Datum
populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
- bool have_record_arg)
+ bool is_json, bool have_record_arg)
{
int json_arg_num = have_record_arg ? 1 : 0;
- Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num);
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;
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)
{
}
}
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();
+ /*
+ * Forcibly update the cached tupdesc, to ensure we have the right tupdesc
+ * to return even if the JSON contains no rows.
+ */
+ update_cached_tupdesc(&cache->c.io.composite, cache->fn_mcxt);
+
state = palloc0(sizeof(PopulateRecordsetState));
/* make tuplestore in a sufficiently long-lived memory context */
state->cache = cache;
state->rec = rec;
- if (jtype == JSONOID)
+ if (is_json)
{
text *json = PG_GETARG_TEXT_PP(json_arg_num);
JsonLexContext *lex;
bool skipNested = false;
JsonbIteratorToken r;
- Assert(jtype == JSONBOID);
-
if (JB_ROOT_IS_SCALAR(jb) || !JB_ROOT_IS_ARRAY(jb))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
}
}
+ /*
+ * Note: we must copy the cached tupdesc because the executor will free
+ * the passed-back setDesc, but we want to hang onto the cache in case
+ * we're called again in the same query.
+ */
rsi->setResult = state->tuple_store;
- rsi->setDesc = cache->c.io.composite.tupdesc;
+ rsi->setDesc = CreateTupleDescCopy(cache->c.io.composite.tupdesc);
PG_RETURN_NULL();
}
}
}
-/*
- * findJsonbValueFromContainer() wrapper that sets up JsonbValue key string.
- */
-static JsonbValue *
-findJsonbValueFromContainerLen(JsonbContainer *container, uint32 flags,
- char *key, uint32 keylen)
-{
- JsonbValue k;
-
- k.type = jbvString;
- k.val.string.val = key;
- k.val.string.len = keylen;
-
- return findJsonbValueFromContainer(container, flags, &k);
-}
-
/*
* Semantic actions for json_strip_nulls.
*
if (JB_ROOT_IS_SCALAR(jb))
{
(void) JsonbIteratorNext(&it, &v, false); /* skip array header */
+ Assert(v.type == jbvArray);
(void) JsonbIteratorNext(&it, &v, false); /* fetch scalar value */
switch (o->type)
it = JsonbIteratorInit(&in->root);
- while ((r = JsonbIteratorNext(&it, &v, skipNested)) != 0)
+ while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
{
skipNested = true;
{
/* skip corresponding value as well */
if (r == WJB_KEY)
- JsonbIteratorNext(&it, &v, true);
+ (void) JsonbIteratorNext(&it, &v, true);
continue;
}
it = JsonbIteratorInit(&in->root);
- while ((r = JsonbIteratorNext(&it, &v, skipNested)) != 0)
+ while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
{
skipNested = true;
{
/* skip corresponding value as well */
if (r == WJB_KEY)
- JsonbIteratorNext(&it, &v, true);
+ (void) JsonbIteratorNext(&it, &v, true);
continue;
}
pushJsonbValue(&state, r, NULL);
- while ((r = JsonbIteratorNext(&it, &v, true)) != 0)
+ while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
{
if (r == WJB_ELEM)
{
/*
* SQL function jsonb_set(jsonb, text[], jsonb, boolean)
- *
*/
Datum
jsonb_set(PG_FUNCTION_ARGS)
/*
* SQL function jsonb_insert(jsonb, text[], jsonb, boolean)
- *
*/
Datum
jsonb_insert(PG_FUNCTION_ARGS)
* Append the all tokens from v2 to res, include last WJB_END_OBJECT
* (the concatenation will be completed).
*/
- while ((r2 = JsonbIteratorNext(it2, &v2, true)) != 0)
+ while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
res = pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
}
if (prepend)
{
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
- while ((r1 = JsonbIteratorNext(it_object, &v1, true)) != 0)
+ while ((r1 = JsonbIteratorNext(it_object, &v1, true)) != WJB_DONE)
pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
- while ((r2 = JsonbIteratorNext(it_array, &v2, true)) != 0)
+ while ((r2 = JsonbIteratorNext(it_array, &v2, true)) != WJB_DONE)
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
}
else
pushJsonbValue(state, r1, &v1);
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
- while ((r2 = JsonbIteratorNext(it_object, &v2, true)) != 0)
+ while ((r2 = JsonbIteratorNext(it_object, &v2, true)) != WJB_DONE)
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
uint32
parse_jsonb_index_flags(Jsonb *jb)
{
- JsonbIterator *it;
- JsonbValue v;
- JsonbIteratorToken type;
- uint32 flags = 0;
+ JsonbIterator *it;
+ JsonbValue v;
+ JsonbIteratorToken type;
+ uint32 flags = 0;
it = JsonbIteratorInit(&jb->root);
type = JsonbIteratorNext(&it, &v, false);
/*
- * We iterate over array (scalar internally is represented as array, so, we
- * will accept it too) to check all its elements. Flag's names are choosen
- * the same as jsonb_typeof uses.
+ * We iterate over array (scalar internally is represented as array, so,
+ * we will accept it too) to check all its elements. Flag names are
+ * chosen the same as jsonb_typeof uses.
*/
if (type != WJB_BEGIN_ARRAY)
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("flag array element is not a string"),
- errhint("Possible values are: \"string\", \"numeric\", \"boolean\", \"key\" and \"all\"")));
+ errhint("Possible values are: \"string\", \"numeric\", \"boolean\", \"key\", and \"all\".")));
if (v.val.string.len == 3 &&
- pg_strncasecmp(v.val.string.val, "all", 3) == 0)
+ pg_strncasecmp(v.val.string.val, "all", 3) == 0)
flags |= jtiAll;
else if (v.val.string.len == 3 &&
pg_strncasecmp(v.val.string.val, "key", 3) == 0)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("wrong flag in flag array: \"%s\"",
pnstrdup(v.val.string.val, v.val.string.len)),
- errhint("Possible values are: \"string\", \"numeric\", \"boolean\", \"key\" and \"all\"")));
+ errhint("Possible values are: \"string\", \"numeric\", \"boolean\", \"key\", and \"all\".")));
}
- /* user should not get it */
+ /* expect end of array now */
if (type != WJB_END_ARRAY)
elog(ERROR, "unexpected end of flag array");
/* get final WJB_DONE and free iterator */
- JsonbIteratorNext(&it, &v, false);
+ type = JsonbIteratorNext(&it, &v, false);
+ if (type != WJB_DONE)
+ elog(ERROR, "unexpected end of flag array");
return flags;
}
/*
* Just recursively iterating over jsonb and call callback on all
- * correspoding elements
+ * corresponding elements
*/
while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
{
}
/* JsonbValue is a value of object or element of array */
- switch(v.type)
+ switch (v.type)
{
case jbvString:
if (flags & jtiString)
case jbvNumeric:
if (flags & jtiNumeric)
{
- char *val;
+ char *val;
val = DatumGetCString(DirectFunctionCall1(numeric_out,
- NumericGetDatum(v.val.numeric)));
+ NumericGetDatum(v.val.numeric)));
action(state, val, strlen(val));
pfree(val);
{
IterateJsonStringValuesState *_state = (IterateJsonStringValuesState *) state;
- switch(tokentype)
+ switch (tokentype)
{
case JSON_TOKEN_STRING:
if (_state->flags & jtiString)
if (_state->flags & jtiKey)
{
- char *val = pstrdup(fname);
+ char *val = pstrdup(fname);
+
_state->action(_state->action_state, val, strlen(val));
}
}