mirror of
https://github.com/postgres/postgres.git
synced 2025-07-15 19:21:59 +03:00
Cache argument type information in json(b) aggregate functions.
These functions have been looking up type info for every row they process. Instead of doing that we only look them up the first time through and stash the information in the aggregate state object. Affects json_agg, json_object_agg, jsonb_agg and jsonb_object_agg. There is plenty more work to do in making these more efficient, especially the jsonb functions, but this is a virtually cost free improvement that can be done right away. Backpatch to 9.5 where the jsonb variants were introduced.
This commit is contained in:
@ -59,6 +59,15 @@ typedef enum /* type categories for datum_to_jsonb */
|
||||
JSONBTYPE_OTHER /* all else */
|
||||
} JsonbTypeCategory;
|
||||
|
||||
typedef struct JsonbAggState
|
||||
{
|
||||
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);
|
||||
static size_t checkStringLen(size_t len);
|
||||
static void jsonb_in_object_start(void *pstate);
|
||||
@ -1573,12 +1582,10 @@ clone_parse_state(JsonbParseState *state)
|
||||
Datum
|
||||
jsonb_agg_transfn(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
|
||||
MemoryContext oldcontext,
|
||||
aggcontext;
|
||||
JsonbAggState *state;
|
||||
JsonbInState elem;
|
||||
JsonbTypeCategory tcategory;
|
||||
Oid outfuncoid;
|
||||
Datum val;
|
||||
JsonbInState *result;
|
||||
bool single_scalar = false;
|
||||
@ -1587,27 +1594,49 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS)
|
||||
JsonbValue v;
|
||||
JsonbIteratorToken type;
|
||||
|
||||
if (val_type == InvalidOid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("could not determine input data type")));
|
||||
|
||||
if (!AggCheckCallContext(fcinfo, &aggcontext))
|
||||
{
|
||||
/* cannot be called directly because of internal-type argument */
|
||||
elog(ERROR, "jsonb_agg_transfn called in non-aggregate context");
|
||||
}
|
||||
|
||||
/* set up the accumulator on the first go round */
|
||||
|
||||
if (PG_ARGISNULL(0))
|
||||
{
|
||||
|
||||
Oid arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
|
||||
|
||||
if (arg_type == InvalidOid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("could not determine input data type")));
|
||||
|
||||
oldcontext = MemoryContextSwitchTo(aggcontext);
|
||||
state = palloc(sizeof(JsonbAggState));
|
||||
result = palloc0(sizeof(JsonbInState));
|
||||
state->res = result;
|
||||
result->res = pushJsonbValue(&result->parseState,
|
||||
WJB_BEGIN_ARRAY, NULL);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
jsonb_categorize_type(arg_type, &state->val_category,
|
||||
&state->val_output_func);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = (JsonbAggState *) PG_GETARG_POINTER(0);
|
||||
result = state->res;
|
||||
}
|
||||
|
||||
/* turn the argument into jsonb in the normal function context */
|
||||
|
||||
val = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
|
||||
|
||||
jsonb_categorize_type(val_type,
|
||||
&tcategory, &outfuncoid);
|
||||
|
||||
memset(&elem, 0, sizeof(JsonbInState));
|
||||
|
||||
datum_to_jsonb(val, PG_ARGISNULL(1), &elem, tcategory, outfuncoid, false);
|
||||
datum_to_jsonb(val, PG_ARGISNULL(1), &elem, state->val_category,
|
||||
state->val_output_func, false);
|
||||
|
||||
jbelem = JsonbValueToJsonb(elem.res);
|
||||
|
||||
@ -1615,20 +1644,6 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS)
|
||||
|
||||
oldcontext = MemoryContextSwitchTo(aggcontext);
|
||||
|
||||
/* set up the accumulator on the first go round */
|
||||
|
||||
if (PG_ARGISNULL(0))
|
||||
{
|
||||
result = palloc0(sizeof(JsonbInState));
|
||||
result->res = pushJsonbValue(&result->parseState,
|
||||
WJB_BEGIN_ARRAY, NULL);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (JsonbInState *) PG_GETARG_POINTER(0);
|
||||
}
|
||||
|
||||
it = JsonbIteratorInit(&jbelem->root);
|
||||
|
||||
while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
|
||||
@ -1669,7 +1684,6 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS)
|
||||
v.val.numeric =
|
||||
DatumGetNumeric(DirectFunctionCall1(numeric_uplus,
|
||||
NumericGetDatum(v.val.numeric)));
|
||||
|
||||
}
|
||||
result->res = pushJsonbValue(&result->parseState,
|
||||
type, &v);
|
||||
@ -1681,13 +1695,13 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS)
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
PG_RETURN_POINTER(state);
|
||||
}
|
||||
|
||||
Datum
|
||||
jsonb_agg_finalfn(PG_FUNCTION_ARGS)
|
||||
{
|
||||
JsonbInState *arg;
|
||||
JsonbAggState *arg;
|
||||
JsonbInState result;
|
||||
Jsonb *out;
|
||||
|
||||
@ -1697,7 +1711,7 @@ jsonb_agg_finalfn(PG_FUNCTION_ARGS)
|
||||
if (PG_ARGISNULL(0))
|
||||
PG_RETURN_NULL(); /* returns null iff no input values */
|
||||
|
||||
arg = (JsonbInState *) PG_GETARG_POINTER(0);
|
||||
arg = (JsonbAggState *) PG_GETARG_POINTER(0);
|
||||
|
||||
/*
|
||||
* We need to do a shallow clone of the argument in case the final
|
||||
@ -1706,12 +1720,11 @@ jsonb_agg_finalfn(PG_FUNCTION_ARGS)
|
||||
* values, just add the final array end marker.
|
||||
*/
|
||||
|
||||
result.parseState = clone_parse_state(arg->parseState);
|
||||
result.parseState = clone_parse_state(arg->res->parseState);
|
||||
|
||||
result.res = pushJsonbValue(&result.parseState,
|
||||
WJB_END_ARRAY, NULL);
|
||||
|
||||
|
||||
out = JsonbValueToJsonb(result.res);
|
||||
|
||||
PG_RETURN_POINTER(out);
|
||||
@ -1723,12 +1736,10 @@ jsonb_agg_finalfn(PG_FUNCTION_ARGS)
|
||||
Datum
|
||||
jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Oid val_type;
|
||||
MemoryContext oldcontext,
|
||||
aggcontext;
|
||||
JsonbInState elem;
|
||||
JsonbTypeCategory tcategory;
|
||||
Oid outfuncoid;
|
||||
JsonbAggState *state;
|
||||
Datum val;
|
||||
JsonbInState *result;
|
||||
bool single_scalar;
|
||||
@ -1744,15 +1755,48 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
|
||||
elog(ERROR, "jsonb_object_agg_transfn called in non-aggregate context");
|
||||
}
|
||||
|
||||
/* set up the accumulator on the first go round */
|
||||
|
||||
if (PG_ARGISNULL(0))
|
||||
{
|
||||
Oid arg_type;
|
||||
|
||||
oldcontext = MemoryContextSwitchTo(aggcontext);
|
||||
state = palloc(sizeof(JsonbAggState));
|
||||
result = palloc0(sizeof(JsonbInState));
|
||||
state->res = result;
|
||||
result->res = pushJsonbValue(&result->parseState,
|
||||
WJB_BEGIN_OBJECT, NULL);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
|
||||
|
||||
if (arg_type == InvalidOid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("could not determine input data type")));
|
||||
|
||||
jsonb_categorize_type(arg_type, &state->key_category,
|
||||
&state->key_output_func);
|
||||
|
||||
arg_type = get_fn_expr_argtype(fcinfo->flinfo, 2);
|
||||
|
||||
if (arg_type == InvalidOid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("could not determine input data type")));
|
||||
|
||||
jsonb_categorize_type(arg_type, &state->val_category,
|
||||
&state->val_output_func);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = (JsonbAggState *) PG_GETARG_POINTER(0);
|
||||
result = state->res;
|
||||
}
|
||||
|
||||
/* turn the argument into jsonb in the normal function context */
|
||||
|
||||
val_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
|
||||
|
||||
if (val_type == InvalidOid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("could not determine input data type")));
|
||||
|
||||
if (PG_ARGISNULL(1))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
@ -1760,53 +1804,28 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
|
||||
|
||||
val = PG_GETARG_DATUM(1);
|
||||
|
||||
jsonb_categorize_type(val_type,
|
||||
&tcategory, &outfuncoid);
|
||||
|
||||
memset(&elem, 0, sizeof(JsonbInState));
|
||||
|
||||
datum_to_jsonb(val, false, &elem, tcategory, outfuncoid, true);
|
||||
datum_to_jsonb(val, false, &elem, state->key_category,
|
||||
state->key_output_func, true);
|
||||
|
||||
jbkey = JsonbValueToJsonb(elem.res);
|
||||
|
||||
val_type = get_fn_expr_argtype(fcinfo->flinfo, 2);
|
||||
|
||||
if (val_type == InvalidOid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("could not determine input data type")));
|
||||
|
||||
val = PG_ARGISNULL(2) ? (Datum) 0 : PG_GETARG_DATUM(2);
|
||||
|
||||
jsonb_categorize_type(val_type,
|
||||
&tcategory, &outfuncoid);
|
||||
|
||||
memset(&elem, 0, sizeof(JsonbInState));
|
||||
|
||||
datum_to_jsonb(val, PG_ARGISNULL(2), &elem, tcategory, outfuncoid, false);
|
||||
datum_to_jsonb(val, PG_ARGISNULL(2), &elem, state->val_category,
|
||||
state->val_output_func, false);
|
||||
|
||||
jbval = JsonbValueToJsonb(elem.res);
|
||||
|
||||
it = JsonbIteratorInit(&jbkey->root);
|
||||
|
||||
/* switch to the aggregate context for accumulation operations */
|
||||
|
||||
oldcontext = MemoryContextSwitchTo(aggcontext);
|
||||
|
||||
/* set up the accumulator on the first go round */
|
||||
|
||||
if (PG_ARGISNULL(0))
|
||||
{
|
||||
result = palloc0(sizeof(JsonbInState));
|
||||
result->res = pushJsonbValue(&result->parseState,
|
||||
WJB_BEGIN_OBJECT, NULL);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (JsonbInState *) PG_GETARG_POINTER(0);
|
||||
}
|
||||
|
||||
it = JsonbIteratorInit(&jbkey->root);
|
||||
|
||||
/*
|
||||
* keys should be scalar, and we should have already checked for that
|
||||
* above when calling datum_to_jsonb, so we only need to look for these
|
||||
@ -1895,7 +1914,6 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
|
||||
v.val.numeric =
|
||||
DatumGetNumeric(DirectFunctionCall1(numeric_uplus,
|
||||
NumericGetDatum(v.val.numeric)));
|
||||
|
||||
}
|
||||
result->res = pushJsonbValue(&result->parseState,
|
||||
single_scalar ? WJB_VALUE : type,
|
||||
@ -1908,13 +1926,13 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
PG_RETURN_POINTER(state);
|
||||
}
|
||||
|
||||
Datum
|
||||
jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
|
||||
{
|
||||
JsonbInState *arg;
|
||||
JsonbAggState *arg;
|
||||
JsonbInState result;
|
||||
Jsonb *out;
|
||||
|
||||
@ -1924,21 +1942,20 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
|
||||
if (PG_ARGISNULL(0))
|
||||
PG_RETURN_NULL(); /* returns null iff no input values */
|
||||
|
||||
arg = (JsonbInState *) PG_GETARG_POINTER(0);
|
||||
arg = (JsonbAggState *) PG_GETARG_POINTER(0);
|
||||
|
||||
/*
|
||||
* We need to do a shallow clone of the argument in case the final
|
||||
* function is called more than once, so we avoid changing the argument. A
|
||||
* shallow clone is sufficient as we aren't going to change any of the
|
||||
* values, just add the final object end marker.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
result.parseState = clone_parse_state(arg->parseState);
|
||||
result.parseState = clone_parse_state(arg->res->parseState);
|
||||
|
||||
result.res = pushJsonbValue(&result.parseState,
|
||||
WJB_END_OBJECT, NULL);
|
||||
|
||||
|
||||
out = JsonbValueToJsonb(result.res);
|
||||
|
||||
PG_RETURN_POINTER(out);
|
||||
|
Reference in New Issue
Block a user