diff options
Diffstat (limited to 'src/backend/utils')
| -rw-r--r-- | src/backend/utils/adt/json.c | 422 | ||||
| -rw-r--r-- | src/backend/utils/adt/jsonb.c | 224 | ||||
| -rw-r--r-- | src/backend/utils/adt/jsonb_util.c | 24 | ||||
| -rw-r--r-- | src/backend/utils/adt/ruleutils.c | 212 | ||||
| -rw-r--r-- | src/backend/utils/misc/queryjumble.c | 12 |
5 files changed, 778 insertions, 116 deletions
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 7879f342e6..d088fafc56 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -13,7 +13,9 @@ */ #include "postgres.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" +#include "common/hashfn.h" #include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" @@ -42,6 +44,42 @@ typedef enum /* type categories for datum_to_json */ JSONTYPE_OTHER /* all else */ } JsonTypeCategory; +/* Common context for key uniqueness check */ +typedef struct HTAB *JsonUniqueCheckState; /* hash table for key names */ + +/* Hash entry for JsonUniqueCheckState */ +typedef struct JsonUniqueHashEntry +{ + const char *key; + int key_len; + int object_id; +} JsonUniqueHashEntry; + +/* Context for key uniqueness check in builder functions */ +typedef struct JsonUniqueBuilderState +{ + JsonUniqueCheckState check; /* unique check */ + StringInfoData skipped_keys; /* skipped keys with NULL values */ + MemoryContext mcxt; /* context for saving skipped keys */ +} JsonUniqueBuilderState; + +/* Element of object stack for key uniqueness check during json parsing */ +typedef struct JsonUniqueStackEntry +{ + struct JsonUniqueStackEntry *parent; + int object_id; +} JsonUniqueStackEntry; + +/* State for key uniqueness check during json parsing */ +typedef struct JsonUniqueParsingState +{ + JsonLexContext *lex; + JsonUniqueCheckState check; + JsonUniqueStackEntry *stack; + int id_counter; + bool unique; +} JsonUniqueParsingState; + typedef struct JsonAggState { StringInfo str; @@ -49,6 +87,7 @@ typedef struct JsonAggState Oid key_output_func; JsonTypeCategory val_category; Oid val_output_func; + JsonUniqueBuilderState unique_check; } JsonAggState; static void composite_to_json(Datum composite, StringInfo result, @@ -722,6 +761,38 @@ row_to_json_pretty(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } +bool +to_json_is_immutable(Oid typoid) +{ + JsonTypeCategory tcategory; + Oid outfuncoid; + + json_categorize_type(typoid, &tcategory, &outfuncoid); + + switch (tcategory) + { + case JSONTYPE_BOOL: + case JSONTYPE_JSON: + return true; + + case JSONTYPE_DATE: + case JSONTYPE_TIMESTAMP: + case JSONTYPE_TIMESTAMPTZ: + return false; + + case JSONTYPE_ARRAY: + return false; /* TODO recurse into elements */ + + case JSONTYPE_COMPOSITE: + return false; /* TODO recurse into fields */ + + case JSONTYPE_NUMERIC: + case JSONTYPE_CAST: + default: + return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE; + } +} + /* * SQL function to_json(anyvalue) */ @@ -754,8 +825,8 @@ to_json(PG_FUNCTION_ARGS) * * aggregate input column as a json array value. */ -Datum -json_agg_transfn(PG_FUNCTION_ARGS) +static Datum +json_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null) { MemoryContext aggcontext, oldcontext; @@ -795,9 +866,14 @@ json_agg_transfn(PG_FUNCTION_ARGS) else { state = (JsonAggState *) PG_GETARG_POINTER(0); - appendStringInfoString(state->str, ", "); } + if (absent_on_null && PG_ARGISNULL(1)) + PG_RETURN_POINTER(state); + + if (state->str->len > 1) + appendStringInfoString(state->str, ", "); + /* fast path for NULLs */ if (PG_ARGISNULL(1)) { @@ -809,7 +885,7 @@ json_agg_transfn(PG_FUNCTION_ARGS) val = PG_GETARG_DATUM(1); /* add some whitespace if structured type and not first item */ - if (!PG_ARGISNULL(0) && + if (!PG_ARGISNULL(0) && state->str->len > 1 && (state->val_category == JSONTYPE_ARRAY || state->val_category == JSONTYPE_COMPOSITE)) { @@ -827,6 +903,25 @@ json_agg_transfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(state); } + +/* + * json_agg aggregate function + */ +Datum +json_agg_transfn(PG_FUNCTION_ARGS) +{ + return json_agg_transfn_worker(fcinfo, false); +} + +/* + * json_agg_strict aggregate function + */ +Datum +json_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_agg_transfn_worker(fcinfo, true); +} + /* * json_agg final function */ @@ -850,18 +945,122 @@ json_agg_finalfn(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, "]")); } +/* Functions implementing hash table for key uniqueness check */ +static uint32 +json_unique_hash(const void *key, Size keysize) +{ + const JsonUniqueHashEntry *entry = (JsonUniqueHashEntry *) key; + uint32 hash = hash_bytes_uint32(entry->object_id); + + hash ^= hash_bytes((const unsigned char *) entry->key, entry->key_len); + + return DatumGetUInt32(hash); +} + +static int +json_unique_hash_match(const void *key1, const void *key2, Size keysize) +{ + const JsonUniqueHashEntry *entry1 = (const JsonUniqueHashEntry *) key1; + const JsonUniqueHashEntry *entry2 = (const JsonUniqueHashEntry *) key2; + + if (entry1->object_id != entry2->object_id) + return entry1->object_id > entry2->object_id ? 1 : -1; + + if (entry1->key_len != entry2->key_len) + return entry1->key_len > entry2->key_len ? 1 : -1; + + return strncmp(entry1->key, entry2->key, entry1->key_len); +} + +/* Functions implementing object key uniqueness check */ +static void +json_unique_check_init(JsonUniqueCheckState *cxt) +{ + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(JsonUniqueHashEntry); + ctl.entrysize = sizeof(JsonUniqueHashEntry); + ctl.hcxt = CurrentMemoryContext; + ctl.hash = json_unique_hash; + ctl.match = json_unique_hash_match; + + *cxt = hash_create("json object hashtable", + 32, + &ctl, + HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE); +} + +static void +json_unique_check_free(JsonUniqueCheckState *cxt) +{ + hash_destroy(*cxt); +} + +static bool +json_unique_check_key(JsonUniqueCheckState *cxt, const char *key, int object_id) +{ + JsonUniqueHashEntry entry; + bool found; + + entry.key = key; + entry.key_len = strlen(key); + entry.object_id = object_id; + + (void) hash_search(*cxt, &entry, HASH_ENTER, &found); + + return !found; +} + +static void +json_unique_builder_init(JsonUniqueBuilderState *cxt) +{ + json_unique_check_init(&cxt->check); + cxt->mcxt = CurrentMemoryContext; + cxt->skipped_keys.data = NULL; +} + +static void +json_unique_builder_free(JsonUniqueBuilderState *cxt) +{ + json_unique_check_free(&cxt->check); + + if (cxt->skipped_keys.data) + pfree(cxt->skipped_keys.data); +} + +/* On-demand initialization of skipped_keys StringInfo structure */ +static StringInfo +json_unique_builder_get_skipped_keys(JsonUniqueBuilderState *cxt) +{ + StringInfo out = &cxt->skipped_keys; + + if (!out->data) + { + MemoryContext oldcxt = MemoryContextSwitchTo(cxt->mcxt); + initStringInfo(out); + MemoryContextSwitchTo(oldcxt); + } + + return out; +} + /* * json_object_agg transition function. * * aggregate two input columns as a single json object value. */ -Datum -json_object_agg_transfn(PG_FUNCTION_ARGS) +static Datum +json_object_agg_transfn_worker(FunctionCallInfo fcinfo, + bool absent_on_null, bool unique_keys) { MemoryContext aggcontext, oldcontext; JsonAggState *state; + StringInfo out; Datum arg; + bool skip; + int key_offset; if (!AggCheckCallContext(fcinfo, &aggcontext)) { @@ -882,6 +1081,10 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) oldcontext = MemoryContextSwitchTo(aggcontext); state = (JsonAggState *) palloc(sizeof(JsonAggState)); state->str = makeStringInfo(); + if (unique_keys) + json_unique_builder_init(&state->unique_check); + else + memset(&state->unique_check, 0, sizeof(state->unique_check)); MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); @@ -909,7 +1112,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) else { state = (JsonAggState *) PG_GETARG_POINTER(0); - appendStringInfoString(state->str, ", "); } /* @@ -925,11 +1127,49 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("field name must not be null"))); + /* Skip null values if absent_on_null */ + skip = absent_on_null && PG_ARGISNULL(2); + + if (skip) + { + /* If key uniqueness check is needed we must save skipped keys */ + if (!unique_keys) + PG_RETURN_POINTER(state); + + out = json_unique_builder_get_skipped_keys(&state->unique_check); + } + else + { + out = state->str; + + /* + * Append comma delimiter only if we have already outputted some fields + * after the initial string "{ ". + */ + if (out->len > 2) + appendStringInfoString(out, ", "); + } + arg = PG_GETARG_DATUM(1); - datum_to_json(arg, false, state->str, state->key_category, + key_offset = out->len; + + datum_to_json(arg, false, out, state->key_category, state->key_output_func, true); + if (unique_keys) + { + const char *key = &out->data[key_offset]; + + if (!json_unique_check_key(&state->unique_check.check, key, 0)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON key %s", key))); + + if (skip) + PG_RETURN_POINTER(state); + } + appendStringInfoString(state->str, " : "); if (PG_ARGISNULL(2)) @@ -944,6 +1184,42 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) } /* + * json_object_agg aggregate function + */ +Datum +json_object_agg_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, false, false); +} + +/* + * json_object_agg_strict aggregate function + */ +Datum +json_object_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, true, false); +} + +/* + * json_object_agg_unique aggregate function + */ +Datum +json_object_agg_unique_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, false, true); +} + +/* + * json_object_agg_unique_strict aggregate function + */ +Datum +json_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, true, true); +} + +/* * json_object_agg final function. */ Datum @@ -960,6 +1236,8 @@ json_object_agg_finalfn(PG_FUNCTION_ARGS) if (state == NULL) PG_RETURN_NULL(); + json_unique_builder_free(&state->unique_check); + /* Else return state with appropriate object terminator added */ PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, " }")); } @@ -984,25 +1262,14 @@ catenate_stringinfo_string(StringInfo buffer, const char *addon) return result; } -/* - * SQL function json_build_object(variadic "any") - */ Datum -json_build_object(PG_FUNCTION_ARGS) +json_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null, bool unique_keys) { - int nargs; int i; const char *sep = ""; StringInfo result; - Datum *args; - bool *nulls; - Oid *types; - - /* fetch argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); + JsonUniqueBuilderState unique_check; if (nargs % 2 != 0) ereport(ERROR, @@ -1016,19 +1283,58 @@ json_build_object(PG_FUNCTION_ARGS) appendStringInfoChar(result, '{'); + if (unique_keys) + json_unique_builder_init(&unique_check); + for (i = 0; i < nargs; i += 2) { - appendStringInfoString(result, sep); - sep = ", "; + StringInfo out; + bool skip; + int key_offset; + + /* Skip null values if absent_on_null */ + skip = absent_on_null && nulls[i + 1]; + + if (skip) + { + /* If key uniqueness check is needed we must save skipped keys */ + if (!unique_keys) + continue; + + out = json_unique_builder_get_skipped_keys(&unique_check); + } + else + { + appendStringInfoString(result, sep); + sep = ", "; + out = result; + } /* process key */ if (nulls[i]) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument %d cannot be null", i + 1), + errmsg("argument %d cannot be null", i + 1), errhint("Object keys should be text."))); - add_json(args[i], false, result, types[i], true); + /* save key offset before key appending */ + key_offset = out->len; + + add_json(args[i], false, out, types[i], true); + + if (unique_keys) + { + /* check key uniqueness after key appending */ + const char *key = &out->data[key_offset]; + + if (!json_unique_check_key(&unique_check.check, key, 0)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON key %s", key))); + + if (skip) + continue; + } appendStringInfoString(result, " : "); @@ -1038,7 +1344,29 @@ json_build_object(PG_FUNCTION_ARGS) appendStringInfoChar(result, '}'); - PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); + if (unique_keys) + json_unique_builder_free(&unique_check); + + return PointerGetDatum(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * SQL function json_build_object(variadic "any") + */ +Datum +json_build_object(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(json_build_object_worker(nargs, args, nulls, types, false, false)); } /* @@ -1050,25 +1378,13 @@ json_build_object_noargs(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); } -/* - * SQL function json_build_array(variadic "any") - */ Datum -json_build_array(PG_FUNCTION_ARGS) +json_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null) { - int nargs; int i; const char *sep = ""; StringInfo result; - Datum *args; - bool *nulls; - Oid *types; - - /* fetch argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); result = makeStringInfo(); @@ -1076,6 +1392,9 @@ json_build_array(PG_FUNCTION_ARGS) for (i = 0; i < nargs; i++) { + if (absent_on_null && nulls[i]) + continue; + appendStringInfoString(result, sep); sep = ", "; add_json(args[i], nulls[i], result, types[i], false); @@ -1083,7 +1402,26 @@ json_build_array(PG_FUNCTION_ARGS) appendStringInfoChar(result, ']'); - PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); + return PointerGetDatum(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * SQL function json_build_array(variadic "any") + */ +Datum +json_build_array(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(json_build_array_worker(nargs, args, nulls, types, false)); } /* diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index f5f40a94bd..a103cbc7c6 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -14,6 +14,7 @@ #include "access/htup_details.h" #include "access/transam.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "funcapi.h" #include "libpq/pqformat.h" @@ -1126,6 +1127,39 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result, datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar); } +bool +to_jsonb_is_immutable(Oid typoid) +{ + JsonbTypeCategory tcategory; + Oid outfuncoid; + + jsonb_categorize_type(typoid, &tcategory, &outfuncoid); + + switch (tcategory) + { + case JSONBTYPE_BOOL: + case JSONBTYPE_JSON: + case JSONBTYPE_JSONB: + return true; + + case JSONBTYPE_DATE: + case JSONBTYPE_TIMESTAMP: + case JSONBTYPE_TIMESTAMPTZ: + return false; + + case JSONBTYPE_ARRAY: + return false; /* TODO recurse into elements */ + + case JSONBTYPE_COMPOSITE: + return false; /* TODO recurse into fields */ + + case JSONBTYPE_NUMERIC: + case JSONBTYPE_JSONCAST: + default: + return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE; + } +} + /* * SQL function to_jsonb(anyvalue) */ @@ -1153,24 +1187,12 @@ to_jsonb(PG_FUNCTION_ARGS) PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } -/* - * SQL function jsonb_build_object(variadic "any") - */ Datum -jsonb_build_object(PG_FUNCTION_ARGS) +jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null, bool unique_keys) { - int nargs; int i; JsonbInState result; - Datum *args; - bool *nulls; - Oid *types; - - /* build argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); if (nargs % 2 != 0) ereport(ERROR, @@ -1183,15 +1205,26 @@ jsonb_build_object(PG_FUNCTION_ARGS) memset(&result, 0, sizeof(JsonbInState)); result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); + result.parseState->unique_keys = unique_keys; + result.parseState->skip_nulls = absent_on_null; for (i = 0; i < nargs; i += 2) { /* process key */ + bool skip; + if (nulls[i]) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument %d: key must not be null", i + 1))); + /* skip null values if absent_on_null */ + skip = absent_on_null && nulls[i + 1]; + + /* we need to save skipped keys for the key uniqueness check */ + if (skip && !unique_keys) + continue; + add_jsonb(args[i], false, &result, types[i], true); /* process value */ @@ -1200,7 +1233,26 @@ jsonb_build_object(PG_FUNCTION_ARGS) result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); - PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); + return JsonbPGetDatum(JsonbValueToJsonb(result.res)); +} + +/* + * SQL function jsonb_build_object(variadic "any") + */ +Datum +jsonb_build_object(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(jsonb_build_object_worker(nargs, args, nulls, types, false, false)); } /* @@ -1219,37 +1271,50 @@ jsonb_build_object_noargs(PG_FUNCTION_ARGS) PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } -/* - * SQL function jsonb_build_array(variadic "any") - */ Datum -jsonb_build_array(PG_FUNCTION_ARGS) +jsonb_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null) { - int nargs; int i; JsonbInState result; - Datum *args; - bool *nulls; - Oid *types; - - /* build argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); memset(&result, 0, sizeof(JsonbInState)); result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL); for (i = 0; i < nargs; i++) + { + if (absent_on_null && nulls[i]) + continue; + add_jsonb(args[i], nulls[i], &result, types[i], false); + } result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); - PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); + return JsonbPGetDatum(JsonbValueToJsonb(result.res)); +} + +/* + * SQL function jsonb_build_array(variadic "any") + */ +Datum +jsonb_build_array(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(jsonb_build_array_worker(nargs, args, nulls, types, false)); } + /* * degenerate case of jsonb_build_array where it gets 0 arguments. */ @@ -1490,6 +1555,8 @@ clone_parse_state(JsonbParseState *state) { ocursor->contVal = icursor->contVal; ocursor->size = icursor->size; + ocursor->unique_keys = icursor->unique_keys; + ocursor->skip_nulls = icursor->skip_nulls; icursor = icursor->next; if (icursor == NULL) break; @@ -1501,12 +1568,8 @@ clone_parse_state(JsonbParseState *state) return result; } - -/* - * jsonb_agg aggregate function - */ -Datum -jsonb_agg_transfn(PG_FUNCTION_ARGS) +static Datum +jsonb_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null) { MemoryContext oldcontext, aggcontext; @@ -1554,6 +1617,9 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS) result = state->res; } + if (absent_on_null && PG_ARGISNULL(1)) + PG_RETURN_POINTER(state); + /* turn the argument into jsonb in the normal function context */ val = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1); @@ -1623,6 +1689,24 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(state); } +/* + * jsonb_agg aggregate function + */ +Datum +jsonb_agg_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_agg_transfn_worker(fcinfo, false); +} + +/* + * jsonb_agg_strict aggregate function + */ +Datum +jsonb_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_agg_transfn_worker(fcinfo, true); +} + Datum jsonb_agg_finalfn(PG_FUNCTION_ARGS) { @@ -1655,11 +1739,9 @@ jsonb_agg_finalfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(out); } -/* - * jsonb_object_agg aggregate function - */ -Datum -jsonb_object_agg_transfn(PG_FUNCTION_ARGS) +static Datum +jsonb_object_agg_transfn_worker(FunctionCallInfo fcinfo, + bool absent_on_null, bool unique_keys) { MemoryContext oldcontext, aggcontext; @@ -1673,6 +1755,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) *jbval; JsonbValue v; JsonbIteratorToken type; + bool skip; if (!AggCheckCallContext(fcinfo, &aggcontext)) { @@ -1692,6 +1775,9 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) state->res = result; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_OBJECT, NULL); + result->parseState->unique_keys = unique_keys; + result->parseState->skip_nulls = absent_on_null; + MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); @@ -1727,6 +1813,15 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("field name must not be null"))); + /* + * Skip null values if absent_on_null unless key uniqueness check is + * needed (because we must save keys in this case). + */ + skip = absent_on_null && PG_ARGISNULL(2); + + if (skip && !unique_keys) + PG_RETURN_POINTER(state); + val = PG_GETARG_DATUM(1); memset(&elem, 0, sizeof(JsonbInState)); @@ -1782,6 +1877,16 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) } result->res = pushJsonbValue(&result->parseState, WJB_KEY, &v); + + if (skip) + { + v.type = jbvNull; + result->res = pushJsonbValue(&result->parseState, + WJB_VALUE, &v); + MemoryContextSwitchTo(oldcontext); + PG_RETURN_POINTER(state); + } + break; case WJB_END_ARRAY: break; @@ -1854,6 +1959,43 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(state); } +/* + * jsonb_object_agg aggregate function + */ +Datum +jsonb_object_agg_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, false, false); +} + + +/* + * jsonb_object_agg_strict aggregate function + */ +Datum +jsonb_object_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, true, false); +} + +/* + * jsonb_object_agg_unique aggregate function + */ +Datum +jsonb_object_agg_unique_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, false, true); +} + +/* + * jsonb_object_agg_unique_strict aggregate function + */ +Datum +jsonb_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, true, true); +} + Datum jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) { diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 60442758b3..aa151a53d6 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -64,7 +64,8 @@ static int lengthCompareJsonbStringValue(const void *a, const void *b); static int lengthCompareJsonbString(const char *val1, int len1, const char *val2, int len2); static int lengthCompareJsonbPair(const void *a, const void *b, void *arg); -static void uniqueifyJsonbObject(JsonbValue *object); +static void uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, + bool skip_nulls); static JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq, JsonbValue *scalarVal); @@ -689,7 +690,9 @@ pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq, appendElement(*pstate, scalarVal); break; case WJB_END_OBJECT: - uniqueifyJsonbObject(&(*pstate)->contVal); + uniqueifyJsonbObject(&(*pstate)->contVal, + (*pstate)->unique_keys, + (*pstate)->skip_nulls); /* fall through! */ case WJB_END_ARRAY: /* Steps here common to WJB_END_OBJECT case */ @@ -732,6 +735,9 @@ pushState(JsonbParseState **pstate) JsonbParseState *ns = palloc(sizeof(JsonbParseState)); ns->next = *pstate; + ns->unique_keys = false; + ns->skip_nulls = false; + return ns; } @@ -1936,7 +1942,7 @@ lengthCompareJsonbPair(const void *a, const void *b, void *binequal) * Sort and unique-ify pairs in JsonbValue object */ static void -uniqueifyJsonbObject(JsonbValue *object) +uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, bool skip_nulls) { bool hasNonUniq = false; @@ -1946,15 +1952,21 @@ uniqueifyJsonbObject(JsonbValue *object) qsort_arg(object->val.object.pairs, object->val.object.nPairs, sizeof(JsonbPair), lengthCompareJsonbPair, &hasNonUniq); - if (hasNonUniq) + if (hasNonUniq && unique_keys) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON object key value"))); + + if (hasNonUniq || skip_nulls) { JsonbPair *ptr = object->val.object.pairs + 1, *res = object->val.object.pairs; while (ptr - object->val.object.pairs < object->val.object.nPairs) { - /* Avoid copying over duplicate */ - if (lengthCompareJsonbStringValue(ptr, res) != 0) + /* Avoid copying over duplicate or null */ + if (lengthCompareJsonbStringValue(ptr, res) != 0 && + (!skip_nulls || ptr->value.type != jbvNull)) { res++; if (ptr != res) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index c7860a7580..6db6c008dd 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -457,6 +457,12 @@ static void get_coercion_expr(Node *arg, deparse_context *context, Node *parentNode); static void get_const_expr(Const *constval, deparse_context *context, int showtype); +static void get_json_constructor(JsonConstructorExpr *ctor, + deparse_context *context, bool showimplicit); +static void get_json_agg_constructor(JsonConstructorExpr *ctor, + deparse_context *context, + const char *funcname, + bool is_json_objectagg); static void get_const_collation(Const *constval, deparse_context *context); static void simple_quote_literal(StringInfo buf, const char *val); static void get_sublink_expr(SubLink *sublink, deparse_context *context); @@ -6245,7 +6251,8 @@ get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno, bool need_paren = (PRETTY_PAREN(context) || IsA(expr, FuncExpr) || IsA(expr, Aggref) - || IsA(expr, WindowFunc)); + || IsA(expr, WindowFunc) + || IsA(expr, JsonConstructorExpr)); if (need_paren) appendStringInfoChar(context->buf, '('); @@ -8093,6 +8100,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_GroupingFunc: case T_WindowFunc: case T_FuncExpr: + case T_JsonConstructorExpr: /* function-like: name(..) or name[..] */ return true; @@ -8380,12 +8388,12 @@ get_rule_expr_paren(Node *node, deparse_context *context, * get_json_format - Parse back a JsonFormat node */ static void -get_json_format(JsonFormat *format, deparse_context *context) +get_json_format(JsonFormat *format, StringInfo buf) { if (format->format_type == JS_FORMAT_DEFAULT) return; - appendStringInfoString(context->buf, + appendStringInfoString(buf, format->format_type == JS_FORMAT_JSONB ? " FORMAT JSONB" : " FORMAT JSON"); @@ -8395,7 +8403,7 @@ get_json_format(JsonFormat *format, deparse_context *context) format->encoding == JS_ENC_UTF16 ? "UTF16" : format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8"; - appendStringInfo(context->buf, " ENCODING %s", encoding); + appendStringInfo(buf, " ENCODING %s", encoding); } } @@ -8403,20 +8411,20 @@ get_json_format(JsonFormat *format, deparse_context *context) * get_json_returning - Parse back a JsonReturning structure */ static void -get_json_returning(JsonReturning *returning, deparse_context *context, +get_json_returning(JsonReturning *returning, StringInfo buf, bool json_format_by_default) { if (!OidIsValid(returning->typid)) return; - appendStringInfo(context->buf, " RETURNING %s", + appendStringInfo(buf, " RETURNING %s", format_type_with_typemod(returning->typid, returning->typmod)); if (!json_format_by_default || returning->format->format_type != (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON)) - get_json_format(returning->format, context); + get_json_format(returning->format, buf); } /* ---------- @@ -9583,10 +9591,14 @@ get_rule_expr(Node *node, deparse_context *context, JsonValueExpr *jve = (JsonValueExpr *) node; get_rule_expr((Node *) jve->raw_expr, context, false); - get_json_format(jve->format, context); + get_json_format(jve->format, context->buf); } break; + case T_JsonConstructorExpr: + get_json_constructor((JsonConstructorExpr *) node, context, false); + break; + case T_List: { char *sep; @@ -9855,17 +9867,89 @@ get_func_expr(FuncExpr *expr, deparse_context *context, appendStringInfoChar(buf, ')'); } +static void +get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf) +{ + if (ctor->absent_on_null) + { + if (ctor->type == JSCTOR_JSON_OBJECT || + ctor->type == JSCTOR_JSON_OBJECTAGG) + appendStringInfoString(buf, " ABSENT ON NULL"); + } + else + { + if (ctor->type == JSCTOR_JSON_ARRAY || + ctor->type == JSCTOR_JSON_ARRAYAGG) + appendStringInfoString(buf, " NULL ON NULL"); + } + + if (ctor->unique) + appendStringInfoString(buf, " WITH UNIQUE KEYS"); + + get_json_returning(ctor->returning, buf, true); +} + +static void +get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context, + bool showimplicit) +{ + StringInfo buf = context->buf; + const char *funcname; + int nargs; + ListCell *lc; + + switch (ctor->type) + { + case JSCTOR_JSON_OBJECT: + funcname = "JSON_OBJECT"; + break; + case JSCTOR_JSON_ARRAY: + funcname = "JSON_ARRAY"; + break; + case JSCTOR_JSON_OBJECTAGG: + return get_json_agg_constructor(ctor, context, "JSON_OBJECTAGG", true); + case JSCTOR_JSON_ARRAYAGG: + return get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false); + default: + elog(ERROR, "invalid JsonConstructorExprType %d", ctor->type); + } + + appendStringInfo(buf, "%s(", funcname); + + nargs = 0; + foreach(lc, ctor->args) + { + if (nargs > 0) + { + const char *sep = ctor->type == JSCTOR_JSON_OBJECT && + (nargs % 2) != 0 ? " : " : ", "; + + appendStringInfoString(buf, sep); + } + + get_rule_expr((Node *) lfirst(lc), context, true); + + nargs++; + } + + get_json_constructor_options(ctor, buf); + + appendStringInfo(buf, ")"); +} + + /* - * get_agg_expr - Parse back an Aggref node + * get_agg_expr_helper - Parse back an Aggref node */ static void -get_agg_expr(Aggref *aggref, deparse_context *context, - Aggref *original_aggref) +get_agg_expr_helper(Aggref *aggref, deparse_context *context, + Aggref *original_aggref, const char *funcname, + const char *options, bool is_json_objectagg) { StringInfo buf = context->buf; Oid argtypes[FUNC_MAX_ARGS]; int nargs; - bool use_variadic; + bool use_variadic = false; /* * For a combining aggregate, we look up and deparse the corresponding @@ -9895,13 +9979,14 @@ get_agg_expr(Aggref *aggref, deparse_context *context, /* Extract the argument types as seen by the parser */ nargs = get_aggregate_argtypes(aggref, argtypes); + if (!funcname) + funcname = generate_function_name(aggref->aggfnoid, nargs, NIL, + argtypes, aggref->aggvariadic, + &use_variadic, + context->special_exprkind); + /* Print the aggregate name, schema-qualified if needed */ - appendStringInfo(buf, "%s(%s", - generate_function_name(aggref->aggfnoid, nargs, - NIL, argtypes, - aggref->aggvariadic, - &use_variadic, - context->special_exprkind), + appendStringInfo(buf, "%s(%s", funcname, (aggref->aggdistinct != NIL) ? "DISTINCT " : ""); if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) @@ -9937,7 +10022,17 @@ get_agg_expr(Aggref *aggref, deparse_context *context, if (tle->resjunk) continue; if (i++ > 0) - appendStringInfoString(buf, ", "); + { + if (is_json_objectagg) + { + if (i > 2) + break; /* skip ABSENT ON NULL and WITH UNIQUE args */ + + appendStringInfoString(buf, " : "); + } + else + appendStringInfoString(buf, ", "); + } if (use_variadic && i == nargs) appendStringInfoString(buf, "VARIADIC "); get_rule_expr(arg, context, true); @@ -9951,6 +10046,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context, } } + if (options) + appendStringInfoString(buf, options); + if (aggref->aggfilter != NULL) { appendStringInfoString(buf, ") FILTER (WHERE "); @@ -9961,6 +10059,16 @@ get_agg_expr(Aggref *aggref, deparse_context *context, } /* + * get_agg_expr - Parse back an Aggref node + */ +static void +get_agg_expr(Aggref *aggref, deparse_context *context, Aggref *original_aggref) +{ + return get_agg_expr_helper(aggref, context, original_aggref, NULL, NULL, + false); +} + +/* * This is a helper function for get_agg_expr(). It's used when we deparse * a combining Aggref; resolve_special_varno locates the corresponding partial * Aggref and then calls this. @@ -9979,10 +10087,12 @@ get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg) } /* - * get_windowfunc_expr - Parse back a WindowFunc node + * get_windowfunc_expr_helper - Parse back a WindowFunc node */ static void -get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) +get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context, + const char *funcname, const char *options, + bool is_json_objectagg) { StringInfo buf = context->buf; Oid argtypes[FUNC_MAX_ARGS]; @@ -10006,16 +10116,30 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) nargs++; } - appendStringInfo(buf, "%s(", - generate_function_name(wfunc->winfnoid, nargs, - argnames, argtypes, - false, NULL, - context->special_exprkind)); + if (!funcname) + funcname = generate_function_name(wfunc->winfnoid, nargs, argnames, + argtypes, false, NULL, + context->special_exprkind); + + appendStringInfo(buf, "%s(", funcname); + /* winstar can be set only in zero-argument aggregates */ if (wfunc->winstar) appendStringInfoChar(buf, '*'); else - get_rule_expr((Node *) wfunc->args, context, true); + { + if (is_json_objectagg) + { + get_rule_expr((Node *) linitial(wfunc->args), context, false); + appendStringInfoString(buf, " : "); + get_rule_expr((Node *) lsecond(wfunc->args), context, false); + } + else + get_rule_expr((Node *) wfunc->args, context, true); + } + + if (options) + appendStringInfoString(buf, options); if (wfunc->aggfilter != NULL) { @@ -10053,6 +10177,15 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) } /* + * get_windowfunc_expr - Parse back a WindowFunc node + */ +static void +get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) +{ + return get_windowfunc_expr_helper(wfunc, context, NULL, NULL, false); +} + +/* * get_func_sql_syntax - Parse back a SQL-syntax function call * * Returns true if we successfully deparsed, false if we did not @@ -10292,6 +10425,31 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context) return false; } +/* + * get_json_agg_constructor - Parse back an aggregate JsonConstructorExpr node + */ +static void +get_json_agg_constructor(JsonConstructorExpr *ctor, deparse_context *context, + const char *funcname, bool is_json_objectagg) +{ + StringInfoData options; + + initStringInfo(&options); + get_json_constructor_options(ctor, &options); + + if (IsA(ctor->func, Aggref)) + return get_agg_expr_helper((Aggref *) ctor->func, context, + (Aggref *) ctor->func, + funcname, options.data, is_json_objectagg); + else if (IsA(ctor->func, WindowFunc)) + return get_windowfunc_expr_helper((WindowFunc *) ctor->func, context, + funcname, options.data, + is_json_objectagg); + else + elog(ERROR, "invalid JsonConstructorExpr underlying node type: %d", + nodeTag(ctor->func)); +} + /* ---------- * get_coercion_expr * diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c index 84435420e4..d14b751058 100644 --- a/src/backend/utils/misc/queryjumble.c +++ b/src/backend/utils/misc/queryjumble.c @@ -763,6 +763,18 @@ JumbleExpr(JumbleState *jstate, Node *node) JumbleExpr(jstate, (Node *) expr->format); } break; + case T_JsonConstructorExpr: + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; + + JumbleExpr(jstate, (Node *) ctor->func); + JumbleExpr(jstate, (Node *) ctor->coercion); + JumbleExpr(jstate, (Node *) ctor->returning); + APP_JUMB(ctor->type); + APP_JUMB(ctor->unique); + APP_JUMB(ctor->absent_on_null); + } + break; case T_List: foreach(temp, (List *) node) { |
