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:
@ -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 \
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
466
src/backend/parser/parse_jsontable.c
Normal file
466
src/backend/parser/parse_jsontable.c
Normal 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);
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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:
|
||||
|
Reference in New Issue
Block a user