1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-02 09:02:37 +03:00

SQL/JSON: add standard JSON constructor functions

This commit introduces the SQL/JSON standard-conforming constructors for
JSON types:

JSON_ARRAY()
JSON_ARRAYAGG()
JSON_OBJECT()
JSON_OBJECTAGG()

Most of the functionality was already present in PostgreSQL-specific
functions, but these include some new functionality such as the ability
to skip or include NULL values, and to allow duplicate keys or throw
error when they are found, as well as the standard specified syntax to
specify output type and format.

Author: Nikita Glukhov <n.gluhov@postgrespro.ru>
Author: Teodor Sigaev <teodor@sigaev.ru>
Author: Oleg Bartunov <obartunov@gmail.com>
Author: Alexander Korotkov <aekorotkov@gmail.com>
Author: Amit Langote <amitlangote09@gmail.com>

Reviewers have included (in no particular order) Andres Freund, Alexander
Korotkov, Pavel Stehule, Andrew Alsup, Erik Rijkers, Zihong Yu,
Himanshu Upadhyaya, Daniel Gustafsson, Justin Pryzby.

Discussion: https://postgr.es/m/CAF4Au4w2x-5LTnN_bxky-mq4=WOqsGsxSpENCzHRAzSnEd8+WQ@mail.gmail.com
Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru
Discussion: https://postgr.es/m/20220616233130.rparivafipt6doj3@alap3.anarazel.de
Discussion: https://postgr.es/m/abd9b83b-aa66-f230-3d6d-734817f0995d%40postgresql.org
This commit is contained in:
Alvaro Herrera
2023-03-29 12:11:36 +02:00
parent 38b7437b90
commit 7081ac46ac
42 changed files with 4472 additions and 143 deletions

View File

@ -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
*/