Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/commands/explain.c21
-rw-r--r--src/backend/executor/execExpr.c11
-rw-r--r--src/backend/executor/execExprInterp.c2
-rw-r--r--src/backend/executor/nodeTableFuncscan.c41
-rw-r--r--src/backend/nodes/makefuncs.c53
-rw-r--r--src/backend/nodes/nodeFuncs.c36
-rw-r--r--src/backend/parser/Makefile1
-rw-r--r--src/backend/parser/gram.y173
-rw-r--r--src/backend/parser/meson.build1
-rw-r--r--src/backend/parser/parse_clause.c14
-rw-r--r--src/backend/parser/parse_expr.c53
-rw-r--r--src/backend/parser/parse_jsontable.c421
-rw-r--r--src/backend/parser/parse_relation.c6
-rw-r--r--src/backend/parser/parse_target.c1
-rw-r--r--src/backend/utils/adt/jsonpath_exec.c372
-rw-r--r--src/backend/utils/adt/ruleutils.c185
16 files changed, 1341 insertions, 50 deletions
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 895d18ebd59..2c5d980f729 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4087,9 +4087,24 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
}
break;
case T_TableFuncScan:
- Assert(rte->rtekind == RTE_TABLEFUNC);
- objectname = "xmltable";
- objecttag = "Table Function Name";
+ {
+ TableFunc *tablefunc = ((TableFuncScan *) plan)->tablefunc;
+
+ Assert(rte->rtekind == RTE_TABLEFUNC);
+ switch (tablefunc->functype)
+ {
+ case TFT_XMLTABLE:
+ objectname = "xmltable";
+ break;
+ case TFT_JSON_TABLE:
+ objectname = "json_table";
+ break;
+ default:
+ elog(ERROR, "invalid TableFunc type %d",
+ (int) tablefunc->functype);
+ }
+ objecttag = "Table Function Name";
+ }
break;
case T_ValuesScan:
Assert(rte->rtekind == RTE_VALUES);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index bc5feb0115a..79087cc6d63 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2436,7 +2436,16 @@ ExecInitExprRec(Expr *node, ExprState *state,
{
JsonExpr *jsexpr = castNode(JsonExpr, node);
- ExecInitJsonExpr(jsexpr, state, resv, resnull, &scratch);
+ /*
+ * No need to initialize a full JsonExprState For
+ * JSON_TABLE(), because the upstream caller tfuncFetchRows()
+ * is only interested in the value of formatted_expr.
+ */
+ if (jsexpr->op == JSON_TABLE_OP)
+ ExecInitExprRec((Expr *) jsexpr->formatted_expr, state,
+ resv, resnull);
+ else
+ ExecInitJsonExpr(jsexpr, state, resv, resnull, &scratch);
break;
}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 24a3990a30a..41af28cb1eb 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4370,6 +4370,8 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op,
break;
}
+ /* JSON_TABLE_OP can't happen here */
+
default:
elog(ERROR, "unrecognized SQL/JSON expression op %d",
(int) jsexpr->op);
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
index 72ca34a2287..f483221bb8e 100644
--- a/src/backend/executor/nodeTableFuncscan.c
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -28,6 +28,7 @@
#include "miscadmin.h"
#include "nodes/execnodes.h"
#include "utils/builtins.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/xml.h"
@@ -161,8 +162,9 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
scanstate->ss.ps.qual =
ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps);
- /* Only XMLTABLE is supported currently */
- scanstate->routine = &XmlTableRoutine;
+ /* Only XMLTABLE and JSON_TABLE are supported currently */
+ scanstate->routine =
+ tf->functype == TFT_XMLTABLE ? &XmlTableRoutine : &JsonbTableRoutine;
scanstate->perTableCxt =
AllocSetContextCreate(CurrentMemoryContext,
@@ -182,6 +184,10 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
ExecInitExprList(tf->colexprs, (PlanState *) scanstate);
scanstate->coldefexprs =
ExecInitExprList(tf->coldefexprs, (PlanState *) scanstate);
+ scanstate->colvalexprs =
+ ExecInitExprList(tf->colvalexprs, (PlanState *) scanstate);
+ scanstate->passingvalexprs =
+ ExecInitExprList(tf->passingvalexprs, (PlanState *) scanstate);
scanstate->notnulls = tf->notnulls;
@@ -274,11 +280,12 @@ tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext)
/*
* Each call to fetch a new set of rows - of which there may be very many
- * if XMLTABLE is being used in a lateral join - will allocate a possibly
- * substantial amount of memory, so we cannot use the per-query context
- * here. perTableCxt now serves the same function as "argcontext" does in
- * FunctionScan - a place to store per-one-call (i.e. one result table)
- * lifetime data (as opposed to per-query or per-result-tuple).
+ * if XMLTABLE or JSON_TABLE is being used in a lateral join - will
+ * allocate a possibly substantial amount of memory, so we cannot use the
+ * per-query context here. perTableCxt now serves the same function as
+ * "argcontext" does in FunctionScan - a place to store per-one-call (i.e.
+ * one result table) lifetime data (as opposed to per-query or
+ * per-result-tuple).
*/
MemoryContextSwitchTo(tstate->perTableCxt);
@@ -369,14 +376,20 @@ tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc)
routine->SetNamespace(tstate, ns_name, ns_uri);
}
- /* Install the row filter expression into the table builder context */
- value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
- if (isnull)
- ereport(ERROR,
- (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("row filter expression must not be null")));
+ /*
+ * Install the row filter expression, if any, into the table builder
+ * context.
+ */
+ if (routine->SetRowFilter)
+ {
+ value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("row filter expression must not be null")));
- routine->SetRowFilter(tstate, TextDatumGetCString(value));
+ routine->SetRowFilter(tstate, TextDatumGetCString(value));
+ }
/*
* Install the column filter expressions into the table builder context.
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index b13cfa4201d..61ac172a857 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -538,6 +538,22 @@ makeFuncExpr(Oid funcid, Oid rettype, List *args,
}
/*
+ * makeStringConst -
+ * build a A_Const node of type T_String for given string
+ */
+Node *
+makeStringConst(char *str, int location)
+{
+ A_Const *n = makeNode(A_Const);
+
+ n->val.sval.type = T_String;
+ n->val.sval.sval = str;
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
* makeDefElem -
* build a DefElem node
*
@@ -905,3 +921,40 @@ makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType item_type,
return (Node *) n;
}
+
+/*
+ * makeJsonTablePathSpec -
+ * Make JsonTablePathSpec node from given path string and name (if any)
+ */
+JsonTablePathSpec *
+makeJsonTablePathSpec(char *string, char *name, int string_location,
+ int name_location)
+{
+ JsonTablePathSpec *pathspec = makeNode(JsonTablePathSpec);
+
+ Assert(string != NULL);
+ pathspec->string = makeStringConst(string, string_location);
+ if (name != NULL)
+ pathspec->name = pstrdup(name);
+
+ pathspec->name_location = name_location;
+ pathspec->location = string_location;
+
+ return pathspec;
+}
+
+/*
+ * makeJsonTablePath -
+ * Make JsonTablePath node for given path string and name
+ */
+JsonTablePath *
+makeJsonTablePath(Const *pathvalue, char *pathname)
+{
+ JsonTablePath *path = makeNode(JsonTablePath);
+
+ Assert(IsA(pathvalue, Const));
+ path->value = pathvalue;
+ path->name = pathname;
+
+ return path;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7d37226bd9c..fcd0d834b20 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2650,6 +2650,10 @@ expression_tree_walker_impl(Node *node,
return true;
if (WALK(tf->coldefexprs))
return true;
+ if (WALK(tf->colvalexprs))
+ return true;
+ if (WALK(tf->passingvalexprs))
+ return true;
}
break;
default:
@@ -3702,6 +3706,8 @@ expression_tree_mutator_impl(Node *node,
MUTATE(newnode->rowexpr, tf->rowexpr, Node *);
MUTATE(newnode->colexprs, tf->colexprs, List *);
MUTATE(newnode->coldefexprs, tf->coldefexprs, List *);
+ MUTATE(newnode->colvalexprs, tf->colvalexprs, List *);
+ MUTATE(newnode->passingvalexprs, tf->passingvalexprs, List *);
return (Node *) newnode;
}
break;
@@ -4127,6 +4133,36 @@ raw_expression_tree_walker_impl(Node *node,
return true;
}
break;
+ case T_JsonTable:
+ {
+ JsonTable *jt = (JsonTable *) node;
+
+ if (WALK(jt->context_item))
+ return true;
+ if (WALK(jt->pathspec))
+ return true;
+ if (WALK(jt->passing))
+ return true;
+ if (WALK(jt->columns))
+ return true;
+ if (WALK(jt->on_error))
+ return true;
+ }
+ break;
+ case T_JsonTableColumn:
+ {
+ JsonTableColumn *jtc = (JsonTableColumn *) node;
+
+ if (WALK(jtc->typeName))
+ return true;
+ if (WALK(jtc->on_empty))
+ return true;
+ if (WALK(jtc->on_error))
+ return true;
+ }
+ break;
+ case T_JsonTablePathSpec:
+ return WALK(((JsonTablePathSpec *) node)->string);
case T_NullTest:
return WALK(((NullTest *) node)->arg);
case T_BooleanTest:
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index 401c16686c6..3162a01f302 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 f1af6147c37..6ea68722e3d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -170,7 +170,6 @@ static void updateRawStmtEnd(RawStmt *rs, int end_location);
static Node *makeColumnRef(char *colname, List *indirection,
int location, core_yyscan_t yyscanner);
static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
-static Node *makeStringConst(char *str, int location);
static Node *makeStringConstCast(char *str, int location, TypeName *typename);
static Node *makeIntConst(int val, int location);
static Node *makeFloatConst(char *str, int location);
@@ -659,12 +658,17 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_argument
json_behavior
json_on_error_clause_opt
+ json_table
+ json_table_column_definition
+ json_table_column_path_clause_opt
%type <list> json_name_and_value_list
json_value_expr_list
json_array_aggregate_order_by_clause_opt
json_arguments
json_behavior_clause_opt
json_passing_clause_opt
+ json_table_column_definition_list
+%type <str> json_table_path_name_opt
%type <ival> json_behavior_type
json_predicate_type_constraint
json_quotes_clause_opt
@@ -737,7 +741,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
KEEP KEY KEYS
@@ -748,8 +752,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION 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 NEW NEXT NFC NFD NFKC NFKD NO
+ NONE NORMALIZE NORMALIZED
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
NULLS_P NUMERIC
@@ -757,8 +761,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
- PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD
- PERIOD PLACING PLANS POLICY
+ PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PATH
+ PERIOD PLACING PLAN PLANS POLICY
+
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
@@ -877,9 +882,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* json_predicate_type_constraint and json_key_uniqueness_constraint_opt
* productions (see comments there).
*/
-%nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */
+%nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */
%nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
- SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT
+ SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
%left '*' '/' '%'
@@ -13493,6 +13498,21 @@ 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;
+ }
;
@@ -14060,6 +14080,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:
@@ -14088,6 +14110,123 @@ xml_namespace_el:
}
;
+json_table:
+ JSON_TABLE '('
+ json_value_expr ',' a_expr json_table_path_name_opt
+ json_passing_clause_opt
+ COLUMNS '(' json_table_column_definition_list ')'
+ json_on_error_clause_opt
+ ')'
+ {
+ JsonTable *n = makeNode(JsonTable);
+ char *pathstring;
+
+ n->context_item = (JsonValueExpr *) $3;
+ if (!IsA($5, A_Const) ||
+ castNode(A_Const, $5)->val.node.type != T_String)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only string constants are supported in JSON_TABLE path specification"),
+ parser_errposition(@5));
+ pathstring = castNode(A_Const, $5)->val.sval.sval;
+ n->pathspec = makeJsonTablePathSpec(pathstring, $6, @5, @6);
+ n->passing = $7;
+ n->columns = $10;
+ n->on_error = (JsonBehavior *) $12;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_table_path_name_opt:
+ AS name { $$ = $2; }
+ | /* empty */ { $$ = NULL; }
+ ;
+
+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:
+ ColId FOR ORDINALITY
+ {
+ JsonTableColumn *n = makeNode(JsonTableColumn);
+
+ n->coltype = JTC_FOR_ORDINALITY;
+ n->name = $1;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | ColId Typename
+ json_table_column_path_clause_opt
+ json_wrapper_behavior
+ json_quotes_clause_opt
+ json_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->pathspec = (JsonTablePathSpec *) $3;
+ n->wrapper = $4;
+ n->quotes = $5;
+ n->on_empty = (JsonBehavior *) linitial($6);
+ n->on_error = (JsonBehavior *) lsecond($6);
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | ColId Typename json_format_clause
+ json_table_column_path_clause_opt
+ json_wrapper_behavior
+ json_quotes_clause_opt
+ json_behavior_clause_opt
+ {
+ JsonTableColumn *n = makeNode(JsonTableColumn);
+
+ n->coltype = JTC_FORMATTED;
+ n->name = $1;
+ n->typeName = $2;
+ n->format = (JsonFormat *) $3;
+ n->pathspec = (JsonTablePathSpec *) $4;
+ n->wrapper = $5;
+ n->quotes = $6;
+ n->on_empty = (JsonBehavior *) linitial($7);
+ n->on_error = (JsonBehavior *) lsecond($7);
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | ColId Typename
+ EXISTS json_table_column_path_clause_opt
+ json_behavior_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->quotes = JS_QUOTES_UNSPEC;
+ n->pathspec = (JsonTablePathSpec *) $4;
+ n->on_empty = (JsonBehavior *) linitial($5);
+ n->on_error = (JsonBehavior *) lsecond($5);
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+json_table_column_path_clause_opt:
+ PATH Sconst
+ { $$ = (Node *) makeJsonTablePathSpec($2, NULL, @2, -1); }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
/*****************************************************************************
*
* Type syntax
@@ -17531,7 +17670,9 @@ unreserved_keyword:
| PARTITION
| PASSING
| PASSWORD
+ | PATH
| PERIOD
+ | PLAN
| PLANS
| POLICY
| PRECEDING
@@ -17698,6 +17839,7 @@ col_name_keyword:
| JSON_QUERY
| JSON_SCALAR
| JSON_SERIALIZE
+ | JSON_TABLE
| JSON_VALUE
| LEAST
| MERGE_ACTION
@@ -18067,6 +18209,7 @@ bare_label_keyword:
| JSON_QUERY
| JSON_SCALAR
| JSON_SERIALIZE
+ | JSON_TABLE
| JSON_VALUE
| KEEP
| KEY
@@ -18151,8 +18294,10 @@ bare_label_keyword:
| PARTITION
| PASSING
| PASSWORD
+ | PATH
| PERIOD
| PLACING
+ | PLAN
| PLANS
| POLICY
| POSITION
@@ -18423,18 +18568,6 @@ makeTypeCast(Node *arg, TypeName *typename, int location)
}
static Node *
-makeStringConst(char *str, int location)
-{
- A_Const *n = makeNode(A_Const);
-
- n->val.sval.type = T_String;
- n->val.sval.sval = str;
- n->location = location;
-
- return (Node *) n;
-}
-
-static Node *
makeStringConstCast(char *str, int location, TypeName *typename)
{
Node *s = makeStringConst(str, location);
diff --git a/src/backend/parser/meson.build b/src/backend/parser/meson.build
index 8e8295640bc..573d70b3d1b 100644
--- a/src/backend/parser/meson.build
+++ b/src/backend/parser/meson.build
@@ -10,6 +10,7 @@ backend_sources += files(
'parse_enr.c',
'parse_expr.c',
'parse_func.c',
+ 'parse_jsontable.c',
'parse_merge.c',
'parse_node.c',
'parse_oper.c',
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index d2ac86777c2..4fc5fc87e07 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -695,7 +695,11 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
char **names;
int colno;
- /* Currently only XMLTABLE is supported */
+ /*
+ * Currently we only support XMLTABLE here. See transformJsonTable() for
+ * JSON_TABLE support.
+ */
+ tf->functype = TFT_XMLTABLE;
constructName = "XMLTABLE";
docType = XMLOID;
@@ -1102,13 +1106,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, JsonTable))
+ nsitem = transformJsonTable(pstate, (JsonTable *) n);
+ else
+ nsitem = transformRangeTableFunc(pstate, (RangeTableFunc *) 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 73c83cea4ac..81e66020858 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4245,7 +4245,8 @@ transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr *expr)
}
/*
- * Transform JSON_VALUE, JSON_QUERY, JSON_EXISTS functions into a JsonExpr node.
+ * Transform JSON_VALUE, JSON_QUERY, JSON_EXISTS, JSON_TABLE functions into
+ * a JsonExpr node.
*/
static Node *
transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
@@ -4269,6 +4270,9 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
func_name = "JSON_VALUE";
default_format = JS_FORMAT_DEFAULT;
break;
+ case JSON_TABLE_OP:
+ func_name = "JSON_TABLE";
+ break;
default:
elog(ERROR, "invalid JsonFuncExpr op %d", (int) func->op);
break;
@@ -4350,6 +4354,42 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->returning->typmod = -1;
}
+ /* JSON_TABLE() COLUMNS can specify a non-boolean type. */
+ if (jsexpr->returning->typid != BOOLOID)
+ {
+ Node *coercion_expr;
+ CaseTestExpr *placeholder = makeNode(CaseTestExpr);
+ int location = exprLocation((Node *) jsexpr);
+
+ /*
+ * We abuse CaseTestExpr here as placeholder to pass the
+ * result of evaluating JSON_EXISTS to the coercion
+ * expression.
+ */
+ placeholder->typeId = BOOLOID;
+ placeholder->typeMod = -1;
+ placeholder->collation = InvalidOid;
+
+ coercion_expr =
+ coerce_to_target_type(pstate, (Node *) placeholder, BOOLOID,
+ jsexpr->returning->typid,
+ jsexpr->returning->typmod,
+ COERCION_EXPLICIT,
+ COERCE_IMPLICIT_CAST,
+ location);
+
+ if (coercion_expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(BOOLOID),
+ format_type_be(jsexpr->returning->typid)),
+ parser_coercion_errposition(pstate, location, (Node *) jsexpr)));
+
+ if (coercion_expr != (Node *) placeholder)
+ jsexpr->coercion_expr = coercion_expr;
+ }
+
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
JSON_BEHAVIOR_FALSE,
jsexpr->returning);
@@ -4414,6 +4454,17 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->returning);
break;
+ case JSON_TABLE_OP:
+ if (!OidIsValid(jsexpr->returning->typid))
+ {
+ jsexpr->returning->typid = exprType(jsexpr->formatted_expr);
+ jsexpr->returning->typmod = -1;
+ }
+ jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
+ JSON_BEHAVIOR_EMPTY,
+ jsexpr->returning);
+ break;
+
default:
elog(ERROR, "invalid JsonFuncExpr op %d", (int) func->op);
break;
diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c
new file mode 100644
index 00000000000..060f62170e8
--- /dev/null
+++ b/src/backend/parser/parse_jsontable.c
@@ -0,0 +1,421 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_jsontable.c
+ * parsing of JSON_TABLE
+ *
+ * Portions Copyright (c) 1996-2024, 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 transformJsonTableColumns() */
+typedef struct JsonTableParseContext
+{
+ ParseState *pstate;
+ JsonTable *jt;
+ TableFunc *tf;
+ List *pathNames; /* list of all path and columns names */
+ int pathNameId; /* path name id counter */
+} JsonTableParseContext;
+
+static JsonTablePlan *transformJsonTableColumns(JsonTableParseContext *cxt,
+ List *columns,
+ List *passingArgs,
+ JsonTablePathSpec *pathspec);
+static JsonFuncExpr *transformJsonTableColumn(JsonTableColumn *jtc,
+ Node *contextItemExpr,
+ List *passingArgs);
+static bool isCompositeType(Oid typid);
+static JsonTablePlan *makeJsonTablePathScan(JsonTablePathSpec *pathspec,
+ bool errorOnError);
+static void CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
+ List *columns);
+static bool LookupPathOrColumnName(JsonTableParseContext *cxt, char *name);
+static char *generateJsonTablePathName(JsonTableParseContext *cxt);
+
+/*
+ * transformJsonTable -
+ * Transform a raw JsonTable into TableFunc
+ *
+ * Mainly, this transforms the JSON_TABLE() document-generating expression
+ * (jt->context_item) and the column-generating expressions (jt->columns) to
+ * populate TableFunc.docexpr and TableFunc.colvalexprs, respectively. Also,
+ * the PASSING values (jt->passing) are transformed and added into
+ * TableFunc.passvalexprs.
+ */
+ParseNamespaceItem *
+transformJsonTable(ParseState *pstate, JsonTable *jt)
+{
+ TableFunc *tf;
+ JsonFuncExpr *jfe;
+ JsonExpr *je;
+ JsonTablePathSpec *rootPathSpec = jt->pathspec;
+ bool is_lateral;
+ JsonTableParseContext cxt = {pstate};
+
+ Assert(IsA(rootPathSpec->string, A_Const) &&
+ castNode(A_Const, rootPathSpec->string)->val.node.type == T_String);
+
+ if (jt->on_error &&
+ jt->on_error->btype != JSON_BEHAVIOR_ERROR &&
+ jt->on_error->btype != JSON_BEHAVIOR_EMPTY &&
+ jt->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid ON ERROR behavior"),
+ errdetail("Only EMPTY or ERROR is allowed in the top-level ON ERROR clause."),
+ parser_errposition(pstate, jt->on_error->location));
+
+ cxt.pathNameId = 0;
+ if (rootPathSpec->name == NULL)
+ rootPathSpec->name = generateJsonTablePathName(&cxt);
+ cxt.pathNames = list_make1(rootPathSpec->name);
+ CheckDuplicateColumnOrPathNames(&cxt, jt->columns);
+
+ /*
+ * 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 = makeNode(TableFunc);
+ tf->functype = TFT_JSON_TABLE;
+
+ /*
+ * Transform JsonFuncExpr representing the top JSON_TABLE context_item and
+ * pathspec into a dummy JSON_TABLE_OP JsonExpr.
+ */
+ jfe = makeNode(JsonFuncExpr);
+ jfe->op = JSON_TABLE_OP;
+ jfe->context_item = jt->context_item;
+ jfe->pathspec = (Node *) rootPathSpec->string;
+ jfe->passing = jt->passing;
+ jfe->on_empty = NULL;
+ jfe->on_error = jt->on_error;
+ jfe->location = jt->location;
+ tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION);
+
+ /*
+ * Create a JsonTablePlan that will generate row pattern that becomes
+ * source data for JSON path expressions in jt->columns. This also adds
+ * the columns' transformed JsonExpr nodes into tf->colvalexprs.
+ */
+ cxt.jt = jt;
+ cxt.tf = tf;
+ tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns,
+ jt->passing,
+ rootPathSpec);
+
+ /*
+ * Copy the transformed PASSING arguments into the TableFunc node, because
+ * they are evaluated separately from the JsonExpr that we just put in
+ * TableFunc.docexpr. JsonExpr.passing_values is still kept around for
+ * get_json_table().
+ */
+ je = (JsonExpr *) tf->docexpr;
+ tf->passingvalexprs = copyObject(je->passing_values);
+
+ 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);
+}
+
+/*
+ * Check if a column / path name is duplicated in the given shared list of
+ * names.
+ */
+static void
+CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
+ List *columns)
+{
+ ListCell *lc1;
+
+ foreach(lc1, columns)
+ {
+ JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1));
+
+ if (LookupPathOrColumnName(cxt, jtc->name))
+ ereport(ERROR,
+ errcode(ERRCODE_DUPLICATE_ALIAS),
+ errmsg("duplicate JSON_TABLE column or path name: %s",
+ jtc->name),
+ parser_errposition(cxt->pstate, jtc->location));
+ cxt->pathNames = lappend(cxt->pathNames, jtc->name);
+ }
+}
+
+/*
+ * Lookup a column/path name in the given name list, returning true if already
+ * there.
+ */
+static bool
+LookupPathOrColumnName(JsonTableParseContext *cxt, char *name)
+{
+ ListCell *lc;
+
+ foreach(lc, cxt->pathNames)
+ {
+ if (strcmp(name, (const char *) lfirst(lc)) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+/* Generate a new unique JSON_TABLE path name. */
+static char *
+generateJsonTablePathName(JsonTableParseContext *cxt)
+{
+ char namebuf[32];
+ char *name = namebuf;
+
+ snprintf(namebuf, sizeof(namebuf), "json_table_path_%d",
+ cxt->pathNameId++);
+
+ name = pstrdup(name);
+ cxt->pathNames = lappend(cxt->pathNames, name);
+
+ return name;
+}
+
+/*
+ * Create a JsonTablePlan that will supply the source row for 'columns'
+ * using 'pathspec' and append the columns' transformed JsonExpr nodes and
+ * their type/collation information to cxt->tf.
+ */
+static JsonTablePlan *
+transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
+ List *passingArgs,
+ JsonTablePathSpec *pathspec)
+{
+ ParseState *pstate = cxt->pstate;
+ JsonTable *jt = cxt->jt;
+ TableFunc *tf = cxt->tf;
+ ListCell *col;
+ bool ordinality_found = false;
+ bool errorOnError = jt->on_error &&
+ jt->on_error->btype == JSON_BEHAVIOR_ERROR;
+ Oid contextItemTypid = exprType(tf->docexpr);
+
+ foreach(col, columns)
+ {
+ JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
+ Oid typid;
+ int32 typmod;
+ Oid typcoll = InvalidOid;
+ Node *colexpr;
+
+ Assert(rawc->name);
+ 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:
+ if (ordinality_found)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot use more than one FOR ORDINALITY column"),
+ parser_errposition(pstate, rawc->location)));
+ ordinality_found = true;
+ colexpr = NULL;
+ typid = INT4OID;
+ typmod = -1;
+ break;
+
+ case JTC_REGULAR:
+ typenameTypeIdAndMod(pstate, rawc->typeName, &typid, &typmod);
+
+ /*
+ * Use JTC_FORMATTED so as to use JSON_QUERY for this column
+ * if the specified type is one that's better handled using
+ * JSON_QUERY() or if non-default WRAPPER or QUOTES behavior
+ * is specified.
+ */
+ if (isCompositeType(typid) ||
+ rawc->quotes != JS_QUOTES_UNSPEC ||
+ rawc->wrapper != JSW_UNSPEC)
+ rawc->coltype = JTC_FORMATTED;
+
+ /* FALLTHROUGH */
+ case JTC_FORMATTED:
+ case JTC_EXISTS:
+ {
+ JsonFuncExpr *jfe;
+ CaseTestExpr *param = makeNode(CaseTestExpr);
+
+ param->collation = InvalidOid;
+ param->typeId = contextItemTypid;
+ param->typeMod = -1;
+
+ jfe = transformJsonTableColumn(rawc, (Node *) param,
+ passingArgs);
+
+ colexpr = transformExpr(pstate, (Node *) jfe,
+ EXPR_KIND_FROM_FUNCTION);
+ assign_expr_collations(pstate, colexpr);
+
+ typid = exprType(colexpr);
+ typmod = exprTypmod(colexpr);
+ typcoll = exprCollation(colexpr);
+ break;
+ }
+
+ default:
+ elog(ERROR, "unknown JSON_TABLE column type: %d", (int) rawc->coltype);
+ break;
+ }
+
+ tf->coltypes = lappend_oid(tf->coltypes, typid);
+ tf->coltypmods = lappend_int(tf->coltypmods, typmod);
+ tf->colcollations = lappend_oid(tf->colcollations, typcoll);
+ tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
+ }
+
+ return makeJsonTablePathScan(pathspec, errorOnError);
+}
+
+/*
+ * Check if the type is "composite" for the purpose of checking whether to use
+ * JSON_VALUE() or JSON_QUERY() for a given JsonTableColumn.
+ */
+static bool
+isCompositeType(Oid typid)
+{
+ char typtype = get_typtype(typid);
+
+ return typid == JSONOID ||
+ typid == JSONBOID ||
+ typid == RECORDOID ||
+ type_is_array(typid) ||
+ typtype == TYPTYPE_COMPOSITE ||
+ /* domain over one of the above? */
+ (typtype == TYPTYPE_DOMAIN &&
+ isCompositeType(getBaseType(typid)));
+}
+
+/*
+ * Transform JSON_TABLE column definition into a JsonFuncExpr
+ * This turns:
+ * - regular column into JSON_VALUE()
+ * - FORMAT JSON column into JSON_QUERY()
+ * - EXISTS column into JSON_EXISTS()
+ */
+static JsonFuncExpr *
+transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
+ List *passingArgs)
+{
+ Node *pathspec;
+ JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
+
+ /*
+ * XXX consider inventing JSON_TABLE_VALUE_OP, etc. and pass the column
+ * name via JsonExpr so that JsonPathValue(), etc. can provide error
+ * message tailored to JSON_TABLE(), such as by mentioning the column
+ * names in the message.
+ */
+ if (jtc->coltype == JTC_REGULAR)
+ jfexpr->op = JSON_VALUE_OP;
+ else if (jtc->coltype == JTC_EXISTS)
+ jfexpr->op = JSON_EXISTS_OP;
+ else
+ jfexpr->op = JSON_QUERY_OP;
+
+ jfexpr->context_item = makeJsonValueExpr((Expr *) contextItemExpr, NULL,
+ makeJsonFormat(JS_FORMAT_DEFAULT,
+ JS_ENC_DEFAULT,
+ -1));
+ if (jtc->pathspec)
+ pathspec = (Node *) jtc->pathspec->string;
+ else
+ {
+ /* Construct default path as '$."column_name"' */
+ StringInfoData path;
+
+ initStringInfo(&path);
+
+ appendStringInfoString(&path, "$.");
+ escape_json(&path, jtc->name);
+
+ pathspec = makeStringConst(path.data, -1);
+ }
+ jfexpr->pathspec = pathspec;
+ jfexpr->passing = passingArgs;
+ jfexpr->output = makeNode(JsonOutput);
+ jfexpr->output->typeName = jtc->typeName;
+ jfexpr->output->returning = makeNode(JsonReturning);
+ jfexpr->output->returning->format = jtc->format;
+ jfexpr->on_empty = jtc->on_empty;
+ jfexpr->on_error = jtc->on_error;
+ jfexpr->quotes = jtc->quotes;
+ jfexpr->wrapper = jtc->wrapper;
+ jfexpr->location = jtc->location;
+
+ return jfexpr;
+}
+
+/*
+ * Create a JsonTablePlan for given path and ON ERROR behavior.
+ */
+static JsonTablePlan *
+makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError)
+{
+ JsonTablePathScan *scan = makeNode(JsonTablePathScan);
+ char *pathstring;
+ Const *value;
+
+ Assert(IsA(pathspec->string, A_Const));
+ pathstring = castNode(A_Const, pathspec->string)->val.sval.sval;
+ value = makeConst(JSONPATHOID, -1, InvalidOid, -1,
+ DirectFunctionCall1(jsonpath_in,
+ CStringGetDatum(pathstring)),
+ false, false);
+
+ scan->plan.type = T_JsonTablePathScan;
+ scan->path = makeJsonTablePath(value, pathspec->name);
+ scan->errorOnError = errorOnError;
+
+ return (JsonTablePlan *) scan;
+}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 427b7325db8..7ca793a369f 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2071,8 +2071,6 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
Assert(list_length(tf->coltypmods) == list_length(tf->colnames));
Assert(list_length(tf->colcollations) == list_length(tf->colnames));
- refname = alias ? alias->aliasname : pstrdup("xmltable");
-
rte->rtekind = RTE_TABLEFUNC;
rte->relid = InvalidOid;
rte->subquery = NULL;
@@ -2082,6 +2080,8 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
rte->colcollations = tf->colcollations;
rte->alias = alias;
+ refname = alias ? alias->aliasname :
+ pstrdup(tf->functype == TFT_XMLTABLE ? "xmltable" : "json_table");
eref = alias ? copyObject(alias) : makeAlias(refname, NIL);
numaliases = list_length(eref->colnames);
@@ -2094,7 +2094,7 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("%s function has %d columns available but %d columns specified",
- "XMLTABLE",
+ tf->functype == TFT_XMLTABLE ? "XMLTABLE" : "JSON_TABLE",
list_length(tf->colnames), numaliases)));
rte->eref = eref;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 1276f336041..ee6fcd0503a 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -2019,6 +2019,7 @@ FigureColnameInternal(Node *node, char **name)
case JSON_VALUE_OP:
*name = "json_value";
return 2;
+ /* JSON_TABLE_OP can't happen here. */
default:
elog(ERROR, "unrecognized JsonExpr op: %d",
(int) ((JsonFuncExpr *) node)->op);
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 1d2d0245e81..75c468bc085 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -61,9 +61,11 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "executor/execExpr.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/miscnodes.h"
+#include "nodes/nodeFuncs.h"
#include "regex/regex.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -71,6 +73,8 @@
#include "utils/float.h"
#include "utils/formatting.h"
#include "utils/jsonpath.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
#include "utils/timestamp.h"
/*
@@ -154,6 +158,63 @@ typedef struct JsonValueListIterator
ListCell *next;
} JsonValueListIterator;
+/* Structures for JSON_TABLE execution */
+
+/*
+ * Struct holding the result of jsonpath evaluation, to be used as source row
+ * for JsonTableGetValue() which in turn computes the values of individual
+ * JSON_TABLE columns.
+ */
+typedef struct JsonTablePlanRowSource
+{
+ Datum value;
+ bool isnull;
+} JsonTablePlanRowSource;
+
+/*
+ * State of evaluation of row pattern derived by applying jsonpath given in
+ * a JsonTablePlan to an input document given in the parent TableFunc.
+ */
+typedef struct JsonTablePlanState
+{
+ /* Original plan */
+ JsonTablePlan *plan;
+
+ /* The following fields are only valid for JsonTablePathScan plans */
+
+ /* jsonpath to evaluate against the input doc to get the row pattern */
+ JsonPath *path;
+
+ /*
+ * Memory context to use when evaluating the row pattern from the jsonpath
+ */
+ MemoryContext mcxt;
+
+ /* PASSING arguments passed to jsonpath executor */
+ List *args;
+
+ /* List and iterator of jsonpath result values */
+ JsonValueList found;
+ JsonValueListIterator iter;
+
+ /* Currently selected row for JsonTableGetValue() to use */
+ JsonTablePlanRowSource current;
+
+ /* Counter for ORDINAL columns */
+ int ordinal;
+} JsonTablePlanState;
+
+/* Random number to identify JsonTableExecContext for sanity checking */
+#define JSON_TABLE_EXEC_CONTEXT_MAGIC 418352867
+
+typedef struct JsonTableExecContext
+{
+ int magic;
+
+ /* State of the plan providing a row evaluated from "root" jsonpath */
+ JsonTablePlanState *rootplanstate;
+} JsonTableExecContext;
+
/* strict/lax flags is decomposed into four [un]wrap/error flags */
#define jspStrictAbsenceOfErrors(cxt) (!(cxt)->laxMode)
#define jspAutoUnwrap(cxt) ((cxt)->laxMode)
@@ -253,6 +314,7 @@ static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, int32 *index);
static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt,
JsonbValue *jbv, int32 id);
+static void JsonValueListClear(JsonValueList *jvl);
static void JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv);
static int JsonValueListLength(const JsonValueList *jvl);
static bool JsonValueListIsEmpty(JsonValueList *jvl);
@@ -272,6 +334,31 @@ static int compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
static void checkTimezoneIsUsedForCast(bool useTz, const char *type1,
const char *type2);
+static void JsonTableInitOpaque(TableFuncScanState *state, int natts);
+static JsonTablePlanState *JsonTableInitPlan(JsonTableExecContext *cxt,
+ JsonTablePlan *plan,
+ List *args,
+ MemoryContext mcxt);
+static void JsonTableSetDocument(TableFuncScanState *state, Datum value);
+static void JsonTableResetRowPattern(JsonTablePlanState *plan, Datum item);
+static bool JsonTableFetchRow(TableFuncScanState *state);
+static Datum JsonTableGetValue(TableFuncScanState *state, int colnum,
+ Oid typid, int32 typmod, bool *isnull);
+static void JsonTableDestroyOpaque(TableFuncScanState *state);
+static bool JsonTablePlanNextRow(JsonTablePlanState *planstate);
+
+const TableFuncRoutine JsonbTableRoutine =
+{
+ .InitOpaque = JsonTableInitOpaque,
+ .SetDocument = JsonTableSetDocument,
+ .SetNamespace = NULL,
+ .SetRowFilter = NULL,
+ .SetColumnFilter = NULL,
+ .FetchRow = JsonTableFetchRow,
+ .GetValue = JsonTableGetValue,
+ .DestroyOpaque = JsonTableDestroyOpaque
+};
+
/****************** User interface to JsonPath executor ********************/
/*
@@ -3384,6 +3471,13 @@ setBaseObject(JsonPathExecContext *cxt, JsonbValue *jbv, int32 id)
}
static void
+JsonValueListClear(JsonValueList *jvl)
+{
+ jvl->singleton = NULL;
+ jvl->list = NIL;
+}
+
+static void
JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
{
if (jvl->singleton)
@@ -3918,3 +4012,281 @@ JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars)
return res;
}
+
+/************************ JSON_TABLE functions ***************************/
+
+/*
+ * Sanity-checks and returns the opaque JsonTableExecContext from the
+ * given executor state struct.
+ */
+static inline JsonTableExecContext *
+GetJsonTableExecContext(TableFuncScanState *state, const char *fname)
+{
+ JsonTableExecContext *result;
+
+ if (!IsA(state, TableFuncScanState))
+ elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+ result = (JsonTableExecContext *) state->opaque;
+ if (result->magic != JSON_TABLE_EXEC_CONTEXT_MAGIC)
+ elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+
+ return result;
+}
+
+/*
+ * JsonTableInitOpaque
+ * Fill in TableFuncScanState->opaque for processing JSON_TABLE
+ *
+ * This initializes the PASSING arguments and the JsonTablePlanState for
+ * JsonTablePlan given in TableFunc.
+ */
+static void
+JsonTableInitOpaque(TableFuncScanState *state, int natts)
+{
+ JsonTableExecContext *cxt;
+ PlanState *ps = &state->ss.ps;
+ TableFuncScan *tfs = castNode(TableFuncScan, ps->plan);
+ TableFunc *tf = tfs->tablefunc;
+ JsonTablePlan *rootplan = (JsonTablePlan *) tf->plan;
+ JsonExpr *je = castNode(JsonExpr, tf->docexpr);
+ List *args = NIL;
+
+ cxt = palloc0(sizeof(JsonTableExecContext));
+ cxt->magic = JSON_TABLE_EXEC_CONTEXT_MAGIC;
+
+ /*
+ * Evaluate JSON_TABLE() PASSING arguments to be passed to the jsonpath
+ * executor via JsonPathVariables.
+ */
+ if (state->passingvalexprs)
+ {
+ ListCell *exprlc;
+ ListCell *namelc;
+
+ Assert(list_length(state->passingvalexprs) ==
+ list_length(je->passing_names));
+ forboth(exprlc, state->passingvalexprs,
+ namelc, je->passing_names)
+ {
+ ExprState *state = lfirst_node(ExprState, exprlc);
+ String *name = lfirst_node(String, namelc);
+ JsonPathVariable *var = palloc(sizeof(*var));
+
+ var->name = pstrdup(name->sval);
+ var->typid = exprType((Node *) state->expr);
+ var->typmod = exprTypmod((Node *) state->expr);
+
+ /*
+ * Evaluate the expression and save the value to be returned by
+ * GetJsonPathVar().
+ */
+ var->value = ExecEvalExpr(state, ps->ps_ExprContext,
+ &var->isnull);
+
+ args = lappend(args, var);
+ }
+ }
+
+ /* Initialize plan */
+ cxt->rootplanstate = JsonTableInitPlan(cxt, rootplan, args,
+ CurrentMemoryContext);
+
+ state->opaque = cxt;
+}
+
+/*
+ * JsonTableDestroyOpaque
+ * Resets state->opaque
+ */
+static void
+JsonTableDestroyOpaque(TableFuncScanState *state)
+{
+ JsonTableExecContext *cxt =
+ GetJsonTableExecContext(state, "JsonTableDestroyOpaque");
+
+ /* not valid anymore */
+ cxt->magic = 0;
+
+ state->opaque = NULL;
+}
+
+/*
+ * JsonTableInitPlan
+ * Initialize information for evaluating jsonpath in the given
+ * JsonTablePlan
+ */
+static JsonTablePlanState *
+JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
+ List *args, MemoryContext mcxt)
+{
+ JsonTablePlanState *planstate = palloc0(sizeof(*planstate));
+
+ planstate->plan = plan;
+
+ if (IsA(plan, JsonTablePathScan))
+ {
+ JsonTablePathScan *scan = (JsonTablePathScan *) plan;
+
+ planstate->path = DatumGetJsonPathP(scan->path->value->constvalue);
+ planstate->args = args;
+ planstate->mcxt = AllocSetContextCreate(mcxt, "JsonTableExecContext",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /* No row pattern evaluated yet. */
+ planstate->current.value = PointerGetDatum(NULL);
+ planstate->current.isnull = true;
+ }
+
+ return planstate;
+}
+
+/*
+ * JsonTableSetDocument
+ * Install the input document and evaluate the row pattern
+ */
+static void
+JsonTableSetDocument(TableFuncScanState *state, Datum value)
+{
+ JsonTableExecContext *cxt =
+ GetJsonTableExecContext(state, "JsonTableSetDocument");
+
+ JsonTableResetRowPattern(cxt->rootplanstate, value);
+}
+
+/*
+ * Evaluate a JsonTablePlan's jsonpath to get a new row pattren from
+ * the given context item
+ */
+static void
+JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
+{
+ JsonTablePathScan *scan = castNode(JsonTablePathScan, planstate->plan);
+ MemoryContext oldcxt;
+ JsonPathExecResult res;
+ Jsonb *js = (Jsonb *) DatumGetJsonbP(item);
+
+ JsonValueListClear(&planstate->found);
+
+ MemoryContextResetOnly(planstate->mcxt);
+
+ oldcxt = MemoryContextSwitchTo(planstate->mcxt);
+
+ res = executeJsonPath(planstate->path, planstate->args,
+ GetJsonPathVar, CountJsonPathVars,
+ js, scan->errorOnError,
+ &planstate->found,
+ true);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ if (jperIsError(res))
+ {
+ Assert(!scan->errorOnError);
+ JsonValueListClear(&planstate->found);
+ }
+
+ /* Reset plan iterator to the beginning of the item list */
+ JsonValueListInitIterator(&planstate->found, &planstate->iter);
+ planstate->current.value = PointerGetDatum(NULL);
+ planstate->current.isnull = true;
+ planstate->ordinal = 0;
+}
+
+/*
+ * Fetch next row from a JsonTablePlan's path evaluation result.
+ *
+ * Returns false if the plan has run out of rows, true otherwise.
+ */
+static bool
+JsonTablePlanNextRow(JsonTablePlanState *planstate)
+{
+ JsonbValue *jbv = JsonValueListNext(&planstate->found, &planstate->iter);
+ MemoryContext oldcxt;
+
+ /* End of list? */
+ if (jbv == NULL)
+ {
+ planstate->current.value = PointerGetDatum(NULL);
+ planstate->current.isnull = true;
+ return false;
+ }
+
+ /*
+ * Set current row item for subsequent JsonTableGetValue() calls for
+ * evaluating individual columns.
+ */
+ oldcxt = MemoryContextSwitchTo(planstate->mcxt);
+ planstate->current.value = JsonbPGetDatum(JsonbValueToJsonb(jbv));
+ planstate->current.isnull = false;
+ MemoryContextSwitchTo(oldcxt);
+
+ /* Next row! */
+ planstate->ordinal++;
+
+ return true;
+}
+
+/*
+ * JsonTableFetchRow
+ * Prepare the next "current" row for upcoming GetValue calls.
+ *
+ * Returns false if no more rows can be returned.
+ */
+static bool
+JsonTableFetchRow(TableFuncScanState *state)
+{
+ JsonTableExecContext *cxt =
+ GetJsonTableExecContext(state, "JsonTableFetchRow");
+
+ return JsonTablePlanNextRow(cxt->rootplanstate);
+}
+
+/*
+ * JsonTableGetValue
+ * Return the value for column number 'colnum' for the current row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
+ */
+static Datum
+JsonTableGetValue(TableFuncScanState *state, int colnum,
+ Oid typid, int32 typmod, bool *isnull)
+{
+ JsonTableExecContext *cxt =
+ GetJsonTableExecContext(state, "JsonTableGetValue");
+ ExprContext *econtext = state->ss.ps.ps_ExprContext;
+ ExprState *estate = list_nth(state->colvalexprs, colnum);
+ JsonTablePlanState *planstate = cxt->rootplanstate;
+ JsonTablePlanRowSource *current = &planstate->current;
+ Datum result;
+
+ /* Row pattern value is NULL */
+ if (current->isnull)
+ {
+ result = (Datum) 0;
+ *isnull = true;
+ }
+ /* Evaluate JsonExpr. */
+ else if (estate)
+ {
+ Datum saved_caseValue = econtext->caseValue_datum;
+ bool saved_caseIsNull = econtext->caseValue_isNull;
+
+ /* Pass the row pattern value via CaseTestExpr. */
+ econtext->caseValue_datum = current->value;
+ econtext->caseValue_isNull = false;
+
+ result = ExecEvalExpr(estate, econtext, isnull);
+
+ econtext->caseValue_datum = saved_caseValue;
+ econtext->caseValue_isNull = saved_caseIsNull;
+ }
+ /* ORDINAL column */
+ else
+ {
+ result = Int32GetDatum(planstate->ordinal);
+ *isnull = false;
+ }
+
+ return result;
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0f7f40c50f6..c9e3ac88cb1 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -524,6 +524,8 @@ static char *flatten_reloptions(Oid relid);
static void get_reloptions(StringInfo buf, Datum reloptions);
static void get_json_path_spec(Node *path_spec, deparse_context *context,
bool showimplicit);
+static void get_json_table_columns(TableFunc *tf, deparse_context *context,
+ bool showimplicit);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -8833,7 +8835,8 @@ get_json_behavior(JsonBehavior *behavior, deparse_context *context,
/*
* get_json_expr_options
*
- * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS.
+ * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS and
+ * JSON_TABLE columns.
*/
static void
get_json_expr_options(JsonExpr *jsexpr, deparse_context *context,
@@ -11519,16 +11522,14 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
/* ----------
- * get_tablefunc - Parse back a table function
+ * get_xmltable - Parse back a XMLTABLE function
* ----------
*/
static void
-get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
+get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
{
StringInfo buf = context->buf;
- /* XMLTABLE is the only existing implementation. */
-
appendStringInfoString(buf, "XMLTABLE(");
if (tf->ns_uris != NIL)
@@ -11619,6 +11620,180 @@ get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
appendStringInfoChar(buf, ')');
}
+/*
+ * get_json_table_columns - Parse back JSON_TABLE columns
+ */
+static void
+get_json_table_columns(TableFunc *tf, deparse_context *context,
+ bool showimplicit)
+{
+ StringInfo buf = context->buf;
+ JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
+ ListCell *lc_colname;
+ ListCell *lc_coltype;
+ ListCell *lc_coltypmod;
+ ListCell *lc_colvalexpr;
+ int colnum = 0;
+
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "COLUMNS (", 0, 0, 0);
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel += PRETTYINDENT_VAR;
+
+ forfour(lc_colname, tf->colnames,
+ lc_coltype, tf->coltypes,
+ lc_coltypmod, tf->coltypmods,
+ lc_colvalexpr, tf->colvalexprs)
+ {
+ char *colname = strVal(lfirst(lc_colname));
+ JsonExpr *colexpr;
+ Oid typid;
+ int32 typmod;
+ bool ordinality;
+ JsonBehaviorType default_behavior;
+
+ typid = lfirst_oid(lc_coltype);
+ typmod = lfirst_int(lc_coltypmod);
+ colexpr = castNode(JsonExpr, lfirst(lc_colvalexpr));
+
+ if (colnum > 0)
+ appendStringInfoString(buf, ", ");
+
+ colnum++;
+
+ ordinality = !colexpr;
+
+ appendContextKeyword(context, "", 0, 0, 0);
+
+ appendStringInfo(buf, "%s %s", quote_identifier(colname),
+ ordinality ? "FOR ORDINALITY" :
+ format_type_with_typemod(typid, typmod));
+ if (ordinality)
+ continue;
+
+ if (colexpr->op == JSON_EXISTS_OP)
+ {
+ appendStringInfoString(buf, " EXISTS");
+ default_behavior = JSON_BEHAVIOR_FALSE;
+ }
+ else
+ {
+ if (colexpr->op == JSON_QUERY_OP)
+ {
+ char typcategory;
+ bool typispreferred;
+
+ get_type_category_preferred(typid, &typcategory, &typispreferred);
+
+ if (typcategory == TYPCATEGORY_STRING)
+ appendStringInfoString(buf,
+ colexpr->format->format_type == JS_FORMAT_JSONB ?
+ " FORMAT JSONB" : " FORMAT JSON");
+ }
+
+ default_behavior = JSON_BEHAVIOR_NULL;
+ }
+
+ if (jexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ default_behavior = JSON_BEHAVIOR_ERROR;
+
+ appendStringInfoString(buf, " PATH ");
+
+ get_json_path_spec(colexpr->path_spec, context, showimplicit);
+
+ get_json_expr_options(colexpr, context, default_behavior);
+ }
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel -= PRETTYINDENT_VAR;
+
+ appendContextKeyword(context, ")", 0, 0, 0);
+}
+
+/* ----------
+ * get_json_table - Parse back a JSON_TABLE function
+ * ----------
+ */
+static void
+get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
+{
+ StringInfo buf = context->buf;
+ JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
+ JsonTablePathScan *root = castNode(JsonTablePathScan, tf->plan);
+
+ appendStringInfoString(buf, "JSON_TABLE(");
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel += PRETTYINDENT_VAR;
+
+ appendContextKeyword(context, "", 0, 0, 0);
+
+ get_rule_expr(jexpr->formatted_expr, context, showimplicit);
+
+ appendStringInfoString(buf, ", ");
+
+ get_const_expr(root->path->value, context, -1);
+
+ appendStringInfo(buf, " AS %s", quote_identifier(root->path->name));
+
+ if (jexpr->passing_values)
+ {
+ ListCell *lc1,
+ *lc2;
+ bool needcomma = false;
+
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "PASSING ", 0, 0, 0);
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel += PRETTYINDENT_VAR;
+
+ forboth(lc1, jexpr->passing_names,
+ lc2, jexpr->passing_values)
+ {
+ if (needcomma)
+ appendStringInfoString(buf, ", ");
+ needcomma = true;
+
+ appendContextKeyword(context, "", 0, 0, 0);
+
+ get_rule_expr((Node *) lfirst(lc2), context, false);
+ appendStringInfo(buf, " AS %s",
+ quote_identifier((lfirst_node(String, lc1))->sval)
+ );
+ }
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel -= PRETTYINDENT_VAR;
+ }
+
+ get_json_table_columns(tf, context, showimplicit);
+
+ if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY)
+ get_json_behavior(jexpr->on_error, context, "ERROR");
+
+ if (PRETTY_INDENT(context))
+ context->indentLevel -= PRETTYINDENT_VAR;
+
+ appendContextKeyword(context, ")", 0, 0, 0);
+}
+
+/* ----------
+ * get_tablefunc - Parse back a table function
+ * ----------
+ */
+static void
+get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
+{
+ /* XMLTABLE and JSON_TABLE are the only existing implementations. */
+
+ if (tf->functype == TFT_XMLTABLE)
+ get_xmltable(tf, context, showimplicit);
+ else if (tf->functype == TFT_JSON_TABLE)
+ get_json_table(tf, context, showimplicit);
+}
+
/* ----------
* get_from_clause - Parse back a FROM clause
*