diff options
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/Makefile | 1 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 199 | ||||
-rw-r--r-- | src/backend/parser/parse_clause.c | 12 | ||||
-rw-r--r-- | src/backend/parser/parse_expr.c | 32 | ||||
-rw-r--r-- | src/backend/parser/parse_jsontable.c | 466 | ||||
-rw-r--r-- | src/backend/parser/parse_relation.c | 3 | ||||
-rw-r--r-- | src/backend/parser/parse_target.c | 3 |
7 files changed, 701 insertions, 15 deletions
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile index 9f1c4022bb..f4c0cc7f10 100644 --- a/src/backend/parser/Makefile +++ b/src/backend/parser/Makefile @@ -23,6 +23,7 @@ OBJS = \ parse_enr.o \ parse_expr.o \ parse_func.o \ + parse_jsontable.o \ parse_merge.o \ parse_node.o \ parse_oper.o \ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e5a3c528aa..13fa5bea87 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -676,15 +676,25 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_object_aggregate_constructor json_array_aggregate_constructor json_path_specification + json_table + json_table_column_definition + json_table_ordinality_column_definition + json_table_regular_column_definition + json_table_formatted_column_definition + json_table_exists_column_definition + json_table_nested_columns %type <list> json_name_and_value_list json_value_expr_list json_array_aggregate_order_by_clause_opt json_arguments json_passing_clause_opt + json_table_columns_clause + json_table_column_definition_list %type <str> json_table_path_name json_as_path_name_clause_opt + json_table_column_path_specification_clause_opt %type <ival> json_encoding json_encoding_clause_opt @@ -698,6 +708,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_behavior_true json_behavior_false json_behavior_unknown + json_behavior_empty json_behavior_empty_array json_behavior_empty_object json_behavior_default @@ -705,6 +716,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_query_behavior json_exists_error_behavior json_exists_error_clause_opt + json_table_error_behavior + json_table_error_clause_opt %type <on_behavior> json_value_on_behavior_clause_opt json_query_on_behavior_clause_opt @@ -779,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG - JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_VALUE + JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_TABLE JSON_VALUE KEY KEYS KEEP @@ -790,8 +803,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE - NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE - NORMALIZE NORMALIZED + NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD NO + NONE NORMALIZE NORMALIZED NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC @@ -799,7 +812,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ORDER ORDINALITY OTHERS OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER - PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY + PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS POLICY POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION @@ -901,7 +914,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); */ %nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */ %nonassoc ERROR_P EMPTY_P DEFAULT ABSENT /* JSON error/empty behavior */ -%nonassoc FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON +%nonassoc FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON COLUMNS %nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP %left Op OPERATOR /* multi-character ops and user-defined operators */ %left '+' '-' @@ -926,6 +939,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); */ %left JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL +%nonassoc json_table_column +%nonassoc NESTED +%left PATH + %nonassoc empty_json_unique %left WITHOUT WITH_LA_UNIQUE @@ -12697,6 +12714,19 @@ table_ref: relation_expr opt_alias_clause $2->alias = $4; $$ = (Node *) $2; } + | json_table opt_alias_clause + { + JsonTable *jt = castNode(JsonTable, $1); + jt->alias = $2; + $$ = (Node *) jt; + } + | LATERAL_P json_table opt_alias_clause + { + JsonTable *jt = castNode(JsonTable, $2); + jt->alias = $3; + jt->lateral = true; + $$ = (Node *) jt; + } ; @@ -13248,6 +13278,8 @@ xmltable_column_option_el: { $$ = makeDefElem("is_not_null", (Node *) makeBoolean(true), @1); } | NULL_P { $$ = makeDefElem("is_not_null", (Node *) makeBoolean(false), @1); } + | PATH b_expr + { $$ = makeDefElem("path", $2, @1); } ; xml_namespace_list: @@ -15774,6 +15806,10 @@ json_behavior_unknown: UNKNOWN { $$ = makeJsonBehavior(JSON_BEHAVIOR_UNKNOWN, NULL); } ; +json_behavior_empty: + EMPTY_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); } + ; + json_behavior_empty_array: EMPTY_P ARRAY { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); } /* non-standard, for Oracle compatibility only */ @@ -15888,6 +15924,153 @@ json_query_on_behavior_clause_opt: { $$.on_empty = NULL; $$.on_error = NULL; } ; +json_table: + JSON_TABLE '(' + json_api_common_syntax + json_table_columns_clause + json_table_error_clause_opt + ')' + { + JsonTable *n = makeNode(JsonTable); + n->common = (JsonCommon *) $3; + n->columns = $4; + n->on_error = $5; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_columns_clause: + COLUMNS '(' json_table_column_definition_list ')' { $$ = $3; } + ; + +json_table_column_definition_list: + json_table_column_definition + { $$ = list_make1($1); } + | json_table_column_definition_list ',' json_table_column_definition + { $$ = lappend($1, $3); } + ; + +json_table_column_definition: + json_table_ordinality_column_definition %prec json_table_column + | json_table_regular_column_definition %prec json_table_column + | json_table_formatted_column_definition %prec json_table_column + | json_table_exists_column_definition %prec json_table_column + | json_table_nested_columns + ; + +json_table_ordinality_column_definition: + ColId FOR ORDINALITY + { + JsonTableColumn *n = makeNode(JsonTableColumn); + n->coltype = JTC_FOR_ORDINALITY; + n->name = $1; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_regular_column_definition: + ColId Typename + json_table_column_path_specification_clause_opt + json_wrapper_clause_opt + json_quotes_clause_opt + json_value_on_behavior_clause_opt + { + JsonTableColumn *n = makeNode(JsonTableColumn); + n->coltype = JTC_REGULAR; + n->name = $1; + n->typeName = $2; + n->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + n->wrapper = $4; /* JSW_NONE */ + n->omit_quotes = $5; /* false */ + n->pathspec = $3; + n->on_empty = $6.on_empty; + n->on_error = $6.on_error; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_exists_column_definition: + ColId Typename + EXISTS json_table_column_path_specification_clause_opt + json_exists_error_clause_opt + { + JsonTableColumn *n = makeNode(JsonTableColumn); + n->coltype = JTC_EXISTS; + n->name = $1; + n->typeName = $2; + n->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + n->wrapper = JSW_NONE; + n->omit_quotes = false; + n->pathspec = $4; + n->on_empty = NULL; + n->on_error = $5; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_error_behavior: + json_behavior_error + | json_behavior_empty + ; + +json_table_error_clause_opt: + json_table_error_behavior ON ERROR_P { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + +json_table_column_path_specification_clause_opt: + PATH Sconst { $$ = $2; } + | /* EMPTY */ %prec json_table_column { $$ = NULL; } + ; + +json_table_formatted_column_definition: + ColId Typename FORMAT json_representation + json_table_column_path_specification_clause_opt + json_wrapper_clause_opt + json_quotes_clause_opt + json_query_on_behavior_clause_opt + { + JsonTableColumn *n = makeNode(JsonTableColumn); + n->coltype = JTC_FORMATTED; + n->name = $1; + n->typeName = $2; + n->format = castNode(JsonFormat, $4); + n->pathspec = $5; + n->wrapper = $6; + if (n->wrapper != JSW_NONE && $7 != JS_QUOTES_UNSPEC) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("SQL/JSON QUOTES behavior must not be specified when WITH WRAPPER is used"), + parser_errposition(@7))); + n->omit_quotes = $7 == JS_QUOTES_OMIT; + n->on_empty = $8.on_empty; + n->on_error = $8.on_error; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_nested_columns: + NESTED path_opt Sconst json_table_columns_clause + { + JsonTableColumn *n = makeNode(JsonTableColumn); + n->coltype = JTC_NESTED; + n->pathspec = $3; + n->columns = $4; + n->location = @1; + $$ = (Node *) n; + } + ; + +path_opt: + PATH { } + | /* EMPTY */ { } + ; + json_returning_clause_opt: RETURNING Typename { @@ -16733,6 +16916,7 @@ unreserved_keyword: | MOVE | NAME_P | NAMES + | NESTED | NEW | NEXT | NFC @@ -16766,6 +16950,7 @@ unreserved_keyword: | PARTITION | PASSING | PASSWORD + | PATH | PLANS | POLICY | PRECEDING @@ -16929,6 +17114,7 @@ col_name_keyword: | JSON_QUERY | JSON_SCALAR | JSON_SERIALIZE + | JSON_TABLE | JSON_VALUE | LEAST | NATIONAL @@ -17296,6 +17482,7 @@ bare_label_keyword: | JSON_QUERY | JSON_SCALAR | JSON_SERIALIZE + | JSON_TABLE | JSON_VALUE | KEEP | KEY @@ -17335,6 +17522,7 @@ bare_label_keyword: | NATIONAL | NATURAL | NCHAR + | NESTED | NEW | NEXT | NFC @@ -17378,6 +17566,7 @@ bare_label_keyword: | PARTITION | PASSING | PASSWORD + | PATH | PLACING | PLANS | POLICY diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index d8b14ba7cd..dafde68b20 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -696,7 +696,9 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf) char **names; int colno; - /* Currently only XMLTABLE is supported */ + /* Currently only XMLTABLE and JSON_TABLE are supported */ + + tf->functype = TFT_XMLTABLE; constructName = "XMLTABLE"; docType = XMLOID; @@ -1100,13 +1102,17 @@ transformFromClauseItem(ParseState *pstate, Node *n, rtr->rtindex = nsitem->p_rtindex; return (Node *) rtr; } - else if (IsA(n, RangeTableFunc)) + else if (IsA(n, RangeTableFunc) || IsA(n, JsonTable)) { /* table function is like a plain relation */ RangeTblRef *rtr; ParseNamespaceItem *nsitem; - nsitem = transformRangeTableFunc(pstate, (RangeTableFunc *) n); + if (IsA(n, RangeTableFunc)) + nsitem = transformRangeTableFunc(pstate, (RangeTableFunc *) n); + else + nsitem = transformJsonTable(pstate, (JsonTable *) n); + *top_nsitem = nsitem; *namespace = list_make1(nsitem); rtr = makeNode(RangeTblRef); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 911f355460..b6a2482f23 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4093,7 +4093,7 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func) Node *pathspec; JsonFormatType format; - if (func->common->pathname) + if (func->common->pathname && func->op != JSON_TABLE_OP) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("JSON_TABLE path name is not allowed here"), @@ -4131,14 +4131,19 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func) transformJsonPassingArgs(pstate, format, func->common->passing, &jsexpr->passing_values, &jsexpr->passing_names); - if (func->op != JSON_EXISTS_OP) + if (func->op != JSON_EXISTS_OP && func->op != JSON_TABLE_OP) jsexpr->on_empty = transformJsonBehavior(pstate, func->on_empty, JSON_BEHAVIOR_NULL); - jsexpr->on_error = transformJsonBehavior(pstate, func->on_error, - func->op == JSON_EXISTS_OP ? - JSON_BEHAVIOR_FALSE : - JSON_BEHAVIOR_NULL); + if (func->op == JSON_EXISTS_OP) + jsexpr->on_error = transformJsonBehavior(pstate, func->on_error, + JSON_BEHAVIOR_FALSE); + else if (func->op == JSON_TABLE_OP) + jsexpr->on_error = transformJsonBehavior(pstate, func->on_error, + JSON_BEHAVIOR_EMPTY); + else + jsexpr->on_error = transformJsonBehavior(pstate, func->on_error, + JSON_BEHAVIOR_NULL); return jsexpr; } @@ -4439,6 +4444,21 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) jsexpr->result_coercion->expr = NULL; } break; + + case JSON_TABLE_OP: + jsexpr->returning = makeNode(JsonReturning); + jsexpr->returning->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + jsexpr->returning->typid = exprType(contextItemExpr); + jsexpr->returning->typmod = -1; + + if (jsexpr->returning->typid != JSONBOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("JSON_TABLE() is not yet implemented for json type"), + errhint("Try casting the argument to jsonb"), + parser_errposition(pstate, func->location))); + + break; } if (exprType(contextItemExpr) != JSONBOID) diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c new file mode 100644 index 0000000000..dd75a40bf6 --- /dev/null +++ b/src/backend/parser/parse_jsontable.c @@ -0,0 +1,466 @@ +/*------------------------------------------------------------------------- + * + * parse_jsontable.c + * parsing of JSON_TABLE + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/parser/parse_jsontable.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/pg_collation.h" +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/optimizer.h" +#include "parser/parse_clause.h" +#include "parser/parse_collate.h" +#include "parser/parse_expr.h" +#include "parser/parse_relation.h" +#include "parser/parse_type.h" +#include "utils/builtins.h" +#include "utils/json.h" +#include "utils/lsyscache.h" + +/* Context for JSON_TABLE transformation */ +typedef struct JsonTableContext +{ + ParseState *pstate; /* parsing state */ + JsonTable *table; /* untransformed node */ + TableFunc *tablefunc; /* transformed node */ + List *pathNames; /* list of all path and columns names */ + Oid contextItemTypid; /* type oid of context item (json/jsonb) */ +} JsonTableContext; + +static JsonTableParent * transformJsonTableColumns(JsonTableContext *cxt, + List *columns, + char *pathSpec, + int location); + +static Node * +makeStringConst(char *str, int location) +{ + A_Const *n = makeNode(A_Const); + + n->val.node.type = T_String; + n->val.sval.sval = str; + n->location = location; + + return (Node *)n; +} + +/* + * Transform JSON_TABLE column + * - regular column into JSON_VALUE() + * - FORMAT JSON column into JSON_QUERY() + * - EXISTS column into JSON_EXISTS() + */ +static Node * +transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr, + List *passingArgs, bool errorOnError) +{ + JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr); + JsonCommon *common = makeNode(JsonCommon); + JsonOutput *output = makeNode(JsonOutput); + JsonPathSpec pathspec; + JsonFormat *default_format; + + jfexpr->op = + jtc->coltype == JTC_REGULAR ? JSON_VALUE_OP : + jtc->coltype == JTC_EXISTS ? JSON_EXISTS_OP : JSON_QUERY_OP; + jfexpr->common = common; + jfexpr->output = output; + jfexpr->on_empty = jtc->on_empty; + jfexpr->on_error = jtc->on_error; + if (!jfexpr->on_error && errorOnError) + jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL); + jfexpr->omit_quotes = jtc->omit_quotes; + jfexpr->wrapper = jtc->wrapper; + jfexpr->location = jtc->location; + + output->typeName = jtc->typeName; + output->returning = makeNode(JsonReturning); + output->returning->format = jtc->format; + + default_format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + + common->pathname = NULL; + common->expr = makeJsonValueExpr((Expr *) contextItemExpr, default_format); + common->passing = passingArgs; + + if (jtc->pathspec) + pathspec = jtc->pathspec; + else + { + /* Construct default path as '$."column_name"' */ + StringInfoData path; + + initStringInfo(&path); + + appendStringInfoString(&path, "$."); + escape_json(&path, jtc->name); + + pathspec = path.data; + } + + common->pathspec = makeStringConst(pathspec, -1); + + return (Node *) jfexpr; +} + +static bool +isJsonTablePathNameDuplicate(JsonTableContext *cxt, const char *pathname) +{ + ListCell *lc; + + foreach(lc, cxt->pathNames) + { + if (!strcmp(pathname, (const char *) lfirst(lc))) + return true; + } + + return false; +} + +/* Register the column name in the path name list. */ +static void +registerJsonTableColumn(JsonTableContext *cxt, char *colname) +{ + if (isJsonTablePathNameDuplicate(cxt, colname)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_ALIAS), + errmsg("duplicate JSON_TABLE column name: %s", colname), + errhint("JSON_TABLE column names must be distinct from one another"))); + + cxt->pathNames = lappend(cxt->pathNames, colname); +} + +/* Recursively register all nested column names in the path name list. */ +static void +registerAllJsonTableColumns(JsonTableContext *cxt, List *columns) +{ + ListCell *lc; + + foreach(lc, columns) + { + JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc)); + + if (jtc->coltype == JTC_NESTED) + registerAllJsonTableColumns(cxt, jtc->columns); + else + registerJsonTableColumn(cxt, jtc->name); + } +} + +static Node * +transformNestedJsonTableColumn(JsonTableContext *cxt, JsonTableColumn *jtc) +{ + JsonTableParent *node; + + node = transformJsonTableColumns(cxt, jtc->columns, jtc->pathspec, + jtc->location); + + return (Node *) node; +} + +static Node * +makeJsonTableSiblingJoin(Node *lnode, Node *rnode) +{ + JsonTableSibling *join = makeNode(JsonTableSibling); + + join->larg = lnode; + join->rarg = rnode; + + return (Node *) join; +} + +/* + * Recursively transform child (nested) JSON_TABLE columns. + * + * Child columns are transformed into a binary tree of union-joined + * JsonTableSiblings. + */ +static Node * +transformJsonTableChildColumns(JsonTableContext *cxt, List *columns) +{ + Node *res = NULL; + ListCell *lc; + + /* transform all nested columns into union join */ + foreach(lc, columns) + { + JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc)); + Node *node; + + if (jtc->coltype != JTC_NESTED) + continue; + + node = transformNestedJsonTableColumn(cxt, jtc); + + /* join transformed node with previous sibling nodes */ + res = res ? makeJsonTableSiblingJoin(res, node) : node; + } + + return res; +} + +/* Check whether type is json/jsonb, array, or record. */ +static bool +typeIsComposite(Oid typid) +{ + char typtype; + + if (typid == JSONOID || + typid == JSONBOID || + typid == RECORDOID || + type_is_array(typid)) + return true; + + typtype = get_typtype(typid); + + if (typtype == TYPTYPE_COMPOSITE) + return true; + + if (typtype == TYPTYPE_DOMAIN) + return typeIsComposite(getBaseType(typid)); + + return false; +} + +/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */ +static void +appendJsonTableColumns(JsonTableContext *cxt, List *columns) +{ + ListCell *col; + ParseState *pstate = cxt->pstate; + JsonTable *jt = cxt->table; + TableFunc *tf = cxt->tablefunc; + bool errorOnError = jt->on_error && + jt->on_error->btype == JSON_BEHAVIOR_ERROR; + + foreach(col, columns) + { + JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col)); + Oid typid; + int32 typmod; + Node *colexpr; + + if (rawc->name) + { + /* make sure column names are unique */ + ListCell *colname; + + foreach(colname, tf->colnames) + if (!strcmp((const char *) colname, rawc->name)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("column name \"%s\" is not unique", + rawc->name), + parser_errposition(pstate, rawc->location))); + + tf->colnames = lappend(tf->colnames, + makeString(pstrdup(rawc->name))); + } + + /* + * Determine the type and typmod for the new column. FOR + * ORDINALITY columns are INTEGER by standard; the others are + * user-specified. + */ + switch (rawc->coltype) + { + case JTC_FOR_ORDINALITY: + colexpr = NULL; + typid = INT4OID; + typmod = -1; + break; + + case JTC_REGULAR: + typenameTypeIdAndMod(pstate, rawc->typeName, &typid, &typmod); + + /* + * Use implicit FORMAT JSON for composite types (arrays and + * records) + */ + if (typeIsComposite(typid)) + rawc->coltype = JTC_FORMATTED; + else if (rawc->wrapper != JSW_NONE) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use WITH WRAPPER clause with scalar columns"), + parser_errposition(pstate, rawc->location))); + else if (rawc->omit_quotes) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use OMIT QUOTES clause with scalar columns"), + parser_errposition(pstate, rawc->location))); + + /* FALLTHROUGH */ + case JTC_EXISTS: + case JTC_FORMATTED: + { + Node *je; + CaseTestExpr *param = makeNode(CaseTestExpr); + + param->collation = InvalidOid; + param->typeId = cxt->contextItemTypid; + param->typeMod = -1; + + je = transformJsonTableColumn(rawc, (Node *) param, + NIL, errorOnError); + + colexpr = transformExpr(pstate, je, EXPR_KIND_FROM_FUNCTION); + assign_expr_collations(pstate, colexpr); + + typid = exprType(colexpr); + typmod = exprTypmod(colexpr); + break; + } + + case JTC_NESTED: + continue; + + default: + elog(ERROR, "unknown JSON_TABLE column type: %d", rawc->coltype); + break; + } + + tf->coltypes = lappend_oid(tf->coltypes, typid); + tf->coltypmods = lappend_int(tf->coltypmods, typmod); + tf->colcollations = lappend_oid(tf->colcollations, + type_is_collatable(typid) + ? DEFAULT_COLLATION_OID + : InvalidOid); + tf->colvalexprs = lappend(tf->colvalexprs, colexpr); + } +} + +/* + * Create transformed JSON_TABLE parent plan node by appending all non-nested + * columns to the TableFunc node and remembering their indices in the + * colvalexprs list. + */ +static JsonTableParent * +makeParentJsonTableNode(JsonTableContext *cxt, char *pathSpec, List *columns) +{ + JsonTableParent *node = makeNode(JsonTableParent); + + node->path = makeConst(JSONPATHOID, -1, InvalidOid, -1, + DirectFunctionCall1(jsonpath_in, + CStringGetDatum(pathSpec)), + false, false); + + /* save start of column range */ + node->colMin = list_length(cxt->tablefunc->colvalexprs); + + appendJsonTableColumns(cxt, columns); + + /* save end of column range */ + node->colMax = list_length(cxt->tablefunc->colvalexprs) - 1; + + node->errorOnError = + cxt->table->on_error && + cxt->table->on_error->btype == JSON_BEHAVIOR_ERROR; + + return node; +} + +static JsonTableParent * +transformJsonTableColumns(JsonTableContext *cxt, List *columns, char *pathSpec, + int location) +{ + JsonTableParent *node; + + /* transform only non-nested columns */ + node = makeParentJsonTableNode(cxt, pathSpec, columns); + + /* transform recursively nested columns */ + node->child = transformJsonTableChildColumns(cxt, columns); + + return node; +} + +/* + * transformJsonTable - + * Transform a raw JsonTable into TableFunc. + * + * Transform the document-generating expression, the row-generating expression, + * the column-generating expressions, and the default value expressions. + */ +ParseNamespaceItem * +transformJsonTable(ParseState *pstate, JsonTable *jt) +{ + JsonTableContext cxt; + TableFunc *tf = makeNode(TableFunc); + JsonFuncExpr *jfe = makeNode(JsonFuncExpr); + JsonCommon *jscommon; + char *rootPath; + bool is_lateral; + + cxt.pstate = pstate; + cxt.table = jt; + cxt.tablefunc = tf; + cxt.pathNames = NIL; + + registerAllJsonTableColumns(&cxt, jt->columns); + + jscommon = copyObject(jt->common); + jscommon->pathspec = makeStringConst(pstrdup("$"), -1); + + jfe->op = JSON_TABLE_OP; + jfe->common = jscommon; + jfe->on_error = jt->on_error; + jfe->location = jt->common->location; + + /* + * We make lateral_only names of this level visible, whether or not the + * RangeTableFunc is explicitly marked LATERAL. This is needed for SQL + * spec compliance and seems useful on convenience grounds for all + * functions in FROM. + * + * (LATERAL can't nest within a single pstate level, so we don't need + * save/restore logic here.) + */ + Assert(!pstate->p_lateral_active); + pstate->p_lateral_active = true; + + tf->functype = TFT_JSON_TABLE; + tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION); + + cxt.contextItemTypid = exprType(tf->docexpr); + + if (!IsA(jt->common->pathspec, A_Const) || + castNode(A_Const, jt->common->pathspec)->val.node.type != T_String) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only string constants supported in JSON_TABLE path specification"), + parser_errposition(pstate, + exprLocation(jt->common->pathspec)))); + + rootPath = castNode(A_Const, jt->common->pathspec)->val.sval.sval; + + tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns, rootPath, + jt->common->location); + + tf->ordinalitycol = -1; /* undefine ordinality column number */ + tf->location = jt->location; + + pstate->p_lateral_active = false; + + /* + * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if + * there are any lateral cross-references in it. + */ + is_lateral = jt->lateral || contain_vars_of_level((Node *) tf, 0); + + return addRangeTableEntryForTableFunc(pstate, + tf, jt->alias, is_lateral, true); +} diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 7efa5f15d7..5448cb01fa 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -1989,7 +1989,8 @@ addRangeTableEntryForTableFunc(ParseState *pstate, bool inFromCl) { RangeTblEntry *rte = makeNode(RangeTblEntry); - char *refname = alias ? alias->aliasname : pstrdup("xmltable"); + char *refname = alias ? alias->aliasname : + pstrdup(tf->functype == TFT_XMLTABLE ? "xmltable" : "json_table"); Alias *eref; int numaliases; diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 31c576cfec..2a1d44b813 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1993,6 +1993,9 @@ FigureColnameInternal(Node *node, char **name) case JSON_EXISTS_OP: *name = "json_exists"; return 2; + case JSON_TABLE_OP: + *name = "json_table"; + return 2; } break; default: |