1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-30 11:03:19 +03:00

JSON_TABLE

This feature allows jsonb data to be treated as a table and thus used in
a FROM clause like other tabular data. Data can be selected from the
jsonb using jsonpath expressions, and hoisted out of nested structures
in the jsonb to form multiple rows, more or less like an outer join.

Nikita Glukhov

Reviewers have included (in no particular order) Andres Freund, Alexander
Korotkov, Pavel Stehule, Andrew Alsup, Erik Rijkers, Zhihong Yu (whose
name I previously misspelled), Himanshu Upadhyaya, Daniel Gustafsson,
Justin Pryzby.

Discussion: https://postgr.es/m/7e2cb85d-24cf-4abb-30a5-1a33715959bd@postgrespro.ru
This commit is contained in:
Andrew Dunstan
2022-04-04 15:36:03 -04:00
parent c42a6fc41d
commit 4e34747c88
31 changed files with 2605 additions and 34 deletions

View File

@ -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 \

View File

@ -676,15 +676,25 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_object_aggregate_constructor
json_array_aggregate_constructor
json_path_specification
json_table
json_table_column_definition
json_table_ordinality_column_definition
json_table_regular_column_definition
json_table_formatted_column_definition
json_table_exists_column_definition
json_table_nested_columns
%type <list> json_name_and_value_list
json_value_expr_list
json_array_aggregate_order_by_clause_opt
json_arguments
json_passing_clause_opt
json_table_columns_clause
json_table_column_definition_list
%type <str> json_table_path_name
json_as_path_name_clause_opt
json_table_column_path_specification_clause_opt
%type <ival> json_encoding
json_encoding_clause_opt
@ -698,6 +708,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_behavior_true
json_behavior_false
json_behavior_unknown
json_behavior_empty
json_behavior_empty_array
json_behavior_empty_object
json_behavior_default
@ -705,6 +716,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_query_behavior
json_exists_error_behavior
json_exists_error_clause_opt
json_table_error_behavior
json_table_error_clause_opt
%type <on_behavior> json_value_on_behavior_clause_opt
json_query_on_behavior_clause_opt
@ -779,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG
JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_VALUE
JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_TABLE JSON_VALUE
KEY KEYS KEEP
@ -790,8 +803,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
MINUTE_P MINVALUE MODE MONTH_P MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE
NORMALIZE NORMALIZED
NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD NO
NONE NORMALIZE NORMALIZED
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
NULLS_P NUMERIC
@ -799,7 +812,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
@ -901,7 +914,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
*/
%nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */
%nonassoc ERROR_P EMPTY_P DEFAULT ABSENT /* JSON error/empty behavior */
%nonassoc FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON
%nonassoc FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON COLUMNS
%nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
@ -926,6 +939,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
*/
%left JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
%nonassoc json_table_column
%nonassoc NESTED
%left PATH
%nonassoc empty_json_unique
%left WITHOUT WITH_LA_UNIQUE
@ -12697,6 +12714,19 @@ table_ref: relation_expr opt_alias_clause
$2->alias = $4;
$$ = (Node *) $2;
}
| json_table opt_alias_clause
{
JsonTable *jt = castNode(JsonTable, $1);
jt->alias = $2;
$$ = (Node *) jt;
}
| LATERAL_P json_table opt_alias_clause
{
JsonTable *jt = castNode(JsonTable, $2);
jt->alias = $3;
jt->lateral = true;
$$ = (Node *) jt;
}
;
@ -13248,6 +13278,8 @@ xmltable_column_option_el:
{ $$ = makeDefElem("is_not_null", (Node *) makeBoolean(true), @1); }
| NULL_P
{ $$ = makeDefElem("is_not_null", (Node *) makeBoolean(false), @1); }
| PATH b_expr
{ $$ = makeDefElem("path", $2, @1); }
;
xml_namespace_list:
@ -15774,6 +15806,10 @@ json_behavior_unknown:
UNKNOWN { $$ = makeJsonBehavior(JSON_BEHAVIOR_UNKNOWN, NULL); }
;
json_behavior_empty:
EMPTY_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); }
;
json_behavior_empty_array:
EMPTY_P ARRAY { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); }
/* non-standard, for Oracle compatibility only */
@ -15888,6 +15924,153 @@ json_query_on_behavior_clause_opt:
{ $$.on_empty = NULL; $$.on_error = NULL; }
;
json_table:
JSON_TABLE '('
json_api_common_syntax
json_table_columns_clause
json_table_error_clause_opt
')'
{
JsonTable *n = makeNode(JsonTable);
n->common = (JsonCommon *) $3;
n->columns = $4;
n->on_error = $5;
n->location = @1;
$$ = (Node *) n;
}
;
json_table_columns_clause:
COLUMNS '(' json_table_column_definition_list ')' { $$ = $3; }
;
json_table_column_definition_list:
json_table_column_definition
{ $$ = list_make1($1); }
| json_table_column_definition_list ',' json_table_column_definition
{ $$ = lappend($1, $3); }
;
json_table_column_definition:
json_table_ordinality_column_definition %prec json_table_column
| json_table_regular_column_definition %prec json_table_column
| json_table_formatted_column_definition %prec json_table_column
| json_table_exists_column_definition %prec json_table_column
| json_table_nested_columns
;
json_table_ordinality_column_definition:
ColId FOR ORDINALITY
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_FOR_ORDINALITY;
n->name = $1;
n->location = @1;
$$ = (Node *) n;
}
;
json_table_regular_column_definition:
ColId Typename
json_table_column_path_specification_clause_opt
json_wrapper_clause_opt
json_quotes_clause_opt
json_value_on_behavior_clause_opt
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_REGULAR;
n->name = $1;
n->typeName = $2;
n->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
n->wrapper = $4; /* JSW_NONE */
n->omit_quotes = $5; /* false */
n->pathspec = $3;
n->on_empty = $6.on_empty;
n->on_error = $6.on_error;
n->location = @1;
$$ = (Node *) n;
}
;
json_table_exists_column_definition:
ColId Typename
EXISTS json_table_column_path_specification_clause_opt
json_exists_error_clause_opt
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_EXISTS;
n->name = $1;
n->typeName = $2;
n->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
n->wrapper = JSW_NONE;
n->omit_quotes = false;
n->pathspec = $4;
n->on_empty = NULL;
n->on_error = $5;
n->location = @1;
$$ = (Node *) n;
}
;
json_table_error_behavior:
json_behavior_error
| json_behavior_empty
;
json_table_error_clause_opt:
json_table_error_behavior ON ERROR_P { $$ = $1; }
| /* EMPTY */ { $$ = NULL; }
;
json_table_column_path_specification_clause_opt:
PATH Sconst { $$ = $2; }
| /* EMPTY */ %prec json_table_column { $$ = NULL; }
;
json_table_formatted_column_definition:
ColId Typename FORMAT json_representation
json_table_column_path_specification_clause_opt
json_wrapper_clause_opt
json_quotes_clause_opt
json_query_on_behavior_clause_opt
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_FORMATTED;
n->name = $1;
n->typeName = $2;
n->format = castNode(JsonFormat, $4);
n->pathspec = $5;
n->wrapper = $6;
if (n->wrapper != JSW_NONE && $7 != JS_QUOTES_UNSPEC)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("SQL/JSON QUOTES behavior must not be specified when WITH WRAPPER is used"),
parser_errposition(@7)));
n->omit_quotes = $7 == JS_QUOTES_OMIT;
n->on_empty = $8.on_empty;
n->on_error = $8.on_error;
n->location = @1;
$$ = (Node *) n;
}
;
json_table_nested_columns:
NESTED path_opt Sconst json_table_columns_clause
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
n->pathspec = $3;
n->columns = $4;
n->location = @1;
$$ = (Node *) n;
}
;
path_opt:
PATH { }
| /* EMPTY */ { }
;
json_returning_clause_opt:
RETURNING Typename
{
@ -16733,6 +16916,7 @@ unreserved_keyword:
| MOVE
| NAME_P
| NAMES
| NESTED
| NEW
| NEXT
| NFC
@ -16766,6 +16950,7 @@ unreserved_keyword:
| PARTITION
| PASSING
| PASSWORD
| PATH
| PLANS
| POLICY
| PRECEDING
@ -16929,6 +17114,7 @@ col_name_keyword:
| JSON_QUERY
| JSON_SCALAR
| JSON_SERIALIZE
| JSON_TABLE
| JSON_VALUE
| LEAST
| NATIONAL
@ -17296,6 +17482,7 @@ bare_label_keyword:
| JSON_QUERY
| JSON_SCALAR
| JSON_SERIALIZE
| JSON_TABLE
| JSON_VALUE
| KEEP
| KEY
@ -17335,6 +17522,7 @@ bare_label_keyword:
| NATIONAL
| NATURAL
| NCHAR
| NESTED
| NEW
| NEXT
| NFC
@ -17378,6 +17566,7 @@ bare_label_keyword:
| PARTITION
| PASSING
| PASSWORD
| PATH
| PLACING
| PLANS
| POLICY

View File

@ -696,7 +696,9 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
char **names;
int colno;
/* Currently only XMLTABLE is supported */
/* Currently only XMLTABLE and JSON_TABLE are supported */
tf->functype = TFT_XMLTABLE;
constructName = "XMLTABLE";
docType = XMLOID;
@ -1100,13 +1102,17 @@ transformFromClauseItem(ParseState *pstate, Node *n,
rtr->rtindex = nsitem->p_rtindex;
return (Node *) rtr;
}
else if (IsA(n, RangeTableFunc))
else if (IsA(n, RangeTableFunc) || IsA(n, JsonTable))
{
/* table function is like a plain relation */
RangeTblRef *rtr;
ParseNamespaceItem *nsitem;
nsitem = transformRangeTableFunc(pstate, (RangeTableFunc *) n);
if (IsA(n, RangeTableFunc))
nsitem = transformRangeTableFunc(pstate, (RangeTableFunc *) n);
else
nsitem = transformJsonTable(pstate, (JsonTable *) n);
*top_nsitem = nsitem;
*namespace = list_make1(nsitem);
rtr = makeNode(RangeTblRef);

View File

@ -4093,7 +4093,7 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
Node *pathspec;
JsonFormatType format;
if (func->common->pathname)
if (func->common->pathname && func->op != JSON_TABLE_OP)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("JSON_TABLE path name is not allowed here"),
@ -4131,14 +4131,19 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
transformJsonPassingArgs(pstate, format, func->common->passing,
&jsexpr->passing_values, &jsexpr->passing_names);
if (func->op != JSON_EXISTS_OP)
if (func->op != JSON_EXISTS_OP && func->op != JSON_TABLE_OP)
jsexpr->on_empty = transformJsonBehavior(pstate, func->on_empty,
JSON_BEHAVIOR_NULL);
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
func->op == JSON_EXISTS_OP ?
JSON_BEHAVIOR_FALSE :
JSON_BEHAVIOR_NULL);
if (func->op == JSON_EXISTS_OP)
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
JSON_BEHAVIOR_FALSE);
else if (func->op == JSON_TABLE_OP)
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
JSON_BEHAVIOR_EMPTY);
else
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
JSON_BEHAVIOR_NULL);
return jsexpr;
}
@ -4439,6 +4444,21 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->result_coercion->expr = NULL;
}
break;
case JSON_TABLE_OP:
jsexpr->returning = makeNode(JsonReturning);
jsexpr->returning->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
jsexpr->returning->typid = exprType(contextItemExpr);
jsexpr->returning->typmod = -1;
if (jsexpr->returning->typid != JSONBOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("JSON_TABLE() is not yet implemented for json type"),
errhint("Try casting the argument to jsonb"),
parser_errposition(pstate, func->location)));
break;
}
if (exprType(contextItemExpr) != JSONBOID)

