diff options
author | Andrew Dunstan <andrew@dunslane.net> | 2022-03-03 13:02:10 -0500 |
---|---|---|
committer | Andrew Dunstan <andrew@dunslane.net> | 2022-03-27 17:03:34 -0400 |
commit | f4fb45d15c59d7add2e1b81a9d477d0119a9691a (patch) | |
tree | 9025afb61fd4409ae48cd21d47c7fd58647e2633 /src/backend/parser/parse_expr.c | |
parent | f79b803dcc98d707450e158db3638dc67ff8380b (diff) | |
download | postgresql-f4fb45d15c59d7add2e1b81a9d477d0119a9691a.tar.gz postgresql-f4fb45d15c59d7add2e1b81a9d477d0119a9691a.zip |
SQL/JSON constructors
This patch introduces the SQL/JSON standard constructors for JSON:
JSON()
JSON_ARRAY()
JSON_ARRAYAGG()
JSON_OBJECT()
JSON_OBJECTAGG()
For the most part these functions provide facilities that mimic
existing json/jsonb functions. However, they also offer some useful
additional functionality. In addition to text input, the JSON() function
accepts bytea input, which it will decode and constuct a json value from.
The other functions provide useful options for handling duplicate keys
and null values.
This series of patches will be followed by a consolidated documentation
patch.
Nikita Glukhov
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/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru
Diffstat (limited to 'src/backend/parser/parse_expr.c')
-rw-r--r-- | src/backend/parser/parse_expr.c | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 985ddbedf11..6b93a76bca6 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" @@ -75,6 +77,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, @@ -302,6 +312,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)); @@ -3280,3 +3310,562 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *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 contructor 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_json = false; + bool have_jsonb = false; + + foreach(lc, args) + { + Node *expr = lfirst(lc); + Oid typid = exprType(expr); + + have_json |= typid == JSONOID; + have_jsonb |= typid == JSONBOID; + + if (have_jsonb) + break; + } + + if (have_jsonb) + { + returning->typid = JSONBOID; + returning->format->format_type = JS_FORMAT_JSONB; + } + else + { + /* Note: this includes the have_json case */ + + /* 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 ? returning->format->location : -1; + + /* 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"; /* F_JSONB_OBJECT_AGG_UNIQUE_STRICT */ + else + aggfnname = "pg_catalog.jsonb_object_agg_strict"; /* F_JSONB_OBJECT_AGG_STRICT */ + else + if (agg->unique) + aggfnname = "pg_catalog.jsonb_object_agg_unique"; /* F_JSONB_OBJECT_AGG_UNIQUE */ + else + aggfnname = "pg_catalog.jsonb_object_agg"; /* F_JSONB_OBJECT_AGG */ + + aggtype = JSONBOID; + } + else + { + if (agg->absent_on_null) + if (agg->unique) + aggfnname = "pg_catalog.json_object_agg_unique_strict"; /* F_JSON_OBJECT_AGG_UNIQUE_STRICT */ + else + aggfnname = "pg_catalog.json_object_agg_strict"; /* F_JSON_OBJECT_AGG_STRICT */ + else + if (agg->unique) + aggfnname = "pg_catalog.json_object_agg_unique"; /* F_JSON_OBJECT_AGG_UNIQUE */ + else + aggfnname = "pg_catalog.json_object_agg"; /* F_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); +} |