422 lines
12 KiB
C
422 lines
12 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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;
|
|
}
|