View File

@ -0,0 +1,466 @@
/*-------------------------------------------------------------------------
*
* parse_jsontable.c
* parsing of JSON_TABLE
*
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/parser/parse_jsontable.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parse_clause.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/json.h"
#include "utils/lsyscache.h"
/* Context for JSON_TABLE transformation */
typedef struct JsonTableContext
{
ParseState *pstate; /* parsing state */
JsonTable *table; /* untransformed node */
TableFunc *tablefunc; /* transformed node */
List *pathNames; /* list of all path and columns names */
Oid contextItemTypid; /* type oid of context item (json/jsonb) */
} JsonTableContext;
static JsonTableParent * transformJsonTableColumns(JsonTableContext *cxt,
List *columns,
char *pathSpec,
int location);
static Node *
makeStringConst(char *str, int location)
{
A_Const *n = makeNode(A_Const);
n->val.node.type = T_String;
n->val.sval.sval = str;
n->location = location;
return (Node *)n;
}
/*
* Transform JSON_TABLE column
* - regular column into JSON_VALUE()
* - FORMAT JSON column into JSON_QUERY()
* - EXISTS column into JSON_EXISTS()
*/
static Node *
transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
List *passingArgs, bool errorOnError)
{
JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
JsonCommon *common = makeNode(JsonCommon);
JsonOutput *output = makeNode(JsonOutput);
JsonPathSpec pathspec;
JsonFormat *default_format;
jfexpr->op =
jtc->coltype == JTC_REGULAR ? JSON_VALUE_OP :
jtc->coltype == JTC_EXISTS ? JSON_EXISTS_OP : JSON_QUERY_OP;
jfexpr->common = common;
jfexpr->output = output;
jfexpr->on_empty = jtc->on_empty;
jfexpr->on_error = jtc->on_error;
if (!jfexpr->on_error && errorOnError)
jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL);
jfexpr->omit_quotes = jtc->omit_quotes;
jfexpr->wrapper = jtc->wrapper;
jfexpr->location = jtc->location;
output->typeName = jtc->typeName;
output->returning = makeNode(JsonReturning);
output->returning->format = jtc->format;
default_format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
common->pathname = NULL;
common->expr = makeJsonValueExpr((Expr *) contextItemExpr, default_format);
common->passing = passingArgs;
if (jtc->pathspec)
pathspec = jtc->pathspec;
else
{
/* Construct default path as '$."column_name"' */
StringInfoData path;
initStringInfo(&path);
appendStringInfoString(&path, "$.");
escape_json(&path, jtc->name);
pathspec = path.data;
}
common->pathspec = makeStringConst(pathspec, -1);
return (Node *) jfexpr;
}
static bool
isJsonTablePathNameDuplicate(JsonTableContext *cxt, const char *pathname)
{
ListCell *lc;
foreach(lc, cxt->pathNames)
{
if (!strcmp(pathname, (const char *) lfirst(lc)))
return true;
}
return false;
}
/* Register the column name in the path name list. */
static void
registerJsonTableColumn(JsonTableContext *cxt, char *colname)
{
if (isJsonTablePathNameDuplicate(cxt, colname))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_ALIAS),
errmsg("duplicate JSON_TABLE column name: %s", colname),
errhint("JSON_TABLE column names must be distinct from one another")));
cxt->pathNames = lappend(cxt->pathNames, colname);
}
/* Recursively register all nested column names in the path name list. */
static void
registerAllJsonTableColumns(JsonTableContext *cxt, List *columns)
{
ListCell *lc;
foreach(lc, columns)
{
JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
if (jtc->coltype == JTC_NESTED)
registerAllJsonTableColumns(cxt, jtc->columns);
else
registerJsonTableColumn(cxt, jtc->name);
}
}
static Node *
transformNestedJsonTableColumn(JsonTableContext *cxt, JsonTableColumn *jtc)
{
JsonTableParent *node;
node = transformJsonTableColumns(cxt, jtc->columns, jtc->pathspec,
jtc->location);
return (Node *) node;
}
static Node *
makeJsonTableSiblingJoin(Node *lnode, Node *rnode)
{
JsonTableSibling *join = makeNode(JsonTableSibling);
join->larg = lnode;
join->rarg = rnode;
return (Node *) join;
}
/*
* Recursively transform child (nested) JSON_TABLE columns.
*
* Child columns are transformed into a binary tree of union-joined
* JsonTableSiblings.
*/
static Node *
transformJsonTableChildColumns(JsonTableContext *cxt, List *columns)
{
Node *res = NULL;
ListCell *lc;
/* transform all nested columns into union join */
foreach(lc, columns)
{
JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
Node *node;
if (jtc->coltype != JTC_NESTED)
continue;
node = transformNestedJsonTableColumn(cxt, jtc);
/* join transformed node with previous sibling nodes */
res = res ? makeJsonTableSiblingJoin(res, node) : node;
}
return res;
}
/* Check whether type is json/jsonb, array, or record. */
static bool
typeIsComposite(Oid typid)
{
char typtype;
if (typid == JSONOID ||
typid == JSONBOID ||
typid == RECORDOID ||
type_is_array(typid))
return true;
typtype = get_typtype(typid);
if (typtype == TYPTYPE_COMPOSITE)
return true;
if (typtype == TYPTYPE_DOMAIN)
return typeIsComposite(getBaseType(typid));
return false;
}
/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
static void
appendJsonTableColumns(JsonTableContext *cxt, List *columns)
{
ListCell *col;
ParseState *pstate = cxt->pstate;
JsonTable *jt = cxt->table;
TableFunc *tf = cxt->tablefunc;
bool errorOnError = jt->on_error &&
jt->on_error->btype == JSON_BEHAVIOR_ERROR;
foreach(col, columns)
{
JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
Oid typid;
int32 typmod;
Node *colexpr;
if (rawc->name)
{
/* make sure column names are unique */
ListCell *colname;
foreach(colname, tf->colnames)
if (!strcmp((const char *) colname, rawc->name))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column name \"%s\" is not unique",
rawc->name),
parser_errposition(pstate, rawc->location)));
tf->colnames = lappend(tf->colnames,
makeString(pstrdup(rawc->name)));
}
/*
* Determine the type and typmod for the new column. FOR
* ORDINALITY columns are INTEGER by standard; the others are
* user-specified.
*/
switch (rawc->coltype)
{
case JTC_FOR_ORDINALITY:
colexpr = NULL;
typid = INT4OID;
typmod = -1;
break;
case JTC_REGULAR:
typenameTypeIdAndMod(pstate, rawc->typeName, &typid, &typmod);
/*
* Use implicit FORMAT JSON for composite types (arrays and
* records)
*/
if (typeIsComposite(typid))
rawc->coltype = JTC_FORMATTED;
else if (rawc->wrapper != JSW_NONE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use WITH WRAPPER clause with scalar columns"),
parser_errposition(pstate, rawc->location)));
else if (rawc->omit_quotes)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use OMIT QUOTES clause with scalar columns"),
parser_errposition(pstate, rawc->location)));
/* FALLTHROUGH */
case JTC_EXISTS:
case JTC_FORMATTED:
{
Node *je;
CaseTestExpr *param = makeNode(CaseTestExpr);
param->collation = InvalidOid;
param->typeId = cxt->contextItemTypid;
param->typeMod = -1;
je = transformJsonTableColumn(rawc, (Node *) param,
NIL, errorOnError);
colexpr = transformExpr(pstate, je, EXPR_KIND_FROM_FUNCTION);
assign_expr_collations(pstate, colexpr);
typid = exprType(colexpr);
typmod = exprTypmod(colexpr);
break;
}
case JTC_NESTED:
continue;
default:
elog(ERROR, "unknown JSON_TABLE column type: %d", rawc->coltype);
break;
}
tf->coltypes = lappend_oid(tf->coltypes, typid);
tf->coltypmods = lappend_int(tf->coltypmods, typmod);
tf->colcollations = lappend_oid(tf->colcollations,
type_is_collatable(typid)
? DEFAULT_COLLATION_OID
: InvalidOid);
tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
}
}
/*
* Create transformed JSON_TABLE parent plan node by appending all non-nested
* columns to the TableFunc node and remembering their indices in the
* colvalexprs list.
*/
static JsonTableParent *
makeParentJsonTableNode(JsonTableContext *cxt, char *pathSpec, List *columns)
{
JsonTableParent *node = makeNode(JsonTableParent);
node->path = makeConst(JSONPATHOID, -1, InvalidOid, -1,
DirectFunctionCall1(jsonpath_in,
CStringGetDatum(pathSpec)),
false, false);
/* save start of column range */
node->colMin = list_length(cxt->tablefunc->colvalexprs);
appendJsonTableColumns(cxt, columns);
/* save end of column range */
node->colMax = list_length(cxt->tablefunc->colvalexprs) - 1;
node->errorOnError =
cxt->table->on_error &&
cxt->table->on_error->btype == JSON_BEHAVIOR_ERROR;
return node;
}
static JsonTableParent *
transformJsonTableColumns(JsonTableContext *cxt, List *columns, char *pathSpec,
int location)
{
JsonTableParent *node;
/* transform only non-nested columns */
node = makeParentJsonTableNode(cxt, pathSpec, columns);
/* transform recursively nested columns */
node->child = transformJsonTableChildColumns(cxt, columns);
return node;
}
/*
* transformJsonTable -
* Transform a raw JsonTable into TableFunc.
*
* Transform the document-generating expression, the row-generating expression,
* the column-generating expressions, and the default value expressions.
*/
ParseNamespaceItem *
transformJsonTable(ParseState *pstate, JsonTable *jt)
{
JsonTableContext cxt;
TableFunc *tf = makeNode(TableFunc);
JsonFuncExpr *jfe = makeNode(JsonFuncExpr);
JsonCommon *jscommon;
char *rootPath;
bool is_lateral;
cxt.pstate = pstate;
cxt.table = jt;
cxt.tablefunc = tf;
cxt.pathNames = NIL;
registerAllJsonTableColumns(&cxt, jt->columns);
jscommon = copyObject(jt->common);
jscommon->pathspec = makeStringConst(pstrdup("$"), -1);
jfe->op = JSON_TABLE_OP;
jfe->common = jscommon;
jfe->on_error = jt->on_error;
jfe->location = jt->common->location;
/*
* We make lateral_only names of this level visible, whether or not the
* RangeTableFunc is explicitly marked LATERAL. This is needed for SQL
* spec compliance and seems useful on convenience grounds for all
* functions in FROM.
*
* (LATERAL can't nest within a single pstate level, so we don't need
* save/restore logic here.)
*/
Assert(!pstate->p_lateral_active);
pstate->p_lateral_active = true;
tf->functype = TFT_JSON_TABLE;
tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION);
cxt.contextItemTypid = exprType(tf->docexpr);
if (!IsA(jt->common->pathspec, A_Const) ||
castNode(A_Const, jt->common->pathspec)->val.node.type != T_String)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("only string constants supported in JSON_TABLE path specification"),
parser_errposition(pstate,
exprLocation(jt->common->pathspec))));
rootPath = castNode(A_Const, jt->common->pathspec)->val.sval.sval;
tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns, rootPath,
jt->common->location);
tf->ordinalitycol = -1; /* undefine ordinality column number */
tf->location = jt->location;
pstate->p_lateral_active = false;
/*
* Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
* there are any lateral cross-references in it.
*/
is_lateral = jt->lateral || contain_vars_of_level((Node *) tf, 0);
return addRangeTableEntryForTableFunc(pstate,
tf, jt->alias, is_lateral, true);
}

View File

@ -1989,7 +1989,8 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
char *refname = alias ? alias->aliasname : pstrdup("xmltable");
char *refname = alias ? alias->aliasname :
pstrdup(tf->functype == TFT_XMLTABLE ? "xmltable" : "json_table");
Alias *eref;
int numaliases;

View File

@ -1993,6 +1993,9 @@ FigureColnameInternal(Node *node, char **name)
case JSON_EXISTS_OP:
*name = "json_exists";
return 2;
case JSON_TABLE_OP:
*name = "json_table";
return 2;
}
break;
default: