summaryrefslogtreecommitdiff
path: root/src/backend/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils')
-rw-r--r--src/backend/utils/adt/json.c422
-rw-r--r--src/backend/utils/adt/jsonb.c224
-rw-r--r--src/backend/utils/adt/jsonb_util.c24
-rw-r--r--src/backend/utils/adt/ruleutils.c212
-rw-r--r--src/backend/utils/misc/queryjumble.c12
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)
{