aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
authorAndrew Dunstan <andrew@dunslane.net>2022-03-03 13:00:49 -0500
committerAndrew Dunstan <andrew@dunslane.net>2022-03-22 17:32:54 -0400
commit865fe4d5df560a6f5353da652018ff876978ad2d (patch)
tree47d3609a7b5fbf1fb60a1559a1af40b0df961de6 /src/backend/parser
parenta3b071bbe050252b35c589a7f1a2ee2f7ee3e9d4 (diff)
downloadpostgresql-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.y65
-rw-r--r--src/backend/parser/parse_expr.c181
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;
+}