diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 974d7be8c2f..38e7f467605 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -15229,6 +15229,10 @@ table2-mapping
JSON
functions and operators
+
+ SQL/JSON
+ functions and expressions
+
This section describes:
@@ -15247,6 +15251,42 @@ table2-mapping
+
+ To provide native support for JSON data types within the SQL environment,
+ PostgreSQL implements the
+ SQL/JSON data model.
+ This model comprises sequences of items. Each item can hold SQL scalar
+ values, with an additional SQL/JSON null value, and composite data structures
+ that use JSON arrays and objects. The model is a formalization of the implied
+ data model in the JSON specification
+ RFC 7159.
+
+
+
+ SQL/JSON allows you to handle JSON data alongside regular SQL data,
+ with transaction support, including:
+
+
+
+
+ Uploading JSON data into the database and storing it in
+ regular SQL columns as character or binary strings.
+
+
+
+
+ Generating JSON objects and arrays from relational data.
+
+
+
+
+ Querying JSON data using SQL/JSON query functions and
+ SQL/JSON path language expressions.
+
+
+
+
+
To learn more about the SQL/JSON standard, see
. For details on JSON types
@@ -15677,6 +15717,12 @@ table2-mapping
shows the functions that are
available for constructing json and jsonb values.
+ Some functions in this table have a RETURNING clause,
+ which specifies the data type returned. It must be one of json,
+ jsonb, bytea, a character string type (text,
+ char, varchar, or nchar), or a type
+ for which there is a cast from json to that type.
+ By default, the json type is returned.
@@ -15760,6 +15806,45 @@ table2-mapping
+
+
+
+ json_array
+ json_array (
+ { value_expression FORMAT JSON } , ...
+ { NULL | ABSENT } ON NULL
+ RETURNING data_type FORMAT JSON ENCODING UTF8 )
+
+
+ json_array (
+ query_expression
+ RETURNING data_type FORMAT JSON ENCODING UTF8 )
+
+
+ Constructs a JSON array from either a series of
+ value_expression parameters or from the results
+ of query_expression,
+ which must be a SELECT query returning a single column. If
+ ABSENT ON NULL is specified, NULL values are ignored.
+ This is always the case if a
+ query_expression is used.
+
+
+ json_array(1,true,json '{"a":null}')
+ [1, true, {"a":null}]
+
+
+ json_array(SELECT * FROM (VALUES(1),(2)) t)
+ [1, 2]
+
+
+
@@ -15833,6 +15918,38 @@ table2-mapping
+
+
+ json_object
+ json_object (
+ { key_expression { VALUE | ':' }
+ value_expression FORMAT JSON ENCODING UTF8 }, ...
+ { NULL | ABSENT } ON NULL
+ { WITH | WITHOUT } UNIQUE KEYS
+ RETURNING data_type FORMAT JSON ENCODING UTF8 )
+
+
+ Constructs a JSON object of all the key/value pairs given,
+ or an empty object if none are given.
+ key_expression is a scalar expression
+ defining the JSON key, which is
+ converted to the text type.
+ It cannot be NULL nor can it
+ belong to a type that has a cast to the json type.
+ If WITH UNIQUE KEYS is specified, there must not
+ be any duplicate key_expression.
+ Any pair for which the value_expression
+ evaluates to NULL is omitted from the output
+ if ABSENT ON NULL is specified;
+ if NULL ON NULL is specified or the clause
+ omitted, the key is included with value NULL.
+
+
+ json_object('code' VALUE 'P123', 'title': 'Jaws')
+ {"code" : "P123", "title" : "Jaws"}
+
+
+
@@ -20077,6 +20194,28 @@ SELECT NULLIF(value, '(none)') ...
No
+
+
+ json_objectagg
+ json_objectagg (
+ { key_expression { VALUE | ':' } value_expression }
+ { NULL | ABSENT } ON NULL
+ { WITH | WITHOUT } UNIQUE KEYS
+ RETURNING data_type FORMAT JSON ENCODING UTF8 )
+
+
+ Behaves like json_object, but as an
+ aggregate function, so it only takes one
+ key_expression and one
+ value_expression parameter.
+
+
+ SELECT json_objectagg(k:v) FROM (VALUES ('a'::text,current_date),('b',current_date + 1)) AS t(k,v)
+ { "a" : "2022-05-10", "b" : "2022-05-11" }
+
+ No
+
+
@@ -20098,9 +20237,122 @@ SELECT NULLIF(value, '(none)') ...
Collects all the key/value pairs into a JSON object. Key arguments
- are coerced to text; value arguments are converted as
- per to_json or to_jsonb.
- Values can be null, but not keys.
+ are coerced to text; value arguments are converted as per
+ to_json or to_jsonb.
+ Values can be null, but keys cannot.
+
+ No
+
+
+
+
+
+ json_object_agg_strict
+
+ json_object_agg_strict (
+ key "any",
+ value "any" )
+ json
+
+
+
+ jsonb_object_agg_strict
+
+ jsonb_object_agg_strict (
+ key "any",
+ value "any" )
+ jsonb
+
+
+ Collects all the key/value pairs into a JSON object. Key arguments
+ are coerced to text; value arguments are converted as per
+ to_json or to_jsonb.
+ The key can not be null. If the
+ value is null then the entry is skipped,
+
+ No
+
+
+
+
+
+ json_object_agg_unique
+
+ json_object_agg_unique (
+ key "any",
+ value "any" )
+ json
+
+
+
+ jsonb_object_agg_unique
+
+ jsonb_object_agg_unique (
+ key "any",
+ value "any" )
+ jsonb
+
+
+ Collects all the key/value pairs into a JSON object. Key arguments
+ are coerced to text; value arguments are converted as per
+ to_json or to_jsonb.
+ Values can be null, but keys cannot.
+ If there is a duplicate key an error is thrown.
+
+ No
+
+
+
+
+ json_arrayagg
+ json_arrayagg (
+ value_expression
+ ORDER BY sort_expression
+ { NULL | ABSENT } ON NULL
+ RETURNING data_type FORMAT JSON ENCODING UTF8 )
+
+
+ Behaves in the same way as json_array
+ but as an aggregate function so it only takes one
+ value_expression parameter.
+ If ABSENT ON NULL is specified, any NULL
+ values are omitted.
+ If ORDER BY is specified, the elements will
+ appear in the array in that order rather than in the input order.
+
+
+ SELECT json_arrayagg(v) FROM (VALUES(2),(1)) t(v)
+ [2, 1]
+
+ No
+
+
+
+
+
+ json_object_agg_unique_strict
+
+ json_object_agg_unique_strict (
+ key "any",
+ value "any" )
+ json
+
+
+
+ jsonb_object_agg_unique_strict
+
+ jsonb_object_agg_unique_strict (
+ key "any",
+ value "any" )
+ jsonb
+
+
+ Collects all the key/value pairs into a JSON object. Key arguments
+ are coerced to text; value arguments are converted as per
+ to_json or to_jsonb.
+ The key can not be null. If the
+ value is null then the entry is skipped.
+ If there is a duplicate key an error is thrown.
No
@@ -20183,6 +20435,29 @@ SELECT NULLIF(value, '(none)') ...
No
+
+
+
+ json_agg_strict
+
+ json_agg_strict ( anyelement )
+ json
+
+
+
+ jsonb_agg_strict
+
+ jsonb_agg_strict ( anyelement )
+ jsonb
+
+
+ Collects all the input values, skipping nulls, into a JSON array.
+ Values are converted to JSON as per to_json
+ or to_jsonb.
+
+ No
+
+
@@ -20278,7 +20553,12 @@ SELECT NULLIF(value, '(none)') ...
The aggregate functions array_agg,
json_agg, jsonb_agg,
+ json_agg_strict, jsonb_agg_strict,
json_object_agg, jsonb_object_agg,
+ json_object_agg_strict, jsonb_object_agg_strict,
+ json_object_agg_unique, jsonb_object_agg_unique,
+ json_object_agg_unique_strict,
+ jsonb_object_agg_unique_strict,
string_agg,
and xmlagg, as well as similar user-defined
aggregate functions, produce meaningfully different result values
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c61f23c6c18..6c5a3780292 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2279,6 +2279,97 @@ ExecInitExprRec(Expr *node, ExprState *state,
break;
}
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *jve = (JsonValueExpr *) node;
+
+ ExecInitExprRec(jve->raw_expr, state, resv, resnull);
+
+ if (jve->formatted_expr)
+ {
+ Datum *innermost_caseval = state->innermost_caseval;
+ bool *innermost_isnull = state->innermost_casenull;
+
+ state->innermost_caseval = resv;
+ state->innermost_casenull = resnull;
+
+ ExecInitExprRec(jve->formatted_expr, state, resv, resnull);
+
+ state->innermost_caseval = innermost_caseval;
+ state->innermost_casenull = innermost_isnull;
+ }
+ break;
+ }
+
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+ List *args = ctor->args;
+ ListCell *lc;
+ int nargs = list_length(args);
+ int argno = 0;
+
+ if (ctor->func)
+ {
+ ExecInitExprRec(ctor->func, state, resv, resnull);
+ }
+ else
+ {
+ JsonConstructorExprState *jcstate;
+
+ jcstate = palloc0(sizeof(JsonConstructorExprState));
+
+ scratch.opcode = EEOP_JSON_CONSTRUCTOR;
+ scratch.d.json_constructor.jcstate = jcstate;
+
+ jcstate->constructor = ctor;
+ jcstate->arg_values = (Datum *) palloc(sizeof(Datum) * nargs);
+ jcstate->arg_nulls = (bool *) palloc(sizeof(bool) * nargs);
+ jcstate->arg_types = (Oid *) palloc(sizeof(Oid) * nargs);
+ jcstate->nargs = nargs;
+
+ foreach(lc, args)
+ {
+ Expr *arg = (Expr *) lfirst(lc);
+
+ jcstate->arg_types[argno] = exprType((Node *) arg);
+
+ if (IsA(arg, Const))
+ {
+ /* Don't evaluate const arguments every round */
+ Const *con = (Const *) arg;
+
+ jcstate->arg_values[argno] = con->constvalue;
+ jcstate->arg_nulls[argno] = con->constisnull;
+ }
+ else
+ {
+ ExecInitExprRec(arg, state,
+ &jcstate->arg_values[argno],
+ &jcstate->arg_nulls[argno]);
+ }
+ argno++;
+ }
+
+ ExprEvalPushStep(state, &scratch);
+ }
+
+ if (ctor->coercion)
+ {
+ Datum *innermost_caseval = state->innermost_caseval;
+ bool *innermost_isnull = state->innermost_casenull;
+
+ state->innermost_caseval = resv;
+ state->innermost_casenull = resnull;
+
+ ExecInitExprRec(ctor->coercion, state, resv, resnull);
+
+ state->innermost_caseval = innermost_caseval;
+ state->innermost_casenull = innermost_isnull;
+ }
+ }
+ break;
+
case T_NullTest:
{
NullTest *ntest = (NullTest *) node;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index dd7d1af220a..a37ba4dd55b 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -71,6 +71,8 @@
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/expandedrecord.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -474,6 +476,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_SCALARARRAYOP,
&&CASE_EEOP_HASHED_SCALARARRAYOP,
&&CASE_EEOP_XMLEXPR,
+ &&CASE_EEOP_JSON_CONSTRUCTOR,
&&CASE_EEOP_AGGREF,
&&CASE_EEOP_GROUPING_FUNC,
&&CASE_EEOP_WINDOW_FUNC,
@@ -1511,6 +1514,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_JSON_CONSTRUCTOR)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalJsonConstructor(state, op, econtext);
+ EEO_NEXT();
+ }
+
EEO_CASE(EEOP_AGGREF)
{
/*
@@ -4437,3 +4447,43 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
MemoryContextSwitchTo(oldContext);
}
+
+/*
+ * Evaluate a JSON constructor expression.
+ */
+void
+ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ Datum res;
+ JsonConstructorExprState *jcstate = op->d.json_constructor.jcstate;
+ JsonConstructorExpr *ctor = jcstate->constructor;
+ bool is_jsonb = ctor->returning->format->format_type == JS_FORMAT_JSONB;
+ bool isnull = false;
+
+ if (ctor->type == JSCTOR_JSON_ARRAY)
+ res = (is_jsonb ?
+ jsonb_build_array_worker :
+ json_build_array_worker) (jcstate->nargs,
+ jcstate->arg_values,
+ jcstate->arg_nulls,
+ jcstate->arg_types,
+ jcstate->constructor->absent_on_null);
+ else if (ctor->type == JSCTOR_JSON_OBJECT)
+ res = (is_jsonb ?
+ jsonb_build_object_worker :
+ json_build_object_worker) (jcstate->nargs,
+ jcstate->arg_values,
+ jcstate->arg_nulls,
+ jcstate->arg_types,
+ jcstate->constructor->absent_on_null,
+ jcstate->constructor->unique);
+ else
+ {
+ res = (Datum) 0;
+ elog(ERROR, "invalid JsonConstructorExpr type %d", ctor->type);
+ }
+
+ *op->resvalue = res;
+ *op->resnull = isnull;
+}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 1c722c79552..12d39394b31 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1842,6 +1842,12 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_JSON_CONSTRUCTOR:
+ build_EvalXFunc(b, mod, "ExecEvalJsonConstructor",
+ v_state, op, v_econtext);
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ break;
+
case EEOP_AGGREF:
{
LLVMValueRef v_aggno;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 876fb640294..315eeb11720 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -132,6 +132,7 @@ void *referenced_functions[] =
ExecEvalSysVar,
ExecEvalWholeRowVar,
ExecEvalXmlExpr,
+ ExecEvalJsonConstructor,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 216383ca239..23c71528065 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -19,6 +19,7 @@
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "utils/errcodes.h"
#include "utils/lsyscache.h"
@@ -825,3 +826,71 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+/*
+ * makeJsonFormat -
+ * creates a JsonFormat node
+ */
+JsonFormat *
+makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location)
+{
+ JsonFormat *jf = makeNode(JsonFormat);
+
+ jf->format_type = type;
+ jf->encoding = encoding;
+ jf->location = location;
+
+ return jf;
+}
+
+/*
+ * makeJsonValueExpr -
+ * creates a JsonValueExpr node
+ */
+JsonValueExpr *
+makeJsonValueExpr(Expr *expr, JsonFormat *format)
+{
+ JsonValueExpr *jve = makeNode(JsonValueExpr);
+
+ jve->raw_expr = expr;
+ jve->formatted_expr = NULL;
+ jve->format = format;
+
+ return jve;
+}
+
+/*
+ * makeJsonEncoding -
+ * converts JSON encoding name to enum JsonEncoding
+ */
+JsonEncoding
+makeJsonEncoding(char *name)
+{
+ if (!pg_strcasecmp(name, "utf8"))
+ return JS_ENC_UTF8;
+ if (!pg_strcasecmp(name, "utf16"))
+ return JS_ENC_UTF16;
+ if (!pg_strcasecmp(name, "utf32"))
+ return JS_ENC_UTF32;
+
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized JSON encoding: %s", name));
+
+ return JS_ENC_DEFAULT;
+}
+
+/*
+ * makeJsonKeyValue -
+ * creates a JsonKeyValue node
+ */
+Node *
+makeJsonKeyValue(Node *key, Node *value)
+{
+ JsonKeyValue *n = makeNode(JsonKeyValue);
+
+ n->key = (Expr *) key;
+ n->value = castNode(JsonValueExpr, value);
+
+ return (Node *) n;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index dc8415a693c..69907fbcde8 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -249,6 +249,18 @@ exprType(const Node *expr)
case T_PlaceHolderVar:
type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_JsonValueExpr:
+ {
+ const JsonValueExpr *jve = (const JsonValueExpr *) expr;
+
+ type = exprType((Node *)
+ (jve->formatted_expr ? jve->formatted_expr :
+ jve->raw_expr));
+ }
+ break;
+ case T_JsonConstructorExpr:
+ type = ((const JsonConstructorExpr *) expr)->returning->typid;
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -479,6 +491,10 @@ exprTypmod(const Node *expr)
return ((const SetToDefault *) expr)->typeMod;
case T_PlaceHolderVar:
return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+ case T_JsonValueExpr:
+ return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
+ case T_JsonConstructorExpr:
+ return -1; /* XXX maybe expr->returning->typmod? */
default:
break;
}
@@ -954,6 +970,19 @@ exprCollation(const Node *expr)
case T_PlaceHolderVar:
coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_JsonValueExpr:
+ coll = exprCollation((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
+ break;
+ case T_JsonConstructorExpr:
+ {
+ const JsonConstructorExpr *ctor = (const JsonConstructorExpr *) expr;
+
+ if (ctor->coercion)
+ coll = exprCollation((Node *) ctor->coercion);
+ else
+ coll = InvalidOid;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1161,6 +1190,21 @@ exprSetCollation(Node *expr, Oid collation)
/* NextValueExpr's result is an integer type ... */
Assert(!OidIsValid(collation)); /* ... so never set a collation */
break;
+ case T_JsonValueExpr:
+ exprSetCollation((Node *) ((JsonValueExpr *) expr)->formatted_expr,
+ collation);
+ break;
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *ctor = (JsonConstructorExpr *) expr;
+
+ if (ctor->coercion)
+ exprSetCollation((Node *) ctor->coercion, collation);
+ else
+ Assert(!OidIsValid(collation)); /* result is always a
+ * json[b] type */
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1603,6 +1647,12 @@ exprLocation(const Node *expr)
case T_PartitionRangeDatum:
loc = ((const PartitionRangeDatum *) expr)->location;
break;
+ case T_JsonValueExpr:
+ loc = exprLocation((Node *) ((const JsonValueExpr *) expr)->raw_expr);
+ break;
+ case T_JsonConstructorExpr:
+ loc = ((const JsonConstructorExpr *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2334,6 +2384,28 @@ expression_tree_walker_impl(Node *node,
return true;
}
break;
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *jve = (JsonValueExpr *) node;
+
+ if (WALK(jve->raw_expr))
+ return true;
+ if (WALK(jve->formatted_expr))
+ return true;
+ }
+ break;
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+
+ if (WALK(ctor->args))
+ return true;
+ if (WALK(ctor->func))
+ return true;
+ if (WALK(ctor->coercion))
+ return true;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -2664,6 +2736,7 @@ expression_tree_mutator_impl(Node *node,
case T_RangeTblRef:
case T_SortGroupClause:
case T_CTESearchClause:
+ case T_JsonFormat:
return (Node *) copyObject(node);
case T_WithCheckOption:
{
@@ -3307,6 +3380,41 @@ expression_tree_mutator_impl(Node *node,
return (Node *) newnode;
}
break;
+ case T_JsonReturning:
+ {
+ JsonReturning *jr = (JsonReturning *) node;
+ JsonReturning *newnode;
+
+ FLATCOPY(newnode, jr, JsonReturning);
+ MUTATE(newnode->format, jr->format, JsonFormat *);
+
+ return (Node *) newnode;
+ }
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *jve = (JsonValueExpr *) node;
+ JsonValueExpr *newnode;
+
+ FLATCOPY(newnode, jve, JsonValueExpr);
+ MUTATE(newnode->raw_expr, jve->raw_expr, Expr *);
+ MUTATE(newnode->formatted_expr, jve->formatted_expr, Expr *);
+ MUTATE(newnode->format, jve->format, JsonFormat *);
+
+ return (Node *) newnode;
+ }
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *jve = (JsonConstructorExpr *) node;
+ JsonConstructorExpr *newnode;
+
+ FLATCOPY(newnode, jve, JsonConstructorExpr);
+ MUTATE(newnode->args, jve->args, List *);
+ MUTATE(newnode->func, jve->func, Expr *);
+ MUTATE(newnode->coercion, jve->coercion, Expr *);
+ MUTATE(newnode->returning, jve->returning, JsonReturning *);
+
+ return (Node *) newnode;
+ }
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3578,6 +3686,7 @@ raw_expression_tree_walker_impl(Node *node,
case T_ParamRef:
case T_A_Const:
case T_A_Star:
+ case T_JsonFormat:
/* primitive node types with no subnodes */
break;
case T_Alias:
@@ -4040,6 +4149,118 @@ raw_expression_tree_walker_impl(Node *node,
case T_CommonTableExpr:
/* search_clause and cycle_clause are not interesting here */
return WALK(((CommonTableExpr *) node)->ctequery);
+ case T_JsonReturning:
+ return WALK(((JsonReturning *) node)->format);
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *jve = (JsonValueExpr *) node;
+
+ if (WALK(jve->raw_expr))
+ return true;
+ if (WALK(jve->formatted_expr))
+ return true;
+ if (WALK(jve->format))
+ return true;
+ }
+ break;
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+
+ if (WALK(ctor->args))
+ return true;
+ if (WALK(ctor->func))
+ return true;
+ if (WALK(ctor->coercion))
+ return true;
+ if (WALK(ctor->returning))
+ return true;
+ }
+ break;
+ case T_JsonOutput:
+ {
+ JsonOutput *out = (JsonOutput *) node;
+
+ if (WALK(out->typeName))
+ return true;
+ if (WALK(out->returning))
+ return true;
+ }
+ break;
+ case T_JsonKeyValue:
+ {
+ JsonKeyValue *jkv = (JsonKeyValue *) node;
+
+ if (WALK(jkv->key))
+ return true;
+ if (WALK(jkv->value))
+ return true;
+ }
+ break;
+ case T_JsonObjectConstructor:
+ {
+ JsonObjectConstructor *joc = (JsonObjectConstructor *) node;
+
+ if (WALK(joc->output))
+ return true;
+ if (WALK(joc->exprs))
+ return true;
+ }
+ break;
+ case T_JsonArrayConstructor:
+ {
+ JsonArrayConstructor *jac = (JsonArrayConstructor *) node;
+
+ if (WALK(jac->output))
+ return true;
+ if (WALK(jac->exprs))
+ return true;
+ }
+ break;
+ case T_JsonAggConstructor:
+ {
+ JsonAggConstructor *ctor = (JsonAggConstructor *) node;
+
+ if (WALK(ctor->output))
+ return true;
+ if (WALK(ctor->agg_order))
+ return true;
+ if (WALK(ctor->agg_filter))
+ return true;
+ if (WALK(ctor->over))
+ return true;
+ }
+ break;
+ case T_JsonObjectAgg:
+ {
+ JsonObjectAgg *joa = (JsonObjectAgg *) node;
+
+ if (WALK(joa->constructor))
+ return true;
+ if (WALK(joa->arg))
+ return true;
+ }
+ break;
+ case T_JsonArrayAgg:
+ {
+ JsonArrayAgg *jaa = (JsonArrayAgg *) node;
+
+ if (WALK(jaa->constructor))
+ return true;
+ if (WALK(jaa->arg))
+ return true;
+ }
+ break;
+ case T_JsonArrayQueryConstructor:
+ {
+ JsonArrayQueryConstructor *jaqc = (JsonArrayQueryConstructor *) node;
+
+ if (WALK(jaqc->output))
+ return true;
+ if (WALK(jaqc->query))
+ return true;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index fc245c60e41..a9c7bc342ec 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -51,6 +51,8 @@
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
@@ -383,6 +385,33 @@ contain_mutable_functions_walker(Node *node, void *context)
context))
return true;
+ if (IsA(node, JsonConstructorExpr))
+ {
+ const JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
+ ListCell *lc;
+ bool is_jsonb;
+
+ is_jsonb = ctor->returning->format->format_type == JS_FORMAT_JSONB;
+
+ /*
+ * Check argument_type => json[b] conversions specifically. We still
+ * recurse to check 'args' below, but here we want to specifically
+ * check whether or not the emitted clause would fail to be immutable
+ * because of TimeZone, for example.
+ */
+ foreach(lc, ctor->args)
+ {
+ Oid typid = exprType(lfirst(lc));
+
+ if (is_jsonb ?
+ !to_jsonb_is_immutable(typid) :
+ !to_json_is_immutable(typid))
+ return true;
+ }
+
+ /* Check all subnodes */
+ }
+
if (IsA(node, NextValueExpr))
{
/* NextValueExpr is volatile */
@@ -2786,6 +2815,32 @@ eval_const_expressions_mutator(Node *node,
}
break;
}
+
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *jve = (JsonValueExpr *) node;
+ Node *raw;
+
+ raw = eval_const_expressions_mutator((Node *) jve->raw_expr,
+ context);
+ if (raw && IsA(raw, Const))
+ {
+ Node *formatted;
+ Node *save_case_val = context->case_val;
+
+ context->case_val = raw;
+
+ formatted = eval_const_expressions_mutator((Node *) jve->formatted_expr,
+ context);
+
+ context->case_val = save_case_val;
+
+ if (formatted && IsA(formatted, Const))
+ return formatted;
+ }
+ break;
+ }
+
case T_SubPlan:
case T_AlternativeSubPlan:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index efe88ccf9da..54c7896a6c6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -644,6 +644,22 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type hash_partbound_elem
+%type json_format_clause_opt
+ json_value_expr
+ json_output_clause_opt
+ json_name_and_value
+ json_aggregate_func
+
+%type json_name_and_value_list
+ json_value_expr_list
+ json_array_aggregate_order_by_clause_opt
+
+%type json_encoding_clause_opt
+
+%type json_key_uniqueness_constraint_opt
+ json_object_constructor_null_clause_opt
+ json_array_constructor_null_clause_opt
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -669,7 +685,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
*/
/* ordinary key words in alphabetical order */
-%token ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
+%token ABORT_P ABSENT ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
ASENSITIVE ASSERTION ASSIGNMENT ASYMMETRIC ATOMIC AT ATTACH ATTRIBUTE AUTHORIZATION
@@ -695,7 +711,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXTENSION EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
- FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
+ FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
@@ -706,9 +722,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
- JOIN
+ JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG
- KEY
+ KEY KEYS
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
@@ -772,9 +788,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* NOT_LA exists so that productions such as NOT LIKE can be given the same
* precedence as LIKE; otherwise they'd effectively have the same precedence
* as NOT, at least with respect to their left-hand subexpression.
- * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
+ * FORMAT_LA, NULLS_LA, WITH_LA, and WITHOUT_LA are needed to make the grammar
+ * LALR(1).
*/
-%token NOT_LA NULLS_LA WITH_LA
+%token FORMAT_LA NOT_LA NULLS_LA WITH_LA WITHOUT_LA
/*
* The grammar likewise thinks these tokens are keywords, but they are never
@@ -792,6 +809,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* Precedence: lowest to highest */
%nonassoc SET /* see relation_expr_opt_alias */
+%right FORMAT
%left UNION EXCEPT
%left INTERSECT
%left OR
@@ -11698,6 +11716,7 @@ utility_option_elem:
utility_option_name:
NonReservedWord { $$ = $1; }
| analyze_keyword { $$ = "analyze"; }
+ | FORMAT_LA { $$ = "format"; }
;
utility_option_arg:
@@ -15185,6 +15204,16 @@ func_expr: func_application within_group_clause filter_clause over_clause
n->over = $4;
$$ = (Node *) n;
}
+ | json_aggregate_func filter_clause over_clause
+ {
+ JsonAggConstructor *n = IsA($1, JsonObjectAgg) ?
+ ((JsonObjectAgg *) $1)->constructor :
+ ((JsonArrayAgg *) $1)->constructor;
+
+ n->agg_filter = $2;
+ n->over = $3;
+ $$ = (Node *) $1;
+ }
| func_expr_common_subexpr
{ $$ = $1; }
;
@@ -15198,6 +15227,7 @@ func_expr: func_application within_group_clause filter_clause over_clause
func_expr_windowless:
func_application { $$ = $1; }
| func_expr_common_subexpr { $$ = $1; }
+ | json_aggregate_func { $$ = $1; }
;
/*
@@ -15543,6 +15573,79 @@ func_expr_common_subexpr:
n->location = @1;
$$ = (Node *) n;
}
+ | JSON_OBJECT '(' func_arg_list ')'
+ {
+ /* Support for legacy (non-standard) json_object() */
+ $$ = (Node *) makeFuncCall(SystemFuncName("json_object"),
+ $3, COERCE_EXPLICIT_CALL, @1);
+ }
+ | JSON_OBJECT '(' json_name_and_value_list
+ json_object_constructor_null_clause_opt
+ json_key_uniqueness_constraint_opt
+ json_output_clause_opt ')'
+ {
+ JsonObjectConstructor *n = makeNode(JsonObjectConstructor);
+
+ n->exprs = $3;
+ n->absent_on_null = $4;
+ n->unique = $5;
+ n->output = (JsonOutput *) $6;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | JSON_OBJECT '(' json_output_clause_opt ')'
+ {
+ JsonObjectConstructor *n = makeNode(JsonObjectConstructor);
+
+ n->exprs = NULL;
+ n->absent_on_null = false;
+ n->unique = false;
+ n->output = (JsonOutput *) $3;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | JSON_ARRAY '('
+ json_value_expr_list
+ json_array_constructor_null_clause_opt
+ json_output_clause_opt
+ ')'
+ {
+ JsonArrayConstructor *n = makeNode(JsonArrayConstructor);
+
+ n->exprs = $3;
+ n->absent_on_null = $4;
+ n->output = (JsonOutput *) $5;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | JSON_ARRAY '('
+ select_no_parens
+ json_format_clause_opt
+ /* json_array_constructor_null_clause_opt */
+ json_output_clause_opt
+ ')'
+ {
+ JsonArrayQueryConstructor *n = makeNode(JsonArrayQueryConstructor);
+
+ n->query = $3;
+ n->format = (JsonFormat *) $4;
+ n->absent_on_null = true; /* XXX */
+ n->output = (JsonOutput *) $5;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | JSON_ARRAY '('
+ json_output_clause_opt
+ ')'
+ {
+ JsonArrayConstructor *n = makeNode(JsonArrayConstructor);
+
+ n->exprs = NIL;
+ n->absent_on_null = true;
+ n->output = (JsonOutput *) $3;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
;
/*
@@ -16267,6 +16370,132 @@ opt_asymmetric: ASYMMETRIC
| /*EMPTY*/
;
+/* SQL/JSON support */
+json_value_expr:
+ a_expr json_format_clause_opt
+ {
+ $$ = (Node *) makeJsonValueExpr((Expr *) $1, castNode(JsonFormat, $2));
+ }
+ ;
+
+json_format_clause_opt:
+ FORMAT_LA JSON json_encoding_clause_opt
+ {
+ $$ = (Node *) makeJsonFormat(JS_FORMAT_JSON, $3, @1);
+ }
+ | /* EMPTY */
+ {
+ $$ = (Node *) makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+ }
+ ;
+
+json_encoding_clause_opt:
+ ENCODING name { $$ = makeJsonEncoding($2); }
+ | /* EMPTY */ { $$ = JS_ENC_DEFAULT; }
+ ;
+
+json_output_clause_opt:
+ RETURNING Typename json_format_clause_opt
+ {
+ JsonOutput *n = makeNode(JsonOutput);
+
+ n->typeName = $2;
+ n->returning = makeNode(JsonReturning);
+ n->returning->format = (JsonFormat *) $3;
+ $$ = (Node *) n;
+ }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+/* KEYS is a noise word here */
+json_key_uniqueness_constraint_opt:
+ WITH UNIQUE KEYS { $$ = true; }
+ | WITH UNIQUE { $$ = true; }
+ | WITHOUT_LA UNIQUE KEYS { $$ = false; }
+ | WITHOUT_LA UNIQUE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+json_name_and_value_list:
+ json_name_and_value
+ { $$ = list_make1($1); }
+ | json_name_and_value_list ',' json_name_and_value
+ { $$ = lappend($1, $3); }
+ ;
+
+json_name_and_value:
+/* Supporting this syntax seems to require major surgery
+ KEY c_expr VALUE_P json_value_expr
+ { $$ = makeJsonKeyValue($2, $4); }
+ |
+*/
+ c_expr VALUE_P json_value_expr
+ { $$ = makeJsonKeyValue($1, $3); }
+ |
+ a_expr ':' json_value_expr
+ { $$ = makeJsonKeyValue($1, $3); }
+ ;
+
+/* empty means false for objects, true for arrays */
+json_object_constructor_null_clause_opt:
+ NULL_P ON NULL_P { $$ = false; }
+ | ABSENT ON NULL_P { $$ = true; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+json_array_constructor_null_clause_opt:
+ NULL_P ON NULL_P { $$ = false; }
+ | ABSENT ON NULL_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+json_value_expr_list:
+ json_value_expr { $$ = list_make1($1); }
+ | json_value_expr_list ',' json_value_expr { $$ = lappend($1, $3);}
+ ;
+
+json_aggregate_func:
+ JSON_OBJECTAGG '('
+ json_name_and_value
+ json_object_constructor_null_clause_opt
+ json_key_uniqueness_constraint_opt
+ json_output_clause_opt
+ ')'
+ {
+ JsonObjectAgg *n = makeNode(JsonObjectAgg);
+
+ n->arg = (JsonKeyValue *) $3;
+ n->absent_on_null = $4;
+ n->unique = $5;
+ n->constructor = makeNode(JsonAggConstructor);
+ n->constructor->output = (JsonOutput *) $6;
+ n->constructor->agg_order = NULL;
+ n->constructor->location = @1;
+ $$ = (Node *) n;
+ }
+ | JSON_ARRAYAGG '('
+ json_value_expr
+ json_array_aggregate_order_by_clause_opt
+ json_array_constructor_null_clause_opt
+ json_output_clause_opt
+ ')'
+ {
+ JsonArrayAgg *n = makeNode(JsonArrayAgg);
+
+ n->arg = (JsonValueExpr *) $3;
+ n->absent_on_null = $5;
+ n->constructor = makeNode(JsonAggConstructor);
+ n->constructor->agg_order = $4;
+ n->constructor->output = (JsonOutput *) $6;
+ n->constructor->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_array_aggregate_order_by_clause_opt:
+ ORDER BY sortby_list { $$ = $3; }
+ | /* EMPTY */ { $$ = NIL; }
+ ;
/*****************************************************************************
*
@@ -16718,6 +16947,7 @@ BareColLabel: IDENT { $$ = $1; }
*/
unreserved_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -16814,6 +17044,7 @@ unreserved_keyword:
| FIRST_P
| FOLLOWING
| FORCE
+ | FORMAT
| FORWARD
| FUNCTION
| FUNCTIONS
@@ -16846,7 +17077,9 @@ unreserved_keyword:
| INSTEAD
| INVOKER
| ISOLATION
+ | JSON
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
@@ -17058,6 +17291,10 @@ col_name_keyword:
| INT_P
| INTEGER
| INTERVAL
+ | JSON_ARRAY
+ | JSON_ARRAYAGG
+ | JSON_OBJECT
+ | JSON_OBJECTAGG
| LEAST
| NATIONAL
| NCHAR
@@ -17227,6 +17464,7 @@ reserved_keyword:
*/
bare_label_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -17366,6 +17604,7 @@ bare_label_keyword:
| FOLLOWING
| FORCE
| FOREIGN
+ | FORMAT
| FORWARD
| FREEZE
| FULL
@@ -17411,7 +17650,13 @@ bare_label_keyword:
| IS
| ISOLATION
| JOIN
+ | JSON
+ | JSON_ARRAY
+ | JSON_ARRAYAGG
+ | JSON_OBJECT
+ | JSON_OBJECTAGG
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 23314175522..a134878b1e9 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -15,6 +15,8 @@
#include "postgres.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "miscadmin.h"
@@ -34,6 +36,7 @@
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/date.h"
+#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/timestamp.h"
#include "utils/xml.h"
@@ -72,6 +75,14 @@ static Node *transformWholeRowRef(ParseState *pstate,
static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformJsonObjectConstructor(ParseState *pstate,
+ JsonObjectConstructor *ctor);
+static Node *transformJsonArrayConstructor(ParseState *pstate,
+ JsonArrayConstructor *ctor);
+static Node *transformJsonArrayQueryConstructor(ParseState *pstate,
+ JsonArrayQueryConstructor *ctor);
+static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg);
+static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg);
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -294,6 +305,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
break;
}
+ case T_JsonObjectConstructor:
+ result = transformJsonObjectConstructor(pstate, (JsonObjectConstructor *) expr);
+ break;
+
+ case T_JsonArrayConstructor:
+ result = transformJsonArrayConstructor(pstate, (JsonArrayConstructor *) expr);
+ break;
+
+ case T_JsonArrayQueryConstructor:
+ result = transformJsonArrayQueryConstructor(pstate, (JsonArrayQueryConstructor *) expr);
+ break;
+
+ case T_JsonObjectAgg:
+ result = transformJsonObjectAgg(pstate, (JsonObjectAgg *) expr);
+ break;
+
+ case T_JsonArrayAgg:
+ result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3047,3 +3078,742 @@ ParseExprKindName(ParseExprKind exprKind)
}
return "unrecognized expression kind";
}
+
+/*
+ * Make string Const node from JSON encoding name.
+ *
+ * UTF8 is default encoding.
+ */
+static Const *
+getJsonEncodingConst(JsonFormat *format)
+{
+ JsonEncoding encoding;
+ const char *enc;
+ Name encname = palloc(sizeof(NameData));
+
+ if (!format ||
+ format->format_type == JS_FORMAT_DEFAULT ||
+ format->encoding == JS_ENC_DEFAULT)
+ encoding = JS_ENC_UTF8;
+ else
+ encoding = format->encoding;
+
+ switch (encoding)
+ {
+ case JS_ENC_UTF16:
+ enc = "UTF16";
+ break;
+ case JS_ENC_UTF32:
+ enc = "UTF32";
+ break;
+ case JS_ENC_UTF8:
+ enc = "UTF8";
+ break;
+ default:
+ elog(ERROR, "invalid JSON encoding: %d", encoding);
+ break;
+ }
+
+ namestrcpy(encname, enc);
+
+ return makeConst(NAMEOID, -1, InvalidOid, NAMEDATALEN,
+ NameGetDatum(encname), false, false);
+}
+
+/*
+ * Make bytea => text conversion using specified JSON format encoding.
+ */
+static Node *
+makeJsonByteaToTextConversion(Node *expr, JsonFormat *format, int location)
+{
+ Const *encoding = getJsonEncodingConst(format);
+ FuncExpr *fexpr = makeFuncExpr(F_CONVERT_FROM, TEXTOID,
+ list_make2(expr, encoding),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+
+ fexpr->location = location;
+
+ return (Node *) fexpr;
+}
+
+/*
+ * Make CaseTestExpr node.
+ */
+static Node *
+makeCaseTestExpr(Node *expr)
+{
+ CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+
+ placeholder->typeId = exprType(expr);
+ placeholder->typeMod = exprTypmod(expr);
+ placeholder->collation = exprCollation(expr);
+
+ return (Node *) placeholder;
+}
+
+/*
+ * Transform JSON value expression using specified input JSON format or
+ * default format otherwise.
+ */
+static Node *
+transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve,
+ JsonFormatType default_format)
+{
+ Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr);
+ Node *rawexpr;
+ JsonFormatType format;
+ Oid exprtype;
+ int location;
+ char typcategory;
+ bool typispreferred;
+
+ /*
+ * Using JSON_VALUE here is slightly bogus: perhaps we need to be passed a
+ * JsonConstructorType so that we can use one of JSON_OBJECTAGG, etc.
+ */
+ if (exprType(expr) == UNKNOWNOID)
+ expr = coerce_to_specific_type(pstate, expr, TEXTOID, "JSON_VALUE");
+
+ rawexpr = expr;
+ exprtype = exprType(expr);
+ location = exprLocation(expr);
+
+ get_type_category_preferred(exprtype, &typcategory, &typispreferred);
+
+ if (ve->format->format_type != JS_FORMAT_DEFAULT)
+ {
+ if (ve->format->encoding != JS_ENC_DEFAULT && exprtype != BYTEAOID)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("JSON ENCODING clause is only allowed for bytea input type"),
+ parser_errposition(pstate, ve->format->location));
+
+ if (exprtype == JSONOID || exprtype == JSONBOID)
+ {
+ format = JS_FORMAT_DEFAULT; /* do not format json[b] types */
+ ereport(WARNING,
+ errmsg("FORMAT JSON has no effect for json and jsonb types"),
+ parser_errposition(pstate, ve->format->location));
+ }
+ else
+ format = ve->format->format_type;
+ }
+ else if (exprtype == JSONOID || exprtype == JSONBOID)
+ format = JS_FORMAT_DEFAULT; /* do not format json[b] types */
+ else
+ format = default_format;
+
+ if (format != JS_FORMAT_DEFAULT)
+ {
+ Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+ Node *orig = makeCaseTestExpr(expr);
+ Node *coerced;
+
+ expr = orig;
+
+ if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg(ve->format->format_type == JS_FORMAT_DEFAULT ?
+ "cannot use non-string types with implicit FORMAT JSON clause" :
+ "cannot use non-string types with explicit FORMAT JSON clause"),
+ parser_errposition(pstate, ve->format->location >= 0 ?
+ ve->format->location : location));
+
+ /* Convert encoded JSON text from bytea. */
+ if (format == JS_FORMAT_JSON && exprtype == BYTEAOID)
+ {
+ expr = makeJsonByteaToTextConversion(expr, ve->format, location);
+ exprtype = TEXTOID;
+ }
+
+ /* Try to coerce to the target type. */
+ coerced = coerce_to_target_type(pstate, expr, exprtype,
+ targettype, -1,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+
+ if (!coerced)
+ {
+ /* If coercion failed, use to_json()/to_jsonb() functions. */
+ Oid fnoid = targettype == JSONOID ? F_TO_JSON : F_TO_JSONB;
+ FuncExpr *fexpr = makeFuncExpr(fnoid, targettype,
+ list_make1(expr),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+
+ fexpr->location = location;
+
+ coerced = (Node *) fexpr;
+ }
+
+ if (coerced == orig)
+ expr = rawexpr;
+ else
+ {
+ ve = copyObject(ve);
+ ve->raw_expr = (Expr *) rawexpr;
+ ve->formatted_expr = (Expr *) coerced;
+
+ expr = (Node *) ve;
+ }
+ }
+
+ return expr;
+}
+
+/*
+ * Checks specified output format for its applicability to the target type.
+ */
+static void
+checkJsonOutputFormat(ParseState *pstate, const JsonFormat *format,
+ Oid targettype, bool allow_format_for_non_strings)
+{
+ if (!allow_format_for_non_strings &&
+ format->format_type != JS_FORMAT_DEFAULT &&
+ (targettype != BYTEAOID &&
+ targettype != JSONOID &&
+ targettype != JSONBOID))
+ {
+ char typcategory;
+ bool typispreferred;
+
+ get_type_category_preferred(targettype, &typcategory, &typispreferred);
+
+ if (typcategory != TYPCATEGORY_STRING)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, format->location),
+ errmsg("cannot use JSON format with non-string output types"));
+ }
+
+ if (format->format_type == JS_FORMAT_JSON)
+ {
+ JsonEncoding enc = format->encoding != JS_ENC_DEFAULT ?
+ format->encoding : JS_ENC_UTF8;
+
+ if (targettype != BYTEAOID &&
+ format->encoding != JS_ENC_DEFAULT)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, format->location),
+ errmsg("cannot set JSON encoding for non-bytea output types"));
+
+ if (enc != JS_ENC_UTF8)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported JSON encoding"),
+ errhint("Only UTF8 JSON encoding is supported."),
+ parser_errposition(pstate, format->location));
+ }
+}
+
+/*
+ * Transform JSON output clause.
+ *
+ * Assigns target type oid and modifier.
+ * Assigns default format or checks specified format for its applicability to
+ * the target type.
+ */
+static JsonReturning *
+transformJsonOutput(ParseState *pstate, const JsonOutput *output,
+ bool allow_format)
+{
+ JsonReturning *ret;
+
+ /* if output clause is not specified, make default clause value */
+ if (!output)
+ {
+ ret = makeNode(JsonReturning);
+
+ ret->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+ ret->typid = InvalidOid;
+ ret->typmod = -1;
+
+ return ret;
+ }
+
+ ret = copyObject(output->returning);
+
+ typenameTypeIdAndMod(pstate, output->typeName, &ret->typid, &ret->typmod);
+
+ if (output->typeName->setof)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("returning SETOF types is not supported in SQL/JSON functions"));
+
+ if (ret->format->format_type == JS_FORMAT_DEFAULT)
+ /* assign JSONB format when returning jsonb, or JSON format otherwise */
+ ret->format->format_type =
+ ret->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON;
+ else
+ checkJsonOutputFormat(pstate, ret->format, ret->typid, allow_format);
+
+ return ret;
+}
+
+/*
+ * Transform JSON output clause of JSON constructor functions.
+ *
+ * Derive RETURNING type, if not specified, from argument types.
+ */
+static JsonReturning *
+transformJsonConstructorOutput(ParseState *pstate, JsonOutput *output,
+ List *args)
+{
+ JsonReturning *returning = transformJsonOutput(pstate, output, true);
+
+ if (!OidIsValid(returning->typid))
+ {
+ ListCell *lc;
+ bool have_jsonb = false;
+
+ foreach(lc, args)
+ {
+ Node *expr = lfirst(lc);
+ Oid typid = exprType(expr);
+
+ have_jsonb |= typid == JSONBOID;
+
+ if (have_jsonb)
+ break;
+ }
+
+ if (have_jsonb)
+ {
+ returning->typid = JSONBOID;
+ returning->format->format_type = JS_FORMAT_JSONB;
+ }
+ else
+ {
+ /* XXX TEXT is default by the standard, but we return JSON */
+ returning->typid = JSONOID;
+ returning->format->format_type = JS_FORMAT_JSON;
+ }
+
+ returning->typmod = -1;
+ }
+
+ return returning;
+}
+
+/*
+ * Coerce json[b]-valued function expression to the output type.
+ */
+static Node *
+coerceJsonFuncExpr(ParseState *pstate, Node *expr,
+ const JsonReturning *returning, bool report_error)
+{
+ Node *res;
+ int location;
+ Oid exprtype = exprType(expr);
+
+ /* if output type is not specified or equals to function type, return */
+ if (!OidIsValid(returning->typid) || returning->typid == exprtype)
+ return expr;
+
+ location = exprLocation(expr);
+
+ if (location < 0)
+ location = returning->format->location;
+
+ /* special case for RETURNING bytea FORMAT json */
+ if (returning->format->format_type == JS_FORMAT_JSON &&
+ returning->typid == BYTEAOID)
+ {
+ /* encode json text into bytea using pg_convert_to() */
+ Node *texpr = coerce_to_specific_type(pstate, expr, TEXTOID,
+ "JSON_FUNCTION");
+ Const *enc = getJsonEncodingConst(returning->format);
+ FuncExpr *fexpr = makeFuncExpr(F_CONVERT_TO, BYTEAOID,
+ list_make2(texpr, enc),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+
+ fexpr->location = location;
+
+ return (Node *) fexpr;
+ }
+
+ /* try to coerce expression to the output type */
+ res = coerce_to_target_type(pstate, expr, exprtype,
+ returning->typid, returning->typmod,
+ /* XXX throwing errors when casting to char(N) */
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+
+ if (!res && report_error)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprtype),
+ format_type_be(returning->typid)),
+ parser_coercion_errposition(pstate, location, expr));
+
+ return res;
+}
+
+static Node *
+makeJsonConstructorExpr(ParseState *pstate, JsonConstructorType type,
+ List *args, Expr *fexpr, JsonReturning *returning,
+ bool unique, bool absent_on_null, int location)
+{
+ JsonConstructorExpr *jsctor = makeNode(JsonConstructorExpr);
+ Node *placeholder;
+ Node *coercion;
+ Oid intermediate_typid =
+ returning->format->format_type == JS_FORMAT_JSONB ? JSONBOID : JSONOID;
+
+ jsctor->args = args;
+ jsctor->func = fexpr;
+ jsctor->type = type;
+ jsctor->returning = returning;
+ jsctor->unique = unique;
+ jsctor->absent_on_null = absent_on_null;
+ jsctor->location = location;
+
+ if (fexpr)
+ placeholder = makeCaseTestExpr((Node *) fexpr);
+ else
+ {
+ CaseTestExpr *cte = makeNode(CaseTestExpr);
+
+ cte->typeId = intermediate_typid;
+ cte->typeMod = -1;
+ cte->collation = InvalidOid;
+
+ placeholder = (Node *) cte;
+ }
+
+ coercion = coerceJsonFuncExpr(pstate, placeholder, returning, true);
+
+ if (coercion != placeholder)
+ jsctor->coercion = (Expr *) coercion;
+
+ return (Node *) jsctor;
+}
+
+/*
+ * Transform JSON_OBJECT() constructor.
+ *
+ * JSON_OBJECT() is transformed into json[b]_build_object[_ext]() call
+ * depending on the output JSON format. The first two arguments of
+ * json[b]_build_object_ext() are absent_on_null and check_key_uniqueness.
+ *
+ * Then function call result is coerced to the target type.
+ */
+static Node *
+transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor)
+{
+ JsonReturning *returning;
+ List *args = NIL;
+
+ /* transform key-value pairs, if any */
+ if (ctor->exprs)
+ {
+ ListCell *lc;
+
+ /* transform and append key-value arguments */
+ foreach(lc, ctor->exprs)
+ {
+ JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc));
+ Node *key = transformExprRecurse(pstate, (Node *) kv->key);
+ Node *val = transformJsonValueExpr(pstate, kv->value,
+ JS_FORMAT_DEFAULT);
+
+ args = lappend(args, key);
+ args = lappend(args, val);
+ }
+ }
+
+ returning = transformJsonConstructorOutput(pstate, ctor->output, args);
+
+ return makeJsonConstructorExpr(pstate, JSCTOR_JSON_OBJECT, args, NULL,
+ returning, ctor->unique,
+ ctor->absent_on_null, ctor->location);
+}
+
+/*
+ * Transform JSON_ARRAY(query [FORMAT] [RETURNING] [ON NULL]) into
+ * (SELECT JSON_ARRAYAGG(a [FORMAT] [RETURNING] [ON NULL]) FROM (query) q(a))
+ */
+static Node *
+transformJsonArrayQueryConstructor(ParseState *pstate,
+ JsonArrayQueryConstructor *ctor)
+{
+ SubLink *sublink = makeNode(SubLink);
+ SelectStmt *select = makeNode(SelectStmt);
+ RangeSubselect *range = makeNode(RangeSubselect);
+ Alias *alias = makeNode(Alias);
+ ResTarget *target = makeNode(ResTarget);
+ JsonArrayAgg *agg = makeNode(JsonArrayAgg);
+ ColumnRef *colref = makeNode(ColumnRef);
+ Query *query;
+ ParseState *qpstate;
+
+ /* Transform query only for counting target list entries. */
+ qpstate = make_parsestate(pstate);
+
+ query = transformStmt(qpstate, ctor->query);
+
+ if (count_nonjunk_tlist_entries(query->targetList) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("subquery must return only one column"),
+ parser_errposition(pstate, ctor->location));
+
+ free_parsestate(qpstate);
+
+ colref->fields = list_make2(makeString(pstrdup("q")),
+ makeString(pstrdup("a")));
+ colref->location = ctor->location;
+
+ agg->arg = makeJsonValueExpr((Expr *) colref, ctor->format);
+ agg->absent_on_null = ctor->absent_on_null;
+ agg->constructor = makeNode(JsonAggConstructor);
+ agg->constructor->agg_order = NIL;
+ agg->constructor->output = ctor->output;
+ agg->constructor->location = ctor->location;
+
+ target->name = NULL;
+ target->indirection = NIL;
+ target->val = (Node *) agg;
+ target->location = ctor->location;
+
+ alias->aliasname = pstrdup("q");
+ alias->colnames = list_make1(makeString(pstrdup("a")));
+
+ range->lateral = false;
+ range->subquery = ctor->query;
+ range->alias = alias;
+
+ select->targetList = list_make1(target);
+ select->fromClause = list_make1(range);
+
+ sublink->subLinkType = EXPR_SUBLINK;
+ sublink->subLinkId = 0;
+ sublink->testexpr = NULL;
+ sublink->operName = NIL;
+ sublink->subselect = (Node *) select;
+ sublink->location = ctor->location;
+
+ return transformExprRecurse(pstate, (Node *) sublink);
+}
+
+/*
+ * Common code for JSON_OBJECTAGG and JSON_ARRAYAGG transformation.
+ */
+static Node *
+transformJsonAggConstructor(ParseState *pstate, JsonAggConstructor *agg_ctor,
+ JsonReturning *returning, List *args,
+ const char *aggfn, Oid aggtype,
+ JsonConstructorType ctor_type,
+ bool unique, bool absent_on_null)
+{
+ Oid aggfnoid;
+ Node *node;
+ Expr *aggfilter = agg_ctor->agg_filter ? (Expr *)
+ transformWhereClause(pstate, agg_ctor->agg_filter,
+ EXPR_KIND_FILTER, "FILTER") : NULL;
+
+ aggfnoid = DatumGetInt32(DirectFunctionCall1(regprocin,
+ CStringGetDatum(aggfn)));
+
+ if (agg_ctor->over)
+ {
+ /* window function */
+ WindowFunc *wfunc = makeNode(WindowFunc);
+
+ wfunc->winfnoid = aggfnoid;
+ wfunc->wintype = aggtype;
+ /* wincollid and inputcollid will be set by parse_collate.c */
+ wfunc->args = args;
+ /* winref will be set by transformWindowFuncCall */
+ wfunc->winstar = false;
+ wfunc->winagg = true;
+ wfunc->aggfilter = aggfilter;
+ wfunc->location = agg_ctor->location;
+
+ /*
+ * ordered aggs not allowed in windows yet
+ */
+ if (agg_ctor->agg_order != NIL)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("aggregate ORDER BY is not implemented for window functions"),
+ parser_errposition(pstate, agg_ctor->location));
+
+ /* parse_agg.c does additional window-func-specific processing */
+ transformWindowFuncCall(pstate, wfunc, agg_ctor->over);
+
+ node = (Node *) wfunc;
+ }
+ else
+ {
+ Aggref *aggref = makeNode(Aggref);
+
+ aggref->aggfnoid = aggfnoid;
+ aggref->aggtype = aggtype;
+
+ /* aggcollid and inputcollid will be set by parse_collate.c */
+ aggref->aggtranstype = InvalidOid; /* will be set by planner */
+ /* aggargtypes will be set by transformAggregateCall */
+ /* aggdirectargs and args will be set by transformAggregateCall */
+ /* aggorder and aggdistinct will be set by transformAggregateCall */
+ aggref->aggfilter = aggfilter;
+ aggref->aggstar = false;
+ aggref->aggvariadic = false;
+ aggref->aggkind = AGGKIND_NORMAL;
+ /* agglevelsup will be set by transformAggregateCall */
+ aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
+ aggref->location = agg_ctor->location;
+
+ transformAggregateCall(pstate, aggref, args, agg_ctor->agg_order, false);
+
+ node = (Node *) aggref;
+ }
+
+ return makeJsonConstructorExpr(pstate, ctor_type, NIL, (Expr *) node,
+ returning, unique, absent_on_null,
+ agg_ctor->location);
+}
+
+/*
+ * Transform JSON_OBJECTAGG() aggregate function.
+ *
+ * JSON_OBJECTAGG() is transformed into
+ * json[b]_objectagg(key, value, absent_on_null, check_unique) call depending on
+ * the output JSON format. Then the function call result is coerced to the
+ * target output type.
+ */
+static Node *
+transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg)
+{
+ JsonReturning *returning;
+ Node *key;
+ Node *val;
+ List *args;
+ const char *aggfnname;
+ Oid aggtype;
+
+ key = transformExprRecurse(pstate, (Node *) agg->arg->key);
+ val = transformJsonValueExpr(pstate, agg->arg->value, JS_FORMAT_DEFAULT);
+ args = list_make2(key, val);
+
+ returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+ args);
+
+ if (returning->format->format_type == JS_FORMAT_JSONB)
+ {
+ if (agg->absent_on_null)
+ if (agg->unique)
+ aggfnname = "pg_catalog.jsonb_object_agg_unique_strict";
+ else
+ aggfnname = "pg_catalog.jsonb_object_agg_strict";
+ else if (agg->unique)
+ aggfnname = "pg_catalog.jsonb_object_agg_unique";
+ else
+ aggfnname = "pg_catalog.jsonb_object_agg";
+
+ aggtype = JSONBOID;
+ }
+ else
+ {
+ if (agg->absent_on_null)
+ if (agg->unique)
+ aggfnname = "pg_catalog.json_object_agg_unique_strict";
+ else
+ aggfnname = "pg_catalog.json_object_agg_strict";
+ else if (agg->unique)
+ aggfnname = "pg_catalog.json_object_agg_unique";
+ else
+ aggfnname = "pg_catalog.json_object_agg";
+
+ aggtype = JSONOID;
+ }
+
+ return transformJsonAggConstructor(pstate, agg->constructor, returning,
+ args, aggfnname, aggtype,
+ JSCTOR_JSON_OBJECTAGG,
+ agg->unique, agg->absent_on_null);
+}
+
+/*
+ * Transform JSON_ARRAYAGG() aggregate function.
+ *
+ * JSON_ARRAYAGG() is transformed into json[b]_agg[_strict]() call depending
+ * on the output JSON format and absent_on_null. Then the function call result
+ * is coerced to the target output type.
+ */
+static Node *
+transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg)
+{
+ JsonReturning *returning;
+ Node *arg;
+ const char *aggfnname;
+ Oid aggtype;
+
+ arg = transformJsonValueExpr(pstate, agg->arg, JS_FORMAT_DEFAULT);
+
+ returning = transformJsonConstructorOutput(pstate, agg->constructor->output,
+ list_make1(arg));
+
+ if (returning->format->format_type == JS_FORMAT_JSONB)
+ {
+ aggfnname = agg->absent_on_null ?
+ "pg_catalog.jsonb_agg_strict" : "pg_catalog.jsonb_agg";
+ aggtype = JSONBOID;
+ }
+ else
+ {
+ aggfnname = agg->absent_on_null ?
+ "pg_catalog.json_agg_strict" : "pg_catalog.json_agg";
+ aggtype = JSONOID;
+ }
+
+ return transformJsonAggConstructor(pstate, agg->constructor, returning,
+ list_make1(arg), aggfnname, aggtype,
+ JSCTOR_JSON_ARRAYAGG,
+ false, agg->absent_on_null);
+}
+
+/*
+ * Transform JSON_ARRAY() constructor.
+ *
+ * JSON_ARRAY() is transformed into json[b]_build_array[_ext]() call
+ * depending on the output JSON format. The first argument of
+ * json[b]_build_array_ext() is absent_on_null.
+ *
+ * Then function call result is coerced to the target type.
+ */
+static Node *
+transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
+{
+ JsonReturning *returning;
+ List *args = NIL;
+
+ /* transform element expressions, if any */
+ if (ctor->exprs)
+ {
+ ListCell *lc;
+
+ /* transform and append element arguments */
+ foreach(lc, ctor->exprs)
+ {
+ JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc));
+ Node *val = transformJsonValueExpr(pstate, jsval,
+ JS_FORMAT_DEFAULT);
+
+ args = lappend(args, val);
+ }
+ }
+
+ returning = transformJsonConstructorOutput(pstate, ctor->output, args);
+
+ return makeJsonConstructorExpr(pstate, JSCTOR_JSON_ARRAY, args, NULL,
+ returning, false, ctor->absent_on_null,
+ ctor->location);
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 25781db5c1d..e77b542fd76 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1907,8 +1907,26 @@ FigureColnameInternal(Node *node, char **name)
}
break;
case T_XmlSerialize:
+ /* make XMLSERIALIZE act like a regular function */
*name = "xmlserialize";
return 2;
+ case T_JsonObjectConstructor:
+ /* make JSON_OBJECT act like a regular function */
+ *name = "json_object";
+ return 2;
+ case T_JsonArrayConstructor:
+ case T_JsonArrayQueryConstructor:
+ /* make JSON_ARRAY act like a regular function */
+ *name = "json_array";
+ return 2;
+ case T_JsonObjectAgg:
+ /* make JSON_OBJECTAGG act like a regular function */
+ *name = "json_objectagg";
+ return 2;
+ case T_JsonArrayAgg:
+ /* make JSON_ARRAYAGG act like a regular function */
+ *name = "json_arrayagg";
+ return 2;
default:
break;
}
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index aa4dce6ee9c..65eb0876575 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -137,6 +137,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
*/
switch (cur_token)
{
+ case FORMAT:
+ cur_token_length = 6;
+ break;
case NOT:
cur_token_length = 3;
break;
@@ -150,6 +153,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
case USCONST:
cur_token_length = strlen(yyextra->core_yy_extra.scanbuf + *llocp);
break;
+ case WITHOUT:
+ cur_token_length = 7;
+ break;
default:
return cur_token;
}
@@ -188,6 +194,16 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
/* Replace cur_token if needed, based on lookahead */
switch (cur_token)
{
+ case FORMAT:
+ /* Replace FORMAT by FORMAT_LA if it's followed by JSON */
+ switch (next_token)
+ {
+ case JSON:
+ cur_token = FORMAT_LA;
+ break;
+ }
+ break;
+
case NOT:
/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
switch (next_token)
@@ -224,6 +240,16 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
}
break;
+ case WITHOUT:
+ /* Replace WITHOUT by WITHOUT_LA if it's followed by UNIQUE */
+ switch (next_token)
+ {
+ case UNIQUE:
+ cur_token = WITHOUT_LA;
+ break;
+ }
+ break;
+
case UIDENT:
case USCONST:
/* Look ahead for UESCAPE */
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 06d8bea9cde..dcd2bb2234a 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,34 @@ typedef enum /* type categories for datum_to_json */
JSONTYPE_OTHER /* all else */
} JsonTypeCategory;
+
+/*
+ * Support for fast key uniqueness checking.
+ *
+ * We maintain a hash table of used keys in JSON objects for fast detection
+ * of duplicates.
+ */
+/* 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 struct for key uniqueness check during JSON building */
+typedef struct JsonUniqueBuilderState
+{
+ JsonUniqueCheckState check; /* unique check */
+ StringInfoData skipped_keys; /* skipped keys with NULL values */
+ MemoryContext mcxt; /* context for saving skipped keys */
+} JsonUniqueBuilderState;
+
+
+/* State struct for JSON aggregation */
typedef struct JsonAggState
{
StringInfo str;
@@ -49,6 +79,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,
@@ -723,6 +754,48 @@ row_to_json_pretty(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
}
+/*
+ * Is the given type immutable when coming out of a JSON context?
+ *
+ * At present, datetimes are all considered mutable, because they
+ * depend on timezone. XXX we should also drill down into objects
+ * and arrays, but do not.
+ */
+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:
+ case JSONTYPE_NULL:
+ 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:
+ case JSONTYPE_OTHER:
+ return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE;
+ }
+
+ return false; /* not reached */
+}
+
/*
* SQL function to_json(anyvalue)
*/
@@ -755,8 +828,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;
@@ -796,9 +869,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))
{
@@ -810,7 +888,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))
{
@@ -828,6 +906,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
*/
@@ -851,18 +948,120 @@ 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);
+}
+
+/*
+ * Uniqueness detection support.
+ *
+ * In order to detect uniqueness during building or parsing of a JSON
+ * object, we maintain a hash table of key names already seen.
+ */
+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_builder_init(JsonUniqueBuilderState *cxt)
+{
+ json_unique_check_init(&cxt->check);
+ cxt->mcxt = CurrentMemoryContext;
+ cxt->skipped_keys.data = NULL;
+}
+
+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;
+}
+
+/*
+ * On-demand initialization of a throwaway StringInfo. This is used to
+ * read a key name that we don't need to store in the output object, for
+ * duplicate key detection when the value is NULL.
+ */
+static StringInfo
+json_unique_builder_get_throwawaybuf(JsonUniqueBuilderState *cxt)
+{
+ StringInfo out = &cxt->skipped_keys;
+
+ if (!out->data)
+ {
+ MemoryContext oldcxt = MemoryContextSwitchTo(cxt->mcxt);
+
+ initStringInfo(out);
+ MemoryContextSwitchTo(oldcxt);
+ }
+ else
+ /* Just reset the string to empty */
+ out->len = 0;
+
+ 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))
{
@@ -877,12 +1076,16 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
/*
* Make the StringInfo in a context where it will persist for the
* duration of the aggregate call. Switching context is only needed
- * for this initial step, as the StringInfo routines make sure they
- * use the right context to enlarge the object if necessary.
+ * for this initial step, as the StringInfo and dynahash routines make
+ * sure they use the right context to enlarge the object if necessary.
*/
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);
@@ -910,7 +1113,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
else
{
state = (JsonAggState *) PG_GETARG_POINTER(0);
- appendStringInfoString(state->str, ", ");
}
/*
@@ -923,14 +1125,56 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
if (PG_ARGISNULL(1))
ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("field name must not be null")));
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value not allowed for object key")));
+
+ /* Skip null values if absent_on_null */
+ skip = absent_on_null && PG_ARGISNULL(2);
+
+ if (skip)
+ {
+ /*
+ * We got a NULL value and we're not storing those; if we're not
+ * testing key uniqueness, we're done. If we are, use the throwaway
+ * buffer to store the key name so that we can check it.
+ */
+ if (!unique_keys)
+ PG_RETURN_POINTER(state);
+
+ out = json_unique_builder_get_throwawaybuf(&state->unique_check);
+ }
+ else
+ {
+ out = state->str;
+
+ /*
+ * Append comma delimiter only if we have already output 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 +1188,42 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(state);
}
+/*
+ * 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.
*/
@@ -985,25 +1265,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,
@@ -1017,19 +1286,57 @@ 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_throwawaybuf(&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),
- errhint("Object keys should be text.")));
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value not allowed for object key")));
- add_json(args[i], false, result, types[i], true);
+ /* save key offset before appending it */
+ 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, " : ");
@@ -1039,7 +1346,27 @@ json_build_object(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_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));
}
/*
@@ -1051,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();
@@ -1077,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);
@@ -1084,7 +1402,27 @@ 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 0539f41c172..cf43c3f2ded 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"
@@ -1149,6 +1150,49 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result,
datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar);
}
+/*
+ * Is the given type immutable when coming out of a JSONB context?
+ *
+ * At present, datetimes are all considered mutable, because they
+ * depend on timezone. XXX we should also drill down into objects and
+ * arrays, but do not.
+ */
+bool
+to_jsonb_is_immutable(Oid typoid)
+{
+ JsonbTypeCategory tcategory;
+ Oid outfuncoid;
+
+ jsonb_categorize_type(typoid, &tcategory, &outfuncoid);
+
+ switch (tcategory)
+ {
+ case JSONBTYPE_NULL:
+ 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:
+ case JSONBTYPE_OTHER:
+ return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE;
+ }
+
+ return false; /* not reached */
+}
+
/*
* SQL function to_jsonb(anyvalue)
*/
@@ -1176,24 +1220,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,
@@ -1206,15 +1238,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 */
@@ -1223,7 +1266,27 @@ 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));
}
/*
@@ -1242,37 +1305,51 @@ 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.
*/
@@ -1506,6 +1583,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;
@@ -1517,12 +1596,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;
@@ -1570,6 +1645,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);
@@ -1639,6 +1717,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)
{
@@ -1672,11 +1768,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;
@@ -1690,6 +1784,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
*jbval;
JsonbValue v;
JsonbIteratorToken type;
+ bool skip;
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
@@ -1709,6 +1804,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);
@@ -1744,6 +1842,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));
@@ -1799,6 +1906,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;
@@ -1871,6 +1988,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 e5b1ebf0c36..eefa429b9c3 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 *binequal);
-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,23 +1952,43 @@ uniqueifyJsonbObject(JsonbValue *object)
qsort_arg(object->val.object.pairs, object->val.object.nPairs, sizeof(JsonbPair),
lengthCompareJsonbPair, &hasNonUniq);
- if (hasNonUniq)
- {
- JsonbPair *ptr = object->val.object.pairs + 1,
- *res = object->val.object.pairs;
+ if (hasNonUniq && unique_keys)
+ ereport(ERROR,
+ errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE),
+ errmsg("duplicate JSON object key"));
- while (ptr - object->val.object.pairs < object->val.object.nPairs)
+ if (hasNonUniq || skip_nulls)
+ {
+ JsonbPair *ptr,
+ *res;
+
+ while (skip_nulls && object->val.object.nPairs > 0 &&
+ object->val.object.pairs->value.type == jbvNull)
{
- /* Avoid copying over duplicate */
- if (lengthCompareJsonbStringValue(ptr, res) != 0)
- {
- res++;
- if (ptr != res)
- memcpy(res, ptr, sizeof(JsonbPair));
- }
- ptr++;
+ /* If skip_nulls is true, remove leading items with null */
+ object->val.object.pairs++;
+ object->val.object.nPairs--;
}
- object->val.object.nPairs = res + 1 - object->val.object.pairs;
+ if (object->val.object.nPairs > 0)
+ {
+ 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 or null */
+ if (lengthCompareJsonbStringValue(ptr, res) != 0 &&
+ (!skip_nulls || ptr->value.type != jbvNull))
+ {
+ res++;
+ if (ptr != res)
+ memcpy(res, ptr, sizeof(JsonbPair));
+ }
+ ptr++;
+ }
+
+ object->val.object.nPairs = res + 1 - object->val.object.pairs;
+ }
}
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 4a98b82f07c..5f953338f3d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -457,9 +457,15 @@ static void get_func_expr(FuncExpr *expr, deparse_context *context,
bool showimplicit);
static void get_agg_expr(Aggref *aggref, deparse_context *context,
Aggref *original_aggref);
+static void get_agg_expr_helper(Aggref *aggref, deparse_context *context,
+ Aggref *original_aggref, const char *funcname,
+ const char *options, bool is_json_objectagg);
static void get_agg_combine_expr(Node *node, deparse_context *context,
void *callback_arg);
static void get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context);
+static void get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
+ const char *funcname, const char *options,
+ bool is_json_objectagg);
static bool get_func_sql_syntax(FuncExpr *expr, deparse_context *context);
static void get_coercion_expr(Node *arg, deparse_context *context,
Oid resulttype, int32 resulttypmod,
@@ -467,6 +473,15 @@ static void get_coercion_expr(Node *arg, deparse_context *context,
static void get_const_expr(Const *constval, deparse_context *context,
int showtype);
static void get_const_collation(Const *constval, deparse_context *context);
+static void get_json_format(JsonFormat *format, StringInfo buf);
+static void get_json_constructor(JsonConstructorExpr *ctor,
+ deparse_context *context, bool showimplicit);
+static void get_json_constructor_options(JsonConstructorExpr *ctor,
+ StringInfo buf);
+static void get_json_agg_constructor(JsonConstructorExpr *ctor,
+ deparse_context *context,
+ const char *funcname,
+ bool is_json_objectagg);
static void simple_quote_literal(StringInfo buf, const char *val);
static void get_sublink_expr(SubLink *sublink, deparse_context *context);
static void get_tablefunc(TableFunc *tf, deparse_context *context,
@@ -6280,7 +6295,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, '(');
@@ -8117,6 +8133,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;
@@ -8292,6 +8309,11 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
return false;
}
+ case T_JsonValueExpr:
+ /* maybe simple, check args */
+ return isSimpleNode((Node *) ((JsonValueExpr *) node)->raw_expr,
+ node, prettyFlags);
+
default:
break;
}
@@ -9495,6 +9517,19 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_JsonValueExpr:
+ {
+ JsonValueExpr *jve = (JsonValueExpr *) node;
+
+ get_rule_expr((Node *) jve->raw_expr, context, false);
+ get_json_format(jve->format, context->buf);
+ }
+ break;
+
+ case T_JsonConstructorExpr:
+ get_json_constructor((JsonConstructorExpr *) node, context, false);
+ break;
+
case T_List:
{
char *sep;
@@ -9768,11 +9803,24 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
static void
get_agg_expr(Aggref *aggref, deparse_context *context,
Aggref *original_aggref)
+{
+ get_agg_expr_helper(aggref, context, original_aggref, NULL, NULL,
+ false);
+}
+
+/*
+ * get_agg_expr_helper - subroutine for get_agg_expr and
+ * get_json_agg_constructor
+ */
+static void
+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
@@ -9802,13 +9850,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))
@@ -9844,7 +9893,21 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
if (tle->resjunk)
continue;
if (i++ > 0)
- appendStringInfoString(buf, ", ");
+ {
+ if (is_json_objectagg)
+ {
+ /*
+ * the ABSENT ON NULL and WITH UNIQUE args are printed
+ * separately, so ignore them here
+ */
+ if (i > 2)
+ break;
+
+ appendStringInfoString(buf, " : ");
+ }
+ else
+ appendStringInfoString(buf, ", ");
+ }
if (use_variadic && i == nargs)
appendStringInfoString(buf, "VARIADIC ");
get_rule_expr(arg, context, true);
@@ -9858,6 +9921,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
}
}
+ if (options)
+ appendStringInfoString(buf, options);
+
if (aggref->aggfilter != NULL)
{
appendStringInfoString(buf, ") FILTER (WHERE ");
@@ -9890,6 +9956,19 @@ get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg)
*/
static void
get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
+{
+ get_windowfunc_expr_helper(wfunc, context, NULL, NULL, false);
+}
+
+
+/*
+ * get_windowfunc_expr_helper - subroutine for get_windowfunc_expr and
+ * get_json_agg_constructor
+ */
+static void
+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];
@@ -9913,16 +9992,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)
{
@@ -10483,6 +10576,158 @@ get_const_collation(Const *constval, deparse_context *context)
}
}
+/*
+ * get_json_format - Parse back a JsonFormat node
+ */
+static void
+get_json_format(JsonFormat *format, StringInfo buf)
+{
+ if (format->format_type == JS_FORMAT_DEFAULT)
+ return;
+
+ appendStringInfoString(buf,
+ format->format_type == JS_FORMAT_JSONB ?
+ " FORMAT JSONB" : " FORMAT JSON");
+
+ if (format->encoding != JS_ENC_DEFAULT)
+ {
+ const char *encoding;
+
+ encoding =
+ format->encoding == JS_ENC_UTF16 ? "UTF16" :
+ format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8";
+
+ appendStringInfo(buf, " ENCODING %s", encoding);
+ }
+}
+
+/*
+ * get_json_returning - Parse back a JsonReturning structure
+ */
+static void
+get_json_returning(JsonReturning *returning, StringInfo buf,
+ bool json_format_by_default)
+{
+ if (!OidIsValid(returning->typid))
+ return;
+
+ 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, buf);
+}
+
+/*
+ * get_json_constructor - Parse back a JsonConstructorExpr node
+ */
+static void
+get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
+ bool showimplicit)
+{
+ StringInfo buf = context->buf;
+ const char *funcname;
+ bool is_json_object;
+ int curridx;
+ ListCell *lc;
+
+ if (ctor->type == JSCTOR_JSON_OBJECTAGG)
+ {
+ get_json_agg_constructor(ctor, context, "JSON_OBJECTAGG", true);
+ return;
+ }
+ else if (ctor->type == JSCTOR_JSON_ARRAYAGG)
+ {
+ get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false);
+ return;
+ }
+
+ switch (ctor->type)
+ {
+ case JSCTOR_JSON_OBJECT:
+ funcname = "JSON_OBJECT";
+ break;
+ case JSCTOR_JSON_ARRAY:
+ funcname = "JSON_ARRAY";
+ break;
+ default:
+ elog(ERROR, "invalid JsonConstructorExprType %d", ctor->type);
+ }
+
+ appendStringInfo(buf, "%s(", funcname);
+
+ is_json_object = ctor->type == JSCTOR_JSON_OBJECT;
+ foreach(lc, ctor->args)
+ {
+ curridx = foreach_current_index(lc);
+ if (curridx > 0)
+ {
+ const char *sep;
+
+ sep = (is_json_object && (curridx % 2) != 0) ? " : " : ", ";
+ appendStringInfoString(buf, sep);
+ }
+
+ get_rule_expr((Node *) lfirst(lc), context, true);
+ }
+
+ get_json_constructor_options(ctor, buf);
+ appendStringInfo(buf, ")");
+}
+
+/*
+ * Append options, if any, to the JSON constructor being deparsed
+ */
+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);
+}
+
+/*
+ * 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))
+ get_agg_expr_helper((Aggref *) ctor->func, context,
+ (Aggref *) ctor->func,
+ funcname, options.data, is_json_objectagg);
+ else if (IsA(ctor->func, WindowFunc))
+ 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));
+}
+
/*
* simple_quote_literal - Format a string as a SQL literal, append to buf
*/
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 229b0bd54ea..f0989417f05 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202303292
+#define CATALOG_VERSION_NO 202303293
#endif
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index d7895cd676b..283f494bf59 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -580,14 +580,36 @@
# json
{ aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'json_agg_strict', aggtransfn => 'json_agg_strict_transfn',
+ aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
{ aggfnoid => 'json_object_agg', aggtransfn => 'json_object_agg_transfn',
aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'json_object_agg_unique',
+ aggtransfn => 'json_object_agg_unique_transfn',
+ aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'json_object_agg_strict',
+ aggtransfn => 'json_object_agg_strict_transfn',
+ aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'json_object_agg_unique_strict',
+ aggtransfn => 'json_object_agg_unique_strict_transfn',
+ aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' },
# jsonb
{ aggfnoid => 'jsonb_agg', aggtransfn => 'jsonb_agg_transfn',
aggfinalfn => 'jsonb_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'jsonb_agg_strict', aggtransfn => 'jsonb_agg_strict_transfn',
+ aggfinalfn => 'jsonb_agg_finalfn', aggtranstype => 'internal' },
{ aggfnoid => 'jsonb_object_agg', aggtransfn => 'jsonb_object_agg_transfn',
aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'jsonb_object_agg_unique',
+ aggtransfn => 'jsonb_object_agg_unique_transfn',
+ aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'jsonb_object_agg_strict',
+ aggtransfn => 'jsonb_object_agg_strict_transfn',
+ aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' },
+{ aggfnoid => 'jsonb_object_agg_unique_strict',
+ aggtransfn => 'jsonb_object_agg_unique_strict_transfn',
+ aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' },
# ordered-set and hypothetical-set aggregates
{ aggfnoid => 'percentile_disc(float8,anyelement)', aggkind => 'o',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index def8ee20455..5736c1082cf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8923,6 +8923,10 @@
proname => 'json_agg_transfn', proisstrict => 'f', provolatile => 's',
prorettype => 'internal', proargtypes => 'internal anyelement',
prosrc => 'json_agg_transfn' },
+{ oid => '8950', descr => 'json aggregate transition function',
+ proname => 'json_agg_strict_transfn', proisstrict => 'f', provolatile => 's',
+ prorettype => 'internal', proargtypes => 'internal anyelement',
+ prosrc => 'json_agg_strict_transfn' },
{ oid => '3174', descr => 'json aggregate final function',
proname => 'json_agg_finalfn', proisstrict => 'f', prorettype => 'json',
proargtypes => 'internal', prosrc => 'json_agg_finalfn' },
@@ -8930,10 +8934,29 @@
proname => 'json_agg', prokind => 'a', proisstrict => 'f', provolatile => 's',
prorettype => 'json', proargtypes => 'anyelement',
prosrc => 'aggregate_dummy' },
+{ oid => '8951', descr => 'aggregate input into json',
+ proname => 'json_agg_strict', prokind => 'a', proisstrict => 'f',
+ provolatile => 's', prorettype => 'json', proargtypes => 'anyelement',
+ prosrc => 'aggregate_dummy' },
{ oid => '3180', descr => 'json object aggregate transition function',
proname => 'json_object_agg_transfn', proisstrict => 'f', provolatile => 's',
prorettype => 'internal', proargtypes => 'internal any any',
prosrc => 'json_object_agg_transfn' },
+{ oid => '8952', descr => 'json object aggregate transition function',
+ proname => 'json_object_agg_strict_transfn', proisstrict => 'f',
+ provolatile => 's', prorettype => 'internal',
+ proargtypes => 'internal any any',
+ prosrc => 'json_object_agg_strict_transfn' },
+{ oid => '8953', descr => 'json object aggregate transition function',
+ proname => 'json_object_agg_unique_transfn', proisstrict => 'f',
+ provolatile => 's', prorettype => 'internal',
+ proargtypes => 'internal any any',
+ prosrc => 'json_object_agg_unique_transfn' },
+{ oid => '8954', descr => 'json object aggregate transition function',
+ proname => 'json_object_agg_unique_strict_transfn', proisstrict => 'f',
+ provolatile => 's', prorettype => 'internal',
+ proargtypes => 'internal any any',
+ prosrc => 'json_object_agg_unique_strict_transfn' },
{ oid => '3196', descr => 'json object aggregate final function',
proname => 'json_object_agg_finalfn', proisstrict => 'f',
prorettype => 'json', proargtypes => 'internal',
@@ -8942,6 +8965,20 @@
proname => 'json_object_agg', prokind => 'a', proisstrict => 'f',
provolatile => 's', prorettype => 'json', proargtypes => 'any any',
prosrc => 'aggregate_dummy' },
+{ oid => '8955', descr => 'aggregate non-NULL input into a json object',
+ proname => 'json_object_agg_strict', prokind => 'a', proisstrict => 'f',
+ provolatile => 's', prorettype => 'json', proargtypes => 'any any',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8956',
+ descr => 'aggregate input into a json object with unique keys',
+ proname => 'json_object_agg_unique', prokind => 'a', proisstrict => 'f',
+ provolatile => 's', prorettype => 'json', proargtypes => 'any any',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8957',
+ descr => 'aggregate non-NULL input into a json object with unique keys',
+ proname => 'json_object_agg_unique_strict', prokind => 'a',
+ proisstrict => 'f', provolatile => 's', prorettype => 'json',
+ proargtypes => 'any any', prosrc => 'aggregate_dummy' },
{ oid => '3198', descr => 'build a json array from any inputs',
proname => 'json_build_array', provariadic => 'any', proisstrict => 'f',
provolatile => 's', prorettype => 'json', proargtypes => 'any',
@@ -9814,6 +9851,10 @@
proname => 'jsonb_agg_transfn', proisstrict => 'f', provolatile => 's',
prorettype => 'internal', proargtypes => 'internal anyelement',
prosrc => 'jsonb_agg_transfn' },
+{ oid => '8958', descr => 'jsonb aggregate transition function',
+ proname => 'jsonb_agg_strict_transfn', proisstrict => 'f', provolatile => 's',
+ prorettype => 'internal', proargtypes => 'internal anyelement',
+ prosrc => 'jsonb_agg_strict_transfn' },
{ oid => '3266', descr => 'jsonb aggregate final function',
proname => 'jsonb_agg_finalfn', proisstrict => 'f', provolatile => 's',
prorettype => 'jsonb', proargtypes => 'internal',
@@ -9822,10 +9863,29 @@
proname => 'jsonb_agg', prokind => 'a', proisstrict => 'f',
provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement',
prosrc => 'aggregate_dummy' },
+{ oid => '8959', descr => 'aggregate input into jsonb skipping nulls',
+ proname => 'jsonb_agg_strict', prokind => 'a', proisstrict => 'f',
+ provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement',
+ prosrc => 'aggregate_dummy' },
{ oid => '3268', descr => 'jsonb object aggregate transition function',
proname => 'jsonb_object_agg_transfn', proisstrict => 'f', provolatile => 's',
prorettype => 'internal', proargtypes => 'internal any any',
prosrc => 'jsonb_object_agg_transfn' },
+{ oid => '8960', descr => 'jsonb object aggregate transition function',
+ proname => 'jsonb_object_agg_strict_transfn', proisstrict => 'f',
+ provolatile => 's', prorettype => 'internal',
+ proargtypes => 'internal any any',
+ prosrc => 'jsonb_object_agg_strict_transfn' },
+{ oid => '8961', descr => 'jsonb object aggregate transition function',
+ proname => 'jsonb_object_agg_unique_transfn', proisstrict => 'f',
+ provolatile => 's', prorettype => 'internal',
+ proargtypes => 'internal any any',
+ prosrc => 'jsonb_object_agg_unique_transfn' },
+{ oid => '8962', descr => 'jsonb object aggregate transition function',
+ proname => 'jsonb_object_agg_unique_strict_transfn', proisstrict => 'f',
+ provolatile => 's', prorettype => 'internal',
+ proargtypes => 'internal any any',
+ prosrc => 'jsonb_object_agg_unique_strict_transfn' },
{ oid => '3269', descr => 'jsonb object aggregate final function',
proname => 'jsonb_object_agg_finalfn', proisstrict => 'f', provolatile => 's',
prorettype => 'jsonb', proargtypes => 'internal',
@@ -9834,6 +9894,20 @@
proname => 'jsonb_object_agg', prokind => 'a', proisstrict => 'f',
prorettype => 'jsonb', proargtypes => 'any any',
prosrc => 'aggregate_dummy' },
+{ oid => '8963', descr => 'aggregate non-NULL inputs into jsonb object',
+ proname => 'jsonb_object_agg_strict', prokind => 'a', proisstrict => 'f',
+ prorettype => 'jsonb', proargtypes => 'any any',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8964',
+ descr => 'aggregate inputs into jsonb object checking key uniqueness',
+ proname => 'jsonb_object_agg_unique', prokind => 'a', proisstrict => 'f',
+ prorettype => 'jsonb', proargtypes => 'any any',
+ prosrc => 'aggregate_dummy' },
+{ oid => '8965',
+ descr => 'aggregate non-NULL inputs into jsonb object checking key uniqueness',
+ proname => 'jsonb_object_agg_unique_strict', prokind => 'a',
+ proisstrict => 'f', prorettype => 'jsonb', proargtypes => 'any any',
+ prosrc => 'aggregate_dummy' },
{ oid => '3271', descr => 'build a jsonb array from any inputs',
proname => 'jsonb_build_array', provariadic => 'any', proisstrict => 'f',
provolatile => 's', prorettype => 'jsonb', proargtypes => 'any',
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 06c3adc0a19..f5a72a8b40f 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -21,6 +21,7 @@
struct ExprEvalStep;
struct SubscriptingRefState;
struct ScalarArrayOpExprHashTable;
+struct JsonConstructorExprState;
/* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
/* expression's interpreter has been initialized */
@@ -234,6 +235,7 @@ typedef enum ExprEvalOp
EEOP_SCALARARRAYOP,
EEOP_HASHED_SCALARARRAYOP,
EEOP_XMLEXPR,
+ EEOP_JSON_CONSTRUCTOR,
EEOP_AGGREF,
EEOP_GROUPING_FUNC,
EEOP_WINDOW_FUNC,
@@ -588,6 +590,12 @@ typedef struct ExprEvalStep
bool *argnull;
} xmlexpr;
+ /* for EEOP_JSON_CONSTRUCTOR */
+ struct
+ {
+ struct JsonConstructorExprState *jcstate;
+ } json_constructor;
+
/* for EEOP_AGGREF */
struct
{
@@ -666,6 +674,7 @@ typedef struct ExprEvalStep
int transno;
int setoff;
} agg_trans;
+
} d;
} ExprEvalStep;
@@ -714,6 +723,21 @@ typedef struct SubscriptExecSteps
ExecEvalSubroutine sbs_fetch_old; /* fetch old value for assignment */
} SubscriptExecSteps;
+/* EEOP_JSON_CONSTRUCTOR state, too big to inline */
+typedef struct JsonConstructorExprState
+{
+ JsonConstructorExpr *constructor;
+ Datum *arg_values;
+ bool *arg_nulls;
+ Oid *arg_types;
+ struct
+ {
+ int category;
+ Oid outfuncid;
+ } *arg_type_cache; /* cache for datum_to_json[b]() */
+ int nargs;
+} JsonConstructorExprState;
+
/* functions in execExpr.c */
extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
@@ -770,6 +794,8 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext, TupleTableSlot *slot);
+extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 64651c9b00b..50aa00e0c1b 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -108,4 +108,10 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding,
+ int location);
+extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format);
+extern Node *makeJsonKeyValue(Node *key, Node *value);
+extern JsonEncoding makeJsonEncoding(char *name);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 028588fb330..1c296da326f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1713,6 +1713,113 @@ typedef struct TriggerTransition
bool isTable;
} TriggerTransition;
+/* Nodes for SQL/JSON support */
+
+/*
+ * JsonOutput -
+ * representation of JSON output clause (RETURNING type [FORMAT format])
+ */
+typedef struct JsonOutput
+{
+ NodeTag type;
+ TypeName *typeName; /* RETURNING type name, if specified */
+ JsonReturning *returning; /* RETURNING FORMAT clause and type Oids */
+} JsonOutput;
+
+/*
+ * JsonKeyValue -
+ * untransformed representation of JSON object key-value pair for
+ * JSON_OBJECT() and JSON_OBJECTAGG()
+ */
+typedef struct JsonKeyValue
+{
+ NodeTag type;
+ Expr *key; /* key expression */
+ JsonValueExpr *value; /* JSON value expression */
+} JsonKeyValue;
+
+/*
+ * JsonObjectConstructor -
+ * untransformed representation of JSON_OBJECT() constructor
+ */
+typedef struct JsonObjectConstructor
+{
+ NodeTag type;
+ List *exprs; /* list of JsonKeyValue pairs */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ bool absent_on_null; /* skip NULL values? */
+ bool unique; /* check key uniqueness? */
+ int location; /* token location, or -1 if unknown */
+} JsonObjectConstructor;
+
+/*
+ * JsonArrayConstructor -
+ * untransformed representation of JSON_ARRAY(element,...) constructor
+ */
+typedef struct JsonArrayConstructor
+{
+ NodeTag type;
+ List *exprs; /* list of JsonValueExpr elements */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ bool absent_on_null; /* skip NULL elements? */
+ int location; /* token location, or -1 if unknown */
+} JsonArrayConstructor;
+
+/*
+ * JsonArrayQueryConstructor -
+ * untransformed representation of JSON_ARRAY(subquery) constructor
+ */
+typedef struct JsonArrayQueryConstructor
+{
+ NodeTag type;
+ Node *query; /* subquery */
+ JsonOutput *output; /* RETURNING clause, if specified */
+ JsonFormat *format; /* FORMAT clause for subquery, if specified */
+ bool absent_on_null; /* skip NULL elements? */
+ int location; /* token location, or -1 if unknown */
+} JsonArrayQueryConstructor;
+
+/*
+ * JsonAggConstructor -
+ * common fields of untransformed representation of
+ * JSON_ARRAYAGG() and JSON_OBJECTAGG()
+ */
+typedef struct JsonAggConstructor
+{
+ NodeTag type;
+ JsonOutput *output; /* RETURNING clause, if any */
+ Node *agg_filter; /* FILTER clause, if any */
+ List *agg_order; /* ORDER BY clause, if any */
+ struct WindowDef *over; /* OVER clause, if any */
+ int location; /* token location, or -1 if unknown */
+} JsonAggConstructor;
+
+/*
+ * JsonObjectAgg -
+ * untransformed representation of JSON_OBJECTAGG()
+ */
+typedef struct JsonObjectAgg
+{
+ NodeTag type;
+ JsonAggConstructor *constructor; /* common fields */
+ JsonKeyValue *arg; /* object key-value pair */
+ bool absent_on_null; /* skip NULL values? */
+ bool unique; /* check key uniqueness? */
+} JsonObjectAgg;
+
+/*
+ * JsonArrayAgg -
+ * untransformed representation of JSON_ARRRAYAGG()
+ */
+typedef struct JsonArrayAgg
+{
+ NodeTag type;
+ JsonAggConstructor *constructor; /* common fields */
+ JsonValueExpr *arg; /* array element expression */
+ bool absent_on_null; /* skip NULL elements? */
+} JsonArrayAgg;
+
+
/*****************************************************************************
* Raw Grammar Output Statements
*****************************************************************************/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 8fb5b4b919b..de1701c2136 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1498,6 +1498,91 @@ typedef struct XmlExpr
int location;
} XmlExpr;
+/*
+ * JsonEncoding -
+ * representation of JSON ENCODING clause
+ */
+typedef enum JsonEncoding
+{
+ JS_ENC_DEFAULT, /* unspecified */
+ JS_ENC_UTF8,
+ JS_ENC_UTF16,
+ JS_ENC_UTF32,
+} JsonEncoding;
+
+/*
+ * JsonFormatType -
+ * enumeration of JSON formats used in JSON FORMAT clause
+ */
+typedef enum JsonFormatType
+{
+ JS_FORMAT_DEFAULT, /* unspecified */
+ JS_FORMAT_JSON, /* FORMAT JSON [ENCODING ...] */
+ JS_FORMAT_JSONB /* implicit internal format for RETURNING
+ * jsonb */
+} JsonFormatType;
+
+/*
+ * JsonFormat -
+ * representation of JSON FORMAT clause
+ */
+typedef struct JsonFormat
+{
+ NodeTag type;
+ JsonFormatType format_type; /* format type */
+ JsonEncoding encoding; /* JSON encoding */
+ int location; /* token location, or -1 if unknown */
+} JsonFormat;
+
+/*
+ * JsonReturning -
+ * transformed representation of JSON RETURNING clause
+ */
+typedef struct JsonReturning
+{
+ NodeTag type;
+ JsonFormat *format; /* output JSON format */
+ Oid typid; /* target type Oid */
+ int32 typmod; /* target type modifier */
+} JsonReturning;
+
+/*
+ * JsonValueExpr -
+ * representation of JSON value expression (expr [FORMAT json_format])
+ */
+typedef struct JsonValueExpr
+{
+ NodeTag type;
+ Expr *raw_expr; /* raw expression */
+ Expr *formatted_expr; /* formatted expression or NULL */
+ JsonFormat *format; /* FORMAT clause, if specified */
+} JsonValueExpr;
+
+typedef enum JsonConstructorType
+{
+ JSCTOR_JSON_OBJECT = 1,
+ JSCTOR_JSON_ARRAY = 2,
+ JSCTOR_JSON_OBJECTAGG = 3,
+ JSCTOR_JSON_ARRAYAGG = 4
+} JsonConstructorType;
+
+/*
+ * JsonConstructorExpr -
+ * wrapper over FuncExpr/Aggref/WindowFunc for SQL/JSON constructors
+ */
+typedef struct JsonConstructorExpr
+{
+ Expr xpr;
+ JsonConstructorType type; /* constructor type */
+ List *args;
+ Expr *func; /* underlying json[b]_xxx() function call */
+ Expr *coercion; /* coercion to RETURNING type */
+ JsonReturning *returning; /* RETURNING clause */
+ bool absent_on_null; /* ABSENT ON NULL? */
+ bool unique; /* WITH UNIQUE KEYS? (JSON_OBJECT[AGG] only) */
+ int location;
+} JsonConstructorExpr;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 753e9ee1742..868f389a04d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -26,6 +26,7 @@
/* name, value, category, is-bare-label */
PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("absent", ABSENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -175,6 +176,7 @@ PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("for", FOR, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("foreign", FOREIGN, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("format", FORMAT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("forward", FORWARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("freeze", FREEZE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("from", FROM, RESERVED_KEYWORD, AS_LABEL)
@@ -228,7 +230,13 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index 23e3cc41d65..b75f7d929dd 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -20,5 +20,11 @@
extern void escape_json(StringInfo buf, const char *str);
extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
const int *tzp);
+extern bool to_json_is_immutable(Oid typoid);
+extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null,
+ bool unique_keys);
+extern Datum json_build_array_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null);
#endif /* JSON_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 701e063abd4..649a1644f24 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -321,6 +321,8 @@ typedef struct JsonbParseState
JsonbValue contVal;
Size size;
struct JsonbParseState *next;
+ bool unique_keys; /* Check object key uniqueness */
+ bool skip_nulls; /* Skip null object fields */
} JsonbParseState;
/*
@@ -427,4 +429,11 @@ extern Datum jsonb_set_element(Jsonb *jb, Datum *path, int path_len,
JsonbValue *newval);
extern Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath,
bool *isnull, bool as_text);
+extern bool to_jsonb_is_immutable(Oid typoid);
+extern Datum jsonb_build_object_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null,
+ bool unique_keys);
+extern Datum jsonb_build_array_worker(int nargs, Datum *args, bool *nulls,
+ Oid *types, bool absent_on_null);
+
#endif /* __JSONB_H__ */
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 296cd7193c7..faeb460ef52 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -55,9 +55,11 @@ my %replace_token = (
# or in the block
my %replace_string = (
+ 'FORMAT_LA' => 'format',
'NOT_LA' => 'not',
'NULLS_LA' => 'nulls',
'WITH_LA' => 'with',
+ 'WITHOUT_LA' => 'without',
'TYPECAST' => '::',
'DOT_DOT' => '..',
'COLON_EQUALS' => ':=',
diff --git a/src/interfaces/ecpg/preproc/parser.c b/src/interfaces/ecpg/preproc/parser.c
index f447dc5d84b..a40f4bef093 100644
--- a/src/interfaces/ecpg/preproc/parser.c
+++ b/src/interfaces/ecpg/preproc/parser.c
@@ -78,9 +78,11 @@ filtered_base_yylex(void)
*/
switch (cur_token)
{
+ case FORMAT:
case NOT:
case NULLS_P:
case WITH:
+ case WITHOUT:
case UIDENT:
case USCONST:
break;
@@ -110,6 +112,16 @@ filtered_base_yylex(void)
/* Replace cur_token if needed, based on lookahead */
switch (cur_token)
{
+ case FORMAT:
+ /* Replace FORMAT by FORMAT_LA if it's followed by JSON */
+ switch (next_token)
+ {
+ case JSON:
+ cur_token = FORMAT_LA;
+ break;
+ }
+ break;
+
case NOT:
/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
switch (next_token)
@@ -145,6 +157,16 @@ filtered_base_yylex(void)
break;
}
break;
+
+ case WITHOUT:
+ /* Replace WITHOUT by WITHOUT_LA if it's followed by UNIQUE */
+ switch (next_token)
+ {
+ case UNIQUE:
+ cur_token = WITHOUT_LA;
+ break;
+ }
+ break;
case UIDENT:
case USCONST:
/* Look ahead for UESCAPE */
diff --git a/src/interfaces/ecpg/test/ecpg_schedule b/src/interfaces/ecpg/test/ecpg_schedule
index e034c5a420c..39814a39c17 100644
--- a/src/interfaces/ecpg/test/ecpg_schedule
+++ b/src/interfaces/ecpg/test/ecpg_schedule
@@ -50,6 +50,7 @@ test: sql/indicators
test: sql/oldexec
test: sql/quote
test: sql/show
+test: sql/sqljson
test: sql/insupd
test: sql/parser
test: sql/prepareas
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
new file mode 100644
index 00000000000..64784542ed6
--- /dev/null
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -0,0 +1,203 @@
+/* Processed by ecpg (regression mode) */
+/* These include files are added by the preprocessor */
+#include
+#include
+#include
+/* End of automatic include section */
+#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y))
+
+#line 1 "sqljson.pgc"
+#include
+
+
+#line 1 "sqlca.h"
+#ifndef POSTGRES_SQLCA_H
+#define POSTGRES_SQLCA_H
+
+#ifndef PGDLLIMPORT
+#if defined(WIN32) || defined(__CYGWIN__)
+#define PGDLLIMPORT __declspec (dllimport)
+#else
+#define PGDLLIMPORT
+#endif /* __CYGWIN__ */
+#endif /* PGDLLIMPORT */
+
+#define SQLERRMC_LEN 150
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+struct sqlca_t
+{
+ char sqlcaid[8];
+ long sqlabc;
+ long sqlcode;
+ struct
+ {
+ int sqlerrml;
+ char sqlerrmc[SQLERRMC_LEN];
+ } sqlerrm;
+ char sqlerrp[8];
+ long sqlerrd[6];
+ /* Element 0: empty */
+ /* 1: OID of processed tuple if applicable */
+ /* 2: number of rows processed */
+ /* after an INSERT, UPDATE or */
+ /* DELETE statement */
+ /* 3: empty */
+ /* 4: empty */
+ /* 5: empty */
+ char sqlwarn[8];
+ /* Element 0: set to 'W' if at least one other is 'W' */
+ /* 1: if 'W' at least one character string */
+ /* value was truncated when it was */
+ /* stored into a host variable. */
+
+ /*
+ * 2: if 'W' a (hopefully) non-fatal notice occurred
+ */ /* 3: empty */
+ /* 4: empty */
+ /* 5: empty */
+ /* 6: empty */
+ /* 7: empty */
+
+ char sqlstate[5];
+};
+
+struct sqlca_t *ECPGget_sqlca(void);
+
+#ifndef POSTGRES_ECPG_INTERNAL
+#define sqlca (*ECPGget_sqlca())
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#line 3 "sqljson.pgc"
+
+
+#line 1 "regression.h"
+
+
+
+
+
+
+#line 4 "sqljson.pgc"
+
+
+/* exec sql whenever sqlerror sqlprint ; */
+#line 6 "sqljson.pgc"
+
+
+int
+main ()
+{
+/* exec sql begin declare section */
+
+
+#line 12 "sqljson.pgc"
+ char json [ 1024 ] ;
+/* exec sql end declare section */
+#line 13 "sqljson.pgc"
+
+
+ ECPGdebug (1, stderr);
+
+ { ECPGconnect(__LINE__, 0, "ecpg1_regression" , NULL, NULL , NULL, 0);
+#line 17 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 17 "sqljson.pgc"
+
+ { ECPGsetcommit(__LINE__, "on", NULL);
+#line 18 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 18 "sqljson.pgc"
+
+
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( returning text )", ECPGt_EOIT,
+ ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 20 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 20 "sqljson.pgc"
+
+ printf("Found json=%s\n", json);
+
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( returning text format json )", ECPGt_EOIT,
+ ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 23 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 23 "sqljson.pgc"
+
+ printf("Found json=%s\n", json);
+
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_array ( returning jsonb )", ECPGt_EOIT,
+ ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 26 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 26 "sqljson.pgc"
+
+ printf("Found json=%s\n", json);
+
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_array ( returning jsonb format json )", ECPGt_EOIT,
+ ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 29 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 29 "sqljson.pgc"
+
+ printf("Found json=%s\n", json);
+
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( 1 : 1 , '1' : null with unique )", ECPGt_EOIT,
+ ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 32 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 32 "sqljson.pgc"
+
+ // error
+
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( 1 : 1 , '2' : null , 1 : '2' absent on null without unique keys )", ECPGt_EOIT,
+ ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 35 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 35 "sqljson.pgc"
+
+ printf("Found json=%s\n", json);
+
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( 1 : 1 , '2' : null absent on null without unique returning jsonb )", ECPGt_EOIT,
+ ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 38 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 38 "sqljson.pgc"
+
+ printf("Found json=%s\n", json);
+
+ { ECPGdisconnect(__LINE__, "CURRENT");
+#line 41 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 41 "sqljson.pgc"
+
+
+ return 0;
+}
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
new file mode 100644
index 00000000000..907f773eb98
--- /dev/null
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -0,0 +1,69 @@
+[NO_PID]: ECPGdebug: set to 1
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ECPGconnect: opening database ecpg1_regression on port
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ECPGsetcommit on line 18: action "on"; connection "ecpg1_regression"
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 20: query: select json_object ( returning text ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 20: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 20: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 20: RESULT: {} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 23: query: select json_object ( returning text format json ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 23: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 23: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 23: RESULT: {} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 26: query: select json_array ( returning jsonb ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 26: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 26: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_is_type_an_array on line 26: type (3802); C (1); array (no)
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 26: RESULT: [] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 29: query: select json_array ( returning jsonb format json ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 29: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 29: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 29: RESULT: [] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 32: query: select json_object ( 1 : 1 , '1' : null with unique ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 32: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 32: bad response - ERROR: duplicate JSON key "1"
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 22030 (sqlcode -400): duplicate JSON key "1" on line 32
+[NO_PID]: sqlca: code: -400, state: 22030
+SQL error: duplicate JSON key "1" on line 32
+[NO_PID]: ecpg_execute on line 35: query: select json_object ( 1 : 1 , '2' : null , 1 : '2' absent on null without unique keys ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 35: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 35: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_is_type_an_array on line 35: type (114); C (1); array (no)
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 35: RESULT: {"1" : 1, "1" : "2"} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 38: query: select json_object ( 1 : 1 , '2' : null absent on null without unique returning jsonb ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 38: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 38: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 38: RESULT: {"1": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_finish: connection ecpg1_regression closed
+[NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
new file mode 100644
index 00000000000..aae052a2b9a
--- /dev/null
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -0,0 +1,6 @@
+Found json={}
+Found json={}
+Found json=[]
+Found json=[]
+Found json={"1" : 1, "1" : "2"}
+Found json={"1": 1}
diff --git a/src/interfaces/ecpg/test/sql/Makefile b/src/interfaces/ecpg/test/sql/Makefile
index 876ca8df3e6..d8213b25cea 100644
--- a/src/interfaces/ecpg/test/sql/Makefile
+++ b/src/interfaces/ecpg/test/sql/Makefile
@@ -23,6 +23,7 @@ TESTS = array array.c \
parser parser.c \
quote quote.c \
show show.c \
+ sqljson sqljson.c \
insupd insupd.c \
twophase twophase.c \
insupd insupd.c \
diff --git a/src/interfaces/ecpg/test/sql/meson.build b/src/interfaces/ecpg/test/sql/meson.build
index 5149a738108..f4c9418abb8 100644
--- a/src/interfaces/ecpg/test/sql/meson.build
+++ b/src/interfaces/ecpg/test/sql/meson.build
@@ -25,6 +25,7 @@ pgc_files = [
'quote',
'show',
'sqlda',
+ 'sqljson',
'twophase',
]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
new file mode 100644
index 00000000000..6a582b5b105
--- /dev/null
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -0,0 +1,44 @@
+#include
+
+EXEC SQL INCLUDE sqlca;
+exec sql include ../regression;
+
+EXEC SQL WHENEVER SQLERROR sqlprint;
+
+int
+main ()
+{
+EXEC SQL BEGIN DECLARE SECTION;
+ char json[1024];
+EXEC SQL END DECLARE SECTION;
+
+ ECPGdebug (1, stderr);
+
+ EXEC SQL CONNECT TO REGRESSDB1;
+ EXEC SQL SET AUTOCOMMIT = ON;
+
+ EXEC SQL SELECT JSON_OBJECT(RETURNING text) INTO :json;
+ printf("Found json=%s\n", json);
+
+ EXEC SQL SELECT JSON_OBJECT(RETURNING text FORMAT JSON) INTO :json;
+ printf("Found json=%s\n", json);
+
+ EXEC SQL SELECT JSON_ARRAY(RETURNING jsonb) INTO :json;
+ printf("Found json=%s\n", json);
+
+ EXEC SQL SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON) INTO :json;
+ printf("Found json=%s\n", json);
+
+ EXEC SQL SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE) INTO :json;
+ // error
+
+ EXEC SQL SELECT JSON_OBJECT(1: 1, '2': NULL, 1: '2' ABSENT ON NULL WITHOUT UNIQUE KEYS) INTO :json;
+ printf("Found json=%s\n", json);
+
+ EXEC SQL SELECT JSON_OBJECT(1: 1, '2': NULL ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb) INTO :json;
+ printf("Found json=%s\n", json);
+
+ EXEC SQL DISCONNECT;
+
+ return 0;
+}
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index 56dba9d6e28..aa29bc597bd 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -2119,8 +2119,7 @@ SELECT json_build_object('a', 'b', 'c'); -- error
ERROR: argument list must have even number of elements
HINT: The arguments of json_build_object() must consist of alternating keys and values.
SELECT json_build_object(NULL, 'a'); -- error, key cannot be NULL
-ERROR: argument 1 cannot be null
-HINT: Object keys should be text.
+ERROR: null value not allowed for object key
SELECT json_build_object('a', NULL); -- ok
json_build_object
-------------------
@@ -2149,8 +2148,7 @@ SELECT json_build_object(VARIADIC ARRAY['a', NULL]::text[]); -- ok
(1 row)
SELECT json_build_object(VARIADIC ARRAY[NULL, 'a']::text[]); -- error, key cannot be NULL
-ERROR: argument 1 cannot be null
-HINT: Object keys should be text.
+ERROR: null value not allowed for object key
SELECT json_build_object(VARIADIC '{1,2,3,4}'::text[]); -- ok
json_build_object
------------------------
@@ -2191,8 +2189,7 @@ SELECT json_build_object(1,2);
-- keys must be scalar and not null
SELECT json_build_object(null,2);
-ERROR: argument 1 cannot be null
-HINT: Object keys should be text.
+ERROR: null value not allowed for object key
SELECT json_build_object(r,2) FROM (SELECT 1 AS a, 2 AS b) r;
ERROR: key value must be scalar, not array, composite, or json
SELECT json_build_object(json '{"a":1,"b":2}', 3);
@@ -2218,7 +2215,7 @@ SELECT json_object_agg(name, type) FROM foo;
INSERT INTO foo VALUES (999999, NULL, 'bar');
SELECT json_object_agg(name, type) FROM foo;
-ERROR: field name must not be null
+ERROR: null value not allowed for object key
-- json_object
-- empty object, one dimension
SELECT json_object('{}');
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 02f5348ab1b..a1bdf2c0b5f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1505,8 +1505,10 @@ WHERE a.aggfnoid = p.oid AND
NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2]))
OR (p.pronargs > 2 AND
NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3]))
- -- we could carry the check further, but 3 args is enough for now
- OR (p.pronargs > 3)
+ OR (p.pronargs > 3 AND
+ NOT binary_coercible(p.proargtypes[3], ptr.proargtypes[4]))
+ -- we could carry the check further, but 4 args is enough for now
+ OR (p.pronargs > 4)
);
aggfnoid | proname | oid | proname
----------+---------+-----+---------
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
new file mode 100644
index 00000000000..c4bfe80ba96
--- /dev/null
+++ b/src/test/regress/expected/sqljson.out
@@ -0,0 +1,756 @@
+-- JSON_OBJECT()
+SELECT JSON_OBJECT();
+ json_object
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING json);
+ json_object
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING json FORMAT JSON);
+ json_object
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING jsonb);
+ json_object
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON);
+ json_object
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING text);
+ json_object
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON);
+ json_object
+-------------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8);
+ERROR: cannot set JSON encoding for non-bytea output types
+LINE 1: SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8)...
+ ^
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING);
+ERROR: unrecognized JSON encoding: invalid_encoding
+SELECT JSON_OBJECT(RETURNING bytea);
+ json_object
+-------------
+ \x7b7d
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON);
+ json_object
+-------------
+ \x7b7d
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF8);
+ json_object
+-------------
+ \x7b7d
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF16);
+ERROR: unsupported JSON encoding
+LINE 1: SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF1...
+ ^
+HINT: Only UTF8 JSON encoding is supported.
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF32);
+ERROR: unsupported JSON encoding
+LINE 1: SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF3...
+ ^
+HINT: Only UTF8 JSON encoding is supported.
+SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON);
+ERROR: cannot use non-string types with explicit FORMAT JSON clause
+LINE 1: SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON);
+ ^
+SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF8);
+ERROR: JSON ENCODING clause is only allowed for bytea input type
+LINE 1: SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF...
+ ^
+SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON);
+WARNING: FORMAT JSON has no effect for json and jsonb types
+LINE 1: SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON);
+ ^
+ json_object
+----------------
+ {"foo" : null}
+(1 row)
+
+SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UTF8);
+ERROR: JSON ENCODING clause is only allowed for bytea input type
+LINE 1: SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UT...
+ ^
+SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON);
+WARNING: FORMAT JSON has no effect for json and jsonb types
+LINE 1: SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON);
+ ^
+ json_object
+---------------
+ {"foo": null}
+(1 row)
+
+SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING UTF8);
+ERROR: JSON ENCODING clause is only allowed for bytea input type
+LINE 1: SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING U...
+ ^
+SELECT JSON_OBJECT(NULL: 1);
+ERROR: null value not allowed for object key
+SELECT JSON_OBJECT('a': 2 + 3);
+ json_object
+-------------
+ {"a" : 5}
+(1 row)
+
+SELECT JSON_OBJECT('a' VALUE 2 + 3);
+ json_object
+-------------
+ {"a" : 5}
+(1 row)
+
+--SELECT JSON_OBJECT(KEY 'a' VALUE 2 + 3);
+SELECT JSON_OBJECT('a' || 2: 1);
+ json_object
+-------------
+ {"a2" : 1}
+(1 row)
+
+SELECT JSON_OBJECT(('a' || 2) VALUE 1);
+ json_object
+-------------
+ {"a2" : 1}
+(1 row)
+
+--SELECT JSON_OBJECT('a' || 2 VALUE 1);
+--SELECT JSON_OBJECT(KEY 'a' || 2 VALUE 1);
+SELECT JSON_OBJECT('a': 2::text);
+ json_object
+-------------
+ {"a" : "2"}
+(1 row)
+
+SELECT JSON_OBJECT('a' VALUE 2::text);
+ json_object
+-------------
+ {"a" : "2"}
+(1 row)
+
+--SELECT JSON_OBJECT(KEY 'a' VALUE 2::text);
+SELECT JSON_OBJECT(1::text: 2);
+ json_object
+-------------
+ {"1" : 2}
+(1 row)
+
+SELECT JSON_OBJECT((1::text) VALUE 2);
+ json_object
+-------------
+ {"1" : 2}
+(1 row)
+
+--SELECT JSON_OBJECT(1::text VALUE 2);
+--SELECT JSON_OBJECT(KEY 1::text VALUE 2);
+SELECT JSON_OBJECT(json '[1]': 123);
+ERROR: key value must be scalar, not array, composite, or json
+SELECT JSON_OBJECT(ARRAY[1,2,3]: 'aaa');
+ERROR: key value must be scalar, not array, composite, or json
+SELECT JSON_OBJECT(
+ 'a': '123',
+ 1.23: 123,
+ 'c': json '[ 1,true,{ } ]',
+ 'd': jsonb '{ "x" : 123.45 }'
+);
+ json_object
+-------------------------------------------------------------------
+ {"a": "123", "c": [1, true, {}], "d": {"x": 123.45}, "1.23": 123}
+(1 row)
+
+SELECT JSON_OBJECT(
+ 'a': '123',
+ 1.23: 123,
+ 'c': json '[ 1,true,{ } ]',
+ 'd': jsonb '{ "x" : 123.45 }'
+ RETURNING jsonb
+);
+ json_object
+-------------------------------------------------------------------
+ {"a": "123", "c": [1, true, {}], "d": {"x": 123.45}, "1.23": 123}
+(1 row)
+
+/*
+SELECT JSON_OBJECT(
+ 'a': '123',
+ KEY 1.23 VALUE 123,
+ 'c' VALUE json '[1, true, {}]'
+);
+*/
+SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa'));
+ json_object
+-----------------------------------------------
+ {"a" : "123", "b" : {"a" : 111, "b" : "aaa"}}
+(1 row)
+
+SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa' RETURNING jsonb));
+ json_object
+-------------------------------------------
+ {"a": "123", "b": {"a": 111, "b": "aaa"}}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text));
+ json_object
+-----------------------
+ {"a" : "{\"b\" : 1}"}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text) FORMAT JSON);
+ json_object
+-------------------
+ {"a" : {"b" : 1}}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea));
+ json_object
+---------------------------------
+ {"a" : "\\x7b226222203a20317d"}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea) FORMAT JSON);
+ json_object
+-------------------
+ {"a" : {"b" : 1}}
+(1 row)
+
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2);
+ json_object
+----------------------------------
+ {"a" : "1", "b" : null, "c" : 2}
+(1 row)
+
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL);
+ json_object
+----------------------------------
+ {"a" : "1", "b" : null, "c" : 2}
+(1 row)
+
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL);
+ json_object
+----------------------
+ {"a" : "1", "c" : 2}
+(1 row)
+
+SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE);
+ERROR: duplicate JSON key "1"
+SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE);
+ERROR: duplicate JSON key "1"
+SELECT JSON_OBJECT(1: 1, '1': NULL NULL ON NULL WITH UNIQUE RETURNING jsonb);
+ERROR: duplicate JSON object key
+SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+ERROR: duplicate JSON object key
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 NULL ON NULL WITH UNIQUE);
+ERROR: duplicate JSON key "1"
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE);
+ERROR: duplicate JSON key "1"
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE);
+ json_object
+--------------------
+ {"1" : 1, "1" : 1}
+(1 row)
+
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+ERROR: duplicate JSON object key
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb);
+ json_object
+-------------
+ {"1": 1}
+(1 row)
+
+SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+ json_object
+----------------------------
+ {"1": 1, "3": 1, "5": "a"}
+(1 row)
+
+-- JSON_ARRAY()
+SELECT JSON_ARRAY();
+ json_array
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING json);
+ json_array
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING json FORMAT JSON);
+ json_array
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING jsonb);
+ json_array
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON);
+ json_array
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING text);
+ json_array
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON);
+ json_array
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8);
+ERROR: cannot set JSON encoding for non-bytea output types
+LINE 1: SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8);
+ ^
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING);
+ERROR: unrecognized JSON encoding: invalid_encoding
+SELECT JSON_ARRAY(RETURNING bytea);
+ json_array
+------------
+ \x5b5d
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON);
+ json_array
+------------
+ \x5b5d
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF8);
+ json_array
+------------
+ \x5b5d
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16);
+ERROR: unsupported JSON encoding
+LINE 1: SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16...
+ ^
+HINT: Only UTF8 JSON encoding is supported.
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32);
+ERROR: unsupported JSON encoding
+LINE 1: SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32...
+ ^
+HINT: Only UTF8 JSON encoding is supported.
+SELECT JSON_ARRAY('aaa', 111, true, array[1,2,3], NULL, json '{"a": [1]}', jsonb '["a",3]');
+ json_array
+-----------------------------------------------------
+ ["aaa", 111, true, [1, 2, 3], {"a": [1]}, ["a", 3]]
+(1 row)
+
+SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL);
+ json_array
+------------------
+ ["a", null, "b"]
+(1 row)
+
+SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL);
+ json_array
+------------
+ ["a", "b"]
+(1 row)
+
+SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL);
+ json_array
+------------
+ ["b"]
+(1 row)
+
+SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL RETURNING jsonb);
+ json_array
+------------------
+ ["a", null, "b"]
+(1 row)
+
+SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL RETURNING jsonb);
+ json_array
+------------
+ ["a", "b"]
+(1 row)
+
+SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL RETURNING jsonb);
+ json_array
+------------
+ ["b"]
+(1 row)
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING text));
+ json_array
+-------------------------------
+ ["[\"{ \\\"a\\\" : 123 }\"]"]
+(1 row)
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text));
+ json_array
+-----------------------
+ ["[{ \"a\" : 123 }]"]
+(1 row)
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSON);
+ json_array
+-------------------
+ [[{ "a" : 123 }]]
+(1 row)
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i));
+ json_array
+------------
+ [1, 2, 4]
+(1 row)
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i));
+ json_array
+------------
+ [[1,2], +
+ [3,4]]
+(1 row)
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) RETURNING jsonb);
+ json_array
+------------------
+ [[1, 2], [3, 4]]
+(1 row)
+
+--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL);
+--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL RETURNING jsonb);
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i);
+ json_array
+------------
+ [1, 2, 3]
+(1 row)
+
+-- Should fail
+SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i));
+ERROR: subquery must return only one column
+LINE 1: SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i));
+ ^
+SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i));
+ERROR: subquery must return only one column
+LINE 1: SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i));
+ ^
+SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j));
+ERROR: subquery must return only one column
+LINE 1: SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j));
+ ^
+-- JSON_ARRAYAGG()
+SELECT JSON_ARRAYAGG(i) IS NULL,
+ JSON_ARRAYAGG(i RETURNING jsonb) IS NULL
+FROM generate_series(1, 0) i;
+ ?column? | ?column?
+----------+----------
+ t | t
+(1 row)
+
+SELECT JSON_ARRAYAGG(i),
+ JSON_ARRAYAGG(i RETURNING jsonb)
+FROM generate_series(1, 5) i;
+ json_arrayagg | json_arrayagg
+-----------------+-----------------
+ [1, 2, 3, 4, 5] | [1, 2, 3, 4, 5]
+(1 row)
+
+SELECT JSON_ARRAYAGG(i ORDER BY i DESC)
+FROM generate_series(1, 5) i;
+ json_arrayagg
+-----------------
+ [5, 4, 3, 2, 1]
+(1 row)
+
+SELECT JSON_ARRAYAGG(i::text::json)
+FROM generate_series(1, 5) i;
+ json_arrayagg
+-----------------
+ [1, 2, 3, 4, 5]
+(1 row)
+
+SELECT JSON_ARRAYAGG(JSON_ARRAY(i, i + 1 RETURNING text) FORMAT JSON)
+FROM generate_series(1, 5) i;
+ json_arrayagg
+------------------------------------------
+ [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6]]
+(1 row)
+
+SELECT JSON_ARRAYAGG(NULL),
+ JSON_ARRAYAGG(NULL RETURNING jsonb)
+FROM generate_series(1, 5);
+ json_arrayagg | json_arrayagg
+---------------+---------------
+ [] | []
+(1 row)
+
+SELECT JSON_ARRAYAGG(NULL NULL ON NULL),
+ JSON_ARRAYAGG(NULL NULL ON NULL RETURNING jsonb)
+FROM generate_series(1, 5);
+ json_arrayagg | json_arrayagg
+--------------------------------+--------------------------------
+ [null, null, null, null, null] | [null, null, null, null, null]
+(1 row)
+
+\x
+SELECT
+ JSON_ARRAYAGG(bar) as no_options,
+ JSON_ARRAYAGG(bar RETURNING jsonb) as returning_jsonb,
+ JSON_ARRAYAGG(bar ABSENT ON NULL) as absent_on_null,
+ JSON_ARRAYAGG(bar ABSENT ON NULL RETURNING jsonb) as absentonnull_returning_jsonb,
+ JSON_ARRAYAGG(bar NULL ON NULL) as null_on_null,
+ JSON_ARRAYAGG(bar NULL ON NULL RETURNING jsonb) as nullonnull_returning_jsonb,
+ JSON_ARRAYAGG(foo) as row_no_options,
+ JSON_ARRAYAGG(foo RETURNING jsonb) as row_returning_jsonb,
+ JSON_ARRAYAGG(foo ORDER BY bar) FILTER (WHERE bar > 2) as row_filtered_agg,
+ JSON_ARRAYAGG(foo ORDER BY bar RETURNING jsonb) FILTER (WHERE bar > 2) as row_filtered_agg_returning_jsonb
+FROM
+ (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar);
+-[ RECORD 1 ]--------------------+-------------------------------------------------------------------------------------------------------------------------
+no_options | [1, 2, 3, 4, 5]
+returning_jsonb | [1, 2, 3, 4, 5]
+absent_on_null | [1, 2, 3, 4, 5]
+absentonnull_returning_jsonb | [1, 2, 3, 4, 5]
+null_on_null | [1, 2, 3, 4, 5, null, null, null, null]
+nullonnull_returning_jsonb | [1, 2, 3, 4, 5, null, null, null, null]
+row_no_options | [{"bar":1}, +
+ | {"bar":2}, +
+ | {"bar":3}, +
+ | {"bar":4}, +
+ | {"bar":5}, +
+ | {"bar":null}, +
+ | {"bar":null}, +
+ | {"bar":null}, +
+ | {"bar":null}]
+row_returning_jsonb | [{"bar": 1}, {"bar": 2}, {"bar": 3}, {"bar": 4}, {"bar": 5}, {"bar": null}, {"bar": null}, {"bar": null}, {"bar": null}]
+row_filtered_agg | [{"bar":3}, +
+ | {"bar":4}, +
+ | {"bar":5}]
+row_filtered_agg_returning_jsonb | [{"bar": 3}, {"bar": 4}, {"bar": 5}]
+
+\x
+SELECT
+ bar, JSON_ARRAYAGG(bar) FILTER (WHERE bar > 2) OVER (PARTITION BY foo.bar % 2)
+FROM
+ (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL), (5), (4)) foo(bar);
+ bar | json_arrayagg
+-----+---------------
+ 4 | [4, 4]
+ 4 | [4, 4]
+ 2 | [4, 4]
+ 5 | [5, 3, 5]
+ 3 | [5, 3, 5]
+ 1 | [5, 3, 5]
+ 5 | [5, 3, 5]
+ |
+ |
+ |
+ |
+(11 rows)
+
+-- JSON_OBJECTAGG()
+SELECT JSON_OBJECTAGG('key': 1) IS NULL,
+ JSON_OBJECTAGG('key': 1 RETURNING jsonb) IS NULL
+WHERE FALSE;
+ ?column? | ?column?
+----------+----------
+ t | t
+(1 row)
+
+SELECT JSON_OBJECTAGG(NULL: 1);
+ERROR: null value not allowed for object key
+SELECT JSON_OBJECTAGG(NULL: 1 RETURNING jsonb);
+ERROR: field name must not be null
+SELECT
+ JSON_OBJECTAGG(i: i),
+-- JSON_OBJECTAGG(i VALUE i),
+-- JSON_OBJECTAGG(KEY i VALUE i),
+ JSON_OBJECTAGG(i: i RETURNING jsonb)
+FROM
+ generate_series(1, 5) i;
+ json_objectagg | json_objectagg
+-------------------------------------------------+------------------------------------------
+ { "1" : 1, "2" : 2, "3" : 3, "4" : 4, "5" : 5 } | {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5}
+(1 row)
+
+SELECT
+ JSON_OBJECTAGG(k: v),
+ JSON_OBJECTAGG(k: v NULL ON NULL),
+ JSON_OBJECTAGG(k: v ABSENT ON NULL),
+ JSON_OBJECTAGG(k: v RETURNING jsonb),
+ JSON_OBJECTAGG(k: v NULL ON NULL RETURNING jsonb),
+ JSON_OBJECTAGG(k: v ABSENT ON NULL RETURNING jsonb)
+FROM
+ (VALUES (1, 1), (1, NULL), (2, NULL), (3, 3)) foo(k, v);
+ json_objectagg | json_objectagg | json_objectagg | json_objectagg | json_objectagg | json_objectagg
+----------------------------------------------+----------------------------------------------+----------------------+--------------------------------+--------------------------------+------------------
+ { "1" : 1, "1" : null, "2" : null, "3" : 3 } | { "1" : 1, "1" : null, "2" : null, "3" : 3 } | { "1" : 1, "3" : 3 } | {"1": null, "2": null, "3": 3} | {"1": null, "2": null, "3": 3} | {"1": 1, "3": 3}
+(1 row)
+
+SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+ERROR: duplicate JSON key "1"
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+ERROR: duplicate JSON key "1"
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (0, NULL), (3, NULL), (2, 2), (4, NULL)) foo(k, v);
+ json_objectagg
+----------------------
+ { "1" : 1, "2" : 2 }
+(1 row)
+
+SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS RETURNING jsonb)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+ERROR: duplicate JSON object key
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+ERROR: duplicate JSON object key
+-- Test JSON_OBJECT deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);
+ QUERY PLAN
+------------------------------------------------------------------------------
+ Result
+ Output: JSON_OBJECT('foo' : '1'::json, 'bar' : 'baz'::text RETURNING json)
+(2 rows)
+
+CREATE VIEW json_object_view AS
+SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);
+\sv json_object_view
+CREATE OR REPLACE VIEW public.json_object_view AS
+ SELECT JSON_OBJECT('foo' : '1'::text FORMAT JSON, 'bar' : 'baz'::text RETURNING json) AS "json_object"
+DROP VIEW json_object_view;
+-- Test JSON_ARRAY deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
+ QUERY PLAN
+---------------------------------------------------
+ Result
+ Output: JSON_ARRAY('1'::json, 2 RETURNING json)
+(2 rows)
+
+CREATE VIEW json_array_view AS
+SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
+\sv json_array_view
+CREATE OR REPLACE VIEW public.json_array_view AS
+ SELECT JSON_ARRAY('1'::text FORMAT JSON, 2 RETURNING json) AS "json_array"
+DROP VIEW json_array_view;
+-- Test JSON_OBJECTAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+ Output: JSON_OBJECTAGG(i : (('111'::text || (i)::text))::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE (i > 3))
+ -> Function Scan on pg_catalog.generate_series i
+ Output: i
+ Function Call: generate_series(1, 5)
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------
+ WindowAgg
+ Output: JSON_OBJECTAGG(i : (('111'::text || (i)::text))::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) OVER (?), ((i % 2))
+ -> Sort
+ Output: ((i % 2)), i
+ Sort Key: ((i.i % 2))
+ -> Function Scan on pg_catalog.generate_series i
+ Output: (i % 2), i
+ Function Call: generate_series(1, 5)
+(8 rows)
+
+CREATE VIEW json_objectagg_view AS
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+\sv json_objectagg_view
+CREATE OR REPLACE VIEW public.json_objectagg_view AS
+ SELECT JSON_OBJECTAGG(i : ('111'::text || i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i > 3) AS "json_objectagg"
+ FROM generate_series(1, 5) i(i)
+DROP VIEW json_objectagg_view;
+-- Test JSON_ARRAYAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+ Output: JSON_ARRAYAGG((('111'::text || (i)::text))::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE (i > 3))
+ -> Function Scan on pg_catalog.generate_series i
+ Output: i
+ Function Call: generate_series(1, 5)
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------
+ WindowAgg
+ Output: JSON_ARRAYAGG((('111'::text || (i)::text))::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (?), ((i % 2))
+ -> Sort
+ Output: ((i % 2)), i
+ Sort Key: ((i.i % 2))
+ -> Function Scan on pg_catalog.generate_series i
+ Output: (i % 2), i
+ Function Call: generate_series(1, 5)
+(8 rows)
+
+CREATE VIEW json_arrayagg_view AS
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+\sv json_arrayagg_view
+CREATE OR REPLACE VIEW public.json_arrayagg_view AS
+ SELECT JSON_ARRAYAGG(('111'::text || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) AS "json_arrayagg"
+ FROM generate_series(1, 5) i(i)
+DROP VIEW json_arrayagg_view;
+-- Test JSON_ARRAY(subquery) deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+ QUERY PLAN
+---------------------------------------------------------------------
+ Result
+ Output: $0
+ InitPlan 1 (returns $0)
+ -> Aggregate
+ Output: JSON_ARRAYAGG("*VALUES*".column1 RETURNING jsonb)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(7 rows)
+
+CREATE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+\sv json_array_subquery_view
+CREATE OR REPLACE VIEW public.json_array_subquery_view AS
+ SELECT ( SELECT JSON_ARRAYAGG(q.a RETURNING jsonb) AS "json_arrayagg"
+ FROM ( SELECT foo.i
+ FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i)) q(a)) AS "json_array"
+DROP VIEW json_array_subquery_view;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 15e015b3d64..36240356393 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
# ----------
# Another group of parallel tests (JSON related)
# ----------
-test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
+test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 56b54ba9883..e2d2c70d706 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -880,8 +880,10 @@ WHERE a.aggfnoid = p.oid AND
NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2]))
OR (p.pronargs > 2 AND
NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3]))
- -- we could carry the check further, but 3 args is enough for now
- OR (p.pronargs > 3)
+ OR (p.pronargs > 3 AND
+ NOT binary_coercible(p.proargtypes[3], ptr.proargtypes[4]))
+ -- we could carry the check further, but 4 args is enough for now
+ OR (p.pronargs > 4)
);
-- Cross-check finalfn (if present) against its entry in pg_proc.
diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
new file mode 100644
index 00000000000..fbbf6a6d6e1
--- /dev/null
+++ b/src/test/regress/sql/sqljson.sql
@@ -0,0 +1,284 @@
+-- JSON_OBJECT()
+SELECT JSON_OBJECT();
+SELECT JSON_OBJECT(RETURNING json);
+SELECT JSON_OBJECT(RETURNING json FORMAT JSON);
+SELECT JSON_OBJECT(RETURNING jsonb);
+SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON);
+SELECT JSON_OBJECT(RETURNING text);
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON);
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8);
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING);
+SELECT JSON_OBJECT(RETURNING bytea);
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON);
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF8);
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF16);
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF32);
+
+SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON);
+SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF8);
+SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON);
+SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UTF8);
+SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON);
+SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING UTF8);
+
+SELECT JSON_OBJECT(NULL: 1);
+SELECT JSON_OBJECT('a': 2 + 3);
+SELECT JSON_OBJECT('a' VALUE 2 + 3);
+--SELECT JSON_OBJECT(KEY 'a' VALUE 2 + 3);
+SELECT JSON_OBJECT('a' || 2: 1);
+SELECT JSON_OBJECT(('a' || 2) VALUE 1);
+--SELECT JSON_OBJECT('a' || 2 VALUE 1);
+--SELECT JSON_OBJECT(KEY 'a' || 2 VALUE 1);
+SELECT JSON_OBJECT('a': 2::text);
+SELECT JSON_OBJECT('a' VALUE 2::text);
+--SELECT JSON_OBJECT(KEY 'a' VALUE 2::text);
+SELECT JSON_OBJECT(1::text: 2);
+SELECT JSON_OBJECT((1::text) VALUE 2);
+--SELECT JSON_OBJECT(1::text VALUE 2);
+--SELECT JSON_OBJECT(KEY 1::text VALUE 2);
+SELECT JSON_OBJECT(json '[1]': 123);
+SELECT JSON_OBJECT(ARRAY[1,2,3]: 'aaa');
+
+SELECT JSON_OBJECT(
+ 'a': '123',
+ 1.23: 123,
+ 'c': json '[ 1,true,{ } ]',
+ 'd': jsonb '{ "x" : 123.45 }'
+);
+
+SELECT JSON_OBJECT(
+ 'a': '123',
+ 1.23: 123,
+ 'c': json '[ 1,true,{ } ]',
+ 'd': jsonb '{ "x" : 123.45 }'
+ RETURNING jsonb
+);
+
+/*
+SELECT JSON_OBJECT(
+ 'a': '123',
+ KEY 1.23 VALUE 123,
+ 'c' VALUE json '[1, true, {}]'
+);
+*/
+
+SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa'));
+SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa' RETURNING jsonb));
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text));
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text) FORMAT JSON);
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea));
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea) FORMAT JSON);
+
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2);
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL);
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL);
+
+SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE);
+SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE);
+SELECT JSON_OBJECT(1: 1, '1': NULL NULL ON NULL WITH UNIQUE RETURNING jsonb);
+SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 NULL ON NULL WITH UNIQUE);
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE);
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE);
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb);
+SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+
+
+-- JSON_ARRAY()
+SELECT JSON_ARRAY();
+SELECT JSON_ARRAY(RETURNING json);
+SELECT JSON_ARRAY(RETURNING json FORMAT JSON);
+SELECT JSON_ARRAY(RETURNING jsonb);
+SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON);
+SELECT JSON_ARRAY(RETURNING text);
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON);
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8);
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING);
+SELECT JSON_ARRAY(RETURNING bytea);
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON);
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF8);
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16);
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32);
+
+SELECT JSON_ARRAY('aaa', 111, true, array[1,2,3], NULL, json '{"a": [1]}', jsonb '["a",3]');
+
+SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL);
+SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL);
+SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL);
+SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL RETURNING jsonb);
+SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL RETURNING jsonb);
+SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL RETURNING jsonb);
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING text));
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text));
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSON);
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i));
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i));
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) RETURNING jsonb);
+--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL);
+--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL RETURNING jsonb);
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i);
+-- Should fail
+SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i));
+SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i));
+SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j));
+
+-- JSON_ARRAYAGG()
+SELECT JSON_ARRAYAGG(i) IS NULL,
+ JSON_ARRAYAGG(i RETURNING jsonb) IS NULL
+FROM generate_series(1, 0) i;
+
+SELECT JSON_ARRAYAGG(i),
+ JSON_ARRAYAGG(i RETURNING jsonb)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(i ORDER BY i DESC)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(i::text::json)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(JSON_ARRAY(i, i + 1 RETURNING text) FORMAT JSON)
+FROM generate_series(1, 5) i;
+
+SELECT JSON_ARRAYAGG(NULL),
+ JSON_ARRAYAGG(NULL RETURNING jsonb)
+FROM generate_series(1, 5);
+
+SELECT JSON_ARRAYAGG(NULL NULL ON NULL),
+ JSON_ARRAYAGG(NULL NULL ON NULL RETURNING jsonb)
+FROM generate_series(1, 5);
+
+\x
+SELECT
+ JSON_ARRAYAGG(bar) as no_options,
+ JSON_ARRAYAGG(bar RETURNING jsonb) as returning_jsonb,
+ JSON_ARRAYAGG(bar ABSENT ON NULL) as absent_on_null,
+ JSON_ARRAYAGG(bar ABSENT ON NULL RETURNING jsonb) as absentonnull_returning_jsonb,
+ JSON_ARRAYAGG(bar NULL ON NULL) as null_on_null,
+ JSON_ARRAYAGG(bar NULL ON NULL RETURNING jsonb) as nullonnull_returning_jsonb,
+ JSON_ARRAYAGG(foo) as row_no_options,
+ JSON_ARRAYAGG(foo RETURNING jsonb) as row_returning_jsonb,
+ JSON_ARRAYAGG(foo ORDER BY bar) FILTER (WHERE bar > 2) as row_filtered_agg,
+ JSON_ARRAYAGG(foo ORDER BY bar RETURNING jsonb) FILTER (WHERE bar > 2) as row_filtered_agg_returning_jsonb
+FROM
+ (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar);
+\x
+
+SELECT
+ bar, JSON_ARRAYAGG(bar) FILTER (WHERE bar > 2) OVER (PARTITION BY foo.bar % 2)
+FROM
+ (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL), (5), (4)) foo(bar);
+
+-- JSON_OBJECTAGG()
+SELECT JSON_OBJECTAGG('key': 1) IS NULL,
+ JSON_OBJECTAGG('key': 1 RETURNING jsonb) IS NULL
+WHERE FALSE;
+
+SELECT JSON_OBJECTAGG(NULL: 1);
+
+SELECT JSON_OBJECTAGG(NULL: 1 RETURNING jsonb);
+
+SELECT
+ JSON_OBJECTAGG(i: i),
+-- JSON_OBJECTAGG(i VALUE i),
+-- JSON_OBJECTAGG(KEY i VALUE i),
+ JSON_OBJECTAGG(i: i RETURNING jsonb)
+FROM
+ generate_series(1, 5) i;
+
+SELECT
+ JSON_OBJECTAGG(k: v),
+ JSON_OBJECTAGG(k: v NULL ON NULL),
+ JSON_OBJECTAGG(k: v ABSENT ON NULL),
+ JSON_OBJECTAGG(k: v RETURNING jsonb),
+ JSON_OBJECTAGG(k: v NULL ON NULL RETURNING jsonb),
+ JSON_OBJECTAGG(k: v ABSENT ON NULL RETURNING jsonb)
+FROM
+ (VALUES (1, 1), (1, NULL), (2, NULL), (3, 3)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS)
+FROM (VALUES (1, 1), (0, NULL), (3, NULL), (2, 2), (4, NULL)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS RETURNING jsonb)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+
+SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb)
+FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
+
+-- Test JSON_OBJECT deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);
+
+CREATE VIEW json_object_view AS
+SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);
+
+\sv json_object_view
+
+DROP VIEW json_object_view;
+
+-- Test JSON_ARRAY deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
+
+CREATE VIEW json_array_view AS
+SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json);
+
+\sv json_array_view
+
+DROP VIEW json_array_view;
+
+-- Test JSON_OBJECTAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+
+CREATE VIEW json_objectagg_view AS
+SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+\sv json_objectagg_view
+
+DROP VIEW json_objectagg_view;
+
+-- Test JSON_ARRAYAGG deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (PARTITION BY i % 2)
+FROM generate_series(1,5) i;
+
+CREATE VIEW json_arrayagg_view AS
+SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3)
+FROM generate_series(1,5) i;
+
+\sv json_arrayagg_view
+
+DROP VIEW json_arrayagg_view;
+
+-- Test JSON_ARRAY(subquery) deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+
+CREATE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
+
+\sv json_array_subquery_view
+
+DROP VIEW json_array_subquery_view;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f45bcc52d39..f5cd394b335 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1247,6 +1247,7 @@ JsonBehaviorType
JsonCoercion
JsonCommon
JsonConstructorExpr
+JsonConstructorExprState
JsonConstructorType
JsonEncoding
JsonExpr