diff options
author | Andrew Dunstan <andrew@dunslane.net> | 2022-03-03 13:00:49 -0500 |
---|---|---|
committer | Andrew Dunstan <andrew@dunslane.net> | 2022-03-22 17:32:54 -0400 |
commit | 865fe4d5df560a6f5353da652018ff876978ad2d (patch) | |
tree | 47d3609a7b5fbf1fb60a1559a1af40b0df961de6 /src/backend/parser | |
parent | a3b071bbe050252b35c589a7f1a2ee2f7ee3e9d4 (diff) | |
download | postgresql-865fe4d5df560a6f5353da652018ff876978ad2d.tar.gz postgresql-865fe4d5df560a6f5353da652018ff876978ad2d.zip |
Common SQL/JSON clauses
This introduces some of the building blocks used by the SQL/JSON
constructor and query functions. Specifically, it provides node
executor and grammar support for the FORMAT JSON [ENCODING foo]
clause, and values decorated with it, and for the RETURNING clause.
The following SQL/JSON patches will leverage these.
Nikita Glukhov (who probably deserves an award for perseverance).
Reviewers have included (in no particular order) Andres Freund, Alexander
Korotkov, Pavel Stehule, Andrew Alsup. Erik Rijkers, Zihong Yu and
Himanshu Upadhyaya.
Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/gram.y | 65 | ||||
-rw-r--r-- | src/backend/parser/parse_expr.c | 181 |
2 files changed, 244 insertions, 2 deletions
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 0036c2f9e2d..204b754ebaa 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -635,6 +635,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <defelt> hash_partbound_elem +%type <node> json_format_clause_opt + json_representation + json_value_expr + json_output_clause_opt + +%type <ival> json_encoding + json_encoding_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 @@ -686,7 +694,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 @@ -697,7 +705,7 @@ 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 KEY @@ -781,6 +789,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 @@ -15235,6 +15244,54 @@ opt_asymmetric: ASYMMETRIC | /*EMPTY*/ ; +/* SQL/JSON support */ + +json_value_expr: + a_expr json_format_clause_opt + { + $$ = (Node *) makeJsonValueExpr((Expr *) $1, $2); + } + ; + +json_format_clause_opt: + FORMAT json_representation + { + $$ = $2; + $$.location = @1; + } + | /* EMPTY */ + { + $$ = (Node *) makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + } + ; + +json_representation: + JSON json_encoding_clause_opt + { + $$ = (Node *) makeJsonFormat(JS_FORMAT_JSON, $2, @1); + } + /* | other implementation defined JSON representation options (BSON, AVRO etc) */ + ; + +json_encoding_clause_opt: + ENCODING json_encoding { $$ = $2; } + | /* EMPTY */ { $$ = JS_ENC_DEFAULT; } + ; + +json_encoding: + name { $$ = makeJsonEncoding($1); } + ; + +json_output_clause_opt: + RETURNING Typename json_format_clause_opt + { + JsonOutput *n = makeNode(JsonOutput); + n->typeName = $2; + n->returning.format = $3; + $$ = (Node *) n; + } + | /* EMPTY */ { $$ = NULL; } + ; /***************************************************************************** * @@ -15776,6 +15833,7 @@ unreserved_keyword: | FIRST_P | FOLLOWING | FORCE + | FORMAT | FORWARD | FUNCTION | FUNCTIONS @@ -15807,6 +15865,7 @@ unreserved_keyword: | INSTEAD | INVOKER | ISOLATION + | JSON | KEY | LABEL | LANGUAGE @@ -16323,6 +16382,7 @@ bare_label_keyword: | FOLLOWING | FORCE | FOREIGN + | FORMAT | FORWARD | FREEZE | FULL @@ -16367,6 +16427,7 @@ bare_label_keyword: | IS | ISOLATION | JOIN + | JSON | KEY | LABEL | LANGUAGE diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 1c09ea24cdf..985ddbedf11 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -34,6 +34,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" @@ -3099,3 +3100,183 @@ 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; + + if (exprType(expr) == UNKNOWNOID) + expr = coerce_to_specific_type(pstate, expr, TEXTOID, "JSON_VALUE_EXPR"); + + 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; +} |