mirror of
https://github.com/postgres/postgres.git
synced 2025-07-30 11:03:19 +03:00
Add basic JSON_TABLE() functionality
JSON_TABLE() allows JSON data to be converted into a relational view
and thus used, for example, in a FROM clause, like other tabular
data. Data to show in the view is selected from a source JSON object
using a JSON path expression to get a sequence of JSON objects that's
called a "row pattern", which becomes the source to compute the
SQL/JSON values that populate the view's output columns. Column
values themselves are computed using JSON path expressions applied to
each of the JSON objects comprising the "row pattern", for which the
SQL/JSON query functions added in 6185c9737c
are used.
To implement JSON_TABLE() as a table function, this augments the
TableFunc and TableFuncScanState nodes that are currently used to
support XMLTABLE() with some JSON_TABLE()-specific fields.
Note that the JSON_TABLE() spec includes NESTED COLUMNS and PLAN
clauses, which are required to provide more flexibility to extract
data out of nested JSON objects, but they are not implemented here
to keep this commit of manageable size.
Author: Nikita Glukhov <n.gluhov@postgrespro.ru>
Author: Teodor Sigaev <teodor@sigaev.ru>
Author: Oleg Bartunov <obartunov@gmail.com>
Author: Alexander Korotkov <aekorotkov@gmail.com>
Author: Andrew Dunstan <andrew@dunslane.net>
Author: Amit Langote <amitlangote09@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewers have included (in no particular order):
Andres Freund, Alexander Korotkov, Pavel Stehule, Andrew Alsup,
Erik Rijkers, Zihong Yu, Himanshu Upadhyaya, Daniel Gustafsson,
Justin Pryzby, Álvaro Herrera, Jian He
Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru
Discussion: https://postgr.es/m/20220616233130.rparivafipt6doj3@alap3.anarazel.de
Discussion: https://postgr.es/m/abd9b83b-aa66-f230-3d6d-734817f0995d%40postgresql.org
Discussion: https://postgr.es/m/CA+HiwqE4XTdfb1nW=Ojoy_tQSRhYt-q_kb6i5d4xcKyrLC1Nbg@mail.gmail.com
This commit is contained in:
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -537,6 +537,22 @@ makeFuncExpr(Oid funcid, Oid rettype, List *args,
|
||||
return funcexpr;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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 \
|
||||
|
@ -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
|
||||
@ -18422,18 +18567,6 @@ makeTypeCast(Node *arg, TypeName *typename, int location)
|
||||
return (Node *) n;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
421
src/backend/parser/parse_jsontable.c
Normal file
421
src/backend/parser/parse_jsontable.c
Normal file
@ -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;
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 ********************/
|
||||
|
||||
/*
|
||||
@ -3383,6 +3470,13 @@ setBaseObject(JsonPathExecContext *cxt, JsonbValue *jbv, int32 id)
|
||||
return baseObject;
|
||||
}
|
||||
|
||||
static void
|
||||
JsonValueListClear(JsonValueList *jvl)
|
||||
{
|
||||
jvl->singleton = NULL;
|
||||
jvl->list = NIL;
|
||||
}
|
||||
|
||||
static void
|
||||
JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
|
||||
{
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
Reference in New Issue
Block a user