mirror of
https://github.com/postgres/postgres.git
synced 2025-04-20 00:42:27 +03:00
Detail and hint messages should be full sentences and should end with a period, but some of the messages newly-introduced in v15 did not follow that. Author: Justin Pryzby Reviewed-by: Álvaro Herrera Discussion: https://postgr.es/m/20220719120948.GF12702@telsasoft.com Backpatch-through: 15
733 lines
19 KiB
C
733 lines
19 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* parse_jsontable.c
|
|
* parsing of JSON_TABLE
|
|
*
|
|
* Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/parser/parse_jsontable.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "catalog/pg_collation.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "miscadmin.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "optimizer/optimizer.h"
|
|
#include "parser/parse_clause.h"
|
|
#include "parser/parse_collate.h"
|
|
#include "parser/parse_expr.h"
|
|
#include "parser/parse_relation.h"
|
|
#include "parser/parse_type.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/json.h"
|
|
#include "utils/lsyscache.h"
|
|
|
|
/* Context for JSON_TABLE transformation */
|
|
typedef struct JsonTableContext
|
|
{
|
|
ParseState *pstate; /* parsing state */
|
|
JsonTable *table; /* untransformed node */
|
|
TableFunc *tablefunc; /* transformed node */
|
|
List *pathNames; /* list of all path and columns names */
|
|
int pathNameId; /* path name id counter */
|
|
Oid contextItemTypid; /* type oid of context item (json/jsonb) */
|
|
} JsonTableContext;
|
|
|
|
static JsonTableParent *transformJsonTableColumns(JsonTableContext *cxt,
|
|
JsonTablePlan *plan,
|
|
List *columns,
|
|
char *pathSpec,
|
|
char **pathName,
|
|
int location);
|
|
|
|
static Node *
|
|
makeStringConst(char *str, int location)
|
|
{
|
|
A_Const *n = makeNode(A_Const);
|
|
|
|
n->val.node.type = T_String;
|
|
n->val.sval.sval = str;
|
|
n->location = location;
|
|
|
|
return (Node *) n;
|
|
}
|
|
|
|
/*
|
|
* Transform JSON_TABLE column
|
|
* - regular column into JSON_VALUE()
|
|
* - FORMAT JSON column into JSON_QUERY()
|
|
* - EXISTS column into JSON_EXISTS()
|
|
*/
|
|
static Node *
|
|
transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
|
|
List *passingArgs, bool errorOnError)
|
|
{
|
|
JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
|
|
JsonCommon *common = makeNode(JsonCommon);
|
|
JsonOutput *output = makeNode(JsonOutput);
|
|
char *pathspec;
|
|
JsonFormat *default_format;
|
|
|
|
jfexpr->op =
|
|
jtc->coltype == JTC_REGULAR ? JSON_VALUE_OP :
|
|
jtc->coltype == JTC_EXISTS ? JSON_EXISTS_OP : JSON_QUERY_OP;
|
|
jfexpr->common = common;
|
|
jfexpr->output = output;
|
|
jfexpr->on_empty = jtc->on_empty;
|
|
jfexpr->on_error = jtc->on_error;
|
|
if (!jfexpr->on_error && errorOnError)
|
|
jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL);
|
|
jfexpr->omit_quotes = jtc->omit_quotes;
|
|
jfexpr->wrapper = jtc->wrapper;
|
|
jfexpr->location = jtc->location;
|
|
|
|
output->typeName = jtc->typeName;
|
|
output->returning = makeNode(JsonReturning);
|
|
output->returning->format = jtc->format;
|
|
|
|
default_format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
|
|
|
|
common->pathname = NULL;
|
|
common->expr = makeJsonValueExpr((Expr *) contextItemExpr, default_format);
|
|
common->passing = passingArgs;
|
|
|
|
if (jtc->pathspec)
|
|
pathspec = jtc->pathspec;
|
|
else
|
|
{
|
|
/* Construct default path as '$."column_name"' */
|
|
StringInfoData path;
|
|
|
|
initStringInfo(&path);
|
|
|
|
appendStringInfoString(&path, "$.");
|
|
escape_json(&path, jtc->name);
|
|
|
|
pathspec = path.data;
|
|
}
|
|
|
|
common->pathspec = makeStringConst(pathspec, -1);
|
|
|
|
return (Node *) jfexpr;
|
|
}
|
|
|
|
static bool
|
|
isJsonTablePathNameDuplicate(JsonTableContext *cxt, const char *pathname)
|
|
{
|
|
ListCell *lc;
|
|
|
|
foreach(lc, cxt->pathNames)
|
|
{
|
|
if (!strcmp(pathname, (const char *) lfirst(lc)))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Register the column name in the path name list. */
|
|
static void
|
|
registerJsonTableColumn(JsonTableContext *cxt, char *colname)
|
|
{
|
|
if (isJsonTablePathNameDuplicate(cxt, colname))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_ALIAS),
|
|
errmsg("duplicate JSON_TABLE column name: %s", colname),
|
|
errhint("JSON_TABLE column names must be distinct from one another.")));
|
|
|
|
cxt->pathNames = lappend(cxt->pathNames, colname);
|
|
}
|
|
|
|
/* Recursively register all nested column names in the path name list. */
|
|
static void
|
|
registerAllJsonTableColumns(JsonTableContext *cxt, List *columns)
|
|
{
|
|
ListCell *lc;
|
|
|
|
foreach(lc, columns)
|
|
{
|
|
JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
|
|
|
|
if (jtc->coltype == JTC_NESTED)
|
|
{
|
|
if (jtc->pathname)
|
|
registerJsonTableColumn(cxt, jtc->pathname);
|
|
|
|
registerAllJsonTableColumns(cxt, jtc->columns);
|
|
}
|
|
else
|
|
{
|
|
registerJsonTableColumn(cxt, jtc->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Generate a new unique JSON_TABLE path name. */
|
|
static char *
|
|
generateJsonTablePathName(JsonTableContext *cxt)
|
|
{
|
|
char namebuf[32];
|
|
char *name = namebuf;
|
|
|
|
do
|
|
{
|
|
snprintf(namebuf, sizeof(namebuf), "json_table_path_%d",
|
|
++cxt->pathNameId);
|
|
} while (isJsonTablePathNameDuplicate(cxt, name));
|
|
|
|
name = pstrdup(name);
|
|
cxt->pathNames = lappend(cxt->pathNames, name);
|
|
|
|
return name;
|
|
}
|
|
|
|
/* Collect sibling path names from plan to the specified list. */
|
|
static void
|
|
collectSiblingPathsInJsonTablePlan(JsonTablePlan *plan, List **paths)
|
|
{
|
|
if (plan->plan_type == JSTP_SIMPLE)
|
|
*paths = lappend(*paths, plan->pathname);
|
|
else if (plan->plan_type == JSTP_JOINED)
|
|
{
|
|
if (plan->join_type == JSTPJ_INNER ||
|
|
plan->join_type == JSTPJ_OUTER)
|
|
{
|
|
Assert(plan->plan1->plan_type == JSTP_SIMPLE);
|
|
*paths = lappend(*paths, plan->plan1->pathname);
|
|
}
|
|
else if (plan->join_type == JSTPJ_CROSS ||
|
|
plan->join_type == JSTPJ_UNION)
|
|
{
|
|
collectSiblingPathsInJsonTablePlan(plan->plan1, paths);
|
|
collectSiblingPathsInJsonTablePlan(plan->plan2, paths);
|
|
}
|
|
else
|
|
elog(ERROR, "invalid JSON_TABLE join type %d",
|
|
plan->join_type);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Validate child JSON_TABLE plan by checking that:
|
|
* - all nested columns have path names specified
|
|
* - all nested columns have corresponding node in the sibling plan
|
|
* - plan does not contain duplicate or extra nodes
|
|
*/
|
|
static void
|
|
validateJsonTableChildPlan(ParseState *pstate, JsonTablePlan *plan,
|
|
List *columns)
|
|
{
|
|
ListCell *lc1;
|
|
List *siblings = NIL;
|
|
int nchildren = 0;
|
|
|
|
if (plan)
|
|
collectSiblingPathsInJsonTablePlan(plan, &siblings);
|
|
|
|
foreach(lc1, columns)
|
|
{
|
|
JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1));
|
|
|
|
if (jtc->coltype == JTC_NESTED)
|
|
{
|
|
ListCell *lc2;
|
|
bool found = false;
|
|
|
|
if (!jtc->pathname)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("nested JSON_TABLE columns must contain an explicit AS pathname specification if an explicit PLAN clause is used"),
|
|
parser_errposition(pstate, jtc->location)));
|
|
|
|
/* find nested path name in the list of sibling path names */
|
|
foreach(lc2, siblings)
|
|
{
|
|
if ((found = !strcmp(jtc->pathname, lfirst(lc2))))
|
|
break;
|
|
}
|
|
|
|
if (!found)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("invalid JSON_TABLE plan"),
|
|
errdetail("Plan node for nested path %s was not found in plan.", jtc->pathname),
|
|
parser_errposition(pstate, jtc->location)));
|
|
|
|
nchildren++;
|
|
}
|
|
}
|
|
|
|
if (list_length(siblings) > nchildren)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("invalid JSON_TABLE plan"),
|
|
errdetail("Plan node contains some extra or duplicate sibling nodes."),
|
|
parser_errposition(pstate, plan ? plan->location : -1)));
|
|
}
|
|
|
|
static JsonTableColumn *
|
|
findNestedJsonTableColumn(List *columns, const char *pathname)
|
|
{
|
|
ListCell *lc;
|
|
|
|
foreach(lc, columns)
|
|
{
|
|
JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
|
|
|
|
if (jtc->coltype == JTC_NESTED &&
|
|
jtc->pathname &&
|
|
!strcmp(jtc->pathname, pathname))
|
|
return jtc;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Node *
|
|
transformNestedJsonTableColumn(JsonTableContext *cxt, JsonTableColumn *jtc,
|
|
JsonTablePlan *plan)
|
|
{
|
|
JsonTableParent *node;
|
|
char *pathname = jtc->pathname;
|
|
|
|
node = transformJsonTableColumns(cxt, plan, jtc->columns, jtc->pathspec,
|
|
&pathname, jtc->location);
|
|
node->name = pstrdup(pathname);
|
|
|
|
return (Node *) node;
|
|
}
|
|
|
|
static Node *
|
|
makeJsonTableSiblingJoin(bool cross, Node *lnode, Node *rnode)
|
|
{
|
|
JsonTableSibling *join = makeNode(JsonTableSibling);
|
|
|
|
join->larg = lnode;
|
|
join->rarg = rnode;
|
|
join->cross = cross;
|
|
|
|
return (Node *) join;
|
|
}
|
|
|
|
/*
|
|
* Recursively transform child JSON_TABLE plan.
|
|
*
|
|
* Default plan is transformed into a cross/union join of its nested columns.
|
|
* Simple and outer/inner plans are transformed into a JsonTableParent by
|
|
* finding and transforming corresponding nested column.
|
|
* Sibling plans are recursively transformed into a JsonTableSibling.
|
|
*/
|
|
static Node *
|
|
transformJsonTableChildPlan(JsonTableContext *cxt, JsonTablePlan *plan,
|
|
List *columns)
|
|
{
|
|
JsonTableColumn *jtc = NULL;
|
|
|
|
if (!plan || plan->plan_type == JSTP_DEFAULT)
|
|
{
|
|
/* unspecified or default plan */
|
|
Node *res = NULL;
|
|
ListCell *lc;
|
|
bool cross = plan && (plan->join_type & JSTPJ_CROSS);
|
|
|
|
/* transform all nested columns into cross/union join */
|
|
foreach(lc, columns)
|
|
{
|
|
JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
|
|
Node *node;
|
|
|
|
if (jtc->coltype != JTC_NESTED)
|
|
continue;
|
|
|
|
node = transformNestedJsonTableColumn(cxt, jtc, plan);
|
|
|
|
/* join transformed node with previous sibling nodes */
|
|
res = res ? makeJsonTableSiblingJoin(cross, res, node) : node;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
else if (plan->plan_type == JSTP_SIMPLE)
|
|
{
|
|
jtc = findNestedJsonTableColumn(columns, plan->pathname);
|
|
}
|
|
else if (plan->plan_type == JSTP_JOINED)
|
|
{
|
|
if (plan->join_type == JSTPJ_INNER ||
|
|
plan->join_type == JSTPJ_OUTER)
|
|
{
|
|
Assert(plan->plan1->plan_type == JSTP_SIMPLE);
|
|
jtc = findNestedJsonTableColumn(columns, plan->plan1->pathname);
|
|
}
|
|
else
|
|
{
|
|
Node *node1 = transformJsonTableChildPlan(cxt, plan->plan1,
|
|
columns);
|
|
Node *node2 = transformJsonTableChildPlan(cxt, plan->plan2,
|
|
columns);
|
|
|
|
return makeJsonTableSiblingJoin(plan->join_type == JSTPJ_CROSS,
|
|
node1, node2);
|
|
}
|
|
}
|
|
else
|
|
elog(ERROR, "invalid JSON_TABLE plan type %d", plan->plan_type);
|
|
|
|
if (!jtc)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("invalid JSON_TABLE plan"),
|
|
errdetail("Path name was %s not found in nested columns list.",
|
|
plan->pathname),
|
|
parser_errposition(cxt->pstate, plan->location)));
|
|
|
|
return transformNestedJsonTableColumn(cxt, jtc, plan);
|
|
}
|
|
|
|
/* Check whether type is json/jsonb, array, or record. */
|
|
static bool
|
|
typeIsComposite(Oid typid)
|
|
{
|
|
char typtype;
|
|
|
|
if (typid == JSONOID ||
|
|
typid == JSONBOID ||
|
|
typid == RECORDOID ||
|
|
type_is_array(typid))
|
|
return true;
|
|
|
|
typtype = get_typtype(typid);
|
|
|
|
if (typtype == TYPTYPE_COMPOSITE)
|
|
return true;
|
|
|
|
if (typtype == TYPTYPE_DOMAIN)
|
|
return typeIsComposite(getBaseType(typid));
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
|
|
static void
|
|
appendJsonTableColumns(JsonTableContext *cxt, List *columns)
|
|
{
|
|
ListCell *col;
|
|
ParseState *pstate = cxt->pstate;
|
|
JsonTable *jt = cxt->table;
|
|
TableFunc *tf = cxt->tablefunc;
|
|
bool errorOnError = jt->on_error &&
|
|
jt->on_error->btype == JSON_BEHAVIOR_ERROR;
|
|
|
|
foreach(col, columns)
|
|
{
|
|
JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
|
|
Oid typid;
|
|
int32 typmod;
|
|
Node *colexpr;
|
|
|
|
if (rawc->name)
|
|
{
|
|
/* make sure column names are unique */
|
|
ListCell *colname;
|
|
|
|
foreach(colname, tf->colnames)
|
|
if (!strcmp((const char *) colname, rawc->name))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("column name \"%s\" is not unique",
|
|
rawc->name),
|
|
parser_errposition(pstate, rawc->location)));
|
|
|
|
tf->colnames = lappend(tf->colnames,
|
|
makeString(pstrdup(rawc->name)));
|
|
}
|
|
|
|
/*
|
|
* Determine the type and typmod for the new column. FOR ORDINALITY
|
|
* columns are INTEGER by standard; the others are user-specified.
|
|
*/
|
|
switch (rawc->coltype)
|
|
{
|
|
case JTC_FOR_ORDINALITY:
|
|
colexpr = NULL;
|
|
typid = INT4OID;
|
|
typmod = -1;
|
|
break;
|
|
|
|
case JTC_REGULAR:
|
|
typenameTypeIdAndMod(pstate, rawc->typeName, &typid, &typmod);
|
|
|
|
/*
|
|
* Use implicit FORMAT JSON for composite types (arrays and
|
|
* records)
|
|
*/
|
|
if (typeIsComposite(typid))
|
|
rawc->coltype = JTC_FORMATTED;
|
|
else if (rawc->wrapper != JSW_NONE)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("cannot use WITH WRAPPER clause with scalar columns"),
|
|
parser_errposition(pstate, rawc->location)));
|
|
else if (rawc->omit_quotes)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("cannot use OMIT QUOTES clause with scalar columns"),
|
|
parser_errposition(pstate, rawc->location)));
|
|
|
|
/* FALLTHROUGH */
|
|
case JTC_EXISTS:
|
|
case JTC_FORMATTED:
|
|
{
|
|
Node *je;
|
|
CaseTestExpr *param = makeNode(CaseTestExpr);
|
|
|
|
param->collation = InvalidOid;
|
|
param->typeId = cxt->contextItemTypid;
|
|
param->typeMod = -1;
|
|
|
|
je = transformJsonTableColumn(rawc, (Node *) param,
|
|
NIL, errorOnError);
|
|
|
|
colexpr = transformExpr(pstate, je, EXPR_KIND_FROM_FUNCTION);
|
|
assign_expr_collations(pstate, colexpr);
|
|
|
|
typid = exprType(colexpr);
|
|
typmod = exprTypmod(colexpr);
|
|
break;
|
|
}
|
|
|
|
case JTC_NESTED:
|
|
continue;
|
|
|
|
default:
|
|
elog(ERROR, "unknown JSON_TABLE column type: %d", rawc->coltype);
|
|
break;
|
|
}
|
|
|
|
tf->coltypes = lappend_oid(tf->coltypes, typid);
|
|
tf->coltypmods = lappend_int(tf->coltypmods, typmod);
|
|
tf->colcollations = lappend_oid(tf->colcollations, get_typcollation(typid));
|
|
tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create transformed JSON_TABLE parent plan node by appending all non-nested
|
|
* columns to the TableFunc node and remembering their indices in the
|
|
* colvalexprs list.
|
|
*/
|
|
static JsonTableParent *
|
|
makeParentJsonTableNode(JsonTableContext *cxt, char *pathSpec, List *columns)
|
|
{
|
|
JsonTableParent *node = makeNode(JsonTableParent);
|
|
|
|
node->path = makeConst(JSONPATHOID, -1, InvalidOid, -1,
|
|
DirectFunctionCall1(jsonpath_in,
|
|
CStringGetDatum(pathSpec)),
|
|
false, false);
|
|
|
|
/* save start of column range */
|
|
node->colMin = list_length(cxt->tablefunc->colvalexprs);
|
|
|
|
appendJsonTableColumns(cxt, columns);
|
|
|
|
/* save end of column range */
|
|
node->colMax = list_length(cxt->tablefunc->colvalexprs) - 1;
|
|
|
|
node->errorOnError =
|
|
cxt->table->on_error &&
|
|
cxt->table->on_error->btype == JSON_BEHAVIOR_ERROR;
|
|
|
|
return node;
|
|
}
|
|
|
|
static JsonTableParent *
|
|
transformJsonTableColumns(JsonTableContext *cxt, JsonTablePlan *plan,
|
|
List *columns, char *pathSpec, char **pathName,
|
|
int location)
|
|
{
|
|
JsonTableParent *node;
|
|
JsonTablePlan *childPlan;
|
|
bool defaultPlan = !plan || plan->plan_type == JSTP_DEFAULT;
|
|
|
|
if (!*pathName)
|
|
{
|
|
if (cxt->table->plan)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("invalid JSON_TABLE expression"),
|
|
errdetail("JSON_TABLE columns must contain "
|
|
"explicit AS pathname specification if "
|
|
"explicit PLAN clause is used"),
|
|
parser_errposition(cxt->pstate, location)));
|
|
|
|
*pathName = generateJsonTablePathName(cxt);
|
|
}
|
|
|
|
if (defaultPlan)
|
|
childPlan = plan;
|
|
else
|
|
{
|
|
/* validate parent and child plans */
|
|
JsonTablePlan *parentPlan;
|
|
|
|
if (plan->plan_type == JSTP_JOINED)
|
|
{
|
|
if (plan->join_type != JSTPJ_INNER &&
|
|
plan->join_type != JSTPJ_OUTER)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("invalid JSON_TABLE plan"),
|
|
errdetail("Expected INNER or OUTER JSON_TABLE plan node."),
|
|
parser_errposition(cxt->pstate, plan->location)));
|
|
|
|
parentPlan = plan->plan1;
|
|
childPlan = plan->plan2;
|
|
|
|
Assert(parentPlan->plan_type != JSTP_JOINED);
|
|
Assert(parentPlan->pathname);
|
|
}
|
|
else
|
|
{
|
|
parentPlan = plan;
|
|
childPlan = NULL;
|
|
}
|
|
|
|
if (strcmp(parentPlan->pathname, *pathName))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("invalid JSON_TABLE plan"),
|
|
errdetail("Path name mismatch: expected %s but %s is given.",
|
|
*pathName, parentPlan->pathname),
|
|
parser_errposition(cxt->pstate, plan->location)));
|
|
|
|
validateJsonTableChildPlan(cxt->pstate, childPlan, columns);
|
|
}
|
|
|
|
/* transform only non-nested columns */
|
|
node = makeParentJsonTableNode(cxt, pathSpec, columns);
|
|
node->name = pstrdup(*pathName);
|
|
|
|
if (childPlan || defaultPlan)
|
|
{
|
|
/* transform recursively nested columns */
|
|
node->child = transformJsonTableChildPlan(cxt, childPlan, columns);
|
|
if (node->child)
|
|
node->outerJoin = !plan || (plan->join_type & JSTPJ_OUTER);
|
|
/* else: default plan case, no children found */
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/*
|
|
* transformJsonTable -
|
|
* Transform a raw JsonTable into TableFunc.
|
|
*
|
|
* Transform the document-generating expression, the row-generating expression,
|
|
* the column-generating expressions, and the default value expressions.
|
|
*/
|
|
ParseNamespaceItem *
|
|
transformJsonTable(ParseState *pstate, JsonTable *jt)
|
|
{
|
|
JsonTableContext cxt;
|
|
TableFunc *tf = makeNode(TableFunc);
|
|
JsonFuncExpr *jfe = makeNode(JsonFuncExpr);
|
|
JsonTablePlan *plan = jt->plan;
|
|
JsonCommon *jscommon;
|
|
char *rootPathName = jt->common->pathname;
|
|
char *rootPath;
|
|
bool is_lateral;
|
|
|
|
cxt.pstate = pstate;
|
|
cxt.table = jt;
|
|
cxt.tablefunc = tf;
|
|
cxt.pathNames = NIL;
|
|
cxt.pathNameId = 0;
|
|
|
|
if (rootPathName)
|
|
registerJsonTableColumn(&cxt, rootPathName);
|
|
|
|
registerAllJsonTableColumns(&cxt, jt->columns);
|
|
|
|
#if 0 /* XXX it' unclear from the standard whether
|
|
* root path name is mandatory or not */
|
|
if (plan && plan->plan_type != JSTP_DEFAULT && !rootPathName)
|
|
{
|
|
/* Assign root path name and create corresponding plan node */
|
|
JsonTablePlan *rootNode = makeNode(JsonTablePlan);
|
|
JsonTablePlan *rootPlan = (JsonTablePlan *)
|
|
makeJsonTableJoinedPlan(JSTPJ_OUTER, (Node *) rootNode,
|
|
(Node *) plan, jt->location);
|
|
|
|
rootPathName = generateJsonTablePathName(&cxt);
|
|
|
|
rootNode->plan_type = JSTP_SIMPLE;
|
|
rootNode->pathname = rootPathName;
|
|
|
|
plan = rootPlan;
|
|
}
|
|
#endif
|
|
|
|
jscommon = copyObject(jt->common);
|
|
jscommon->pathspec = makeStringConst(pstrdup("$"), -1);
|
|
|
|
jfe->op = JSON_TABLE_OP;
|
|
jfe->common = jscommon;
|
|
jfe->on_error = jt->on_error;
|
|
jfe->location = jt->common->location;
|
|
|
|
/*
|
|
* We make lateral_only names of this level visible, whether or not the
|
|
* RangeTableFunc is explicitly marked LATERAL. This is needed for SQL
|
|
* spec compliance and seems useful on convenience grounds for all
|
|
* functions in FROM.
|
|
*
|
|
* (LATERAL can't nest within a single pstate level, so we don't need
|
|
* save/restore logic here.)
|
|
*/
|
|
Assert(!pstate->p_lateral_active);
|
|
pstate->p_lateral_active = true;
|
|
|
|
tf->functype = TFT_JSON_TABLE;
|
|
tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION);
|
|
|
|
cxt.contextItemTypid = exprType(tf->docexpr);
|
|
|
|
if (!IsA(jt->common->pathspec, A_Const) ||
|
|
castNode(A_Const, jt->common->pathspec)->val.node.type != T_String)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("only string constants supported in JSON_TABLE path specification"),
|
|
parser_errposition(pstate,
|
|
exprLocation(jt->common->pathspec))));
|
|
|
|
rootPath = castNode(A_Const, jt->common->pathspec)->val.sval.sval;
|
|
|
|
tf->plan = (Node *) transformJsonTableColumns(&cxt, plan, jt->columns,
|
|
rootPath, &rootPathName,
|
|
jt->common->location);
|
|
|
|
tf->ordinalitycol = -1; /* undefine ordinality column number */
|
|
tf->location = jt->location;
|
|
|
|
pstate->p_lateral_active = false;
|
|
|
|
/*
|
|
* Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
|
|
* there are any lateral cross-references in it.
|
|
*/
|
|
is_lateral = jt->lateral || contain_vars_of_level((Node *) tf, 0);
|
|
|
|
return addRangeTableEntryForTableFunc(pstate,
|
|
tf, jt->alias, is_lateral, true);
|
|
}
|