1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-30 11:03:19 +03:00

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
This commit is contained in:
Andrew Dunstan
2022-03-03 13:00:49 -05:00
parent a3b071bbe0
commit 865fe4d5df
17 changed files with 758 additions and 2 deletions

View File

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

View File

@ -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;
}