SQL/JSON json_table plan clause
Hi hackers!
This thread is further work continued in [1]/messages/by-id/CA+HiwqE4XTdfb1nW=Ojoy_tQSRhYt-q_kb6i5d4xcKyrLC1Nbg@mail.gmail.com, where Amit Langote
suggested starting discussion on the remaining SQL/JSON feature
'PLAN clause for JSON_TABLE' anew.
We'd like to help with merging SQL/JSON patches into vanilla,
and have adapted PLAN clause to recent changes in JSON_TABLE
function.
While doing this we've found that some tests with the PLAN clause
were incorrect, along with JSON_TABLE behavior with this clause.
We've corrected this behavior, but these corrections required reverting
some removed and heavily refactored code, so we'd be glad for review
and feedback on this patch.
Also, we've noticed that Oracle set the PLAN clause for JSON_TABLE
aside for some reason.
[1]: /messages/by-id/CA+HiwqE4XTdfb1nW=Ojoy_tQSRhYt-q_kb6i5d4xcKyrLC1Nbg@mail.gmail.com
/messages/by-id/CA+HiwqE4XTdfb1nW=Ojoy_tQSRhYt-q_kb6i5d4xcKyrLC1Nbg@mail.gmail.com
--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
https://postgrespro.ru/
Attachments:
v18-0001-JSON-TABLE-PLAN-clause.patchapplication/octet-stream; name=v18-0001-JSON-TABLE-PLAN-clause.patchDownload
From 211ac193e401e2c442ca53b4db1c17b71fd5bd3f Mon Sep 17 00:00:00 2001
From: Nikita Malakhov <n.malakhov@postgrespro.ru>
Date: Fri, 13 Dec 2024 21:25:09 +0300
Subject: [PATCH] [PATCH 1/1] Add the PLAN clauses for JSON_TABLE
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This adds the PLAN clauses for JSON_TABLE, which allow the user
to specify how data from nested paths are joined, allowing
considerable freedom in shaping the tabular output of JSON_TABLE.
PLAN DEFAULT allows the user to specify the global strategies when
dealing with sibling or child nested paths. The is often sufficient
to achieve the necessary goal, and is considerably simpler than the
full PLAN clause, which allows the user to specify the strategy to be
used for each named nested path.
This is a part of the v45-0001-JSON_TABLE.patch from
https://www.postgresql.org/message-id/CA%2BHiwqE1gcPkQhBko%2BUbvVvAtRBaLfOpmHbFrK79pW_5F51Oww%40mail.gmail.com
Original patch was modified according to recent changes in SQL/JSON
JSON_TABLE, and invalid PLAN test results found while adapting
original patch to these changes.
Author: Nikita Glukhov
Author: Teodor Sigaev
Author: Oleg Bartunov
Author: Alexander Korotkov
Author: Andrew Dunstan
Author: Amit Langote
Author: Anton A. Melnikov
Author: Nikita Malakhov
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
---
src/backend/nodes/makefuncs.c | 54 +
src/backend/parser/gram.y | 101 +-
src/backend/parser/parse_jsontable.c | 520 ++++++--
src/backend/utils/adt/jsonpath_exec.c | 248 ++--
src/backend/utils/adt/ruleutils.c | 53 +-
src/include/nodes/makefuncs.h | 5 +
src/include/nodes/parsenodes.h | 83 +-
src/include/nodes/primnodes.h | 6 +-
.../regress/expected/sqljson_jsontable.out | 1107 ++++++++++++++++-
src/test/regress/sql/sqljson_jsontable.sql | 561 ++++++++-
src/tools/pgindent/typedefs.list | 7 +
11 files changed, 2413 insertions(+), 332 deletions(-)
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 7e5df7bea4..5e06926629 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -913,6 +913,60 @@ makeJsonBehavior(JsonBehaviorType btype, Node *expr, int location)
return behavior;
}
+/*
+ * makeJsonTableDefaultPlan -
+ * creates a JsonTablePlanSpec node to represent a "default" JSON_TABLE plan
+ * with given join strategy
+ */
+Node *
+makeJsonTableDefaultPlan(JsonTablePlanJoinType join_type, int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_DEFAULT;
+ n->join_type = join_type;
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonTableSimplePlan -
+ * creates a JsonTablePlanSpec node to represent a "simple" JSON_TABLE plan
+ * for given PATH
+ */
+Node *
+makeJsonTableSimplePlan(char *pathname, int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_SIMPLE;
+ n->pathname = pathname;
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonTableJoinedPlan -
+ * creates a JsonTablePlanSpec node to represent join between the given
+ * pair of plans
+ */
+Node *
+makeJsonTableJoinedPlan(JsonTablePlanJoinType type, Node *plan1, Node *plan2,
+ int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_JOINED;
+ n->join_type = type;
+ n->plan1 = castNode(JsonTablePlanSpec, plan1);
+ n->plan2 = castNode(JsonTablePlanSpec, plan2);
+ n->location = location;
+
+ return (Node *) n;
+}
+
/*
* makeJsonKeyValue -
* creates a JsonKeyValue node
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 67eb96396a..16c4c9f9ce 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -648,6 +648,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_table
json_table_column_definition
json_table_column_path_clause_opt
+ json_table_plan_clause_opt
+ json_table_plan
+ json_table_plan_simple
+ json_table_plan_outer
+ json_table_plan_inner
+ json_table_plan_union
+ json_table_plan_cross
+ json_table_plan_primary
%type <list> json_name_and_value_list
json_value_expr_list
json_array_aggregate_order_by_clause_opt
@@ -659,6 +667,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> json_behavior_type
json_predicate_type_constraint
json_quotes_clause_opt
+ json_table_default_plan_choices
+ json_table_default_plan_inner_outer
+ json_table_default_plan_union_cross
json_wrapper_behavior
%type <boolean> json_key_uniqueness_constraint_opt
json_object_constructor_null_clause_opt
@@ -14217,6 +14228,7 @@ json_table:
json_value_expr ',' a_expr json_table_path_name_opt
json_passing_clause_opt
COLUMNS '(' json_table_column_definition_list ')'
+ json_table_plan_clause_opt
json_on_error_clause_opt
')'
{
@@ -14228,13 +14240,15 @@ json_table:
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"),
+ 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->planspec = (JsonTablePlanSpec *) $12;
+ n->on_error = (JsonBehavior *) $13;
n->location = @1;
$$ = (Node *) n;
}
@@ -14326,8 +14340,7 @@ json_table_column_definition:
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
- n->pathspec = (JsonTablePathSpec *)
- makeJsonTablePathSpec($3, NULL, @3, -1);
+ n->pathspec = makeJsonTablePathSpec($3, NULL, @3, -1);
n->columns = $6;
n->location = @1;
$$ = (Node *) n;
@@ -14338,8 +14351,7 @@ json_table_column_definition:
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
- n->pathspec = (JsonTablePathSpec *)
- makeJsonTablePathSpec($3, $5, @3, @5);
+ n->pathspec = makeJsonTablePathSpec($3, $5, @3, @5);
n->columns = $8;
n->location = @1;
$$ = (Node *) n;
@@ -14358,6 +14370,83 @@ json_table_column_path_clause_opt:
{ $$ = NULL; }
;
+json_table_plan_clause_opt:
+ PLAN '(' json_table_plan ')'
+ { $$ = $3; }
+ | PLAN DEFAULT '(' json_table_default_plan_choices ')'
+ { $$ = makeJsonTableDefaultPlan($4, @1); }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
+json_table_plan:
+ json_table_plan_simple
+ | json_table_plan_outer
+ | json_table_plan_inner
+ | json_table_plan_union
+ | json_table_plan_cross
+ ;
+
+json_table_plan_simple:
+ name
+ { $$ = makeJsonTableSimplePlan($1, @1); }
+ ;
+
+json_table_plan_outer:
+ json_table_plan_simple OUTER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_OUTER, $1, $3, @1); }
+ ;
+
+json_table_plan_inner:
+ json_table_plan_simple INNER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_INNER, $1, $3, @1); }
+ ;
+
+json_table_plan_union:
+ json_table_plan_primary UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_UNION, $1, $3, @1); }
+ | json_table_plan_union UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_UNION, $1, $3, @1); }
+ ;
+
+json_table_plan_cross:
+ json_table_plan_primary CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_CROSS, $1, $3, @1); }
+ | json_table_plan_cross CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_CROSS, $1, $3, @1); }
+ ;
+
+json_table_plan_primary:
+ json_table_plan_simple
+ { $$ = $1; }
+ | '(' json_table_plan ')'
+ {
+ castNode(JsonTablePlanSpec, $2)->location = @1;
+ $$ = $2;
+ }
+ ;
+
+json_table_default_plan_choices:
+ json_table_default_plan_inner_outer
+ { $$ = $1 | JSTP_JOIN_UNION; }
+ | json_table_default_plan_union_cross
+ { $$ = $1 | JSTP_JOIN_OUTER; }
+ | json_table_default_plan_inner_outer ',' json_table_default_plan_union_cross
+ { $$ = $1 | $3; }
+ | json_table_default_plan_union_cross ',' json_table_default_plan_inner_outer
+ { $$ = $1 | $3; }
+ ;
+
+json_table_default_plan_inner_outer:
+ INNER_P { $$ = JSTP_JOIN_INNER; }
+ | OUTER_P { $$ = JSTP_JOIN_OUTER; }
+ ;
+
+json_table_default_plan_union_cross:
+ UNION { $$ = JSTP_JOIN_UNION; }
+ | CROSS { $$ = JSTP_JOIN_CROSS; }
+ ;
+
/*****************************************************************************
*
* Type syntax
diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c
index ecb140e6e7..ab1533f495 100644
--- a/src/backend/parser/parse_jsontable.c
+++ b/src/backend/parser/parse_jsontable.c
@@ -31,33 +31,44 @@
/* Context for transformJsonTableColumns() */
typedef struct JsonTableParseContext
{
- ParseState *pstate;
- JsonTable *jt;
- TableFunc *tf;
+ ParseState *pstate; /* parsing state */
+ JsonTable *jt; /* untransformed node */
+ TableFunc *tf; /* transformed node */
List *pathNames; /* list of all path and columns names */
int pathNameId; /* path name id counter */
} JsonTableParseContext;
static JsonTablePlan *transformJsonTableColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *planspec,
List *columns,
- List *passingArgs,
- JsonTablePathSpec *pathspec);
-static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt,
- List *passingArgs,
- List *columns);
+ List *passing_Args,
+ JsonTablePathSpec * pathspec);
static JsonFuncExpr *transformJsonTableColumn(JsonTableColumn *jtc,
Node *contextItemExpr,
- List *passingArgs);
+ List *passingArgs,
+ bool errorOnError);
static bool isCompositeType(Oid typid);
-static JsonTablePlan *makeJsonTablePathScan(JsonTablePathSpec *pathspec,
- bool errorOnError,
- int colMin, int colMax,
- JsonTablePlan *childplan);
+static JsonTablePlan *makeParentJsonTablePathScan(JsonTableParseContext *cxt,
+ JsonTablePathSpec * pathspec,
+ List *columns,
+ List *passing_Args);
static void CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
List *columns);
static bool LookupPathOrColumnName(JsonTableParseContext *cxt, char *name);
static char *generateJsonTablePathName(JsonTableParseContext *cxt);
-static JsonTablePlan *makeJsonTableSiblingJoin(JsonTablePlan *lplan,
+static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *plan,
+ List *columns,
+ List *passing_Args);
+static void validateJsonTableChildPlan(ParseState *pstate,
+ JsonTablePlanSpec *plan,
+ List *columns);
+static JsonTablePlan *transformJsonTableNestedColumn(JsonTableParseContext *cxt,
+ JsonTableColumn *jtc,
+ JsonTablePlanSpec *planspec,
+ List *passing_Args);
+static JsonTablePlan *makeJsonTableSiblingJoin(bool cross,
+ JsonTablePlan *lplan,
JsonTablePlan *rplan);
/*
@@ -76,6 +87,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
TableFunc *tf;
JsonFuncExpr *jfe;
JsonExpr *je;
+ JsonTablePlanSpec *plan = jt->planspec;
JsonTablePathSpec *rootPathSpec = jt->pathspec;
bool is_lateral;
JsonTableParseContext cxt = {pstate};
@@ -94,8 +106,21 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
parser_errposition(pstate, jt->on_error->location));
cxt.pathNameId = 0;
+
if (rootPathSpec->name == NULL)
+ {
+ if (jt->planspec != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE expression"),
+ errdetail("JSON_TABLE path must contain"
+ " explicit AS pathname specification if"
+ " explicit PLAN clause is used"),
+ parser_errposition(pstate, rootPathSpec->location)));
+
rootPathSpec->name = generateJsonTablePathName(&cxt);
+ }
+
cxt.pathNames = list_make1(rootPathSpec->name);
CheckDuplicateColumnOrPathNames(&cxt, jt->columns);
@@ -135,7 +160,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
*/
cxt.jt = jt;
cxt.tf = tf;
- tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns,
+ tf->plan = (Node *) transformJsonTableColumns(&cxt, plan, jt->columns,
jt->passing,
rootPathSpec);
@@ -246,24 +271,89 @@ generateJsonTablePathName(JsonTableParseContext *cxt)
* their type/collation information to cxt->tf.
*/
static JsonTablePlan *
-transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
- List *passingArgs,
+transformJsonTableColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *planspec,
+ List *columns,
+ List *passing_Args,
JsonTablePathSpec *pathspec)
{
+ JsonTablePathScan *scan;
+ JsonTablePlanSpec *childPlanSpec;
+ bool defaultPlan = planspec == NULL ||
+ planspec->plan_type == JSTP_DEFAULT;
+
+ if (defaultPlan)
+ childPlanSpec = planspec;
+ else
+ {
+ /* validate parent and child plans */
+ JsonTablePlanSpec *parentPlanSpec;
+
+ if (planspec->plan_type == JSTP_JOINED)
+ {
+ if (planspec->join_type != JSTP_JOIN_INNER &&
+ planspec->join_type != JSTP_JOIN_OUTER)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("Expected INNER or OUTER."),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ parentPlanSpec = planspec->plan1;
+ childPlanSpec = planspec->plan2;
+
+ Assert(parentPlanSpec->plan_type != JSTP_JOINED);
+ Assert(parentPlanSpec->pathname);
+ }
+ else
+ {
+ parentPlanSpec = planspec;
+ childPlanSpec = NULL;
+ }
+
+ if (strcmp(parentPlanSpec->pathname, pathspec->name) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan"),
+ errdetail("PATH name mismatch: expected %s but %s is given.",
+ pathspec->name, parentPlanSpec->pathname),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ validateJsonTableChildPlan(cxt->pstate, childPlanSpec, columns);
+ }
+
+ /* transform only non-nested columns */
+ scan = (JsonTablePathScan *) makeParentJsonTablePathScan(cxt, pathspec,
+ columns,
+ passing_Args);
+
+ if (childPlanSpec || defaultPlan)
+ {
+ /* transform recursively nested columns */
+ scan->child = transformJsonTableNestedColumns(cxt, childPlanSpec,
+ columns, passing_Args);
+ if (scan->child)
+ scan->outerJoin = planspec == NULL ||
+ (planspec->join_type & JSTP_JOIN_OUTER);
+ /* else: default plan case, no children found */
+ }
+
+ return (JsonTablePlan *) scan;
+}
+
+/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
+static void
+appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs)
+{
+ ListCell *col;
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;
+ JsonBehavior *on_error = jt->on_error;
+ bool errorOnError = on_error &&
+ on_error->btype == JSON_BEHAVIOR_ERROR;
Oid contextItemTypid = exprType(tf->docexpr);
- int colMin,
- colMax;
- JsonTablePlan *childplan;
-
- /* Start of column range */
- colMin = list_length(tf->colvalexprs);
foreach(col, columns)
{
@@ -273,12 +363,9 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
Oid typcoll = InvalidOid;
Node *colexpr;
- if (rawc->coltype != JTC_NESTED)
- {
- Assert(rawc->name);
+ if (rawc->name)
tf->colnames = lappend(tf->colnames,
makeString(pstrdup(rawc->name)));
- }
/*
* Determine the type and typmod for the new column. FOR ORDINALITY
@@ -324,7 +411,7 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
param->typeMod = -1;
jfe = transformJsonTableColumn(rawc, (Node *) param,
- passingArgs);
+ passingArgs, errorOnError);
colexpr = transformExpr(pstate, (Node *) jfe,
EXPR_KIND_FROM_FUNCTION);
@@ -349,56 +436,21 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
tf->colcollations = lappend_oid(tf->colcollations, typcoll);
tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
}
-
- /* End of column range. */
- if (list_length(tf->colvalexprs) == colMin)
- {
- /* No columns in this Scan beside the nested ones. */
- colMax = colMin = -1;
- }
- else
- colMax = list_length(tf->colvalexprs) - 1;
-
- /* Recursively transform nested columns */
- childplan = transformJsonTableNestedColumns(cxt, passingArgs, columns);
-
- /* Create a "parent" scan responsible for all columns handled above. */
- return makeJsonTablePathScan(pathspec, errorOnError, colMin, colMax,
- childplan);
-}
-
-/*
- * 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:
+ * Transform JSON_TABLE column
* - 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)
+ List *passingArgs, bool errorOnError)
{
- Node *pathspec;
JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
+ Node *pathspec;
+ JsonFormat *default_format;
if (jtc->coltype == JTC_REGULAR)
jfexpr->op = JSON_VALUE_OP;
@@ -406,15 +458,21 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
jfexpr->op = JSON_EXISTS_OP;
else
jfexpr->op = JSON_QUERY_OP;
+ jfexpr->output = makeNode(JsonOutput);
+ jfexpr->on_empty = jtc->on_empty;
+ jfexpr->on_error = jtc->on_error;
+ if (jfexpr->on_error == NULL && errorOnError)
+ jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL, -1);
+ jfexpr->quotes = jtc->quotes;
+ jfexpr->wrapper = jtc->wrapper;
+ jfexpr->location = jtc->location;
- /* Pass the column name so any runtime JsonExpr errors can print it. */
- Assert(jtc->name != NULL);
- jfexpr->column_name = pstrdup(jtc->name);
+ jfexpr->output->typeName = jtc->typeName;
+ jfexpr->output->returning = makeNode(JsonReturning);
+ jfexpr->output->returning->format = jtc->format;
+
+ default_format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
- jfexpr->context_item = makeJsonValueExpr((Expr *) contextItemExpr, NULL,
- makeJsonFormat(JS_FORMAT_DEFAULT,
- JS_ENC_DEFAULT,
- -1));
if (jtc->pathspec)
pathspec = (Node *) jtc->pathspec->string;
else
@@ -429,64 +487,171 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
pathspec = makeStringConst(path.data, -1);
}
+
+ jfexpr->context_item = makeJsonValueExpr((Expr *) contextItemExpr, NULL,
+ default_format);
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;
}
+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->pathspec->name &&
+ !strcmp(jtc->pathspec->name, pathname))
+ return jtc;
+ }
+
+ return NULL;
+}
/*
* Recursively transform nested columns and create child plan(s) that will be
* used to evaluate their row patterns.
+ *
+ * Default plan is transformed into a cross/union join of its nested columns.
+ * Simple and outer/inner plans are transformed into a JsonTablePlan by
+ * finding and transforming corresponding nested column.
+ * Sibling plans are recursively transformed into a JsonTableSiblingJoin.
*/
static JsonTablePlan *
transformJsonTableNestedColumns(JsonTableParseContext *cxt,
- List *passingArgs,
- List *columns)
+ JsonTablePlanSpec *planspec,
+ List *columns,
+ List *passingArgs)
{
- JsonTablePlan *plan = NULL;
- ListCell *lc;
+ JsonTableColumn *jtc = NULL;
- /*
- * If there are multiple NESTED COLUMNS clauses in 'columns', their
- * respective plans will be combined using a "sibling join" plan, which
- * effectively does a UNION of the sets of rows coming from each nested
- * plan.
- */
- foreach(lc, columns)
+ if (!planspec || planspec->plan_type == JSTP_DEFAULT)
{
- JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
- JsonTablePlan *nested;
+ /* unspecified or default plan */
+ JsonTablePlan *plan = NULL;
+ ListCell *lc;
+ bool cross = planspec && (planspec->join_type & JSTP_JOIN_CROSS);
+
+ /*
+ * If there are multiple NESTED COLUMNS clauses in 'columns', their
+ * respective plans will be combined using a "sibling join" plan, which
+ * effectively does a UNION of the sets of rows coming from each nested
+ * plan.
+ */
+ foreach(lc, columns)
+ {
+ JsonTableColumn *col = castNode(JsonTableColumn, lfirst(lc));
+ JsonTablePlan *nested;
- if (jtc->coltype != JTC_NESTED)
- continue;
+ if (col->coltype != JTC_NESTED)
+ continue;
- if (jtc->pathspec->name == NULL)
- jtc->pathspec->name = generateJsonTablePathName(cxt);
+ nested = transformJsonTableNestedColumn(cxt, col, planspec,
+ passingArgs);
- nested = transformJsonTableColumns(cxt, jtc->columns, passingArgs,
- jtc->pathspec);
+ /* Join nested plan with previous sibling nested plans. */
+ if (plan)
+ plan = makeJsonTableSiblingJoin(cross, plan, nested);
+ else
+ plan = nested;
+ }
- if (plan)
- plan = makeJsonTableSiblingJoin(plan, nested);
+ return plan;
+ }
+ else if (planspec->plan_type == JSTP_SIMPLE)
+ {
+ jtc = findNestedJsonTableColumn(columns, planspec->pathname);
+ }
+ else if (planspec->plan_type == JSTP_JOINED)
+ {
+ if (planspec->join_type == JSTP_JOIN_INNER ||
+ planspec->join_type == JSTP_JOIN_OUTER)
+ {
+ Assert(planspec->plan1->plan_type == JSTP_SIMPLE);
+ jtc = findNestedJsonTableColumn(columns, planspec->plan1->pathname);
+ }
else
- plan = nested;
+ {
+ JsonTablePlan *lplan = transformJsonTableNestedColumns(cxt,
+ planspec->plan1,
+ columns,
+ passingArgs);
+ JsonTablePlan *rplan = transformJsonTableNestedColumns(cxt,
+ planspec->plan2,
+ columns,
+ passingArgs);
+
+ return makeJsonTableSiblingJoin(planspec->join_type == JSTP_JOIN_CROSS,
+ lplan, rplan);
+ }
}
+ else
+ elog(ERROR, "invalid JSON_TABLE plan type %d", planspec->plan_type);
+
+ if (!jtc)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("PATH name was %s not found in nested columns list.",
+ planspec->pathname),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ return transformJsonTableNestedColumn(cxt, jtc, planspec, passingArgs);
+}
+
+static JsonTablePlan *
+transformJsonTableNestedColumn(JsonTableParseContext *cxt, JsonTableColumn *jtc,
+ JsonTablePlanSpec *planspec,
+ List *passing_Args)
+{
+ if (jtc->pathspec->name == NULL)
+ {
+ if (cxt->jt->planspec != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE expression"),
+ errdetail("JSON_TABLE path must contain"
+ " explicit AS pathname specification if"
+ " explicit PLAN clause is used"),
+ parser_errposition(cxt->pstate, jtc->location)));
+
+ jtc->pathspec->name = generateJsonTablePathName(cxt);
+ }
+
+ return transformJsonTableColumns(cxt, planspec, jtc->columns,
+ passing_Args,
+ jtc->pathspec);
+}
+
+/*
+ * Create a JsonTablePlan that will perform a join of the rows coming from
+ * 'lplan' and 'rplan'.
+ *
+ * The default way of "joining" the rows is to perform a UNION between the
+ * sets of rows from 'lplan' and 'rplan'.
+ */
+static JsonTablePlan *
+makeJsonTableSiblingJoin(bool cross, JsonTablePlan *lplan, JsonTablePlan *rplan)
+{
+ JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
+
+ join->plan.type = T_JsonTableSiblingJoin;
+ join->lplan = lplan;
+ join->rplan = rplan;
+ join->cross = cross;
- return plan;
+ return (JsonTablePlan *) join;
}
/*
- * Create a JsonTablePlan for given path and ON ERROR behavior.
+ * 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.
*
* colMin and colMin give the range of columns computed by this scan in the
* global flat list of column expressions that will be passed to the
@@ -494,11 +659,11 @@ transformJsonTableNestedColumns(JsonTableParseContext *cxt,
* thus computed by 'childplan'.
*/
static JsonTablePlan *
-makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
- int colMin, int colMax,
- JsonTablePlan *childplan)
+makeParentJsonTablePathScan(JsonTableParseContext *cxt, JsonTablePathSpec * pathspec,
+ List *columns, List *passing_Args)
{
JsonTablePathScan *scan = makeNode(JsonTablePathScan);
+ JsonBehavior *on_error = cxt->jt->on_error;
char *pathstring;
Const *value;
@@ -511,31 +676,128 @@ makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
scan->plan.type = T_JsonTablePathScan;
scan->path = makeJsonTablePath(value, pathspec->name);
- scan->errorOnError = errorOnError;
- scan->child = childplan;
+ /* save start of column range */
+ scan->colMin = list_length(cxt->tf->colvalexprs);
+
+ appendJsonTableColumns(cxt, columns, passing_Args);
+
+ /* End of column range. */
+ if (list_length(cxt->tf->colvalexprs) == scan->colMin)
+ {
+ /* No columns in this Scan beside the nested ones. */
+ scan->colMax = scan->colMin = -1;
+ }
+ else
+ scan->colMax = list_length(cxt->tf->colvalexprs) - 1;
- scan->colMin = colMin;
- scan->colMax = colMax;
+ scan->errorOnError = on_error && on_error->btype == JSON_BEHAVIOR_ERROR;
return (JsonTablePlan *) scan;
}
/*
- * Create a JsonTablePlan that will perform a join of the rows coming from
- * 'lplan' and 'rplan'.
- *
- * The default way of "joining" the rows is to perform a UNION between the
- * sets of rows from 'lplan' and 'rplan'.
+ * Check if the type is "composite" for the purpose of checking whether to use
+ * JSON_VALUE() or JSON_QUERY() for a given JsonTableColumn.typName.
*/
-static JsonTablePlan *
-makeJsonTableSiblingJoin(JsonTablePlan *lplan, JsonTablePlan *rplan)
+static bool
+isCompositeType(Oid typid)
{
- JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
+ char typtype = get_typtype(typid);
- join->plan.type = T_JsonTableSiblingJoin;
- join->lplan = lplan;
- join->rplan = rplan;
+ 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)));
+}
- return (JsonTablePlan *) join;
+/* Collect sibling path names from plan to the specified list. */
+static void
+collectSiblingPathsInJsonTablePlan(JsonTablePlanSpec *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 == JSTP_JOIN_INNER ||
+ plan->join_type == JSTP_JOIN_OUTER)
+ {
+ Assert(plan->plan1->plan_type == JSTP_SIMPLE);
+ *paths = lappend(*paths, plan->plan1->pathname);
+ }
+ else if (plan->join_type == JSTP_JOIN_CROSS ||
+ plan->join_type == JSTP_JOIN_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, JsonTablePlanSpec *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->pathspec->name == NULL)
+ 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->pathspec->name, lfirst(lc2))))
+ break;
+ }
+
+ if (!found)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE specification"),
+ errdetail("PLAN clause for nested path %s was not found.",
+ jtc->pathspec->name),
+ parser_errposition(pstate, jtc->location));
+
+ nchildren++;
+ }
+ }
+
+ if (list_length(siblings) > nchildren)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("PLAN clause contains some extra or duplicate sibling nodes."),
+ parser_errposition(pstate, plan ? plan->location : -1));
}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 391c129ff8..a6e294639e 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -213,6 +213,13 @@ typedef struct JsonTablePlanState
/* Parent plan, if this is a nested plan */
struct JsonTablePlanState *parent;
+
+ /**/
+ bool cross;
+ bool outerJoin;
+ bool advanceNested;
+ bool advanceRight;
+ bool reset;
} JsonTablePlanState;
/* Random number to identify JsonTableExecContext for sanity checking */
@@ -349,7 +356,6 @@ static int compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
bool useTz, bool *cast_error);
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,
@@ -357,15 +363,15 @@ static JsonTablePlanState *JsonTableInitPlan(JsonTableExecContext *cxt,
List *args,
MemoryContext mcxt);
static void JsonTableSetDocument(TableFuncScanState *state, Datum value);
-static void JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item);
+static void JsonTableResetContextItem(JsonTablePlanState *plan, Datum item);
+static void JsonTablePlanReset(JsonTablePlanState *planstate);
+static void JsonTableRescan(JsonTablePlanState *planstate);
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 JsonTablePlanScanNextRow(JsonTablePlanState *planstate);
-static void JsonTableResetNestedPlan(JsonTablePlanState *planstate);
-static bool JsonTablePlanJoinNextRow(JsonTablePlanState *planstate);
static bool JsonTablePlanNextRow(JsonTablePlanState *planstate);
+static bool JsonTablePlanPathNextRow(JsonTablePlanState *planstate);
const TableFuncRoutine JsonbTableRoutine =
{
@@ -4124,6 +4130,7 @@ JsonTableInitOpaque(TableFuncScanState *state, int natts)
* Evaluate JSON_TABLE() PASSING arguments to be passed to the jsonpath
* executor via JsonPathVariables.
*/
+
if (state->passingvalexprs)
{
ListCell *exprlc;
@@ -4155,15 +4162,13 @@ JsonTableInitOpaque(TableFuncScanState *state, int natts)
}
cxt->colplanstates = palloc(sizeof(JsonTablePlanState *) *
- list_length(tf->colvalexprs));
-
+ list_length(tf->colvalexprs));
/*
* Initialize plan for the root path and, recursively, also any child
* plans that compute the NESTED paths.
*/
cxt->rootplanstate = JsonTableInitPlan(cxt, rootplan, NULL, args,
- CurrentMemoryContext);
-
+ CurrentMemoryContext);
state->opaque = cxt;
}
@@ -4201,8 +4206,9 @@ JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
if (IsA(plan, JsonTablePathScan))
{
JsonTablePathScan *scan = (JsonTablePathScan *) plan;
- int i;
+ int i;
+ planstate->outerJoin = scan->outerJoin;
planstate->path = DatumGetJsonPathP(scan->path->value->constvalue);
planstate->args = args;
planstate->mcxt = AllocSetContextCreate(mcxt, "JsonTableExecContext",
@@ -4222,6 +4228,8 @@ JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
{
JsonTableSiblingJoin *join = (JsonTableSiblingJoin *) plan;
+ planstate->cross = join->cross;
+
planstate->left = JsonTableInitPlan(cxt, join->lplan, parentstate,
args, mcxt);
planstate->right = JsonTableInitPlan(cxt, join->rplan, parentstate,
@@ -4241,7 +4249,7 @@ JsonTableSetDocument(TableFuncScanState *state, Datum value)
JsonTableExecContext *cxt =
GetJsonTableExecContext(state, "JsonTableSetDocument");
- JsonTableResetRowPattern(cxt->rootplanstate, value);
+ JsonTableResetContextItem(cxt->rootplanstate, value);
}
/*
@@ -4249,7 +4257,7 @@ JsonTableSetDocument(TableFuncScanState *state, Datum value)
* the given context item
*/
static void
-JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
+JsonTableResetContextItem(JsonTablePlanState *planstate, Datum item)
{
JsonTablePathScan *scan = castNode(JsonTablePathScan, planstate->plan);
MemoryContext oldcxt;
@@ -4276,64 +4284,63 @@ JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
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;
+ JsonTableRescan(planstate);
}
-/*
- * Fetch next row from a JsonTablePlan.
- *
- * Returns false if the plan has run out of rows, true otherwise.
- */
-static bool
-JsonTablePlanNextRow(JsonTablePlanState *planstate)
+/* Recursively reset planstate and its child nodes */
+static void
+JsonTableRescan(JsonTablePlanState *planstate)
{
if (IsA(planstate->plan, JsonTablePathScan))
- return JsonTablePlanScanNextRow(planstate);
- else if (IsA(planstate->plan, JsonTableSiblingJoin))
- return JsonTablePlanJoinNextRow(planstate);
- else
- elog(ERROR, "invalid JsonTablePlan %d", (int) planstate->plan->type);
+ {
+ /* 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;
- Assert(false);
- /* Appease compiler */
- return false;
+ if (planstate->nested)
+ JsonTableRescan(planstate->nested);
+ }
+ else if (IsA(planstate, JsonTableSiblingJoin))
+ {
+ JsonTableRescan(planstate->left);
+ JsonTableRescan(planstate->right);
+ planstate->advanceRight = false;
+ }
+}
+
+/* Recursively set 'reset' flag of planstate and its child nodes */
+static void
+JsonTablePlanReset(JsonTablePlanState *planstate)
+{
+ if (IsA(planstate->plan, JsonTablePathScan))
+ {
+ planstate->reset = true;
+ planstate->advanceNested = false;
+
+ if (planstate->nested)
+ JsonTablePlanReset(planstate->nested);
+ }
+ else if (IsA(planstate->plan, JsonTableSiblingJoin))
+ {
+ JsonTablePlanReset(planstate->left);
+ JsonTablePlanReset(planstate->right);
+ planstate->advanceRight = false;
+ }
}
/*
- * Fetch next row from a JsonTablePlan's path evaluation result and from
- * any child nested path(s).
+ * Fetch next row from a JsonTablePlan's path evaluation result.
*
- * Returns true if any of the paths (this or the nested) has more rows to
- * return.
- *
- * By fetching the nested path(s)'s rows based on the parent row at each
- * level, this essentially joins the rows of different levels. If a nested
- * path at a given level has no matching rows, the columns of that level will
- * compute to NULL, making it an OUTER join.
+ * Returns false if the plan has run out of rows, true otherwise.
*/
static bool
-JsonTablePlanScanNextRow(JsonTablePlanState *planstate)
+JsonTablePlanPathNextRow(JsonTablePlanState *planstate)
{
- JsonbValue *jbv;
+ JsonbValue *jbv = JsonValueListNext(&planstate->found, &planstate->iter);
MemoryContext oldcxt;
- /*
- * If planstate already has an active row and there is a nested plan,
- * check if it has an active row to join with the former.
- */
- if (!planstate->current.isnull)
- {
- if (planstate->nested && JsonTablePlanNextRow(planstate->nested))
- return true;
- }
-
- /* Fetch new row from the list of found values to set as active. */
- jbv = JsonValueListNext(&planstate->found, &planstate->iter);
-
/* End of list? */
if (jbv == NULL)
{
@@ -4354,73 +4361,96 @@ JsonTablePlanScanNextRow(JsonTablePlanState *planstate)
/* Next row! */
planstate->ordinal++;
- /* Process nested plan(s), if any. */
- if (planstate->nested)
- {
- /* Re-evaluate the nested path using the above parent row. */
- JsonTableResetNestedPlan(planstate->nested);
-
- /*
- * Now fetch the nested plan's current row to be joined against the
- * parent row. Any further nested plans' paths will be re-evaluated
- * recursively, level at a time, after setting each nested plan's
- * current row.
- */
- (void) JsonTablePlanNextRow(planstate->nested);
- }
-
- /* There are more rows. */
return true;
}
/*
- * Re-evaluate the row pattern of a nested plan using the new parent row
- * pattern.
+ * Fetch next row from a JsonTablePlan.
+ *
+ * Returns false if the plan has run out of rows, true otherwise.
*/
-static void
-JsonTableResetNestedPlan(JsonTablePlanState *planstate)
+static bool
+JsonTablePlanNextRow(JsonTablePlanState *planstate)
{
- /* This better be a child plan. */
- Assert(planstate->parent != NULL);
- if (IsA(planstate->plan, JsonTablePathScan))
+ if (IsA(planstate->plan, JsonTableSiblingJoin))
{
- JsonTablePlanState *parent = planstate->parent;
+ if (planstate->advanceRight)
+ {
+ /* fetch next inner row */
+ if (JsonTablePlanNextRow(planstate->right))
+ return true;
- if (!parent->current.isnull)
- JsonTableResetRowPattern(planstate, parent->current.value);
+ /* inner rows are exhausted */
+ if (planstate->cross)
+ planstate->advanceRight = false; /* next outer row */
+ else
+ return false; /* end of scan */
+ }
- /*
- * If this plan itself has a child nested plan, it will be reset when
- * the caller calls JsonTablePlanNextRow() on this plan.
- */
+ while (!planstate->advanceRight)
+ {
+ /* fetch next outer row */
+ bool more = JsonTablePlanNextRow(planstate->left);
+
+ if (planstate->cross)
+ {
+ if (!more)
+ return false; /* end of scan */
+
+ JsonTableRescan(planstate->right);
+
+ if (!JsonTablePlanNextRow(planstate->right))
+ continue; /* next outer row */
+
+ planstate->advanceRight = true; /* next inner row */
+ }
+ else if (!more)
+ {
+ if (!JsonTablePlanNextRow(planstate->right))
+ return false; /* end of scan */
+
+ planstate->advanceRight = true; /* next inner row */
+ }
+
+ break;
+ }
}
- else if (IsA(planstate->plan, JsonTableSiblingJoin))
+ else
{
- JsonTableResetNestedPlan(planstate->left);
- JsonTableResetNestedPlan(planstate->right);
- }
-}
+ /* reset context item if requested */
+ if (planstate->reset)
+ {
+ JsonTablePlanState *parent = planstate->parent;
-/*
- * Fetch the next row from a JsonTableSiblingJoin.
- *
- * This is essentially a UNION between the rows from left and right siblings.
- */
-static bool
-JsonTablePlanJoinNextRow(JsonTablePlanState *planstate)
-{
+ Assert(parent != NULL && !parent->current.isnull);
+ JsonTableResetContextItem(planstate, parent->current.value);
+ planstate->reset = false;
+ }
- /* Fetch row from left sibling. */
- if (!JsonTablePlanNextRow(planstate->left))
- {
- /*
- * Left sibling ran out of rows, so start fetching from the right
- * sibling.
- */
- if (!JsonTablePlanNextRow(planstate->right))
+ if (planstate->advanceNested)
+ {
+ /* fetch next nested row */
+ planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
+ if (planstate->advanceNested)
+ return true;
+ }
+
+ for (;;)
{
- /* Right sibling ran out of row, so there are more rows. */
- return false;
+ if (!JsonTablePlanPathNextRow(planstate))
+ return false;
+
+ if (planstate->nested == NULL)
+ break;
+
+ JsonTablePlanReset(planstate->nested);
+ planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
+
+ if (!planstate->advanceNested && !planstate->outerJoin)
+ continue;
+
+ if (planstate->advanceNested || planstate->nested)
+ break;
}
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2194ab3dfa..136a4b2273 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -11908,6 +11908,49 @@ get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
}
}
+/*
+ * get_json_table_plan - Parse back a JSON_TABLE plan
+ */
+static void
+get_json_table_plan(TableFunc *tf, JsonTablePlan *plan, deparse_context *context,
+ bool parenthesize)
+{
+ if (parenthesize)
+ appendStringInfoChar(context->buf, '(');
+
+ if (IsA(plan, JsonTablePathScan))
+ {
+ JsonTablePathScan *s = castNode(JsonTablePathScan, plan);
+
+ appendStringInfoString(context->buf, quote_identifier(s->path->name));
+
+ if (s->child)
+ {
+ appendStringInfoString(context->buf,
+ s->outerJoin ? " OUTER " : " INNER ");
+ get_json_table_plan(tf, s->child, context,
+ IsA(s->child, JsonTableSiblingJoin));
+ }
+ }
+ else if (IsA(plan, JsonTableSiblingJoin))
+ {
+ JsonTableSiblingJoin *j = (JsonTableSiblingJoin *) plan;
+
+ get_json_table_plan(tf, j->lplan, context,
+ IsA(j->lplan, JsonTableSiblingJoin) ||
+ castNode(JsonTablePathScan, j->lplan)->child);
+
+ appendStringInfoString(context->buf, j->cross ? " CROSS " : " UNION ");
+
+ get_json_table_plan(tf, j->rplan, context,
+ IsA(j->rplan, JsonTableSiblingJoin) ||
+ castNode(JsonTablePathScan, j->rplan)->child);
+ }
+
+ if (parenthesize)
+ appendStringInfoChar(context->buf, ')');
+}
+
/*
* get_json_table_columns - Parse back JSON_TABLE columns
*/
@@ -11917,6 +11960,7 @@ get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
bool showimplicit)
{
StringInfo buf = context->buf;
+ JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
ListCell *lc_colname;
ListCell *lc_coltype;
ListCell *lc_coltypmod;
@@ -11996,6 +12040,9 @@ get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
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);
@@ -12073,7 +12120,11 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
get_json_table_columns(tf, castNode(JsonTablePathScan, tf->plan), context,
showimplicit);
- if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY)
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "PLAN ", 0, 0, 0);
+ get_json_table_plan(tf, (JsonTablePlan *)root, context, true);
+
+ if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY)
get_json_behavior(jexpr->on_error, context, "ERROR");
if (PRETTY_INDENT(context))
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 028f8815d1..a1e95c8448 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -124,5 +124,10 @@ extern JsonTablePath *makeJsonTablePath(Const *pathvalue, char *pathname);
extern JsonTablePathSpec *makeJsonTablePathSpec(char *string, char *name,
int string_location,
int name_location);
+extern Node *makeJsonTableDefaultPlan(JsonTablePlanJoinType join_type,
+ int location);
+extern Node *makeJsonTableSimplePlan(char *pathname, int location);
+extern Node *makeJsonTableJoinedPlan(JsonTablePlanJoinType type,
+ Node *plan1, Node *plan2, int location);
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0f9462493e..6fc2c1f20a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1810,27 +1810,10 @@ typedef struct JsonTablePathSpec
Node *string;
char *name;
- ParseLoc name_location;
- ParseLoc location; /* location of 'string' */
+ int name_location;
+ int location; /* location of 'string' */
} JsonTablePathSpec;
-/*
- * JsonTable -
- * untransformed representation of JSON_TABLE
- */
-typedef struct JsonTable
-{
- NodeTag type;
- JsonValueExpr *context_item; /* context item expression */
- JsonTablePathSpec *pathspec; /* JSON path specification */
- List *passing; /* list of PASSING clause arguments, if any */
- List *columns; /* list of JsonTableColumn */
- JsonBehavior *on_error; /* ON ERROR behavior */
- Alias *alias; /* table alias in FROM clause */
- bool lateral; /* does it have LATERAL prefix? */
- ParseLoc location; /* token location, or -1 if unknown */
-} JsonTable;
-
/*
* JsonTableColumnType -
* enumeration of JSON_TABLE column types
@@ -1861,9 +1844,69 @@ typedef struct JsonTableColumn
List *columns; /* nested columns */
JsonBehavior *on_empty; /* ON EMPTY behavior */
JsonBehavior *on_error; /* ON ERROR behavior */
- ParseLoc location; /* token location, or -1 if unknown */
+ int location; /* token location, or -1 if unknown */
} JsonTableColumn;
+/*
+ * JsonTablePlanType -
+ * flags for JSON_TABLE plan node types representation
+ */
+typedef enum JsonTablePlanType
+{
+ JSTP_DEFAULT,
+ JSTP_SIMPLE,
+ JSTP_JOINED,
+} JsonTablePlanType;
+
+/*
+ * JsonTablePlanJoinType -
+ * JSON_TABLE join types for JSTP_JOINED plans
+ */
+typedef enum JsonTablePlanJoinType
+{
+ JSTP_JOIN_INNER = 0x01,
+ JSTP_JOIN_OUTER = 0x02,
+ JSTP_JOIN_CROSS = 0x04,
+ JSTP_JOIN_UNION = 0x08,
+} JsonTablePlanJoinType;
+
+/*
+ * JsonTablePlanSpec -
+ * untransformed representation of JSON_TABLE's PLAN clause
+ */
+typedef struct JsonTablePlanSpec
+{
+ NodeTag type;
+
+ JsonTablePlanType plan_type; /* plan type */
+ JsonTablePlanJoinType join_type; /* join type (for joined plan only) */
+ char *pathname; /* path name (for simple plan only) */
+
+ /* For joined plans */
+ struct JsonTablePlanSpec *plan1; /* first joined plan */
+ struct JsonTablePlanSpec *plan2; /* second joined plan */
+
+ int location; /* token location, or -1 if unknown */
+} JsonTablePlanSpec;
+
+/*
+ * JsonTable -
+ * untransformed representation of JSON_TABLE
+ */
+typedef struct JsonTable
+{
+ NodeTag type;
+ JsonValueExpr *context_item; /* context item expression */
+ JsonTablePathSpec *pathspec; /* JSON path specification */
+ List *passing; /* list of PASSING clause arguments, if any */
+ List *columns; /* list of JsonTableColumn */
+ JsonTablePlanSpec *planspec; /* join plan, if specified */
+ JsonBehavior *on_error; /* ON ERROR behavior */
+ Alias *alias; /* table alias in FROM clause */
+ bool lateral; /* does it have LATERAL prefix? */
+ int location; /* token location, or -1 if unknown */
+} JsonTable;
+
/*
* JsonKeyValue -
* untransformed representation of JSON object key-value pair for
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index b0ef1952e8..ced9e052cc 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1905,6 +1905,7 @@ typedef struct JsonTablePathScan
/* Plan(s) for nested columns, if any. */
JsonTablePlan *child;
+ bool outerJoin; /* outer or inner join for nested columns? */
/*
* 0-based index in TableFunc.colvalexprs of the 1st and the last column
@@ -1924,8 +1925,9 @@ typedef struct JsonTableSiblingJoin
{
JsonTablePlan plan;
- JsonTablePlan *lplan;
- JsonTablePlan *rplan;
+ JsonTablePlan *lplan; /* left join node */
+ JsonTablePlan *rplan; /* right join node */
+ bool cross; /* cross or union join? */
} JsonTableSiblingJoin;
/* ----------------
diff --git a/src/test/regress/expected/sqljson_jsontable.out b/src/test/regress/expected/sqljson_jsontable.out
index d62d32241d..1b0240b148 100644
--- a/src/test/regress/expected/sqljson_jsontable.out
+++ b/src/test/regress/expected/sqljson_jsontable.out
@@ -251,6 +251,29 @@ SELECT * FROM JSON_TABLE(jsonb '{"d1": "foo"}', '$'
(1 row)
-- JSON_TABLE: Test backward parsing
+CREATE VIEW jsonb_table_view1 AS
+SELECT * FROM
+ JSON_TABLE(
+ jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
CREATE VIEW jsonb_table_view2 AS
SELECT * FROM
JSON_TABLE(
@@ -300,6 +323,44 @@ SELECT * FROM
ia int[] PATH '$',
ta text[] PATH '$',
jba jsonb[] PATH '$'));
+\sv jsonb_table_view1
+CREATE OR REPLACE VIEW public.jsonb_table_view1 AS
+ SELECT id,
+ a1,
+ b1,
+ a11,
+ a21,
+ a22
+ FROM JSON_TABLE(
+ 'null'::jsonb, '$[*]' AS json_table_path_0
+ PASSING
+ 1 + 2 AS a,
+ '"foo"'::json AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1
+ COLUMNS (
+ a1 integer PATH '$."a1"',
+ b1 text PATH '$."b1"',
+ NESTED PATH '$[*]' AS "p1 1"
+ COLUMNS (
+ a11 text PATH '$."a11"'
+ )
+ ),
+ NESTED PATH '$[2]' AS p2
+ COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1"
+ COLUMNS (
+ a21 text PATH '$."a21"'
+ ),
+ NESTED PATH '$[*]' AS p22
+ COLUMNS (
+ a22 text PATH '$."a22"'
+ )
+ )
+ )
+ PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR
+ )
\sv jsonb_table_view2
CREATE OR REPLACE VIEW public.jsonb_table_view2 AS
SELECT "int",
@@ -321,6 +382,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view2 AS
"numeric" numeric PATH '$',
domain jsonb_test_domain PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view3
CREATE OR REPLACE VIEW public.jsonb_table_view3 AS
@@ -341,6 +403,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view3 AS
jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES,
jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view4
CREATE OR REPLACE VIEW public.jsonb_table_view4 AS
@@ -359,6 +422,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view4 AS
aaa integer PATH '$."aaa"',
aaa1 integer PATH '$."aaa"'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view5
CREATE OR REPLACE VIEW public.jsonb_table_view5 AS
@@ -375,6 +439,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view5 AS
exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR,
exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view6
CREATE OR REPLACE VIEW public.jsonb_table_view6 AS
@@ -397,45 +462,54 @@ CREATE OR REPLACE VIEW public.jsonb_table_view6 AS
ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES,
jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view1;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Table Function Scan on "json_table"
+ Output: "json_table".id, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR)
+(3 rows)
+
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view2;
- QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".domain
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS ("int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS ("int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view3;
- QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jst text FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jst text FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view4;
- QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (jsb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsbq jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (jsb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsbq jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view5;
- QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".exists1, "json_table".exists2, "json_table".exists3
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view6;
- QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".ia, "json_table".ta, "json_table".jba
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js2 json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER KEEP QUOTES, jsb2q jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, ia integer[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js2 json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER KEEP QUOTES, jsb2q jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, ia integer[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
-- JSON_TABLE() with alias
@@ -448,11 +522,11 @@ SELECT * FROM
"int" int PATH '$',
"text" text PATH '$'
)) json_table_func;
- QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table" json_table_func
Output: id, "int", text
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, "int" integer PATH '$', text text PATH '$'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, "int" integer PATH '$', text text PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, FORMAT JSON, VERBOSE)
@@ -464,24 +538,25 @@ SELECT * FROM
"int" int PATH '$',
"text" text PATH '$'
)) json_table_func;
- QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [ +
- { +
- "Plan": { +
- "Node Type": "Table Function Scan", +
- "Parallel Aware": false, +
- "Async Capable": false, +
- "Table Function Name": "json_table", +
- "Alias": "json_table_func", +
- "Disabled": false, +
- "Output": ["id", "\"int\"", "text"], +
- "Table Function Call": "JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '\"foo\"'::jsonb AS \"b c\" COLUMNS (id FOR ORDINALITY, \"int\" integer PATH '$', text text PATH '$'))"+
- } +
- } +
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [ +
+ { +
+ "Plan": { +
+ "Node Type": "Table Function Scan", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Table Function Name": "json_table", +
+ "Alias": "json_table_func", +
+ "Disabled": false, +
+ "Output": ["id", "\"int\"", "text"], +
+ "Table Function Call": "JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '\"foo\"'::jsonb AS \"b c\" COLUMNS (id FOR ORDINALITY, \"int\" integer PATH '$', text text PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)"+
+ } +
+ } +
]
(1 row)
+DROP VIEW jsonb_table_view1;
DROP VIEW jsonb_table_view2;
DROP VIEW jsonb_table_view3;
DROP VIEW jsonb_table_view4;
@@ -526,11 +601,35 @@ FROM
ON true;
ERROR: invalid input syntax for type integer: "err"
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
-ERROR: no SQL/JSON item found for specified path of column "a"
+ERROR: no SQL/JSON item found for specified path
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: jsonpath member accessor can only be applied to an object
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: no SQL/JSON item found for specified path
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 1
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+ERROR: no SQL/JSON item found for specified path
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON ERROR) ERROR ON ERROR) jt;
ERROR: jsonpath member accessor can only be applied to an object
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
-ERROR: no SQL/JSON item found for specified path of column "a"
+ERROR: no SQL/JSON item found for specified path
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
a
---
@@ -710,10 +809,45 @@ LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '...
^
-- JsonPathQuery() error message mentioning column name
SELECT * FROM JSON_TABLE('{"a": [{"b": "1"}, {"b": "2"}]}', '$' COLUMNS (b json path '$.a[*].b' ERROR ON ERROR));
-ERROR: JSON path expression for column "b" should return single item without wrapper
+ERROR: JSON path expression in JSON_QUERY should return single item without wrapper
HINT: Use the WITH WRAPPER clause to wrap SQL/JSON items into an array.
--- JSON_TABLE: nested paths
--- Duplicate path names
+-- JSON_TABLE: nested paths and plans
+-- Should fail (JSON_TABLE columns must contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' -- AS <path name> required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: jsonb '[]', '$' -- AS <path name> required here
+ ^
+DETAIL: JSON_TABLE path must contain explicit AS pathname specification if explicit PLAN clause is used
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS <path name> required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 4: NESTED PATH '$' COLUMNS ( -- AS <path name> required here
+ ^
+DETAIL: JSON_TABLE path must contain explicit AS pathname specification if explicit PLAN clause is used
+-- Should fail (column names must be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column or path name: a
+LINE 4: a int
+ ^
SELECT * FROM JSON_TABLE(
jsonb '[]', '$' AS a
COLUMNS (
@@ -774,6 +908,194 @@ SELECT * FROM JSON_TABLE(
ERROR: duplicate JSON_TABLE column or path name: a
LINE 10: NESTED PATH '$' AS a
^
+-- JSON_TABLE: plan validation
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 12: PLAN (p1)
+ ^
+DETAIL: PATH name mismatch: expected p0 but p1 is given.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p1 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p1 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+ERROR: invalid JSON_TABLE plan clause
+LINE 12: PLAN (p0 UNION p1 UNION p11)
+ ^
+DETAIL: Expected INNER or OUTER.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 8: NESTED PATH '$' AS p2 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p2 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 5: NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ ^
+DETAIL: PLAN clause for nested path p11 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan clause
+LINE 12: PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+ ^
+DETAIL: PLAN clause contains some extra or duplicate sibling nodes.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 6: NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ^
+DETAIL: PLAN clause for nested path p12 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 9: NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ ^
+DETAIL: PLAN clause for nested path p21 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+ bar | foo | baz
+-----+-----+-----
+(0 rows)
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: jsonb 'null', 'strict $[*]' -- without root path name
+ ^
+DETAIL: JSON_TABLE path must contain explicit AS pathname specification if explicit PLAN clause is used
-- JSON_TABLE: plan execution
CREATE TEMP TABLE jsonb_table_test (js jsonb);
INSERT INTO jsonb_table_test
@@ -785,6 +1107,398 @@ VALUES (
{"x": "4", "b": [1, 2], "c": 123}
]'
);
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-- default plans
+create or replace view outer_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt
+);
+NOTICE: view "outer_union" will be a temporary view
+create or replace view outer_cross as (
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt
+);
+NOTICE: view "outer_cross" will be a temporary view
+create or replace view inner_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, union)
+ ) jt
+);
+NOTICE: view "inner_union" will be a temporary view
+create or replace view inner_cross as (
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, cross)
+ ) jt
+);
+NOTICE: view "inner_cross" will be a temporary view
+select * from inner_cross;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+select * from outer_cross;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+select * from inner_union;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+select * from outer_union;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-----------------------------------------------------
+--inner_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 | 0
+ 2 | 2 | 3 | 20
+(9 rows)
+
+--outer_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 0 | 10
+ 2 | 2 | 0 | 0
+ 2 | 2 | 0 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 2 | 2 | 0 | 10
+ 2 | 2 | 0 | 0
+ 2 | 2 | 0 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+--inner_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 0 |
+ 2 | 2 | 2 |
+ 2 | 2 | 0 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 0 |
+ 3 | 3 | 2 |
+ 4 | -1 | 0 |
+ 4 | -1 | 2 |
+(10 rows)
+
+--outer_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 0 |
+ 2 | 2 | 2 |
+ 2 | 2 | 0 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 0 |
+ 3 | 3 | 2 |
+ 4 | -1 | 0 |
+ 4 | -1 | 2 |
+(11 rows)
+
+----------------------------------------
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(9 rows)
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+(6 rows)
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(9 rows)
+
+SELECT * from JSON_TABLE(jsonb '[1, 2, 3,null, 3]', '$[*]? (@ == 3)' PASSING 3 as x COLUMNS(xx int path '$'));
+ xx
+----
+ 3
+ 3
+(2 rows)
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 3 |
+ 2 | 2 | | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(5 rows)
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ -- nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(9 rows)
+
+-- specific plan (p outer (pb union pc))
select
jt.*
from
@@ -794,25 +1508,290 @@ from
columns (
n for ordinality,
a int path 'lax $.a' default -1 on empty,
- nested path 'strict $.b[*]' as pb columns (b_id for ordinality, b int path '$' ),
- nested path 'strict $.c[*]' as pc columns (c_id for ordinality, c int path '$' )
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
+ plan (p outer (pb union pc))
) jt;
- n | a | b_id | b | c_id | c
----+----+------+---+------+----
- 1 | 1 | | | |
- 2 | 2 | 1 | 1 | |
- 2 | 2 | 2 | 2 | |
- 2 | 2 | 3 | 3 | |
- 2 | 2 | | | 1 | 10
- 2 | 2 | | | 2 |
- 2 | 2 | | | 3 | 20
- 3 | 3 | 1 | 1 | |
- 3 | 3 | 2 | 2 | |
- 4 | -1 | 1 | 1 | |
- 4 | -1 | 2 | 2 | |
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
(11 rows)
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+ n | a | c | b
+---+----+----+---
+ 1 | 1 | |
+ 2 | 2 | 10 |
+ 2 | 2 | |
+ 2 | 2 | 20 |
+ 2 | 2 | | 1
+ 2 | 2 | | 2
+ 2 | 2 | | 3
+ 3 | 3 | | 1
+ 3 | 3 | | 2
+ 4 | -1 | | 1
+ 4 | -1 | | 2
+(11 rows)
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (jsonb
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+ n | a | b | b1 | c | c1 | b
+---+---+--------------+-----+------+----+-----
+ 1 | 1 | [1, 10] | 1 | 1 | | 101
+ 1 | 1 | [1, 10] | 1 | null | | 101
+ 1 | 1 | [1, 10] | 1 | 2 | | 101
+ 1 | 1 | [1, 10] | 10 | 1 | | 110
+ 1 | 1 | [1, 10] | 10 | null | | 110
+ 1 | 1 | [1, 10] | 10 | 2 | | 110
+ 1 | 1 | [2] | 2 | 1 | | 102
+ 1 | 1 | [2] | 2 | null | | 102
+ 1 | 1 | [2] | 2 | 2 | | 102
+ 1 | 1 | [3, 30, 300] | 3 | 1 | | 103
+ 1 | 1 | [3, 30, 300] | 3 | null | | 103
+ 1 | 1 | [3, 30, 300] | 3 | 2 | | 103
+ 1 | 1 | [3, 30, 300] | 30 | 1 | | 130
+ 1 | 1 | [3, 30, 300] | 30 | null | | 130
+ 1 | 1 | [3, 30, 300] | 30 | 2 | | 130
+ 1 | 1 | [3, 30, 300] | 300 | 1 | | 400
+ 1 | 1 | [3, 30, 300] | 300 | null | | 400
+ 1 | 1 | [3, 30, 300] | 300 | 2 | | 400
+ 2 | 2 | | | | |
+ 3 | | | | | |
+(20 rows)
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+ x | y | y | z
+---+---+--------------+---
+ 2 | 1 | [1, 2, 3] | 1
+ 2 | 1 | [1, 2, 3] | 2
+ 2 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [1, 2, 3] | 1
+ 3 | 1 | [1, 2, 3] | 2
+ 3 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [2, 3, 4, 5] | 2
+ 3 | 1 | [2, 3, 4, 5] | 3
+ 3 | 1 | [2, 3, 4, 5] | 4
+ 3 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [1, 2, 3] | 1
+ 4 | 1 | [1, 2, 3] | 2
+ 4 | 1 | [1, 2, 3] | 3
+ 4 | 1 | [2, 3, 4, 5] | 2
+ 4 | 1 | [2, 3, 4, 5] | 3
+ 4 | 1 | [2, 3, 4, 5] | 4
+ 4 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [3, 4, 5, 6] | 3
+ 4 | 1 | [3, 4, 5, 6] | 4
+ 4 | 1 | [3, 4, 5, 6] | 5
+ 4 | 1 | [3, 4, 5, 6] | 6
+ 2 | 2 | [1, 2, 3] | 2
+ 2 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [1, 2, 3] | 2
+ 3 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [2, 3, 4, 5] | 2
+ 3 | 2 | [2, 3, 4, 5] | 3
+ 3 | 2 | [2, 3, 4, 5] | 4
+ 3 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [1, 2, 3] | 2
+ 4 | 2 | [1, 2, 3] | 3
+ 4 | 2 | [2, 3, 4, 5] | 2
+ 4 | 2 | [2, 3, 4, 5] | 3
+ 4 | 2 | [2, 3, 4, 5] | 4
+ 4 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [3, 4, 5, 6] | 3
+ 4 | 2 | [3, 4, 5, 6] | 4
+ 4 | 2 | [3, 4, 5, 6] | 5
+ 4 | 2 | [3, 4, 5, 6] | 6
+ 2 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [2, 3, 4, 5] | 3
+ 3 | 3 | [2, 3, 4, 5] | 4
+ 3 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [1, 2, 3] | 3
+ 4 | 3 | [2, 3, 4, 5] | 3
+ 4 | 3 | [2, 3, 4, 5] | 4
+ 4 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [3, 4, 5, 6] | 3
+ 4 | 3 | [3, 4, 5, 6] | 4
+ 4 | 3 | [3, 4, 5, 6] | 5
+ 4 | 3 | [3, 4, 5, 6] | 6
+(52 rows)
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+ y
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
+ERROR: only string constants are supported in JSON_TABLE path specification
+LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '...
+ ^
-- PASSING arguments are passed to nested paths and their columns' paths
SELECT *
FROM
@@ -912,6 +1891,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view_nested AS
)
)
)
+ PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR
)
DROP VIEW jsonb_table_view_nested;
CREATE TABLE s (js jsonb);
@@ -1103,6 +2083,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view7 AS
)
)
)
+ PLAN (c1 OUTER ((((json_table_path_0 OUTER z22) UNION json_table_path_1) UNION (json_table_path_2 OUTER z1)) UNION (json_table_path_3 OUTER z21))) EMPTY ARRAY ON ERROR
) sub
DROP VIEW jsonb_table_view7;
DROP TABLE s;
@@ -1119,20 +2100,20 @@ LINE 1: ... * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int) NULL ON ER...
^
DETAIL: Only EMPTY [ ARRAY ] or ERROR is allowed in the top-level ON ERROR clause.
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int true on empty));
-ERROR: invalid ON EMPTY behavior for column "a"
+ERROR: invalid ON EMPTY behavior
LINE 1: ...T * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int true on em...
^
-DETAIL: Only ERROR, NULL, or DEFAULT expression is allowed in ON EMPTY for scalar columns.
+DETAIL: Only ERROR, NULL, or DEFAULT expression is allowed in ON EMPTY for JSON_VALUE().
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int omit quotes true on error));
-ERROR: invalid ON ERROR behavior for column "a"
+ERROR: invalid ON ERROR behavior
LINE 1: ...N_TABLE(jsonb '1', '$' COLUMNS (a int omit quotes true on er...
^
-DETAIL: Only ERROR, NULL, EMPTY ARRAY, EMPTY OBJECT, or DEFAULT expression is allowed in ON ERROR for formatted columns.
+DETAIL: Only ERROR, NULL, EMPTY ARRAY, EMPTY OBJECT, or DEFAULT expression is allowed in ON ERROR for JSON_QUERY().
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int exists empty object on error));
-ERROR: invalid ON ERROR behavior for column "a"
+ERROR: invalid ON ERROR behavior
LINE 1: ...M JSON_TABLE(jsonb '1', '$' COLUMNS (a int exists empty obje...
^
-DETAIL: Only ERROR, TRUE, FALSE, or UNKNOWN is allowed in ON ERROR for EXISTS columns.
+DETAIL: Only ERROR, TRUE, FALSE, or UNKNOWN is allowed in ON ERROR for JSON_EXISTS().
-- Test JSON_TABLE() column deparsing -- don't emit default ON ERROR / EMPTY
-- behavior
CREATE VIEW json_table_view8 AS SELECT * from JSON_TABLE('"a"', '$' COLUMNS (a text PATH '$'));
@@ -1144,6 +2125,7 @@ CREATE OR REPLACE VIEW public.json_table_view8 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
CREATE VIEW json_table_view9 AS SELECT * from JSON_TABLE('"a"', '$' COLUMNS (a text PATH '$') ERROR ON ERROR);
\sv json_table_view9;
@@ -1152,8 +2134,9 @@ CREATE OR REPLACE VIEW public.json_table_view9 AS
FROM JSON_TABLE(
'"a"'::text, '$' AS json_table_path_0
COLUMNS (
- a text PATH '$'
- ) ERROR ON ERROR
+ a text PATH '$' NULL ON EMPTY
+ )
+ PLAN (json_table_path_0) ERROR ON ERROR
)
DROP VIEW json_table_view8, json_table_view9;
-- Test JSON_TABLE() deparsing -- don't emit default ON ERROR behavior
@@ -1166,6 +2149,7 @@ CREATE OR REPLACE VIEW public.json_table_view8 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
CREATE VIEW json_table_view9 AS SELECT * from JSON_TABLE('"a"', '$' COLUMNS (a text PATH '$') EMPTY ARRAY ON ERROR);
\sv json_table_view9;
@@ -1176,5 +2160,6 @@ CREATE OR REPLACE VIEW public.json_table_view9 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
DROP VIEW json_table_view8, json_table_view9;
diff --git a/src/test/regress/sql/sqljson_jsontable.sql b/src/test/regress/sql/sqljson_jsontable.sql
index 154eea79c7..d0c88ec6d1 100644
--- a/src/test/regress/sql/sqljson_jsontable.sql
+++ b/src/test/regress/sql/sqljson_jsontable.sql
@@ -134,6 +134,30 @@ SELECT * FROM JSON_TABLE(jsonb '{"d1": "foo"}', '$'
-- JSON_TABLE: Test backward parsing
+CREATE VIEW jsonb_table_view1 AS
+SELECT * FROM
+ JSON_TABLE(
+ jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
+
CREATE VIEW jsonb_table_view2 AS
SELECT * FROM
JSON_TABLE(
@@ -188,12 +212,14 @@ SELECT * FROM
ta text[] PATH '$',
jba jsonb[] PATH '$'));
+\sv jsonb_table_view1
\sv jsonb_table_view2
\sv jsonb_table_view3
\sv jsonb_table_view4
\sv jsonb_table_view5
\sv jsonb_table_view6
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view1;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view2;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view3;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view4;
@@ -221,6 +247,7 @@ SELECT * FROM
"text" text PATH '$'
)) json_table_func;
+DROP VIEW jsonb_table_view1;
DROP VIEW jsonb_table_view2;
DROP VIEW jsonb_table_view3;
DROP VIEW jsonb_table_view4;
@@ -253,6 +280,14 @@ FROM
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
ON true;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON ERROR) ERROR ON ERROR) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
@@ -323,9 +358,36 @@ SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int)
-- JsonPathQuery() error message mentioning column name
SELECT * FROM JSON_TABLE('{"a": [{"b": "1"}, {"b": "2"}]}', '$' COLUMNS (b json path '$.a[*].b' ERROR ON ERROR));
--- JSON_TABLE: nested paths
+-- JSON_TABLE: nested paths and plans
+
+-- Should fail (JSON_TABLE columns must contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' -- AS <path name> required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS <path name> required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+-- Should fail (column names must be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
--- Duplicate path names
SELECT * FROM JSON_TABLE(
jsonb '[]', '$' AS a
COLUMNS (
@@ -376,6 +438,161 @@ SELECT * FROM JSON_TABLE(
)
) jt;
+-- JSON_TABLE: plan validation
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
-- JSON_TABLE: plan execution
@@ -391,6 +608,73 @@ VALUES (
]'
);
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+
+-- default plans
+
+create or replace view outer_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt
+);
+
+create or replace view outer_cross as (
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt
+);
+
+create or replace view inner_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, union)
+ ) jt
+);
+
+create or replace view inner_cross as (
select
jt.*
from
@@ -400,11 +684,280 @@ from
columns (
n for ordinality,
a int path 'lax $.a' default -1 on empty,
- nested path 'strict $.b[*]' as pb columns (b_id for ordinality, b int path '$' ),
- nested path 'strict $.c[*]' as pc columns (c_id for ordinality, c int path '$' )
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, cross)
+ ) jt
+);
+
+
+select * from inner_cross;
+select * from outer_cross;
+select * from inner_union;
+select * from outer_union;
+-----------------------------------------------------
+--inner_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+
+--outer_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
)
+ plan default (outer, cross)
) jt;
+--inner_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+
+--outer_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, union)
+ ) jt;
+
+----------------------------------------
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+
+SELECT * from JSON_TABLE(jsonb '[1, 2, 3,null, 3]', '$[*]? (@ == 3)' PASSING 3 as x COLUMNS(xx int path '$'));
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ -- nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+
+-- specific plan (p outer (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb union pc))
+ ) jt;
+
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (jsonb
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
+
-- PASSING arguments are passed to nested paths and their columns' paths
SELECT *
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ce33e55bf1..cf6aabeec2 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1362,6 +1362,7 @@ JsonPathKeyword
JsonPathParseItem
JsonPathParseResult
JsonPathPredicateCallback
+JsonPathSpec
JsonPathString
JsonPathVariable
JsonQuotes
@@ -1377,9 +1378,15 @@ JsonTableParseContext
JsonTablePath
JsonTablePathScan
JsonTablePathSpec
+JsonTableJoinState
JsonTablePlan
JsonTablePlanRowSource
+JsonTablePlanSpec
JsonTablePlanState
+JsonTablePlanStateType
+JsonTablePlanJoinType
+JsonTablePlanType
+JsonTableScanState
JsonTableSiblingJoin
JsonTokenType
JsonTransformStringValuesAction
--
2.25.1
On 2024-12-17 Tu 10:11 AM, Nikita Malakhov wrote:
Hi hackers!
This thread is further work continued in [1], where Amit Langote
suggested starting discussion on the remaining SQL/JSON feature
'PLAN clause for JSON_TABLE' anew.We'd like to help with merging SQL/JSON patches into vanilla,
and have adapted PLAN clause to recent changes in JSON_TABLE
function.While doing this we've found that some tests with the PLAN clause
were incorrect, along with JSON_TABLE behavior with this clause.
We've corrected this behavior, but these corrections required reverting
some removed and heavily refactored code, so we'd be glad for review
and feedback on this patch.Also, we've noticed that Oracle set the PLAN clause for JSON_TABLE
aside for some reason.
I noticed that several months ago, and I was somewhat surprised. I'm not
sure anyone else has it either. But that doesn't mean we shouldn't have
it. It's in the standard and seems useful, so I'm very glad to see this
effort.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
The following review has been posted through the commitfest application:
make installcheck-world: tested, failed
Implements feature: tested, failed
Spec compliant: not tested
Documentation: not tested
Hi!
Proposed patch tested at Macosx, compiled and build without warnings.
All tests passed without errors.
JSON_TABLE clause PLAN tested, works as intended.
+1 to commit.
Hi hackers!
Patch has been rebased onto the current master.
--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
https://postgrespro.ru/
Attachments:
v19-0001-JSON-TABLE-PLAN-clause.patchapplication/octet-stream; name=v19-0001-JSON-TABLE-PLAN-clause.patchDownload
From 0ff0e773ef0277b2553a4014ca7775bb71001812 Mon Sep 17 00:00:00 2001
From: Nikita Malakhov <n.malakhov@postgrespro.ru>
Date: Sat, 1 Feb 2025 04:11:38 +0300
Subject: [PATCH] Add the PLAN clauses for JSON_TABLE
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This patch adds the PLAN clauses for JSON_TABLE, which allow the user
to specify how data from nested paths are joined, allowing considerable
freedom in shaping the tabular output of JSON_TABLE.
PLAN DEFAULT allows the user to specify the global strategies when
dealing with sibling or child nested paths. The is often sufficient
to achieve the necessary goal, and is considerably simpler than the
full PLAN clause, which allows the user to specify the strategy to be
used for each named nested path.
This is a part of the v45-0001-JSON_TABLE.patch from
https://www.postgresql.org/message-id/CA%2BHiwqE1gcPkQhBko%2BUbvVvAtRBaLfOpmHbFrK79pW_5F51Oww%40mail.gmail.com
Original patch was modified according to recent changes in SQL/JSON
JSON_TABLE, and invalid PLAN test results found while adapting
original patch to these changes.
Author: Nikita Glukhov
Author: Teodor Sigaev
Author: Oleg Bartunov
Author: Alexander Korotkov
Author: Andrew Dunstan
Author: Amit Langote
Author: Anton A. Melnikov
Author: Nikita Malakhov
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
---
src/backend/nodes/makefuncs.c | 54 +
src/backend/parser/gram.y | 101 +-
src/backend/parser/parse_jsontable.c | 520 ++++++--
src/backend/utils/adt/jsonpath_exec.c | 248 ++--
src/backend/utils/adt/ruleutils.c | 53 +-
src/include/nodes/makefuncs.h | 5 +
src/include/nodes/parsenodes.h | 83 +-
src/include/nodes/primnodes.h | 6 +-
.../regress/expected/sqljson_jsontable.out | 1107 ++++++++++++++++-
src/test/regress/sql/sqljson_jsontable.sql | 561 ++++++++-
src/tools/pgindent/typedefs.list | 7 +
11 files changed, 2413 insertions(+), 332 deletions(-)
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 007612563ca..13093b00da3 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -916,6 +916,60 @@ makeJsonBehavior(JsonBehaviorType btype, Node *expr, int location)
return behavior;
}
+/*
+ * makeJsonTableDefaultPlan -
+ * creates a JsonTablePlanSpec node to represent a "default" JSON_TABLE plan
+ * with given join strategy
+ */
+Node *
+makeJsonTableDefaultPlan(JsonTablePlanJoinType join_type, int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_DEFAULT;
+ n->join_type = join_type;
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonTableSimplePlan -
+ * creates a JsonTablePlanSpec node to represent a "simple" JSON_TABLE plan
+ * for given PATH
+ */
+Node *
+makeJsonTableSimplePlan(char *pathname, int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_SIMPLE;
+ n->pathname = pathname;
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonTableJoinedPlan -
+ * creates a JsonTablePlanSpec node to represent join between the given
+ * pair of plans
+ */
+Node *
+makeJsonTableJoinedPlan(JsonTablePlanJoinType type, Node *plan1, Node *plan2,
+ int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_JOINED;
+ n->join_type = type;
+ n->plan1 = castNode(JsonTablePlanSpec, plan1);
+ n->plan2 = castNode(JsonTablePlanSpec, plan2);
+ n->location = location;
+
+ return (Node *) n;
+}
+
/*
* makeJsonKeyValue -
* creates a JsonKeyValue node
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d7f9c00c409..77e47d21601 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -656,6 +656,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_table
json_table_column_definition
json_table_column_path_clause_opt
+ json_table_plan_clause_opt
+ json_table_plan
+ json_table_plan_simple
+ json_table_plan_outer
+ json_table_plan_inner
+ json_table_plan_union
+ json_table_plan_cross
+ json_table_plan_primary
%type <list> json_name_and_value_list
json_value_expr_list
json_array_aggregate_order_by_clause_opt
@@ -667,6 +675,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> json_behavior_type
json_predicate_type_constraint
json_quotes_clause_opt
+ json_table_default_plan_choices
+ json_table_default_plan_inner_outer
+ json_table_default_plan_union_cross
json_wrapper_behavior
%type <boolean> json_key_uniqueness_constraint_opt
json_object_constructor_null_clause_opt
@@ -14285,6 +14296,7 @@ json_table:
json_value_expr ',' a_expr json_table_path_name_opt
json_passing_clause_opt
COLUMNS '(' json_table_column_definition_list ')'
+ json_table_plan_clause_opt
json_on_error_clause_opt
')'
{
@@ -14296,13 +14308,15 @@ json_table:
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"),
+ 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->planspec = (JsonTablePlanSpec *) $12;
+ n->on_error = (JsonBehavior *) $13;
n->location = @1;
$$ = (Node *) n;
}
@@ -14394,8 +14408,7 @@ json_table_column_definition:
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
- n->pathspec = (JsonTablePathSpec *)
- makeJsonTablePathSpec($3, NULL, @3, -1);
+ n->pathspec = makeJsonTablePathSpec($3, NULL, @3, -1);
n->columns = $6;
n->location = @1;
$$ = (Node *) n;
@@ -14406,8 +14419,7 @@ json_table_column_definition:
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
- n->pathspec = (JsonTablePathSpec *)
- makeJsonTablePathSpec($3, $5, @3, @5);
+ n->pathspec = makeJsonTablePathSpec($3, $5, @3, @5);
n->columns = $8;
n->location = @1;
$$ = (Node *) n;
@@ -14426,6 +14438,83 @@ json_table_column_path_clause_opt:
{ $$ = NULL; }
;
+json_table_plan_clause_opt:
+ PLAN '(' json_table_plan ')'
+ { $$ = $3; }
+ | PLAN DEFAULT '(' json_table_default_plan_choices ')'
+ { $$ = makeJsonTableDefaultPlan($4, @1); }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
+json_table_plan:
+ json_table_plan_simple
+ | json_table_plan_outer
+ | json_table_plan_inner
+ | json_table_plan_union
+ | json_table_plan_cross
+ ;
+
+json_table_plan_simple:
+ name
+ { $$ = makeJsonTableSimplePlan($1, @1); }
+ ;
+
+json_table_plan_outer:
+ json_table_plan_simple OUTER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_OUTER, $1, $3, @1); }
+ ;
+
+json_table_plan_inner:
+ json_table_plan_simple INNER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_INNER, $1, $3, @1); }
+ ;
+
+json_table_plan_union:
+ json_table_plan_primary UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_UNION, $1, $3, @1); }
+ | json_table_plan_union UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_UNION, $1, $3, @1); }
+ ;
+
+json_table_plan_cross:
+ json_table_plan_primary CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_CROSS, $1, $3, @1); }
+ | json_table_plan_cross CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_CROSS, $1, $3, @1); }
+ ;
+
+json_table_plan_primary:
+ json_table_plan_simple
+ { $$ = $1; }
+ | '(' json_table_plan ')'
+ {
+ castNode(JsonTablePlanSpec, $2)->location = @1;
+ $$ = $2;
+ }
+ ;
+
+json_table_default_plan_choices:
+ json_table_default_plan_inner_outer
+ { $$ = $1 | JSTP_JOIN_UNION; }
+ | json_table_default_plan_union_cross
+ { $$ = $1 | JSTP_JOIN_OUTER; }
+ | json_table_default_plan_inner_outer ',' json_table_default_plan_union_cross
+ { $$ = $1 | $3; }
+ | json_table_default_plan_union_cross ',' json_table_default_plan_inner_outer
+ { $$ = $1 | $3; }
+ ;
+
+json_table_default_plan_inner_outer:
+ INNER_P { $$ = JSTP_JOIN_INNER; }
+ | OUTER_P { $$ = JSTP_JOIN_OUTER; }
+ ;
+
+json_table_default_plan_union_cross:
+ UNION { $$ = JSTP_JOIN_UNION; }
+ | CROSS { $$ = JSTP_JOIN_CROSS; }
+ ;
+
/*****************************************************************************
*
* Type syntax
diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c
index 13d533b83f3..e8646b780a0 100644
--- a/src/backend/parser/parse_jsontable.c
+++ b/src/backend/parser/parse_jsontable.c
@@ -31,33 +31,44 @@
/* Context for transformJsonTableColumns() */
typedef struct JsonTableParseContext
{
- ParseState *pstate;
- JsonTable *jt;
- TableFunc *tf;
+ ParseState *pstate; /* parsing state */
+ JsonTable *jt; /* untransformed node */
+ TableFunc *tf; /* transformed node */
List *pathNames; /* list of all path and columns names */
int pathNameId; /* path name id counter */
} JsonTableParseContext;
static JsonTablePlan *transformJsonTableColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *planspec,
List *columns,
- List *passingArgs,
- JsonTablePathSpec *pathspec);
-static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt,
- List *passingArgs,
- List *columns);
+ List *passing_Args,
+ JsonTablePathSpec * pathspec);
static JsonFuncExpr *transformJsonTableColumn(JsonTableColumn *jtc,
Node *contextItemExpr,
- List *passingArgs);
+ List *passingArgs,
+ bool errorOnError);
static bool isCompositeType(Oid typid);
-static JsonTablePlan *makeJsonTablePathScan(JsonTablePathSpec *pathspec,
- bool errorOnError,
- int colMin, int colMax,
- JsonTablePlan *childplan);
+static JsonTablePlan *makeParentJsonTablePathScan(JsonTableParseContext *cxt,
+ JsonTablePathSpec * pathspec,
+ List *columns,
+ List *passing_Args);
static void CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
List *columns);
static bool LookupPathOrColumnName(JsonTableParseContext *cxt, char *name);
static char *generateJsonTablePathName(JsonTableParseContext *cxt);
-static JsonTablePlan *makeJsonTableSiblingJoin(JsonTablePlan *lplan,
+static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *plan,
+ List *columns,
+ List *passing_Args);
+static void validateJsonTableChildPlan(ParseState *pstate,
+ JsonTablePlanSpec *plan,
+ List *columns);
+static JsonTablePlan *transformJsonTableNestedColumn(JsonTableParseContext *cxt,
+ JsonTableColumn *jtc,
+ JsonTablePlanSpec *planspec,
+ List *passing_Args);
+static JsonTablePlan *makeJsonTableSiblingJoin(bool cross,
+ JsonTablePlan *lplan,
JsonTablePlan *rplan);
/*
@@ -76,6 +87,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
TableFunc *tf;
JsonFuncExpr *jfe;
JsonExpr *je;
+ JsonTablePlanSpec *plan = jt->planspec;
JsonTablePathSpec *rootPathSpec = jt->pathspec;
bool is_lateral;
JsonTableParseContext cxt = {pstate};
@@ -94,8 +106,21 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
parser_errposition(pstate, jt->on_error->location));
cxt.pathNameId = 0;
+
if (rootPathSpec->name == NULL)
+ {
+ if (jt->planspec != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE expression"),
+ errdetail("JSON_TABLE path must contain"
+ " explicit AS pathname specification if"
+ " explicit PLAN clause is used"),
+ parser_errposition(pstate, rootPathSpec->location)));
+
rootPathSpec->name = generateJsonTablePathName(&cxt);
+ }
+
cxt.pathNames = list_make1(rootPathSpec->name);
CheckDuplicateColumnOrPathNames(&cxt, jt->columns);
@@ -135,7 +160,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
*/
cxt.jt = jt;
cxt.tf = tf;
- tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns,
+ tf->plan = (Node *) transformJsonTableColumns(&cxt, plan, jt->columns,
jt->passing,
rootPathSpec);
@@ -246,24 +271,89 @@ generateJsonTablePathName(JsonTableParseContext *cxt)
* their type/collation information to cxt->tf.
*/
static JsonTablePlan *
-transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
- List *passingArgs,
+transformJsonTableColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *planspec,
+ List *columns,
+ List *passing_Args,
JsonTablePathSpec *pathspec)
{
+ JsonTablePathScan *scan;
+ JsonTablePlanSpec *childPlanSpec;
+ bool defaultPlan = planspec == NULL ||
+ planspec->plan_type == JSTP_DEFAULT;
+
+ if (defaultPlan)
+ childPlanSpec = planspec;
+ else
+ {
+ /* validate parent and child plans */
+ JsonTablePlanSpec *parentPlanSpec;
+
+ if (planspec->plan_type == JSTP_JOINED)
+ {
+ if (planspec->join_type != JSTP_JOIN_INNER &&
+ planspec->join_type != JSTP_JOIN_OUTER)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("Expected INNER or OUTER."),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ parentPlanSpec = planspec->plan1;
+ childPlanSpec = planspec->plan2;
+
+ Assert(parentPlanSpec->plan_type != JSTP_JOINED);
+ Assert(parentPlanSpec->pathname);
+ }
+ else
+ {
+ parentPlanSpec = planspec;
+ childPlanSpec = NULL;
+ }
+
+ if (strcmp(parentPlanSpec->pathname, pathspec->name) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan"),
+ errdetail("PATH name mismatch: expected %s but %s is given.",
+ pathspec->name, parentPlanSpec->pathname),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ validateJsonTableChildPlan(cxt->pstate, childPlanSpec, columns);
+ }
+
+ /* transform only non-nested columns */
+ scan = (JsonTablePathScan *) makeParentJsonTablePathScan(cxt, pathspec,
+ columns,
+ passing_Args);
+
+ if (childPlanSpec || defaultPlan)
+ {
+ /* transform recursively nested columns */
+ scan->child = transformJsonTableNestedColumns(cxt, childPlanSpec,
+ columns, passing_Args);
+ if (scan->child)
+ scan->outerJoin = planspec == NULL ||
+ (planspec->join_type & JSTP_JOIN_OUTER);
+ /* else: default plan case, no children found */
+ }
+
+ return (JsonTablePlan *) scan;
+}
+
+/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
+static void
+appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs)
+{
+ ListCell *col;
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;
+ JsonBehavior *on_error = jt->on_error;
+ bool errorOnError = on_error &&
+ on_error->btype == JSON_BEHAVIOR_ERROR;
Oid contextItemTypid = exprType(tf->docexpr);
- int colMin,
- colMax;
- JsonTablePlan *childplan;
-
- /* Start of column range */
- colMin = list_length(tf->colvalexprs);
foreach(col, columns)
{
@@ -273,12 +363,9 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
Oid typcoll = InvalidOid;
Node *colexpr;
- if (rawc->coltype != JTC_NESTED)
- {
- Assert(rawc->name);
+ if (rawc->name)
tf->colnames = lappend(tf->colnames,
makeString(pstrdup(rawc->name)));
- }
/*
* Determine the type and typmod for the new column. FOR ORDINALITY
@@ -324,7 +411,7 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
param->typeMod = -1;
jfe = transformJsonTableColumn(rawc, (Node *) param,
- passingArgs);
+ passingArgs, errorOnError);
colexpr = transformExpr(pstate, (Node *) jfe,
EXPR_KIND_FROM_FUNCTION);
@@ -349,56 +436,21 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
tf->colcollations = lappend_oid(tf->colcollations, typcoll);
tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
}
-
- /* End of column range. */
- if (list_length(tf->colvalexprs) == colMin)
- {
- /* No columns in this Scan beside the nested ones. */
- colMax = colMin = -1;
- }
- else
- colMax = list_length(tf->colvalexprs) - 1;
-
- /* Recursively transform nested columns */
- childplan = transformJsonTableNestedColumns(cxt, passingArgs, columns);
-
- /* Create a "parent" scan responsible for all columns handled above. */
- return makeJsonTablePathScan(pathspec, errorOnError, colMin, colMax,
- childplan);
-}
-
-/*
- * 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:
+ * Transform JSON_TABLE column
* - 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)
+ List *passingArgs, bool errorOnError)
{
- Node *pathspec;
JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
+ Node *pathspec;
+ JsonFormat *default_format;
if (jtc->coltype == JTC_REGULAR)
jfexpr->op = JSON_VALUE_OP;
@@ -406,15 +458,21 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
jfexpr->op = JSON_EXISTS_OP;
else
jfexpr->op = JSON_QUERY_OP;
+ jfexpr->output = makeNode(JsonOutput);
+ jfexpr->on_empty = jtc->on_empty;
+ jfexpr->on_error = jtc->on_error;
+ if (jfexpr->on_error == NULL && errorOnError)
+ jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL, -1);
+ jfexpr->quotes = jtc->quotes;
+ jfexpr->wrapper = jtc->wrapper;
+ jfexpr->location = jtc->location;
- /* Pass the column name so any runtime JsonExpr errors can print it. */
- Assert(jtc->name != NULL);
- jfexpr->column_name = pstrdup(jtc->name);
+ jfexpr->output->typeName = jtc->typeName;
+ jfexpr->output->returning = makeNode(JsonReturning);
+ jfexpr->output->returning->format = jtc->format;
+
+ default_format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
- jfexpr->context_item = makeJsonValueExpr((Expr *) contextItemExpr, NULL,
- makeJsonFormat(JS_FORMAT_DEFAULT,
- JS_ENC_DEFAULT,
- -1));
if (jtc->pathspec)
pathspec = (Node *) jtc->pathspec->string;
else
@@ -429,64 +487,171 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
pathspec = makeStringConst(path.data, -1);
}
+
+ jfexpr->context_item = makeJsonValueExpr((Expr *) contextItemExpr, NULL,
+ default_format);
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;
}
+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->pathspec->name &&
+ !strcmp(jtc->pathspec->name, pathname))
+ return jtc;
+ }
+
+ return NULL;
+}
/*
* Recursively transform nested columns and create child plan(s) that will be
* used to evaluate their row patterns.
+ *
+ * Default plan is transformed into a cross/union join of its nested columns.
+ * Simple and outer/inner plans are transformed into a JsonTablePlan by
+ * finding and transforming corresponding nested column.
+ * Sibling plans are recursively transformed into a JsonTableSiblingJoin.
*/
static JsonTablePlan *
transformJsonTableNestedColumns(JsonTableParseContext *cxt,
- List *passingArgs,
- List *columns)
+ JsonTablePlanSpec *planspec,
+ List *columns,
+ List *passingArgs)
{
- JsonTablePlan *plan = NULL;
- ListCell *lc;
+ JsonTableColumn *jtc = NULL;
- /*
- * If there are multiple NESTED COLUMNS clauses in 'columns', their
- * respective plans will be combined using a "sibling join" plan, which
- * effectively does a UNION of the sets of rows coming from each nested
- * plan.
- */
- foreach(lc, columns)
+ if (!planspec || planspec->plan_type == JSTP_DEFAULT)
{
- JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
- JsonTablePlan *nested;
+ /* unspecified or default plan */
+ JsonTablePlan *plan = NULL;
+ ListCell *lc;
+ bool cross = planspec && (planspec->join_type & JSTP_JOIN_CROSS);
+
+ /*
+ * If there are multiple NESTED COLUMNS clauses in 'columns', their
+ * respective plans will be combined using a "sibling join" plan, which
+ * effectively does a UNION of the sets of rows coming from each nested
+ * plan.
+ */
+ foreach(lc, columns)
+ {
+ JsonTableColumn *col = castNode(JsonTableColumn, lfirst(lc));
+ JsonTablePlan *nested;
- if (jtc->coltype != JTC_NESTED)
- continue;
+ if (col->coltype != JTC_NESTED)
+ continue;
- if (jtc->pathspec->name == NULL)
- jtc->pathspec->name = generateJsonTablePathName(cxt);
+ nested = transformJsonTableNestedColumn(cxt, col, planspec,
+ passingArgs);
- nested = transformJsonTableColumns(cxt, jtc->columns, passingArgs,
- jtc->pathspec);
+ /* Join nested plan with previous sibling nested plans. */
+ if (plan)
+ plan = makeJsonTableSiblingJoin(cross, plan, nested);
+ else
+ plan = nested;
+ }
- if (plan)
- plan = makeJsonTableSiblingJoin(plan, nested);
+ return plan;
+ }
+ else if (planspec->plan_type == JSTP_SIMPLE)
+ {
+ jtc = findNestedJsonTableColumn(columns, planspec->pathname);
+ }
+ else if (planspec->plan_type == JSTP_JOINED)
+ {
+ if (planspec->join_type == JSTP_JOIN_INNER ||
+ planspec->join_type == JSTP_JOIN_OUTER)
+ {
+ Assert(planspec->plan1->plan_type == JSTP_SIMPLE);
+ jtc = findNestedJsonTableColumn(columns, planspec->plan1->pathname);
+ }
else
- plan = nested;
+ {
+ JsonTablePlan *lplan = transformJsonTableNestedColumns(cxt,
+ planspec->plan1,
+ columns,
+ passingArgs);
+ JsonTablePlan *rplan = transformJsonTableNestedColumns(cxt,
+ planspec->plan2,
+ columns,
+ passingArgs);
+
+ return makeJsonTableSiblingJoin(planspec->join_type == JSTP_JOIN_CROSS,
+ lplan, rplan);
+ }
}
+ else
+ elog(ERROR, "invalid JSON_TABLE plan type %d", planspec->plan_type);
+
+ if (!jtc)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("PATH name was %s not found in nested columns list.",
+ planspec->pathname),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ return transformJsonTableNestedColumn(cxt, jtc, planspec, passingArgs);
+}
+
+static JsonTablePlan *
+transformJsonTableNestedColumn(JsonTableParseContext *cxt, JsonTableColumn *jtc,
+ JsonTablePlanSpec *planspec,
+ List *passing_Args)
+{
+ if (jtc->pathspec->name == NULL)
+ {
+ if (cxt->jt->planspec != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE expression"),
+ errdetail("JSON_TABLE path must contain"
+ " explicit AS pathname specification if"
+ " explicit PLAN clause is used"),
+ parser_errposition(cxt->pstate, jtc->location)));
+
+ jtc->pathspec->name = generateJsonTablePathName(cxt);
+ }
+
+ return transformJsonTableColumns(cxt, planspec, jtc->columns,
+ passing_Args,
+ jtc->pathspec);
+}
+
+/*
+ * Create a JsonTablePlan that will perform a join of the rows coming from
+ * 'lplan' and 'rplan'.
+ *
+ * The default way of "joining" the rows is to perform a UNION between the
+ * sets of rows from 'lplan' and 'rplan'.
+ */
+static JsonTablePlan *
+makeJsonTableSiblingJoin(bool cross, JsonTablePlan *lplan, JsonTablePlan *rplan)
+{
+ JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
+
+ join->plan.type = T_JsonTableSiblingJoin;
+ join->lplan = lplan;
+ join->rplan = rplan;
+ join->cross = cross;
- return plan;
+ return (JsonTablePlan *) join;
}
/*
- * Create a JsonTablePlan for given path and ON ERROR behavior.
+ * 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.
*
* colMin and colMin give the range of columns computed by this scan in the
* global flat list of column expressions that will be passed to the
@@ -494,11 +659,11 @@ transformJsonTableNestedColumns(JsonTableParseContext *cxt,
* thus computed by 'childplan'.
*/
static JsonTablePlan *
-makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
- int colMin, int colMax,
- JsonTablePlan *childplan)
+makeParentJsonTablePathScan(JsonTableParseContext *cxt, JsonTablePathSpec * pathspec,
+ List *columns, List *passing_Args)
{
JsonTablePathScan *scan = makeNode(JsonTablePathScan);
+ JsonBehavior *on_error = cxt->jt->on_error;
char *pathstring;
Const *value;
@@ -511,31 +676,128 @@ makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
scan->plan.type = T_JsonTablePathScan;
scan->path = makeJsonTablePath(value, pathspec->name);
- scan->errorOnError = errorOnError;
- scan->child = childplan;
+ /* save start of column range */
+ scan->colMin = list_length(cxt->tf->colvalexprs);
+
+ appendJsonTableColumns(cxt, columns, passing_Args);
+
+ /* End of column range. */
+ if (list_length(cxt->tf->colvalexprs) == scan->colMin)
+ {
+ /* No columns in this Scan beside the nested ones. */
+ scan->colMax = scan->colMin = -1;
+ }
+ else
+ scan->colMax = list_length(cxt->tf->colvalexprs) - 1;
- scan->colMin = colMin;
- scan->colMax = colMax;
+ scan->errorOnError = on_error && on_error->btype == JSON_BEHAVIOR_ERROR;
return (JsonTablePlan *) scan;
}
/*
- * Create a JsonTablePlan that will perform a join of the rows coming from
- * 'lplan' and 'rplan'.
- *
- * The default way of "joining" the rows is to perform a UNION between the
- * sets of rows from 'lplan' and 'rplan'.
+ * Check if the type is "composite" for the purpose of checking whether to use
+ * JSON_VALUE() or JSON_QUERY() for a given JsonTableColumn.typName.
*/
-static JsonTablePlan *
-makeJsonTableSiblingJoin(JsonTablePlan *lplan, JsonTablePlan *rplan)
+static bool
+isCompositeType(Oid typid)
{
- JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
+ char typtype = get_typtype(typid);
- join->plan.type = T_JsonTableSiblingJoin;
- join->lplan = lplan;
- join->rplan = rplan;
+ 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)));
+}
- return (JsonTablePlan *) join;
+/* Collect sibling path names from plan to the specified list. */
+static void
+collectSiblingPathsInJsonTablePlan(JsonTablePlanSpec *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 == JSTP_JOIN_INNER ||
+ plan->join_type == JSTP_JOIN_OUTER)
+ {
+ Assert(plan->plan1->plan_type == JSTP_SIMPLE);
+ *paths = lappend(*paths, plan->plan1->pathname);
+ }
+ else if (plan->join_type == JSTP_JOIN_CROSS ||
+ plan->join_type == JSTP_JOIN_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, JsonTablePlanSpec *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->pathspec->name == NULL)
+ 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->pathspec->name, lfirst(lc2))))
+ break;
+ }
+
+ if (!found)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE specification"),
+ errdetail("PLAN clause for nested path %s was not found.",
+ jtc->pathspec->name),
+ parser_errposition(pstate, jtc->location));
+
+ nchildren++;
+ }
+ }
+
+ if (list_length(siblings) > nchildren)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("PLAN clause contains some extra or duplicate sibling nodes."),
+ parser_errposition(pstate, plan ? plan->location : -1));
}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index dbab24737ef..f68437e875c 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -213,6 +213,13 @@ typedef struct JsonTablePlanState
/* Parent plan, if this is a nested plan */
struct JsonTablePlanState *parent;
+
+ /**/
+ bool cross;
+ bool outerJoin;
+ bool advanceNested;
+ bool advanceRight;
+ bool reset;
} JsonTablePlanState;
/* Random number to identify JsonTableExecContext for sanity checking */
@@ -349,7 +356,6 @@ static int compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
bool useTz, bool *cast_error);
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,
@@ -357,15 +363,15 @@ static JsonTablePlanState *JsonTableInitPlan(JsonTableExecContext *cxt,
List *args,
MemoryContext mcxt);
static void JsonTableSetDocument(TableFuncScanState *state, Datum value);
-static void JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item);
+static void JsonTableResetContextItem(JsonTablePlanState *plan, Datum item);
+static void JsonTablePlanReset(JsonTablePlanState *planstate);
+static void JsonTableRescan(JsonTablePlanState *planstate);
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 JsonTablePlanScanNextRow(JsonTablePlanState *planstate);
-static void JsonTableResetNestedPlan(JsonTablePlanState *planstate);
-static bool JsonTablePlanJoinNextRow(JsonTablePlanState *planstate);
static bool JsonTablePlanNextRow(JsonTablePlanState *planstate);
+static bool JsonTablePlanPathNextRow(JsonTablePlanState *planstate);
const TableFuncRoutine JsonbTableRoutine =
{
@@ -4124,6 +4130,7 @@ JsonTableInitOpaque(TableFuncScanState *state, int natts)
* Evaluate JSON_TABLE() PASSING arguments to be passed to the jsonpath
* executor via JsonPathVariables.
*/
+
if (state->passingvalexprs)
{
ListCell *exprlc;
@@ -4155,15 +4162,13 @@ JsonTableInitOpaque(TableFuncScanState *state, int natts)
}
cxt->colplanstates = palloc(sizeof(JsonTablePlanState *) *
- list_length(tf->colvalexprs));
-
+ list_length(tf->colvalexprs));
/*
* Initialize plan for the root path and, recursively, also any child
* plans that compute the NESTED paths.
*/
cxt->rootplanstate = JsonTableInitPlan(cxt, rootplan, NULL, args,
- CurrentMemoryContext);
-
+ CurrentMemoryContext);
state->opaque = cxt;
}
@@ -4201,8 +4206,9 @@ JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
if (IsA(plan, JsonTablePathScan))
{
JsonTablePathScan *scan = (JsonTablePathScan *) plan;
- int i;
+ int i;
+ planstate->outerJoin = scan->outerJoin;
planstate->path = DatumGetJsonPathP(scan->path->value->constvalue);
planstate->args = args;
planstate->mcxt = AllocSetContextCreate(mcxt, "JsonTableExecContext",
@@ -4222,6 +4228,8 @@ JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
{
JsonTableSiblingJoin *join = (JsonTableSiblingJoin *) plan;
+ planstate->cross = join->cross;
+
planstate->left = JsonTableInitPlan(cxt, join->lplan, parentstate,
args, mcxt);
planstate->right = JsonTableInitPlan(cxt, join->rplan, parentstate,
@@ -4241,7 +4249,7 @@ JsonTableSetDocument(TableFuncScanState *state, Datum value)
JsonTableExecContext *cxt =
GetJsonTableExecContext(state, "JsonTableSetDocument");
- JsonTableResetRowPattern(cxt->rootplanstate, value);
+ JsonTableResetContextItem(cxt->rootplanstate, value);
}
/*
@@ -4249,7 +4257,7 @@ JsonTableSetDocument(TableFuncScanState *state, Datum value)
* the given context item
*/
static void
-JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
+JsonTableResetContextItem(JsonTablePlanState *planstate, Datum item)
{
JsonTablePathScan *scan = castNode(JsonTablePathScan, planstate->plan);
MemoryContext oldcxt;
@@ -4276,64 +4284,63 @@ JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
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;
+ JsonTableRescan(planstate);
}
-/*
- * Fetch next row from a JsonTablePlan.
- *
- * Returns false if the plan has run out of rows, true otherwise.
- */
-static bool
-JsonTablePlanNextRow(JsonTablePlanState *planstate)
+/* Recursively reset planstate and its child nodes */
+static void
+JsonTableRescan(JsonTablePlanState *planstate)
{
if (IsA(planstate->plan, JsonTablePathScan))
- return JsonTablePlanScanNextRow(planstate);
- else if (IsA(planstate->plan, JsonTableSiblingJoin))
- return JsonTablePlanJoinNextRow(planstate);
- else
- elog(ERROR, "invalid JsonTablePlan %d", (int) planstate->plan->type);
+ {
+ /* 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;
- Assert(false);
- /* Appease compiler */
- return false;
+ if (planstate->nested)
+ JsonTableRescan(planstate->nested);
+ }
+ else if (IsA(planstate, JsonTableSiblingJoin))
+ {
+ JsonTableRescan(planstate->left);
+ JsonTableRescan(planstate->right);
+ planstate->advanceRight = false;
+ }
+}
+
+/* Recursively set 'reset' flag of planstate and its child nodes */
+static void
+JsonTablePlanReset(JsonTablePlanState *planstate)
+{
+ if (IsA(planstate->plan, JsonTablePathScan))
+ {
+ planstate->reset = true;
+ planstate->advanceNested = false;
+
+ if (planstate->nested)
+ JsonTablePlanReset(planstate->nested);
+ }
+ else if (IsA(planstate->plan, JsonTableSiblingJoin))
+ {
+ JsonTablePlanReset(planstate->left);
+ JsonTablePlanReset(planstate->right);
+ planstate->advanceRight = false;
+ }
}
/*
- * Fetch next row from a JsonTablePlan's path evaluation result and from
- * any child nested path(s).
+ * Fetch next row from a JsonTablePlan's path evaluation result.
*
- * Returns true if any of the paths (this or the nested) has more rows to
- * return.
- *
- * By fetching the nested path(s)'s rows based on the parent row at each
- * level, this essentially joins the rows of different levels. If a nested
- * path at a given level has no matching rows, the columns of that level will
- * compute to NULL, making it an OUTER join.
+ * Returns false if the plan has run out of rows, true otherwise.
*/
static bool
-JsonTablePlanScanNextRow(JsonTablePlanState *planstate)
+JsonTablePlanPathNextRow(JsonTablePlanState *planstate)
{
- JsonbValue *jbv;
+ JsonbValue *jbv = JsonValueListNext(&planstate->found, &planstate->iter);
MemoryContext oldcxt;
- /*
- * If planstate already has an active row and there is a nested plan,
- * check if it has an active row to join with the former.
- */
- if (!planstate->current.isnull)
- {
- if (planstate->nested && JsonTablePlanNextRow(planstate->nested))
- return true;
- }
-
- /* Fetch new row from the list of found values to set as active. */
- jbv = JsonValueListNext(&planstate->found, &planstate->iter);
-
/* End of list? */
if (jbv == NULL)
{
@@ -4354,73 +4361,96 @@ JsonTablePlanScanNextRow(JsonTablePlanState *planstate)
/* Next row! */
planstate->ordinal++;
- /* Process nested plan(s), if any. */
- if (planstate->nested)
- {
- /* Re-evaluate the nested path using the above parent row. */
- JsonTableResetNestedPlan(planstate->nested);
-
- /*
- * Now fetch the nested plan's current row to be joined against the
- * parent row. Any further nested plans' paths will be re-evaluated
- * recursively, level at a time, after setting each nested plan's
- * current row.
- */
- (void) JsonTablePlanNextRow(planstate->nested);
- }
-
- /* There are more rows. */
return true;
}
/*
- * Re-evaluate the row pattern of a nested plan using the new parent row
- * pattern.
+ * Fetch next row from a JsonTablePlan.
+ *
+ * Returns false if the plan has run out of rows, true otherwise.
*/
-static void
-JsonTableResetNestedPlan(JsonTablePlanState *planstate)
+static bool
+JsonTablePlanNextRow(JsonTablePlanState *planstate)
{
- /* This better be a child plan. */
- Assert(planstate->parent != NULL);
- if (IsA(planstate->plan, JsonTablePathScan))
+ if (IsA(planstate->plan, JsonTableSiblingJoin))
{
- JsonTablePlanState *parent = planstate->parent;
+ if (planstate->advanceRight)
+ {
+ /* fetch next inner row */
+ if (JsonTablePlanNextRow(planstate->right))
+ return true;
- if (!parent->current.isnull)
- JsonTableResetRowPattern(planstate, parent->current.value);
+ /* inner rows are exhausted */
+ if (planstate->cross)
+ planstate->advanceRight = false; /* next outer row */
+ else
+ return false; /* end of scan */
+ }
- /*
- * If this plan itself has a child nested plan, it will be reset when
- * the caller calls JsonTablePlanNextRow() on this plan.
- */
+ while (!planstate->advanceRight)
+ {
+ /* fetch next outer row */
+ bool more = JsonTablePlanNextRow(planstate->left);
+
+ if (planstate->cross)
+ {
+ if (!more)
+ return false; /* end of scan */
+
+ JsonTableRescan(planstate->right);
+
+ if (!JsonTablePlanNextRow(planstate->right))
+ continue; /* next outer row */
+
+ planstate->advanceRight = true; /* next inner row */
+ }
+ else if (!more)
+ {
+ if (!JsonTablePlanNextRow(planstate->right))
+ return false; /* end of scan */
+
+ planstate->advanceRight = true; /* next inner row */
+ }
+
+ break;
+ }
}
- else if (IsA(planstate->plan, JsonTableSiblingJoin))
+ else
{
- JsonTableResetNestedPlan(planstate->left);
- JsonTableResetNestedPlan(planstate->right);
- }
-}
+ /* reset context item if requested */
+ if (planstate->reset)
+ {
+ JsonTablePlanState *parent = planstate->parent;
-/*
- * Fetch the next row from a JsonTableSiblingJoin.
- *
- * This is essentially a UNION between the rows from left and right siblings.
- */
-static bool
-JsonTablePlanJoinNextRow(JsonTablePlanState *planstate)
-{
+ Assert(parent != NULL && !parent->current.isnull);
+ JsonTableResetContextItem(planstate, parent->current.value);
+ planstate->reset = false;
+ }
- /* Fetch row from left sibling. */
- if (!JsonTablePlanNextRow(planstate->left))
- {
- /*
- * Left sibling ran out of rows, so start fetching from the right
- * sibling.
- */
- if (!JsonTablePlanNextRow(planstate->right))
+ if (planstate->advanceNested)
+ {
+ /* fetch next nested row */
+ planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
+ if (planstate->advanceNested)
+ return true;
+ }
+
+ for (;;)
{
- /* Right sibling ran out of row, so there are more rows. */
- return false;
+ if (!JsonTablePlanPathNextRow(planstate))
+ return false;
+
+ if (planstate->nested == NULL)
+ break;
+
+ JsonTablePlanReset(planstate->nested);
+ planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
+
+ if (!planstate->advanceNested && !planstate->outerJoin)
+ continue;
+
+ if (planstate->advanceNested || planstate->nested)
+ break;
}
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 54dad975553..f4df1623f89 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -11980,6 +11980,49 @@ get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
}
}
+/*
+ * get_json_table_plan - Parse back a JSON_TABLE plan
+ */
+static void
+get_json_table_plan(TableFunc *tf, JsonTablePlan *plan, deparse_context *context,
+ bool parenthesize)
+{
+ if (parenthesize)
+ appendStringInfoChar(context->buf, '(');
+
+ if (IsA(plan, JsonTablePathScan))
+ {
+ JsonTablePathScan *s = castNode(JsonTablePathScan, plan);
+
+ appendStringInfoString(context->buf, quote_identifier(s->path->name));
+
+ if (s->child)
+ {
+ appendStringInfoString(context->buf,
+ s->outerJoin ? " OUTER " : " INNER ");
+ get_json_table_plan(tf, s->child, context,
+ IsA(s->child, JsonTableSiblingJoin));
+ }
+ }
+ else if (IsA(plan, JsonTableSiblingJoin))
+ {
+ JsonTableSiblingJoin *j = (JsonTableSiblingJoin *) plan;
+
+ get_json_table_plan(tf, j->lplan, context,
+ IsA(j->lplan, JsonTableSiblingJoin) ||
+ castNode(JsonTablePathScan, j->lplan)->child);
+
+ appendStringInfoString(context->buf, j->cross ? " CROSS " : " UNION ");
+
+ get_json_table_plan(tf, j->rplan, context,
+ IsA(j->rplan, JsonTableSiblingJoin) ||
+ castNode(JsonTablePathScan, j->rplan)->child);
+ }
+
+ if (parenthesize)
+ appendStringInfoChar(context->buf, ')');
+}
+
/*
* get_json_table_columns - Parse back JSON_TABLE columns
*/
@@ -11989,6 +12032,7 @@ get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
bool showimplicit)
{
StringInfo buf = context->buf;
+ JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
ListCell *lc_colname;
ListCell *lc_coltype;
ListCell *lc_coltypmod;
@@ -12068,6 +12112,9 @@ get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
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);
@@ -12145,7 +12192,11 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
get_json_table_columns(tf, castNode(JsonTablePathScan, tf->plan), context,
showimplicit);
- if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY)
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "PLAN ", 0, 0, 0);
+ get_json_table_plan(tf, (JsonTablePlan *)root, context, true);
+
+ if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY)
get_json_behavior(jexpr->on_error, context, "ERROR");
if (PRETTY_INDENT(context))
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 5473ce9a288..17d761ef142 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -124,5 +124,10 @@ extern JsonTablePath *makeJsonTablePath(Const *pathvalue, char *pathname);
extern JsonTablePathSpec *makeJsonTablePathSpec(char *string, char *name,
int string_location,
int name_location);
+extern Node *makeJsonTableDefaultPlan(JsonTablePlanJoinType join_type,
+ int location);
+extern Node *makeJsonTableSimplePlan(char *pathname, int location);
+extern Node *makeJsonTableJoinedPlan(JsonTablePlanJoinType type,
+ Node *plan1, Node *plan2, int location);
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ffe155ee20e..0916bfa7e04 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1854,27 +1854,10 @@ typedef struct JsonTablePathSpec
Node *string;
char *name;
- ParseLoc name_location;
- ParseLoc location; /* location of 'string' */
+ int name_location;
+ int location; /* location of 'string' */
} JsonTablePathSpec;
-/*
- * JsonTable -
- * untransformed representation of JSON_TABLE
- */
-typedef struct JsonTable
-{
- NodeTag type;
- JsonValueExpr *context_item; /* context item expression */
- JsonTablePathSpec *pathspec; /* JSON path specification */
- List *passing; /* list of PASSING clause arguments, if any */
- List *columns; /* list of JsonTableColumn */
- JsonBehavior *on_error; /* ON ERROR behavior */
- Alias *alias; /* table alias in FROM clause */
- bool lateral; /* does it have LATERAL prefix? */
- ParseLoc location; /* token location, or -1 if unknown */
-} JsonTable;
-
/*
* JsonTableColumnType -
* enumeration of JSON_TABLE column types
@@ -1905,9 +1888,69 @@ typedef struct JsonTableColumn
List *columns; /* nested columns */
JsonBehavior *on_empty; /* ON EMPTY behavior */
JsonBehavior *on_error; /* ON ERROR behavior */
- ParseLoc location; /* token location, or -1 if unknown */
+ int location; /* token location, or -1 if unknown */
} JsonTableColumn;
+/*
+ * JsonTablePlanType -
+ * flags for JSON_TABLE plan node types representation
+ */
+typedef enum JsonTablePlanType
+{
+ JSTP_DEFAULT,
+ JSTP_SIMPLE,
+ JSTP_JOINED,
+} JsonTablePlanType;
+
+/*
+ * JsonTablePlanJoinType -
+ * JSON_TABLE join types for JSTP_JOINED plans
+ */
+typedef enum JsonTablePlanJoinType
+{
+ JSTP_JOIN_INNER = 0x01,
+ JSTP_JOIN_OUTER = 0x02,
+ JSTP_JOIN_CROSS = 0x04,
+ JSTP_JOIN_UNION = 0x08,
+} JsonTablePlanJoinType;
+
+/*
+ * JsonTablePlanSpec -
+ * untransformed representation of JSON_TABLE's PLAN clause
+ */
+typedef struct JsonTablePlanSpec
+{
+ NodeTag type;
+
+ JsonTablePlanType plan_type; /* plan type */
+ JsonTablePlanJoinType join_type; /* join type (for joined plan only) */
+ char *pathname; /* path name (for simple plan only) */
+
+ /* For joined plans */
+ struct JsonTablePlanSpec *plan1; /* first joined plan */
+ struct JsonTablePlanSpec *plan2; /* second joined plan */
+
+ int location; /* token location, or -1 if unknown */
+} JsonTablePlanSpec;
+
+/*
+ * JsonTable -
+ * untransformed representation of JSON_TABLE
+ */
+typedef struct JsonTable
+{
+ NodeTag type;
+ JsonValueExpr *context_item; /* context item expression */
+ JsonTablePathSpec *pathspec; /* JSON path specification */
+ List *passing; /* list of PASSING clause arguments, if any */
+ List *columns; /* list of JsonTableColumn */
+ JsonTablePlanSpec *planspec; /* join plan, if specified */
+ JsonBehavior *on_error; /* ON ERROR behavior */
+ Alias *alias; /* table alias in FROM clause */
+ bool lateral; /* does it have LATERAL prefix? */
+ int location; /* token location, or -1 if unknown */
+} JsonTable;
+
/*
* JsonKeyValue -
* untransformed representation of JSON object key-value pair for
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 839e71d52f4..806aeb35d9c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1910,6 +1910,7 @@ typedef struct JsonTablePathScan
/* Plan(s) for nested columns, if any. */
JsonTablePlan *child;
+ bool outerJoin; /* outer or inner join for nested columns? */
/*
* 0-based index in TableFunc.colvalexprs of the 1st and the last column
@@ -1929,8 +1930,9 @@ typedef struct JsonTableSiblingJoin
{
JsonTablePlan plan;
- JsonTablePlan *lplan;
- JsonTablePlan *rplan;
+ JsonTablePlan *lplan; /* left join node */
+ JsonTablePlan *rplan; /* right join node */
+ bool cross; /* cross or union join? */
} JsonTableSiblingJoin;
/* ----------------
diff --git a/src/test/regress/expected/sqljson_jsontable.out b/src/test/regress/expected/sqljson_jsontable.out
index 458c5aaa5b0..2d924bc7f43 100644
--- a/src/test/regress/expected/sqljson_jsontable.out
+++ b/src/test/regress/expected/sqljson_jsontable.out
@@ -251,6 +251,29 @@ SELECT * FROM JSON_TABLE(jsonb '{"d1": "foo"}', '$'
(1 row)
-- JSON_TABLE: Test backward parsing
+CREATE VIEW jsonb_table_view1 AS
+SELECT * FROM
+ JSON_TABLE(
+ jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
CREATE VIEW jsonb_table_view2 AS
SELECT * FROM
JSON_TABLE(
@@ -300,6 +323,44 @@ SELECT * FROM
ia int[] PATH '$',
ta text[] PATH '$',
jba jsonb[] PATH '$'));
+\sv jsonb_table_view1
+CREATE OR REPLACE VIEW public.jsonb_table_view1 AS
+ SELECT id,
+ a1,
+ b1,
+ a11,
+ a21,
+ a22
+ FROM JSON_TABLE(
+ 'null'::jsonb, '$[*]' AS json_table_path_0
+ PASSING
+ 1 + 2 AS a,
+ '"foo"'::json AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1
+ COLUMNS (
+ a1 integer PATH '$."a1"',
+ b1 text PATH '$."b1"',
+ NESTED PATH '$[*]' AS "p1 1"
+ COLUMNS (
+ a11 text PATH '$."a11"'
+ )
+ ),
+ NESTED PATH '$[2]' AS p2
+ COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1"
+ COLUMNS (
+ a21 text PATH '$."a21"'
+ ),
+ NESTED PATH '$[*]' AS p22
+ COLUMNS (
+ a22 text PATH '$."a22"'
+ )
+ )
+ )
+ PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR
+ )
\sv jsonb_table_view2
CREATE OR REPLACE VIEW public.jsonb_table_view2 AS
SELECT "int",
@@ -321,6 +382,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view2 AS
"numeric" numeric PATH '$',
domain jsonb_test_domain PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view3
CREATE OR REPLACE VIEW public.jsonb_table_view3 AS
@@ -341,6 +403,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view3 AS
jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES,
jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view4
CREATE OR REPLACE VIEW public.jsonb_table_view4 AS
@@ -359,6 +422,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view4 AS
aaa integer PATH '$."aaa"',
aaa1 integer PATH '$."aaa"'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view5
CREATE OR REPLACE VIEW public.jsonb_table_view5 AS
@@ -375,6 +439,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view5 AS
exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR,
exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view6
CREATE OR REPLACE VIEW public.jsonb_table_view6 AS
@@ -397,45 +462,54 @@ CREATE OR REPLACE VIEW public.jsonb_table_view6 AS
ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES,
jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view1;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Table Function Scan on "json_table"
+ Output: "json_table".id, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR)
+(3 rows)
+
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view2;
- QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".domain
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS ("int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS ("int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view3;
- QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jst text FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jst text FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view4;
- QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (jsb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsbq jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (jsb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsbq jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view5;
- QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".exists1, "json_table".exists2, "json_table".exists3
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view6;
- QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".ia, "json_table".ta, "json_table".jba
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js2 json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER KEEP QUOTES, jsb2q jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, ia integer[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js2 json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER KEEP QUOTES, jsb2q jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, ia integer[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
-- JSON_TABLE() with alias
@@ -448,11 +522,11 @@ SELECT * FROM
"int" int PATH '$',
"text" text PATH '$'
)) json_table_func;
- QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table" json_table_func
Output: id, "int", text
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, "int" integer PATH '$', text text PATH '$'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, "int" integer PATH '$', text text PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, FORMAT JSON, VERBOSE)
@@ -464,24 +538,25 @@ SELECT * FROM
"int" int PATH '$',
"text" text PATH '$'
)) json_table_func;
- QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [ +
- { +
- "Plan": { +
- "Node Type": "Table Function Scan", +
- "Parallel Aware": false, +
- "Async Capable": false, +
- "Table Function Name": "json_table", +
- "Alias": "json_table_func", +
- "Disabled": false, +
- "Output": ["id", "\"int\"", "text"], +
- "Table Function Call": "JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '\"foo\"'::jsonb AS \"b c\" COLUMNS (id FOR ORDINALITY, \"int\" integer PATH '$', text text PATH '$'))"+
- } +
- } +
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [ +
+ { +
+ "Plan": { +
+ "Node Type": "Table Function Scan", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Table Function Name": "json_table", +
+ "Alias": "json_table_func", +
+ "Disabled": false, +
+ "Output": ["id", "\"int\"", "text"], +
+ "Table Function Call": "JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '\"foo\"'::jsonb AS \"b c\" COLUMNS (id FOR ORDINALITY, \"int\" integer PATH '$', text text PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)"+
+ } +
+ } +
]
(1 row)
+DROP VIEW jsonb_table_view1;
DROP VIEW jsonb_table_view2;
DROP VIEW jsonb_table_view3;
DROP VIEW jsonb_table_view4;
@@ -526,11 +601,35 @@ FROM
ON true;
ERROR: invalid input syntax for type integer: "err"
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
-ERROR: no SQL/JSON item found for specified path of column "a"
+ERROR: no SQL/JSON item found for specified path
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: jsonpath member accessor can only be applied to an object
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: no SQL/JSON item found for specified path
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 1
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+ERROR: no SQL/JSON item found for specified path
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON ERROR) ERROR ON ERROR) jt;
ERROR: jsonpath member accessor can only be applied to an object
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
-ERROR: no SQL/JSON item found for specified path of column "a"
+ERROR: no SQL/JSON item found for specified path
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
a
---
@@ -710,10 +809,45 @@ LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '...
^
-- JsonPathQuery() error message mentioning column name
SELECT * FROM JSON_TABLE('{"a": [{"b": "1"}, {"b": "2"}]}', '$' COLUMNS (b json path '$.a[*].b' ERROR ON ERROR));
-ERROR: JSON path expression for column "b" must return single item when no wrapper is requested
+ERROR: JSON path expression in JSON_QUERY must return single item when no wrapper is requested
HINT: Use the WITH WRAPPER clause to wrap SQL/JSON items into an array.
--- JSON_TABLE: nested paths
--- Duplicate path names
+-- JSON_TABLE: nested paths and plans
+-- Should fail (JSON_TABLE columns must contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' -- AS <path name> required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: jsonb '[]', '$' -- AS <path name> required here
+ ^
+DETAIL: JSON_TABLE path must contain explicit AS pathname specification if explicit PLAN clause is used
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS <path name> required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 4: NESTED PATH '$' COLUMNS ( -- AS <path name> required here
+ ^
+DETAIL: JSON_TABLE path must contain explicit AS pathname specification if explicit PLAN clause is used
+-- Should fail (column names must be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column or path name: a
+LINE 4: a int
+ ^
SELECT * FROM JSON_TABLE(
jsonb '[]', '$' AS a
COLUMNS (
@@ -774,6 +908,194 @@ SELECT * FROM JSON_TABLE(
ERROR: duplicate JSON_TABLE column or path name: a
LINE 10: NESTED PATH '$' AS a
^
+-- JSON_TABLE: plan validation
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 12: PLAN (p1)
+ ^
+DETAIL: PATH name mismatch: expected p0 but p1 is given.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p1 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p1 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+ERROR: invalid JSON_TABLE plan clause
+LINE 12: PLAN (p0 UNION p1 UNION p11)
+ ^
+DETAIL: Expected INNER or OUTER.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 8: NESTED PATH '$' AS p2 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p2 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 5: NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ ^
+DETAIL: PLAN clause for nested path p11 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan clause
+LINE 12: PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+ ^
+DETAIL: PLAN clause contains some extra or duplicate sibling nodes.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 6: NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ^
+DETAIL: PLAN clause for nested path p12 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 9: NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ ^
+DETAIL: PLAN clause for nested path p21 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+ bar | foo | baz
+-----+-----+-----
+(0 rows)
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: jsonb 'null', 'strict $[*]' -- without root path name
+ ^
+DETAIL: JSON_TABLE path must contain explicit AS pathname specification if explicit PLAN clause is used
-- JSON_TABLE: plan execution
CREATE TEMP TABLE jsonb_table_test (js jsonb);
INSERT INTO jsonb_table_test
@@ -785,6 +1107,398 @@ VALUES (
{"x": "4", "b": [1, 2], "c": 123}
]'
);
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-- default plans
+create or replace view outer_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt
+);
+NOTICE: view "outer_union" will be a temporary view
+create or replace view outer_cross as (
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt
+);
+NOTICE: view "outer_cross" will be a temporary view
+create or replace view inner_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, union)
+ ) jt
+);
+NOTICE: view "inner_union" will be a temporary view
+create or replace view inner_cross as (
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, cross)
+ ) jt
+);
+NOTICE: view "inner_cross" will be a temporary view
+select * from inner_cross;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+select * from outer_cross;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+select * from inner_union;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+select * from outer_union;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-----------------------------------------------------
+--inner_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 | 0
+ 2 | 2 | 3 | 20
+(9 rows)
+
+--outer_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 0 | 10
+ 2 | 2 | 0 | 0
+ 2 | 2 | 0 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 2 | 2 | 0 | 10
+ 2 | 2 | 0 | 0
+ 2 | 2 | 0 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+--inner_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 0 |
+ 2 | 2 | 2 |
+ 2 | 2 | 0 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 0 |
+ 3 | 3 | 2 |
+ 4 | -1 | 0 |
+ 4 | -1 | 2 |
+(10 rows)
+
+--outer_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 0 |
+ 2 | 2 | 2 |
+ 2 | 2 | 0 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 0 |
+ 3 | 3 | 2 |
+ 4 | -1 | 0 |
+ 4 | -1 | 2 |
+(11 rows)
+
+----------------------------------------
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(9 rows)
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+(6 rows)
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(9 rows)
+
+SELECT * from JSON_TABLE(jsonb '[1, 2, 3,null, 3]', '$[*]? (@ == 3)' PASSING 3 as x COLUMNS(xx int path '$'));
+ xx
+----
+ 3
+ 3
+(2 rows)
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 3 |
+ 2 | 2 | | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(5 rows)
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ -- nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(9 rows)
+
+-- specific plan (p outer (pb union pc))
select
jt.*
from
@@ -794,25 +1508,290 @@ from
columns (
n for ordinality,
a int path 'lax $.a' default -1 on empty,
- nested path 'strict $.b[*]' as pb columns (b_id for ordinality, b int path '$' ),
- nested path 'strict $.c[*]' as pc columns (c_id for ordinality, c int path '$' )
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
+ plan (p outer (pb union pc))
) jt;
- n | a | b_id | b | c_id | c
----+----+------+---+------+----
- 1 | 1 | | | |
- 2 | 2 | 1 | 1 | |
- 2 | 2 | 2 | 2 | |
- 2 | 2 | 3 | 3 | |
- 2 | 2 | | | 1 | 10
- 2 | 2 | | | 2 |
- 2 | 2 | | | 3 | 20
- 3 | 3 | 1 | 1 | |
- 3 | 3 | 2 | 2 | |
- 4 | -1 | 1 | 1 | |
- 4 | -1 | 2 | 2 | |
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
(11 rows)
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+ n | a | c | b
+---+----+----+---
+ 1 | 1 | |
+ 2 | 2 | 10 |
+ 2 | 2 | |
+ 2 | 2 | 20 |
+ 2 | 2 | | 1
+ 2 | 2 | | 2
+ 2 | 2 | | 3
+ 3 | 3 | | 1
+ 3 | 3 | | 2
+ 4 | -1 | | 1
+ 4 | -1 | | 2
+(11 rows)
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (jsonb
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+ n | a | b | b1 | c | c1 | b
+---+---+--------------+-----+------+----+-----
+ 1 | 1 | [1, 10] | 1 | 1 | | 101
+ 1 | 1 | [1, 10] | 1 | null | | 101
+ 1 | 1 | [1, 10] | 1 | 2 | | 101
+ 1 | 1 | [1, 10] | 10 | 1 | | 110
+ 1 | 1 | [1, 10] | 10 | null | | 110
+ 1 | 1 | [1, 10] | 10 | 2 | | 110
+ 1 | 1 | [2] | 2 | 1 | | 102
+ 1 | 1 | [2] | 2 | null | | 102
+ 1 | 1 | [2] | 2 | 2 | | 102
+ 1 | 1 | [3, 30, 300] | 3 | 1 | | 103
+ 1 | 1 | [3, 30, 300] | 3 | null | | 103
+ 1 | 1 | [3, 30, 300] | 3 | 2 | | 103
+ 1 | 1 | [3, 30, 300] | 30 | 1 | | 130
+ 1 | 1 | [3, 30, 300] | 30 | null | | 130
+ 1 | 1 | [3, 30, 300] | 30 | 2 | | 130
+ 1 | 1 | [3, 30, 300] | 300 | 1 | | 400
+ 1 | 1 | [3, 30, 300] | 300 | null | | 400
+ 1 | 1 | [3, 30, 300] | 300 | 2 | | 400
+ 2 | 2 | | | | |
+ 3 | | | | | |
+(20 rows)
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+ x | y | y | z
+---+---+--------------+---
+ 2 | 1 | [1, 2, 3] | 1
+ 2 | 1 | [1, 2, 3] | 2
+ 2 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [1, 2, 3] | 1
+ 3 | 1 | [1, 2, 3] | 2
+ 3 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [2, 3, 4, 5] | 2
+ 3 | 1 | [2, 3, 4, 5] | 3
+ 3 | 1 | [2, 3, 4, 5] | 4
+ 3 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [1, 2, 3] | 1
+ 4 | 1 | [1, 2, 3] | 2
+ 4 | 1 | [1, 2, 3] | 3
+ 4 | 1 | [2, 3, 4, 5] | 2
+ 4 | 1 | [2, 3, 4, 5] | 3
+ 4 | 1 | [2, 3, 4, 5] | 4
+ 4 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [3, 4, 5, 6] | 3
+ 4 | 1 | [3, 4, 5, 6] | 4
+ 4 | 1 | [3, 4, 5, 6] | 5
+ 4 | 1 | [3, 4, 5, 6] | 6
+ 2 | 2 | [1, 2, 3] | 2
+ 2 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [1, 2, 3] | 2
+ 3 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [2, 3, 4, 5] | 2
+ 3 | 2 | [2, 3, 4, 5] | 3
+ 3 | 2 | [2, 3, 4, 5] | 4
+ 3 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [1, 2, 3] | 2
+ 4 | 2 | [1, 2, 3] | 3
+ 4 | 2 | [2, 3, 4, 5] | 2
+ 4 | 2 | [2, 3, 4, 5] | 3
+ 4 | 2 | [2, 3, 4, 5] | 4
+ 4 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [3, 4, 5, 6] | 3
+ 4 | 2 | [3, 4, 5, 6] | 4
+ 4 | 2 | [3, 4, 5, 6] | 5
+ 4 | 2 | [3, 4, 5, 6] | 6
+ 2 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [2, 3, 4, 5] | 3
+ 3 | 3 | [2, 3, 4, 5] | 4
+ 3 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [1, 2, 3] | 3
+ 4 | 3 | [2, 3, 4, 5] | 3
+ 4 | 3 | [2, 3, 4, 5] | 4
+ 4 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [3, 4, 5, 6] | 3
+ 4 | 3 | [3, 4, 5, 6] | 4
+ 4 | 3 | [3, 4, 5, 6] | 5
+ 4 | 3 | [3, 4, 5, 6] | 6
+(52 rows)
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+ y
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
+ERROR: only string constants are supported in JSON_TABLE path specification
+LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '...
+ ^
-- PASSING arguments are passed to nested paths and their columns' paths
SELECT *
FROM
@@ -912,6 +1891,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view_nested AS
)
)
)
+ PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR
)
DROP VIEW jsonb_table_view_nested;
CREATE TABLE s (js jsonb);
@@ -1103,6 +2083,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view7 AS
)
)
)
+ PLAN (c1 OUTER ((((json_table_path_0 OUTER z22) UNION json_table_path_1) UNION (json_table_path_2 OUTER z1)) UNION (json_table_path_3 OUTER z21))) EMPTY ARRAY ON ERROR
) sub
DROP VIEW jsonb_table_view7;
DROP TABLE s;
@@ -1119,20 +2100,20 @@ LINE 1: ... * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int) NULL ON ER...
^
DETAIL: Only EMPTY [ ARRAY ] or ERROR is allowed in the top-level ON ERROR clause.
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int true on empty));
-ERROR: invalid ON EMPTY behavior for column "a"
+ERROR: invalid ON EMPTY behavior
LINE 1: ...T * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int true on em...
^
-DETAIL: Only ERROR, NULL, or DEFAULT expression is allowed in ON EMPTY for scalar columns.
+DETAIL: Only ERROR, NULL, or DEFAULT expression is allowed in ON EMPTY for JSON_VALUE().
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int omit quotes true on error));
-ERROR: invalid ON ERROR behavior for column "a"
+ERROR: invalid ON ERROR behavior
LINE 1: ...N_TABLE(jsonb '1', '$' COLUMNS (a int omit quotes true on er...
^
-DETAIL: Only ERROR, NULL, EMPTY ARRAY, EMPTY OBJECT, or DEFAULT expression is allowed in ON ERROR for formatted columns.
+DETAIL: Only ERROR, NULL, EMPTY ARRAY, EMPTY OBJECT, or DEFAULT expression is allowed in ON ERROR for JSON_QUERY().
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int exists empty object on error));
-ERROR: invalid ON ERROR behavior for column "a"
+ERROR: invalid ON ERROR behavior
LINE 1: ...M JSON_TABLE(jsonb '1', '$' COLUMNS (a int exists empty obje...
^
-DETAIL: Only ERROR, TRUE, FALSE, or UNKNOWN is allowed in ON ERROR for EXISTS columns.
+DETAIL: Only ERROR, TRUE, FALSE, or UNKNOWN is allowed in ON ERROR for JSON_EXISTS().
-- Test JSON_TABLE() column deparsing -- don't emit default ON ERROR / EMPTY
-- behavior
CREATE VIEW json_table_view8 AS SELECT * from JSON_TABLE('"a"', '$' COLUMNS (a text PATH '$'));
@@ -1144,6 +2125,7 @@ CREATE OR REPLACE VIEW public.json_table_view8 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
CREATE VIEW json_table_view9 AS SELECT * from JSON_TABLE('"a"', '$' COLUMNS (a text PATH '$') ERROR ON ERROR);
\sv json_table_view9;
@@ -1152,8 +2134,9 @@ CREATE OR REPLACE VIEW public.json_table_view9 AS
FROM JSON_TABLE(
'"a"'::text, '$' AS json_table_path_0
COLUMNS (
- a text PATH '$'
- ) ERROR ON ERROR
+ a text PATH '$' NULL ON EMPTY
+ )
+ PLAN (json_table_path_0) ERROR ON ERROR
)
DROP VIEW json_table_view8, json_table_view9;
-- Test JSON_TABLE() deparsing -- don't emit default ON ERROR behavior
@@ -1166,6 +2149,7 @@ CREATE OR REPLACE VIEW public.json_table_view8 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
CREATE VIEW json_table_view9 AS SELECT * from JSON_TABLE('"a"', '$' COLUMNS (a text PATH '$') EMPTY ARRAY ON ERROR);
\sv json_table_view9;
@@ -1176,5 +2160,6 @@ CREATE OR REPLACE VIEW public.json_table_view9 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
DROP VIEW json_table_view8, json_table_view9;
diff --git a/src/test/regress/sql/sqljson_jsontable.sql b/src/test/regress/sql/sqljson_jsontable.sql
index 154eea79c76..d0c88ec6d10 100644
--- a/src/test/regress/sql/sqljson_jsontable.sql
+++ b/src/test/regress/sql/sqljson_jsontable.sql
@@ -134,6 +134,30 @@ SELECT * FROM JSON_TABLE(jsonb '{"d1": "foo"}', '$'
-- JSON_TABLE: Test backward parsing
+CREATE VIEW jsonb_table_view1 AS
+SELECT * FROM
+ JSON_TABLE(
+ jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
+
CREATE VIEW jsonb_table_view2 AS
SELECT * FROM
JSON_TABLE(
@@ -188,12 +212,14 @@ SELECT * FROM
ta text[] PATH '$',
jba jsonb[] PATH '$'));
+\sv jsonb_table_view1
\sv jsonb_table_view2
\sv jsonb_table_view3
\sv jsonb_table_view4
\sv jsonb_table_view5
\sv jsonb_table_view6
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view1;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view2;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view3;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view4;
@@ -221,6 +247,7 @@ SELECT * FROM
"text" text PATH '$'
)) json_table_func;
+DROP VIEW jsonb_table_view1;
DROP VIEW jsonb_table_view2;
DROP VIEW jsonb_table_view3;
DROP VIEW jsonb_table_view4;
@@ -253,6 +280,14 @@ FROM
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
ON true;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON ERROR) ERROR ON ERROR) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
@@ -323,9 +358,36 @@ SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int)
-- JsonPathQuery() error message mentioning column name
SELECT * FROM JSON_TABLE('{"a": [{"b": "1"}, {"b": "2"}]}', '$' COLUMNS (b json path '$.a[*].b' ERROR ON ERROR));
--- JSON_TABLE: nested paths
+-- JSON_TABLE: nested paths and plans
+
+-- Should fail (JSON_TABLE columns must contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' -- AS <path name> required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS <path name> required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+-- Should fail (column names must be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
--- Duplicate path names
SELECT * FROM JSON_TABLE(
jsonb '[]', '$' AS a
COLUMNS (
@@ -376,6 +438,161 @@ SELECT * FROM JSON_TABLE(
)
) jt;
+-- JSON_TABLE: plan validation
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
-- JSON_TABLE: plan execution
@@ -391,6 +608,73 @@ VALUES (
]'
);
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+
+-- default plans
+
+create or replace view outer_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt
+);
+
+create or replace view outer_cross as (
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt
+);
+
+create or replace view inner_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, union)
+ ) jt
+);
+
+create or replace view inner_cross as (
select
jt.*
from
@@ -400,11 +684,280 @@ from
columns (
n for ordinality,
a int path 'lax $.a' default -1 on empty,
- nested path 'strict $.b[*]' as pb columns (b_id for ordinality, b int path '$' ),
- nested path 'strict $.c[*]' as pc columns (c_id for ordinality, c int path '$' )
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, cross)
+ ) jt
+);
+
+
+select * from inner_cross;
+select * from outer_cross;
+select * from inner_union;
+select * from outer_union;
+-----------------------------------------------------
+--inner_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+
+--outer_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
)
+ plan default (outer, cross)
) jt;
+--inner_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+
+--outer_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, union)
+ ) jt;
+
+----------------------------------------
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+
+SELECT * from JSON_TABLE(jsonb '[1, 2, 3,null, 3]', '$[*]? (@ == 3)' PASSING 3 as x COLUMNS(xx int path '$'));
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ -- nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+
+-- specific plan (p outer (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb union pc))
+ ) jt;
+
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (jsonb
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
+
-- PASSING arguments are passed to nested paths and their columns' paths
SELECT *
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9a3bee93dec..cf0b158d8fb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1367,6 +1367,7 @@ JsonPathKeyword
JsonPathParseItem
JsonPathParseResult
JsonPathPredicateCallback
+JsonPathSpec
JsonPathString
JsonPathVariable
JsonQuotes
@@ -1382,9 +1383,15 @@ JsonTableParseContext
JsonTablePath
JsonTablePathScan
JsonTablePathSpec
+JsonTableJoinState
JsonTablePlan
JsonTablePlanRowSource
+JsonTablePlanSpec
JsonTablePlanState
+JsonTablePlanStateType
+JsonTablePlanJoinType
+JsonTablePlanType
+JsonTableScanState
JsonTableSiblingJoin
JsonTokenType
JsonTransformStringValuesAction
--
2.25.1
Nikita Malakhov писал(а) 2025-02-03 02:43:
Hi hackers!
Patch has been rebased onto the current master.
--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
https://postgrespro.ru/
Hi!
Tested on Mac with 'make check', all tests passed OK.
--
Best regards,
Vladlen Popolitov.
Hi Nikita,
On Wed, Dec 18, 2024 at 12:11 AM Nikita Malakhov <hukutoc@gmail.com> wrote:
Hi hackers!
This thread is further work continued in [1], where Amit Langote
suggested starting discussion on the remaining SQL/JSON feature
'PLAN clause for JSON_TABLE' anew.We'd like to help with merging SQL/JSON patches into vanilla,
and have adapted PLAN clause to recent changes in JSON_TABLE
function.
Thanks for working on this.
While doing this we've found that some tests with the PLAN clause
were incorrect, along with JSON_TABLE behavior with this clause.
We've corrected this behavior, but these corrections required reverting
some removed and heavily refactored code, so we'd be glad for review
and feedback on this patch.
Sorry, I don't fully understand this paragraph. Do you mean that
there might be bugs in the existing JSON_TABLE() functionality that
was committed into v17?
--
Thanks, Amit Langote
Hi Amit!
No, JSON_TABLE is ok. I've meant that in the previous approach to PLAN
clause
implementation there were some wrong tests.
On Tue, Feb 4, 2025 at 6:05 AM Amit Langote <amitlangote09@gmail.com> wrote:
Hi Nikita,
On Wed, Dec 18, 2024 at 12:11 AM Nikita Malakhov <hukutoc@gmail.com>
wrote:Hi hackers!
This thread is further work continued in [1], where Amit Langote
suggested starting discussion on the remaining SQL/JSON feature
'PLAN clause for JSON_TABLE' anew.We'd like to help with merging SQL/JSON patches into vanilla,
and have adapted PLAN clause to recent changes in JSON_TABLE
function.Thanks for working on this.
While doing this we've found that some tests with the PLAN clause
were incorrect, along with JSON_TABLE behavior with this clause.
We've corrected this behavior, but these corrections required reverting
some removed and heavily refactored code, so we'd be glad for review
and feedback on this patch.Sorry, I don't fully understand this paragraph. Do you mean that
there might be bugs in the existing JSON_TABLE() functionality that
was committed into v17?--
Thanks, Amit Langote
--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
https://postgrespro.ru/
Hi Nikita,
I was looking at the patch you posted on Feb 3. Two high-level comments:
* The documentation additions are missing.
* It looks like the patch mixes refactoring changes with new
functionality, which makes it harder to follow what's existing vs.
new. Could you separate these changes so the new additions are easier
to review?
More specifically on the 2nd point:
-/*
- * 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)));
}
...
Diffs like this one make me wonder whether this code actually needs to
change for PLAN clause support -- but I suspect it does not.
+
+ /**/
+ bool cross;
+ bool outerJoin;
+ bool advanceNested;
+ bool advanceRight;
+ bool reset;
Incomplete comment.
@@ -4124,6 +4130,7 @@ JsonTableInitOpaque(TableFuncScanState *state, int natts)
* Evaluate JSON_TABLE() PASSING arguments to be passed to the jsonpath
* executor via JsonPathVariables.
*/
+
if (state->passingvalexprs)
@@ -4155,15 +4162,13 @@ JsonTableInitOpaque(TableFuncScanState *state,
int natts)
}
cxt->colplanstates = palloc(sizeof(JsonTablePlanState *) *
- list_length(tf->colvalexprs));
-
+ list_length(tf->colvalexprs));
/*
* Initialize plan for the root path and, recursively, also any child
* plans that compute the NESTED paths.
*/
cxt->rootplanstate = JsonTableInitPlan(cxt, rootplan, NULL, args,
- CurrentMemoryContext);
-
+ CurrentMemoryContext);
state->opaque = cxt;
}
@@ -4201,8 +4206,9 @@ JsonTableInitPlan(JsonTableExecContext *cxt,
JsonTablePlan *plan,
if (IsA(plan, JsonTablePathScan))
{
JsonTablePathScan *scan = (JsonTablePathScan *) plan;
- int i;
+ int i;
Unnecessary whitespace addition/removal.
/*
- * Transform JSON_TABLE column definition into a JsonFuncExpr
- * This turns:
+ * Transform JSON_TABLE column
Not sure why the comment was rewritten.
-JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
+JsonTableResetContextItem(JsonTablePlanState *planstate, Datum item)
I remember renaming ContextItem with RowPattern, but it seems this is
switching it back.
It makes it look like you're reverting to code from the old patch that
implemented the PLAN clause together with JSON_TABLE(), instead of
building on the committed code and adding just what's needed to
support PLAN clause in JsonTablePlan. Especially, the changes to
parse_jsontable.c and jsonpath_exec.c,
Hi Amit!
Thank you for your feedback, I'll check it out in a couple of days and
reply.
--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
https://postgrespro.ru/
Hi hackers!
Thanks to Amit for reviewing the patch. I've reworked it according to Amit's
recommendations and divided it into 2 parts: part 1 contains PLAN clause
code and part 2 tests. Reworked code is much more compact, so I hope
it would not be so painful to review.
--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
https://postgrespro.ru/
Attachments:
v20-0001-JSON-TABLE-PLAN-clause.patchapplication/octet-stream; name=v20-0001-JSON-TABLE-PLAN-clause.patchDownload
From 78575cda8dd9291470b0efc455070b1652a9bd2c Mon Sep 17 00:00:00 2001
From: Nikita Malakhov <n.malakhov@postgrespro.ru>
Date: Fri, 11 Apr 2025 15:52:05 +0300
Subject: [PATCH] Add the PLAN clauses for JSON_TABLE Revisited part 1
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This patch adds the PLAN clauses for JSON_TABLE, which allow the user
to specify how data from nested paths are joined, allowing considerable
freedom in shaping the tabular output of JSON_TABLE.
PLAN DEFAULT allows the user to specify the global strategies when
dealing with sibling or child nested paths. The is often sufficient
to achieve the necessary goal, and is considerably simpler than the
full PLAN clause, which allows the user to specify the strategy to be
used for each named nested path.
Code was taken from v45-0001-JSON_TABLE.patch from
https://www.postgresql.org/message-id/CA%2BHiwqE1gcPkQhBko%2BUbvVvAtRBaLfOpmHbFrK79pW_5F51Oww%40mail.gmail.com
Original patch was modified according to recent changes in SQL/JSON
JSON_TABLE, and invalid PLAN test results found while adapting
original patch to these changes.
Author: Nikita Glukhov
Author: Teodor Sigaev
Author: Oleg Bartunov
Author: Alexander Korotkov
Author: Andrew Dunstan
Author: Amit Langote
Author: Anton A. Melnikov
Author: Nikita Malakhov
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
---
src/backend/nodes/makefuncs.c | 54 ++++
src/backend/parser/gram.y | 101 ++++++-
src/backend/parser/parse_jsontable.c | 393 +++++++++++++++++++++-----
src/backend/utils/adt/jsonpath_exec.c | 167 ++++++++---
src/backend/utils/adt/ruleutils.c | 53 +++-
src/include/nodes/makefuncs.h | 5 +
src/include/nodes/parsenodes.h | 43 +++
src/include/nodes/primnodes.h | 2 +
src/tools/pgindent/typedefs.list | 7 +
9 files changed, 713 insertions(+), 112 deletions(-)
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index e2d9e9be41a..5349c0e042b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -963,6 +963,60 @@ makeJsonBehavior(JsonBehaviorType btype, Node *expr, int location)
return behavior;
}
+/*
+ * makeJsonTableDefaultPlan -
+ * creates a JsonTablePlanSpec node to represent a "default" JSON_TABLE plan
+ * with given join strategy
+ */
+Node *
+makeJsonTableDefaultPlan(JsonTablePlanJoinType join_type, int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_DEFAULT;
+ n->join_type = join_type;
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonTableSimplePlan -
+ * creates a JsonTablePlanSpec node to represent a "simple" JSON_TABLE plan
+ * for given PATH
+ */
+Node *
+makeJsonTableSimplePlan(char *pathname, int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_SIMPLE;
+ n->pathname = pathname;
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonTableJoinedPlan -
+ * creates a JsonTablePlanSpec node to represent join between the given
+ * pair of plans
+ */
+Node *
+makeJsonTableJoinedPlan(JsonTablePlanJoinType type, Node *plan1, Node *plan2,
+ int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_JOINED;
+ n->join_type = type;
+ n->plan1 = castNode(JsonTablePlanSpec, plan1);
+ n->plan2 = castNode(JsonTablePlanSpec, plan2);
+ n->location = location;
+
+ return (Node *) n;
+}
+
/*
* makeJsonKeyValue -
* creates a JsonKeyValue node
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3c4268b271a..4ca6e1e607c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -656,6 +656,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_table
json_table_column_definition
json_table_column_path_clause_opt
+ json_table_plan_clause_opt
+ json_table_plan
+ json_table_plan_simple
+ json_table_plan_outer
+ json_table_plan_inner
+ json_table_plan_union
+ json_table_plan_cross
+ json_table_plan_primary
%type <list> json_name_and_value_list
json_value_expr_list
json_array_aggregate_order_by_clause_opt
@@ -667,6 +675,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> json_behavior_type
json_predicate_type_constraint
json_quotes_clause_opt
+ json_table_default_plan_choices
+ json_table_default_plan_inner_outer
+ json_table_default_plan_union_cross
json_wrapper_behavior
%type <boolean> json_key_uniqueness_constraint_opt
json_object_constructor_null_clause_opt
@@ -14315,6 +14326,7 @@ json_table:
json_value_expr ',' a_expr json_table_path_name_opt
json_passing_clause_opt
COLUMNS '(' json_table_column_definition_list ')'
+ json_table_plan_clause_opt
json_on_error_clause_opt
')'
{
@@ -14326,13 +14338,15 @@ json_table:
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"),
+ 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->planspec = (JsonTablePlanSpec *) $12;
+ n->on_error = (JsonBehavior *) $13;
n->location = @1;
$$ = (Node *) n;
}
@@ -14424,8 +14438,7 @@ json_table_column_definition:
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
- n->pathspec = (JsonTablePathSpec *)
- makeJsonTablePathSpec($3, NULL, @3, -1);
+ n->pathspec = makeJsonTablePathSpec($3, NULL, @3, -1);
n->columns = $6;
n->location = @1;
$$ = (Node *) n;
@@ -14436,8 +14449,7 @@ json_table_column_definition:
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
- n->pathspec = (JsonTablePathSpec *)
- makeJsonTablePathSpec($3, $5, @3, @5);
+ n->pathspec = makeJsonTablePathSpec($3, $5, @3, @5);
n->columns = $8;
n->location = @1;
$$ = (Node *) n;
@@ -14456,6 +14468,83 @@ json_table_column_path_clause_opt:
{ $$ = NULL; }
;
+json_table_plan_clause_opt:
+ PLAN '(' json_table_plan ')'
+ { $$ = $3; }
+ | PLAN DEFAULT '(' json_table_default_plan_choices ')'
+ { $$ = makeJsonTableDefaultPlan($4, @1); }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
+json_table_plan:
+ json_table_plan_simple
+ | json_table_plan_outer
+ | json_table_plan_inner
+ | json_table_plan_union
+ | json_table_plan_cross
+ ;
+
+json_table_plan_simple:
+ name
+ { $$ = makeJsonTableSimplePlan($1, @1); }
+ ;
+
+json_table_plan_outer:
+ json_table_plan_simple OUTER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_OUTER, $1, $3, @1); }
+ ;
+
+json_table_plan_inner:
+ json_table_plan_simple INNER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_INNER, $1, $3, @1); }
+ ;
+
+json_table_plan_union:
+ json_table_plan_primary UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_UNION, $1, $3, @1); }
+ | json_table_plan_union UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_UNION, $1, $3, @1); }
+ ;
+
+json_table_plan_cross:
+ json_table_plan_primary CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_CROSS, $1, $3, @1); }
+ | json_table_plan_cross CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_CROSS, $1, $3, @1); }
+ ;
+
+json_table_plan_primary:
+ json_table_plan_simple
+ { $$ = $1; }
+ | '(' json_table_plan ')'
+ {
+ castNode(JsonTablePlanSpec, $2)->location = @1;
+ $$ = $2;
+ }
+ ;
+
+json_table_default_plan_choices:
+ json_table_default_plan_inner_outer
+ { $$ = $1 | JSTP_JOIN_UNION; }
+ | json_table_default_plan_union_cross
+ { $$ = $1 | JSTP_JOIN_OUTER; }
+ | json_table_default_plan_inner_outer ',' json_table_default_plan_union_cross
+ { $$ = $1 | $3; }
+ | json_table_default_plan_union_cross ',' json_table_default_plan_inner_outer
+ { $$ = $1 | $3; }
+ ;
+
+json_table_default_plan_inner_outer:
+ INNER_P { $$ = JSTP_JOIN_INNER; }
+ | OUTER_P { $$ = JSTP_JOIN_OUTER; }
+ ;
+
+json_table_default_plan_union_cross:
+ UNION { $$ = JSTP_JOIN_UNION; }
+ | CROSS { $$ = JSTP_JOIN_CROSS; }
+ ;
+
/*****************************************************************************
*
* Type syntax
diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c
index 13d533b83f3..7d7c595c819 100644
--- a/src/backend/parser/parse_jsontable.c
+++ b/src/backend/parser/parse_jsontable.c
@@ -39,17 +39,22 @@ typedef struct JsonTableParseContext
} JsonTableParseContext;
static JsonTablePlan *transformJsonTableColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *planspec,
List *columns,
- List *passingArgs,
- JsonTablePathSpec *pathspec);
+ List *passing_Args,
+ JsonTablePathSpec * pathspec);
static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt,
- List *passingArgs,
+ JsonTablePlanSpec *plan,
+ List *passing_Args,
List *columns);
static JsonFuncExpr *transformJsonTableColumn(JsonTableColumn *jtc,
Node *contextItemExpr,
- List *passingArgs);
+ List *passingArgs,
+ bool errorOnError);
static bool isCompositeType(Oid typid);
-static JsonTablePlan *makeJsonTablePathScan(JsonTablePathSpec *pathspec,
+static JsonTablePlan *makeJsonTablePathScan(JsonTableParseContext *cxt,
+ JsonTablePathSpec *pathspec,
+ JsonTablePlanSpec *planspec,
bool errorOnError,
int colMin, int colMax,
JsonTablePlan *childplan);
@@ -57,8 +62,14 @@ static void CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
List *columns);
static bool LookupPathOrColumnName(JsonTableParseContext *cxt, char *name);
static char *generateJsonTablePathName(JsonTableParseContext *cxt);
-static JsonTablePlan *makeJsonTableSiblingJoin(JsonTablePlan *lplan,
+static void validateJsonTableChildPlan(ParseState *pstate,
+ JsonTablePlanSpec *plan,
+ List *columns);
+static JsonTablePlan *makeJsonTableSiblingJoin(bool cross,
+ JsonTablePlan *lplan,
JsonTablePlan *rplan);
+static void
+appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs);
/*
* transformJsonTable -
@@ -76,6 +87,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
TableFunc *tf;
JsonFuncExpr *jfe;
JsonExpr *je;
+ JsonTablePlanSpec *plan = jt->planspec;
JsonTablePathSpec *rootPathSpec = jt->pathspec;
bool is_lateral;
JsonTableParseContext cxt = {pstate};
@@ -94,8 +106,21 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
parser_errposition(pstate, jt->on_error->location));
cxt.pathNameId = 0;
+
if (rootPathSpec->name == NULL)
+ {
+ if (jt->planspec != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE expression"),
+ errdetail("JSON_TABLE path must contain"
+ " explicit AS pathname specification if"
+ " explicit PLAN clause is used"),
+ parser_errposition(pstate, rootPathSpec->location)));
+
rootPathSpec->name = generateJsonTablePathName(&cxt);
+ }
+
cxt.pathNames = list_make1(rootPathSpec->name);
CheckDuplicateColumnOrPathNames(&cxt, jt->columns);
@@ -135,7 +160,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
*/
cxt.jt = jt;
cxt.tf = tf;
- tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns,
+ tf->plan = (Node *) transformJsonTableColumns(&cxt, plan, jt->columns,
jt->passing,
rootPathSpec);
@@ -246,25 +271,110 @@ generateJsonTablePathName(JsonTableParseContext *cxt)
* their type/collation information to cxt->tf.
*/
static JsonTablePlan *
-transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
- List *passingArgs,
+transformJsonTableColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *planspec,
+ List *columns,
+ List *passing_Args,
JsonTablePathSpec *pathspec)
{
- ParseState *pstate = cxt->pstate;
JsonTable *jt = cxt->jt;
TableFunc *tf = cxt->tf;
- ListCell *col;
- bool ordinality_found = false;
+ JsonTablePathScan *scan;
+ JsonTablePlanSpec *childPlanSpec;
+ bool defaultPlan = planspec == NULL ||
+ planspec->plan_type == JSTP_DEFAULT;
bool errorOnError = jt->on_error &&
jt->on_error->btype == JSON_BEHAVIOR_ERROR;
- Oid contextItemTypid = exprType(tf->docexpr);
int colMin,
colMax;
- JsonTablePlan *childplan;
+ JsonTablePlan *childplan = NULL;
/* Start of column range */
colMin = list_length(tf->colvalexprs);
+ if (defaultPlan)
+ childPlanSpec = planspec;
+ else
+ {
+ /* validate parent and child plans */
+ JsonTablePlanSpec *parentPlanSpec;
+
+ if (planspec->plan_type == JSTP_JOINED)
+ {
+ if (planspec->join_type != JSTP_JOIN_INNER &&
+ planspec->join_type != JSTP_JOIN_OUTER)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("Expected INNER or OUTER."),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ parentPlanSpec = planspec->plan1;
+ childPlanSpec = planspec->plan2;
+
+ Assert(parentPlanSpec->plan_type != JSTP_JOINED);
+ Assert(parentPlanSpec->pathname);
+ }
+ else
+ {
+ parentPlanSpec = planspec;
+ childPlanSpec = NULL;
+ }
+
+ if (strcmp(parentPlanSpec->pathname, pathspec->name) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan"),
+ errdetail("PATH name mismatch: expected %s but %s is given.",
+ pathspec->name, parentPlanSpec->pathname),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ validateJsonTableChildPlan(cxt->pstate, childPlanSpec, columns);
+ }
+
+ appendJsonTableColumns(cxt, columns, passing_Args);
+
+ /* End of column range. */
+ if (list_length(tf->colvalexprs) == colMin)
+ {
+ /* No columns in this Scan beside the nested ones. */
+ colMax = colMin = -1;
+ }
+ else
+ colMax = list_length(tf->colvalexprs) - 1;
+
+ if (childPlanSpec || defaultPlan)
+ {
+ /* transform recursively nested columns */
+ childplan = transformJsonTableNestedColumns(cxt, childPlanSpec,
+ columns, passing_Args);
+ }
+
+ /* transform only non-nested columns */
+ scan = (JsonTablePathScan *) makeJsonTablePathScan(cxt, pathspec,
+ planspec,
+ errorOnError,
+ colMin,
+ colMax,
+ childplan);
+
+ return (JsonTablePlan *) scan;
+}
+
+/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
+static void
+appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs)
+{
+ ListCell *col;
+ ParseState *pstate = cxt->pstate;
+ JsonTable *jt = cxt->jt;
+ TableFunc *tf = cxt->tf;
+ bool ordinality_found = false;
+ JsonBehavior *on_error = jt->on_error;
+ bool errorOnError = on_error &&
+ on_error->btype == JSON_BEHAVIOR_ERROR;
+ Oid contextItemTypid = exprType(tf->docexpr);
+
foreach(col, columns)
{
JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
@@ -273,12 +383,9 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
Oid typcoll = InvalidOid;
Node *colexpr;
- if (rawc->coltype != JTC_NESTED)
- {
- Assert(rawc->name);
+ if (rawc->name)
tf->colnames = lappend(tf->colnames,
makeString(pstrdup(rawc->name)));
- }
/*
* Determine the type and typmod for the new column. FOR ORDINALITY
@@ -324,7 +431,7 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
param->typeMod = -1;
jfe = transformJsonTableColumn(rawc, (Node *) param,
- passingArgs);
+ passingArgs, errorOnError);
colexpr = transformExpr(pstate, (Node *) jfe,
EXPR_KIND_FROM_FUNCTION);
@@ -349,22 +456,6 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
tf->colcollations = lappend_oid(tf->colcollations, typcoll);
tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
}
-
- /* End of column range. */
- if (list_length(tf->colvalexprs) == colMin)
- {
- /* No columns in this Scan beside the nested ones. */
- colMax = colMin = -1;
- }
- else
- colMax = list_length(tf->colvalexprs) - 1;
-
- /* Recursively transform nested columns */
- childplan = transformJsonTableNestedColumns(cxt, passingArgs, columns);
-
- /* Create a "parent" scan responsible for all columns handled above. */
- return makeJsonTablePathScan(pathspec, errorOnError, colMin, colMax,
- childplan);
}
/*
@@ -395,7 +486,7 @@ isCompositeType(Oid typid)
*/
static JsonFuncExpr *
transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
- List *passingArgs)
+ List *passingArgs, bool errorOnError)
{
Node *pathspec;
JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
@@ -437,6 +528,8 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
jfexpr->output->returning->format = jtc->format;
jfexpr->on_empty = jtc->on_empty;
jfexpr->on_error = jtc->on_error;
+ if (jfexpr->on_error == NULL && errorOnError)
+ jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL, -1);
jfexpr->quotes = jtc->quotes;
jfexpr->wrapper = jtc->wrapper;
jfexpr->location = jtc->location;
@@ -444,49 +537,126 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
return jfexpr;
}
+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->pathspec->name &&
+ !strcmp(jtc->pathspec->name, pathname))
+ return jtc;
+ }
+
+ return NULL;
+}
/*
* Recursively transform nested columns and create child plan(s) that will be
* used to evaluate their row patterns.
+ *
+ * Default plan is transformed into a cross/union join of its nested columns.
+ * Simple and outer/inner plans are transformed into a JsonTablePlan by
+ * finding and transforming corresponding nested column.
+ * Sibling plans are recursively transformed into a JsonTableSiblingJoin.
*/
static JsonTablePlan *
transformJsonTableNestedColumns(JsonTableParseContext *cxt,
- List *passingArgs,
- List *columns)
+ JsonTablePlanSpec *planspec,
+ List *columns,
+ List *passingArgs)
{
- JsonTablePlan *plan = NULL;
- ListCell *lc;
+ JsonTableColumn *jtc = NULL;
- /*
- * If there are multiple NESTED COLUMNS clauses in 'columns', their
- * respective plans will be combined using a "sibling join" plan, which
- * effectively does a UNION of the sets of rows coming from each nested
- * plan.
- */
- foreach(lc, columns)
+ if (!planspec || planspec->plan_type == JSTP_DEFAULT)
{
- JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
- JsonTablePlan *nested;
+ /* unspecified or default plan */
+ JsonTablePlan *plan = NULL;
+ ListCell *lc;
+ bool cross = planspec && (planspec->join_type & JSTP_JOIN_CROSS);
- if (jtc->coltype != JTC_NESTED)
- continue;
+ /*
+ * If there are multiple NESTED COLUMNS clauses in 'columns', their
+ * respective plans will be combined using a "sibling join" plan, which
+ * effectively does a UNION of the sets of rows coming from each nested
+ * plan.
+ */
+ foreach(lc, columns)
+ {
+ JsonTableColumn *col = castNode(JsonTableColumn, lfirst(lc));
+ JsonTablePlan *nested;
- if (jtc->pathspec->name == NULL)
- jtc->pathspec->name = generateJsonTablePathName(cxt);
+ if (col->coltype != JTC_NESTED)
+ continue;
- nested = transformJsonTableColumns(cxt, jtc->columns, passingArgs,
- jtc->pathspec);
+ if (col->pathspec->name == NULL)
+ {
+ col->pathspec->name = generateJsonTablePathName(cxt);
+ }
- if (plan)
- plan = makeJsonTableSiblingJoin(plan, nested);
+ nested = transformJsonTableColumns(cxt, planspec, col->columns,
+ passingArgs,
+ col->pathspec);
+
+ /* Join nested plan with previous sibling nested plans. */
+ if (plan)
+ plan = makeJsonTableSiblingJoin(cross, plan, nested);
+ else
+ plan = nested;
+ }
+
+ return plan;
+ }
+ else if (planspec->plan_type == JSTP_SIMPLE)
+ {
+ jtc = findNestedJsonTableColumn(columns, planspec->pathname);
+ }
+ else if (planspec->plan_type == JSTP_JOINED)
+ {
+ if (planspec->join_type == JSTP_JOIN_INNER ||
+ planspec->join_type == JSTP_JOIN_OUTER)
+ {
+ Assert(planspec->plan1->plan_type == JSTP_SIMPLE);
+ jtc = findNestedJsonTableColumn(columns, planspec->plan1->pathname);
+ }
else
- plan = nested;
+ {
+ JsonTablePlan *lplan = transformJsonTableNestedColumns(cxt,
+ planspec->plan1,
+ columns,
+ passingArgs);
+ JsonTablePlan *rplan = transformJsonTableNestedColumns(cxt,
+ planspec->plan2,
+ columns,
+ passingArgs);
+
+ return makeJsonTableSiblingJoin(planspec->join_type == JSTP_JOIN_CROSS,
+ lplan, rplan);
+ }
}
+ else
+ elog(ERROR, "invalid JSON_TABLE plan type %d", planspec->plan_type);
- return plan;
+ if (!jtc)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("PATH name was %s not found in nested columns list.",
+ planspec->pathname),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ return transformJsonTableColumns(cxt, planspec, jtc->columns,
+ passingArgs,
+ jtc->pathspec);
}
/*
- * Create a JsonTablePlan for given path and ON ERROR behavior.
+ * 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.
*
* colMin and colMin give the range of columns computed by this scan in the
* global flat list of column expressions that will be passed to the
@@ -494,7 +664,9 @@ transformJsonTableNestedColumns(JsonTableParseContext *cxt,
* thus computed by 'childplan'.
*/
static JsonTablePlan *
-makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
+makeJsonTablePathScan(JsonTableParseContext *cxt, JsonTablePathSpec *pathspec,
+ JsonTablePlanSpec *planspec,
+ bool errorOnError,
int colMin, int colMax,
JsonTablePlan *childplan)
{
@@ -513,11 +685,18 @@ makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
scan->path = makeJsonTablePath(value, pathspec->name);
scan->errorOnError = errorOnError;
- scan->child = childplan;
+ scan->child = NULL;
+ if(childplan)
+ scan->child = childplan;
scan->colMin = colMin;
scan->colMax = colMax;
+ if (scan->child)
+ scan->outerJoin = planspec == NULL ||
+ (planspec->join_type & JSTP_JOIN_OUTER);
+ /* else: default plan case, no children found */
+
return (JsonTablePlan *) scan;
}
@@ -529,13 +708,101 @@ makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
* sets of rows from 'lplan' and 'rplan'.
*/
static JsonTablePlan *
-makeJsonTableSiblingJoin(JsonTablePlan *lplan, JsonTablePlan *rplan)
+makeJsonTableSiblingJoin(bool cross, JsonTablePlan *lplan, JsonTablePlan *rplan)
{
JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
join->plan.type = T_JsonTableSiblingJoin;
join->lplan = lplan;
join->rplan = rplan;
+ join->cross = cross;
return (JsonTablePlan *) join;
}
+
+/* Collect sibling path names from plan to the specified list. */
+static void
+collectSiblingPathsInJsonTablePlan(JsonTablePlanSpec *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 == JSTP_JOIN_INNER ||
+ plan->join_type == JSTP_JOIN_OUTER)
+ {
+ Assert(plan->plan1->plan_type == JSTP_SIMPLE);
+ *paths = lappend(*paths, plan->plan1->pathname);
+ }
+ else if (plan->join_type == JSTP_JOIN_CROSS ||
+ plan->join_type == JSTP_JOIN_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, JsonTablePlanSpec *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->pathspec->name == NULL)
+ 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->pathspec->name, lfirst(lc2))))
+ break;
+ }
+
+ if (!found)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE specification"),
+ errdetail("PLAN clause for nested path %s was not found.",
+ jtc->pathspec->name),
+ parser_errposition(pstate, jtc->location));
+
+ nchildren++;
+ }
+ }
+
+ if (list_length(siblings) > nchildren)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("PLAN clause contains some extra or duplicate sibling nodes."),
+ parser_errposition(pstate, plan ? plan->location : -1));
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index dbab24737ef..ada5d62412b 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -213,6 +213,13 @@ typedef struct JsonTablePlanState
/* Parent plan, if this is a nested plan */
struct JsonTablePlanState *parent;
+
+ /**/
+ bool cross;
+ bool outerJoin;
+ bool advanceNested;
+ bool advanceRight;
+ bool reset;
} JsonTablePlanState;
/* Random number to identify JsonTableExecContext for sanity checking */
@@ -358,13 +365,13 @@ static JsonTablePlanState *JsonTableInitPlan(JsonTableExecContext *cxt,
MemoryContext mcxt);
static void JsonTableSetDocument(TableFuncScanState *state, Datum value);
static void JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item);
+static void JsonTableRescan(JsonTablePlanState *planstate);
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 JsonTablePlanScanNextRow(JsonTablePlanState *planstate);
static void JsonTableResetNestedPlan(JsonTablePlanState *planstate);
-static bool JsonTablePlanJoinNextRow(JsonTablePlanState *planstate);
static bool JsonTablePlanNextRow(JsonTablePlanState *planstate);
const TableFuncRoutine JsonbTableRoutine =
@@ -4203,6 +4210,7 @@ JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
JsonTablePathScan *scan = (JsonTablePathScan *) plan;
int i;
+ planstate->outerJoin = scan->outerJoin;
planstate->path = DatumGetJsonPathP(scan->path->value->constvalue);
planstate->args = args;
planstate->mcxt = AllocSetContextCreate(mcxt, "JsonTableExecContext",
@@ -4222,6 +4230,8 @@ JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
{
JsonTableSiblingJoin *join = (JsonTableSiblingJoin *) plan;
+ planstate->cross = join->cross;
+
planstate->left = JsonTableInitPlan(cxt, join->lplan, parentstate,
args, mcxt);
planstate->right = JsonTableInitPlan(cxt, join->rplan, parentstate,
@@ -4276,11 +4286,7 @@ JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
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;
+ JsonTableRescan(planstate);
}
/*
@@ -4291,16 +4297,89 @@ JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
static bool
JsonTablePlanNextRow(JsonTablePlanState *planstate)
{
- if (IsA(planstate->plan, JsonTablePathScan))
- return JsonTablePlanScanNextRow(planstate);
- else if (IsA(planstate->plan, JsonTableSiblingJoin))
- return JsonTablePlanJoinNextRow(planstate);
+ if (IsA(planstate->plan, JsonTableSiblingJoin))
+ {
+ if (planstate->advanceRight)
+ {
+ /* fetch next inner row */
+ if (JsonTablePlanNextRow(planstate->right))
+ return true;
+
+ /* inner rows are exhausted */
+ if (planstate->cross)
+ planstate->advanceRight = false; /* next outer row */
+ else
+ return false; /* end of scan */
+ }
+
+ while (!planstate->advanceRight)
+ {
+ /* fetch next outer row */
+ bool more = JsonTablePlanNextRow(planstate->left);
+
+ if (planstate->cross)
+ {
+ if (!more)
+ return false; /* end of scan */
+
+ JsonTableRescan(planstate->right);
+
+ if (!JsonTablePlanNextRow(planstate->right))
+ continue; /* next outer row */
+
+ planstate->advanceRight = true; /* next inner row */
+ }
+ else if (!more)
+ {
+ if (!JsonTablePlanNextRow(planstate->right))
+ return false; /* end of scan */
+
+ planstate->advanceRight = true; /* next inner row */
+ }
+
+ break;
+ }
+ }
else
- elog(ERROR, "invalid JsonTablePlan %d", (int) planstate->plan->type);
+ {
+ /* reset context item if requested */
+ if (planstate->reset)
+ {
+ JsonTablePlanState *parent = planstate->parent;
- Assert(false);
- /* Appease compiler */
- return false;
+ Assert(parent != NULL && !parent->current.isnull);
+ JsonTableResetRowPattern(planstate, parent->current.value);
+ planstate->reset = false;
+ }
+
+ if (planstate->advanceNested)
+ {
+ /* fetch next nested row */
+ planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
+ if (planstate->advanceNested)
+ return true;
+ }
+
+ for (;;)
+ {
+ if (!JsonTablePlanScanNextRow(planstate))
+ return false;
+
+ if (planstate->nested == NULL)
+ break;
+
+ JsonTableResetNestedPlan(planstate->nested);
+ planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
+
+ if (!planstate->advanceNested && !planstate->outerJoin)
+ continue;
+
+ if (planstate->advanceNested || planstate->nested)
+ break;
+ }
+ }
+
+ return true;
}
/*
@@ -4386,47 +4465,28 @@ JsonTableResetNestedPlan(JsonTablePlanState *planstate)
{
JsonTablePlanState *parent = planstate->parent;
- if (!parent->current.isnull)
- JsonTableResetRowPattern(planstate, parent->current.value);
+ planstate->reset = true;
+ planstate->advanceNested = false;
+
+ if (planstate->nested)
+ JsonTableResetNestedPlan(planstate->nested);
/*
* If this plan itself has a child nested plan, it will be reset when
* the caller calls JsonTablePlanNextRow() on this plan.
*/
+ if (!parent->current.isnull)
+ JsonTableResetRowPattern(planstate, parent->current.value);
+
}
else if (IsA(planstate->plan, JsonTableSiblingJoin))
{
JsonTableResetNestedPlan(planstate->left);
JsonTableResetNestedPlan(planstate->right);
+ planstate->advanceRight = false;
}
}
-/*
- * Fetch the next row from a JsonTableSiblingJoin.
- *
- * This is essentially a UNION between the rows from left and right siblings.
- */
-static bool
-JsonTablePlanJoinNextRow(JsonTablePlanState *planstate)
-{
-
- /* Fetch row from left sibling. */
- if (!JsonTablePlanNextRow(planstate->left))
- {
- /*
- * Left sibling ran out of rows, so start fetching from the right
- * sibling.
- */
- if (!JsonTablePlanNextRow(planstate->right))
- {
- /* Right sibling ran out of row, so there are more rows. */
- return false;
- }
- }
-
- return true;
-}
-
/*
* JsonTableFetchRow
* Prepare the next "current" row for upcoming GetValue calls.
@@ -4491,3 +4551,26 @@ JsonTableGetValue(TableFuncScanState *state, int colnum,
return result;
}
+
+/* Recursively reset planstate and its child nodes */
+static void
+JsonTableRescan(JsonTablePlanState *planstate)
+{
+ if (IsA(planstate->plan, JsonTablePathScan))
+ {
+ /* 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;
+
+ if (planstate->nested)
+ JsonTableRescan(planstate->nested);
+ }
+ else if (IsA(planstate, JsonTableSiblingJoin))
+ {
+ JsonTableRescan(planstate->left);
+ JsonTableRescan(planstate->right);
+ planstate->advanceRight = false;
+ }
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9e90acedb91..3a8706bc4ad 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -12059,6 +12059,49 @@ get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
}
}
+/*
+ * get_json_table_plan - Parse back a JSON_TABLE plan
+ */
+static void
+get_json_table_plan(TableFunc *tf, JsonTablePlan *plan, deparse_context *context,
+ bool parenthesize)
+{
+ if (parenthesize)
+ appendStringInfoChar(context->buf, '(');
+
+ if (IsA(plan, JsonTablePathScan))
+ {
+ JsonTablePathScan *s = castNode(JsonTablePathScan, plan);
+
+ appendStringInfoString(context->buf, quote_identifier(s->path->name));
+
+ if (s->child)
+ {
+ appendStringInfoString(context->buf,
+ s->outerJoin ? " OUTER " : " INNER ");
+ get_json_table_plan(tf, s->child, context,
+ IsA(s->child, JsonTableSiblingJoin));
+ }
+ }
+ else if (IsA(plan, JsonTableSiblingJoin))
+ {
+ JsonTableSiblingJoin *j = (JsonTableSiblingJoin *) plan;
+
+ get_json_table_plan(tf, j->lplan, context,
+ IsA(j->lplan, JsonTableSiblingJoin) ||
+ castNode(JsonTablePathScan, j->lplan)->child);
+
+ appendStringInfoString(context->buf, j->cross ? " CROSS " : " UNION ");
+
+ get_json_table_plan(tf, j->rplan, context,
+ IsA(j->rplan, JsonTableSiblingJoin) ||
+ castNode(JsonTablePathScan, j->rplan)->child);
+ }
+
+ if (parenthesize)
+ appendStringInfoChar(context->buf, ')');
+}
+
/*
* get_json_table_columns - Parse back JSON_TABLE columns
*/
@@ -12068,6 +12111,7 @@ get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
bool showimplicit)
{
StringInfo buf = context->buf;
+ JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
ListCell *lc_colname;
ListCell *lc_coltype;
ListCell *lc_coltypmod;
@@ -12147,6 +12191,9 @@ get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
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);
@@ -12224,7 +12271,11 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
get_json_table_columns(tf, castNode(JsonTablePathScan, tf->plan), context,
showimplicit);
- if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY)
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "PLAN ", 0, 0, 0);
+ get_json_table_plan(tf, (JsonTablePlan *)root, context, true);
+
+ if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY)
get_json_behavior(jexpr->on_error, context, "ERROR");
if (PRETTY_INDENT(context))
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 5473ce9a288..17d761ef142 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -124,5 +124,10 @@ extern JsonTablePath *makeJsonTablePath(Const *pathvalue, char *pathname);
extern JsonTablePathSpec *makeJsonTablePathSpec(char *string, char *name,
int string_location,
int name_location);
+extern Node *makeJsonTableDefaultPlan(JsonTablePlanJoinType join_type,
+ int location);
+extern Node *makeJsonTableSimplePlan(char *pathname, int location);
+extern Node *makeJsonTableJoinedPlan(JsonTablePlanJoinType type,
+ Node *plan1, Node *plan2, int location);
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4610fc61293..3c596ce1599 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1863,6 +1863,48 @@ typedef struct JsonTablePathSpec
ParseLoc location; /* location of 'string' */
} JsonTablePathSpec;
+/*
+ * JsonTablePlanType -
+ * flags for JSON_TABLE plan node types representation
+ */
+typedef enum JsonTablePlanType
+{
+ JSTP_DEFAULT,
+ JSTP_SIMPLE,
+ JSTP_JOINED,
+} JsonTablePlanType;
+
+/*
+ * JsonTablePlanJoinType -
+ * JSON_TABLE join types for JSTP_JOINED plans
+ */
+typedef enum JsonTablePlanJoinType
+{
+ JSTP_JOIN_INNER = 0x01,
+ JSTP_JOIN_OUTER = 0x02,
+ JSTP_JOIN_CROSS = 0x04,
+ JSTP_JOIN_UNION = 0x08,
+} JsonTablePlanJoinType;
+
+/*
+ * JsonTablePlanSpec -
+ * untransformed representation of JSON_TABLE's PLAN clause
+ */
+typedef struct JsonTablePlanSpec
+{
+ NodeTag type;
+
+ JsonTablePlanType plan_type; /* plan type */
+ JsonTablePlanJoinType join_type; /* join type (for joined plan only) */
+ char *pathname; /* path name (for simple plan only) */
+
+ /* For joined plans */
+ struct JsonTablePlanSpec *plan1; /* first joined plan */
+ struct JsonTablePlanSpec *plan2; /* second joined plan */
+
+ ParseLoc location; /* token location, or -1 if unknown */
+} JsonTablePlanSpec;
+
/*
* JsonTable -
* untransformed representation of JSON_TABLE
@@ -1874,6 +1916,7 @@ typedef struct JsonTable
JsonTablePathSpec *pathspec; /* JSON path specification */
List *passing; /* list of PASSING clause arguments, if any */
List *columns; /* list of JsonTableColumn */
+ JsonTablePlanSpec *planspec; /* join plan, if specified */
JsonBehavior *on_error; /* ON ERROR behavior */
Alias *alias; /* table alias in FROM clause */
bool lateral; /* does it have LATERAL prefix? */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7d3b4198f26..79ead41d5d0 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1910,6 +1910,7 @@ typedef struct JsonTablePathScan
/* Plan(s) for nested columns, if any. */
JsonTablePlan *child;
+ bool outerJoin; /* outer or inner join for nested columns? */
/*
* 0-based index in TableFunc.colvalexprs of the 1st and the last column
@@ -1931,6 +1932,7 @@ typedef struct JsonTableSiblingJoin
JsonTablePlan *lplan;
JsonTablePlan *rplan;
+ bool cross;
} JsonTableSiblingJoin;
/* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d16bc208654..45df03a5044 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1403,6 +1403,7 @@ JsonPathKeyword
JsonPathParseItem
JsonPathParseResult
JsonPathPredicateCallback
+JsonPathSpec
JsonPathString
JsonPathVariable
JsonQuotes
@@ -1418,9 +1419,15 @@ JsonTableParseContext
JsonTablePath
JsonTablePathScan
JsonTablePathSpec
+JsonTableJoinState
JsonTablePlan
JsonTablePlanRowSource
+JsonTablePlanSpec
JsonTablePlanState
+JsonTablePlanStateType
+JsonTablePlanJoinType
+JsonTablePlanType
+JsonTableScanState
JsonTableSiblingJoin
JsonTokenType
JsonTransformStringValuesAction
--
2.34.1
v20-0002-JSON-TABLE-PLAN-clause.patchapplication/octet-stream; name=v20-0002-JSON-TABLE-PLAN-clause.patchDownload
From 43ddc499d50b2fc12393bb4bf36ae944e502b402 Mon Sep 17 00:00:00 2001
From: Nikita Malakhov <n.malakhov@postgrespro.ru>
Date: Fri, 11 Apr 2025 15:57:30 +0300
Subject: [PATCH] Add the PLAN clauses for JSON_TABLE Revisited part 2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This patch adds tests for the JSON_TABLE PLAN clause.
Code was taken from v45-0001-JSON_TABLE.patch from
https://www.postgresql.org/message-id/CA%2BHiwqE1gcPkQhBko%2BUbvVvAtRBaLfOpmHbFrK79pW_5F51Oww%40mail.gmail.com
Original patch was modified according to recent changes in SQL/JSON
JSON_TABLE, and invalid PLAN test results found while adapting
original patch to these changes.
Author: Nikita Glukhov
Author: Teodor Sigaev
Author: Oleg Bartunov
Author: Alexander Korotkov
Author: Andrew Dunstan
Author: Amit Langote
Author: Anton A. Melnikov
Author: Nikita Malakhov
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
---
.../regress/expected/sqljson_jsontable.out | 1090 ++++++++++++++++-
src/test/regress/sql/sqljson_jsontable.sql | 561 ++++++++-
2 files changed, 1595 insertions(+), 56 deletions(-)
diff --git a/src/test/regress/expected/sqljson_jsontable.out b/src/test/regress/expected/sqljson_jsontable.out
index 458c5aaa5b0..6678704958c 100644
--- a/src/test/regress/expected/sqljson_jsontable.out
+++ b/src/test/regress/expected/sqljson_jsontable.out
@@ -251,6 +251,29 @@ SELECT * FROM JSON_TABLE(jsonb '{"d1": "foo"}', '$'
(1 row)
-- JSON_TABLE: Test backward parsing
+CREATE VIEW jsonb_table_view1 AS
+SELECT * FROM
+ JSON_TABLE(
+ jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
CREATE VIEW jsonb_table_view2 AS
SELECT * FROM
JSON_TABLE(
@@ -300,6 +323,44 @@ SELECT * FROM
ia int[] PATH '$',
ta text[] PATH '$',
jba jsonb[] PATH '$'));
+\sv jsonb_table_view1
+CREATE OR REPLACE VIEW public.jsonb_table_view1 AS
+ SELECT id,
+ a1,
+ b1,
+ a11,
+ a21,
+ a22
+ FROM JSON_TABLE(
+ 'null'::jsonb, '$[*]' AS json_table_path_0
+ PASSING
+ 1 + 2 AS a,
+ '"foo"'::json AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1
+ COLUMNS (
+ a1 integer PATH '$."a1"',
+ b1 text PATH '$."b1"',
+ NESTED PATH '$[*]' AS "p1 1"
+ COLUMNS (
+ a11 text PATH '$."a11"'
+ )
+ ),
+ NESTED PATH '$[2]' AS p2
+ COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1"
+ COLUMNS (
+ a21 text PATH '$."a21"'
+ ),
+ NESTED PATH '$[*]' AS p22
+ COLUMNS (
+ a22 text PATH '$."a22"'
+ )
+ )
+ )
+ PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR
+ )
\sv jsonb_table_view2
CREATE OR REPLACE VIEW public.jsonb_table_view2 AS
SELECT "int",
@@ -321,6 +382,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view2 AS
"numeric" numeric PATH '$',
domain jsonb_test_domain PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view3
CREATE OR REPLACE VIEW public.jsonb_table_view3 AS
@@ -341,6 +403,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view3 AS
jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES,
jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view4
CREATE OR REPLACE VIEW public.jsonb_table_view4 AS
@@ -359,6 +422,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view4 AS
aaa integer PATH '$."aaa"',
aaa1 integer PATH '$."aaa"'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view5
CREATE OR REPLACE VIEW public.jsonb_table_view5 AS
@@ -375,6 +439,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view5 AS
exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR,
exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view6
CREATE OR REPLACE VIEW public.jsonb_table_view6 AS
@@ -397,45 +462,54 @@ CREATE OR REPLACE VIEW public.jsonb_table_view6 AS
ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES,
jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view1;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Table Function Scan on "json_table"
+ Output: "json_table".id, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR)
+(3 rows)
+
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view2;
- QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".domain
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS ("int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS ("int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view3;
- QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jst text FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jst text FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view4;
- QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (jsb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsbq jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (jsb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsbq jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view5;
- QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".exists1, "json_table".exists2, "json_table".exists3
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view6;
- QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".ia, "json_table".ta, "json_table".jba
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js2 json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER KEEP QUOTES, jsb2q jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, ia integer[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js2 json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER KEEP QUOTES, jsb2q jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, ia integer[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
-- JSON_TABLE() with alias
@@ -448,11 +522,11 @@ SELECT * FROM
"int" int PATH '$',
"text" text PATH '$'
)) json_table_func;
- QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table" json_table_func
Output: id, "int", text
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, "int" integer PATH '$', text text PATH '$'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, "int" integer PATH '$', text text PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, FORMAT JSON, VERBOSE)
@@ -464,24 +538,25 @@ SELECT * FROM
"int" int PATH '$',
"text" text PATH '$'
)) json_table_func;
- QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [ +
- { +
- "Plan": { +
- "Node Type": "Table Function Scan", +
- "Parallel Aware": false, +
- "Async Capable": false, +
- "Table Function Name": "json_table", +
- "Alias": "json_table_func", +
- "Disabled": false, +
- "Output": ["id", "\"int\"", "text"], +
- "Table Function Call": "JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '\"foo\"'::jsonb AS \"b c\" COLUMNS (id FOR ORDINALITY, \"int\" integer PATH '$', text text PATH '$'))"+
- } +
- } +
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [ +
+ { +
+ "Plan": { +
+ "Node Type": "Table Function Scan", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Table Function Name": "json_table", +
+ "Alias": "json_table_func", +
+ "Disabled": false, +
+ "Output": ["id", "\"int\"", "text"], +
+ "Table Function Call": "JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '\"foo\"'::jsonb AS \"b c\" COLUMNS (id FOR ORDINALITY, \"int\" integer PATH '$', text text PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)"+
+ } +
+ } +
]
(1 row)
+DROP VIEW jsonb_table_view1;
DROP VIEW jsonb_table_view2;
DROP VIEW jsonb_table_view3;
DROP VIEW jsonb_table_view4;
@@ -525,6 +600,30 @@ FROM
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
ON true;
ERROR: invalid input syntax for type integer: "err"
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+ERROR: no SQL/JSON item found for specified path of column "a"
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: jsonpath member accessor can only be applied to an object
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: no SQL/JSON item found for specified path of column "a"
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 1
+(1 row)
+
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
ERROR: no SQL/JSON item found for specified path of column "a"
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON ERROR) ERROR ON ERROR) jt;
@@ -712,8 +811,44 @@ LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '...
SELECT * FROM JSON_TABLE('{"a": [{"b": "1"}, {"b": "2"}]}', '$' COLUMNS (b json path '$.a[*].b' ERROR ON ERROR));
ERROR: JSON path expression for column "b" must return single item when no wrapper is requested
HINT: Use the WITH WRAPPER clause to wrap SQL/JSON items into an array.
--- JSON_TABLE: nested paths
--- Duplicate path names
+-- JSON_TABLE: nested paths and plans
+-- Should fail (JSON_TABLE columns must contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' -- AS <path name> required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: jsonb '[]', '$' -- AS <path name> required here
+ ^
+DETAIL: JSON_TABLE path must contain explicit AS pathname specification if explicit PLAN clause is used
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS <path name> required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ foo
+-----
+
+(1 row)
+
+-- Should fail (column names must be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column or path name: a
+LINE 4: a int
+ ^
SELECT * FROM JSON_TABLE(
jsonb '[]', '$' AS a
COLUMNS (
@@ -774,6 +909,194 @@ SELECT * FROM JSON_TABLE(
ERROR: duplicate JSON_TABLE column or path name: a
LINE 10: NESTED PATH '$' AS a
^
+-- JSON_TABLE: plan validation
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 12: PLAN (p1)
+ ^
+DETAIL: PATH name mismatch: expected p0 but p1 is given.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p1 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p1 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+ERROR: invalid JSON_TABLE plan clause
+LINE 12: PLAN (p0 UNION p1 UNION p11)
+ ^
+DETAIL: Expected INNER or OUTER.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 8: NESTED PATH '$' AS p2 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p2 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 5: NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ ^
+DETAIL: PLAN clause for nested path p11 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan clause
+LINE 12: PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+ ^
+DETAIL: PLAN clause contains some extra or duplicate sibling nodes.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 6: NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ^
+DETAIL: PLAN clause for nested path p12 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 9: NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ ^
+DETAIL: PLAN clause for nested path p21 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+ bar | foo | baz
+-----+-----+-----
+(0 rows)
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: jsonb 'null', 'strict $[*]' -- without root path name
+ ^
+DETAIL: JSON_TABLE path must contain explicit AS pathname specification if explicit PLAN clause is used
-- JSON_TABLE: plan execution
CREATE TEMP TABLE jsonb_table_test (js jsonb);
INSERT INTO jsonb_table_test
@@ -785,6 +1108,86 @@ VALUES (
{"x": "4", "b": [1, 2], "c": 123}
]'
);
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-- default plans
+create or replace view outer_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt
+);
+NOTICE: view "outer_union" will be a temporary view
+create or replace view outer_cross as (
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt
+);
+NOTICE: view "outer_cross" will be a temporary view
+create or replace view inner_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, union)
+ ) jt
+);
+NOTICE: view "inner_union" will be a temporary view
+create or replace view inner_cross as (
select
jt.*
from
@@ -794,25 +1197,602 @@ from
columns (
n for ordinality,
a int path 'lax $.a' default -1 on empty,
- nested path 'strict $.b[*]' as pb columns (b_id for ordinality, b int path '$' ),
- nested path 'strict $.c[*]' as pc columns (c_id for ordinality, c int path '$' )
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
+ plan default (inner, cross)
+ ) jt
+);
+NOTICE: view "inner_cross" will be a temporary view
+select * from inner_cross;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+select * from outer_cross;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+select * from inner_union;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+select * from outer_union;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-----------------------------------------------------
+--inner_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
) jt;
- n | a | b_id | b | c_id | c
----+----+------+---+------+----
- 1 | 1 | | | |
- 2 | 2 | 1 | 1 | |
- 2 | 2 | 2 | 2 | |
- 2 | 2 | 3 | 3 | |
- 2 | 2 | | | 1 | 10
- 2 | 2 | | | 2 |
- 2 | 2 | | | 3 | 20
- 3 | 3 | 1 | 1 | |
- 3 | 3 | 2 | 2 | |
- 4 | -1 | 1 | 1 | |
- 4 | -1 | 2 | 2 | |
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 | 0
+ 2 | 2 | 3 | 20
+(9 rows)
+
+--outer_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 0 | 10
+ 2 | 2 | 0 | 0
+ 2 | 2 | 0 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 2 | 2 | 0 | 10
+ 2 | 2 | 0 | 0
+ 2 | 2 | 0 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+--inner_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 0 |
+ 2 | 2 | 2 |
+ 2 | 2 | 0 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 0 |
+ 3 | 3 | 2 |
+ 4 | -1 | 0 |
+ 4 | -1 | 2 |
+(10 rows)
+
+--outer_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 0 |
+ 2 | 2 | 2 |
+ 2 | 2 | 0 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 0 |
+ 3 | 3 | 2 |
+ 4 | -1 | 0 |
+ 4 | -1 | 2 |
+(11 rows)
+
+----------------------------------------
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(9 rows)
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+(6 rows)
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(9 rows)
+
+SELECT * from JSON_TABLE(jsonb '[1, 2, 3,null, 3]', '$[*]? (@ == 3)' PASSING 3 as x COLUMNS(xx int path '$'));
+ xx
+----
+ 3
+ 3
+(2 rows)
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 3 |
+ 2 | 2 | | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(5 rows)
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ -- nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(9 rows)
+
+-- specific plan (p outer (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb union pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
(11 rows)
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+ n | a | c | b
+---+----+----+---
+ 1 | 1 | |
+ 2 | 2 | 10 |
+ 2 | 2 | |
+ 2 | 2 | 20 |
+ 2 | 2 | | 1
+ 2 | 2 | | 2
+ 2 | 2 | | 3
+ 3 | 3 | | 1
+ 3 | 3 | | 2
+ 4 | -1 | | 1
+ 4 | -1 | | 2
+(11 rows)
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (jsonb
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+ n | a | b | b1 | c | c1 | b
+---+---+--------------+-----+------+----+-----
+ 1 | 1 | [1, 10] | 1 | 1 | | 101
+ 1 | 1 | [1, 10] | 1 | null | | 101
+ 1 | 1 | [1, 10] | 1 | 2 | | 101
+ 1 | 1 | [1, 10] | 10 | 1 | | 110
+ 1 | 1 | [1, 10] | 10 | null | | 110
+ 1 | 1 | [1, 10] | 10 | 2 | | 110
+ 1 | 1 | [2] | 2 | 1 | | 102
+ 1 | 1 | [2] | 2 | null | | 102
+ 1 | 1 | [2] | 2 | 2 | | 102
+ 1 | 1 | [3, 30, 300] | 3 | 1 | | 103
+ 1 | 1 | [3, 30, 300] | 3 | null | | 103
+ 1 | 1 | [3, 30, 300] | 3 | 2 | | 103
+ 1 | 1 | [3, 30, 300] | 30 | 1 | | 130
+ 1 | 1 | [3, 30, 300] | 30 | null | | 130
+ 1 | 1 | [3, 30, 300] | 30 | 2 | | 130
+ 1 | 1 | [3, 30, 300] | 300 | 1 | | 400
+ 1 | 1 | [3, 30, 300] | 300 | null | | 400
+ 1 | 1 | [3, 30, 300] | 300 | 2 | | 400
+ 2 | 2 | | | | |
+ 3 | | | | | |
+(20 rows)
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+ x | y | y | z
+---+---+--------------+---
+ 2 | 1 | [1, 2, 3] | 1
+ 2 | 1 | [1, 2, 3] | 2
+ 2 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [1, 2, 3] | 1
+ 3 | 1 | [1, 2, 3] | 2
+ 3 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [2, 3, 4, 5] | 2
+ 3 | 1 | [2, 3, 4, 5] | 3
+ 3 | 1 | [2, 3, 4, 5] | 4
+ 3 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [1, 2, 3] | 1
+ 4 | 1 | [1, 2, 3] | 2
+ 4 | 1 | [1, 2, 3] | 3
+ 4 | 1 | [2, 3, 4, 5] | 2
+ 4 | 1 | [2, 3, 4, 5] | 3
+ 4 | 1 | [2, 3, 4, 5] | 4
+ 4 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [3, 4, 5, 6] | 3
+ 4 | 1 | [3, 4, 5, 6] | 4
+ 4 | 1 | [3, 4, 5, 6] | 5
+ 4 | 1 | [3, 4, 5, 6] | 6
+ 2 | 2 | [1, 2, 3] | 2
+ 2 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [1, 2, 3] | 2
+ 3 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [2, 3, 4, 5] | 2
+ 3 | 2 | [2, 3, 4, 5] | 3
+ 3 | 2 | [2, 3, 4, 5] | 4
+ 3 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [1, 2, 3] | 2
+ 4 | 2 | [1, 2, 3] | 3
+ 4 | 2 | [2, 3, 4, 5] | 2
+ 4 | 2 | [2, 3, 4, 5] | 3
+ 4 | 2 | [2, 3, 4, 5] | 4
+ 4 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [3, 4, 5, 6] | 3
+ 4 | 2 | [3, 4, 5, 6] | 4
+ 4 | 2 | [3, 4, 5, 6] | 5
+ 4 | 2 | [3, 4, 5, 6] | 6
+ 2 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [2, 3, 4, 5] | 3
+ 3 | 3 | [2, 3, 4, 5] | 4
+ 3 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [1, 2, 3] | 3
+ 4 | 3 | [2, 3, 4, 5] | 3
+ 4 | 3 | [2, 3, 4, 5] | 4
+ 4 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [3, 4, 5, 6] | 3
+ 4 | 3 | [3, 4, 5, 6] | 4
+ 4 | 3 | [3, 4, 5, 6] | 5
+ 4 | 3 | [3, 4, 5, 6] | 6
+(52 rows)
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+ y
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
+ERROR: only string constants are supported in JSON_TABLE path specification
+LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '...
+ ^
-- PASSING arguments are passed to nested paths and their columns' paths
SELECT *
FROM
@@ -912,6 +1892,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view_nested AS
)
)
)
+ PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR
)
DROP VIEW jsonb_table_view_nested;
CREATE TABLE s (js jsonb);
@@ -1103,6 +2084,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view7 AS
)
)
)
+ PLAN (c1 OUTER ((((json_table_path_0 OUTER z22) UNION json_table_path_1) UNION (json_table_path_2 OUTER z1)) UNION (json_table_path_3 OUTER z21))) EMPTY ARRAY ON ERROR
) sub
DROP VIEW jsonb_table_view7;
DROP TABLE s;
@@ -1144,6 +2126,7 @@ CREATE OR REPLACE VIEW public.json_table_view8 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
CREATE VIEW json_table_view9 AS SELECT * from JSON_TABLE('"a"', '$' COLUMNS (a text PATH '$') ERROR ON ERROR);
\sv json_table_view9;
@@ -1152,8 +2135,9 @@ CREATE OR REPLACE VIEW public.json_table_view9 AS
FROM JSON_TABLE(
'"a"'::text, '$' AS json_table_path_0
COLUMNS (
- a text PATH '$'
- ) ERROR ON ERROR
+ a text PATH '$' NULL ON EMPTY
+ )
+ PLAN (json_table_path_0) ERROR ON ERROR
)
DROP VIEW json_table_view8, json_table_view9;
-- Test JSON_TABLE() deparsing -- don't emit default ON ERROR behavior
@@ -1166,6 +2150,7 @@ CREATE OR REPLACE VIEW public.json_table_view8 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
CREATE VIEW json_table_view9 AS SELECT * from JSON_TABLE('"a"', '$' COLUMNS (a text PATH '$') EMPTY ARRAY ON ERROR);
\sv json_table_view9;
@@ -1176,5 +2161,6 @@ CREATE OR REPLACE VIEW public.json_table_view9 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
DROP VIEW json_table_view8, json_table_view9;
diff --git a/src/test/regress/sql/sqljson_jsontable.sql b/src/test/regress/sql/sqljson_jsontable.sql
index 154eea79c76..d0c88ec6d10 100644
--- a/src/test/regress/sql/sqljson_jsontable.sql
+++ b/src/test/regress/sql/sqljson_jsontable.sql
@@ -134,6 +134,30 @@ SELECT * FROM JSON_TABLE(jsonb '{"d1": "foo"}', '$'
-- JSON_TABLE: Test backward parsing
+CREATE VIEW jsonb_table_view1 AS
+SELECT * FROM
+ JSON_TABLE(
+ jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
+
CREATE VIEW jsonb_table_view2 AS
SELECT * FROM
JSON_TABLE(
@@ -188,12 +212,14 @@ SELECT * FROM
ta text[] PATH '$',
jba jsonb[] PATH '$'));
+\sv jsonb_table_view1
\sv jsonb_table_view2
\sv jsonb_table_view3
\sv jsonb_table_view4
\sv jsonb_table_view5
\sv jsonb_table_view6
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view1;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view2;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view3;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view4;
@@ -221,6 +247,7 @@ SELECT * FROM
"text" text PATH '$'
)) json_table_func;
+DROP VIEW jsonb_table_view1;
DROP VIEW jsonb_table_view2;
DROP VIEW jsonb_table_view3;
DROP VIEW jsonb_table_view4;
@@ -253,6 +280,14 @@ FROM
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
ON true;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON ERROR) ERROR ON ERROR) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
@@ -323,9 +358,36 @@ SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int)
-- JsonPathQuery() error message mentioning column name
SELECT * FROM JSON_TABLE('{"a": [{"b": "1"}, {"b": "2"}]}', '$' COLUMNS (b json path '$.a[*].b' ERROR ON ERROR));
--- JSON_TABLE: nested paths
+-- JSON_TABLE: nested paths and plans
+
+-- Should fail (JSON_TABLE columns must contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' -- AS <path name> required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS <path name> required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+-- Should fail (column names must be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
--- Duplicate path names
SELECT * FROM JSON_TABLE(
jsonb '[]', '$' AS a
COLUMNS (
@@ -376,6 +438,161 @@ SELECT * FROM JSON_TABLE(
)
) jt;
+-- JSON_TABLE: plan validation
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
-- JSON_TABLE: plan execution
@@ -391,6 +608,73 @@ VALUES (
]'
);
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+
+-- default plans
+
+create or replace view outer_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt
+);
+
+create or replace view outer_cross as (
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt
+);
+
+create or replace view inner_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, union)
+ ) jt
+);
+
+create or replace view inner_cross as (
select
jt.*
from
@@ -400,11 +684,280 @@ from
columns (
n for ordinality,
a int path 'lax $.a' default -1 on empty,
- nested path 'strict $.b[*]' as pb columns (b_id for ordinality, b int path '$' ),
- nested path 'strict $.c[*]' as pc columns (c_id for ordinality, c int path '$' )
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, cross)
+ ) jt
+);
+
+
+select * from inner_cross;
+select * from outer_cross;
+select * from inner_union;
+select * from outer_union;
+-----------------------------------------------------
+--inner_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+
+--outer_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
)
+ plan default (outer, cross)
) jt;
+--inner_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+
+--outer_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, union)
+ ) jt;
+
+----------------------------------------
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+
+SELECT * from JSON_TABLE(jsonb '[1, 2, 3,null, 3]', '$[*]? (@ == 3)' PASSING 3 as x COLUMNS(xx int path '$'));
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ -- nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+
+-- specific plan (p outer (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb union pc))
+ ) jt;
+
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (jsonb
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
+
-- PASSING arguments are passed to nested paths and their columns' paths
SELECT *
--
2.34.1
Hi hackers!
Patch was tested with the current master. All is good with minor changes in
messages.
The 1st part contains grammar changes with JSON_TABLE transformations
according
to given PLAN strategy, the 2nd contains corrected tests for JSON_TABLE and
new tests
for PLAN clause.
--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
https://postgrespro.ru/
Attachments:
v21-0002-JSON-TABLE-PLAN-clause.patchapplication/octet-stream; name=v21-0002-JSON-TABLE-PLAN-clause.patchDownload
From f3190eb03506b3ea11494cb6ad843bf0d2c6dfe3 Mon Sep 17 00:00:00 2001
From: Nikita Malakhov <n.malakhov@postgrespro.ru>
Date: Mon, 22 Dec 2025 18:06:14 +0300
Subject: [PATCH] JSON_TABLE PLAN clause part 2/2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This patch corrects json_table tests and adds new tests
for the JSON_TABLE PLAN clause.
Code was taken from v45-0001-JSON_TABLE.patch from
https://www.postgresql.org/message-id/CA%2BHiwqE1gcPkQhBko%2BUbvVvAtRBaLfOpmHbFrK79pW_5F51Oww%40mail.gmail.com
Original patch was modified according to recent changes in SQL/JSON
JSON_TABLE, and invalid PLAN test results found while adapting
original patch to these changes.
Author: Nikita Glukhov
Author: Teodor Sigaev
Author: Oleg Bartunov
Author: Alexander Korotkov
Author: Andrew Dunstan
Author: Amit Langote
Author: Anton A. Melnikov
Author: Nikita Malakhov
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
---
.../regress/expected/sqljson_jsontable.out | 1094 ++++++++++++++++-
src/test/regress/sql/sqljson_jsontable.sql | 561 ++++++++-
2 files changed, 1599 insertions(+), 56 deletions(-)
diff --git a/src/test/regress/expected/sqljson_jsontable.out b/src/test/regress/expected/sqljson_jsontable.out
index 458c5aaa5b0..50fb05cb62e 100644
--- a/src/test/regress/expected/sqljson_jsontable.out
+++ b/src/test/regress/expected/sqljson_jsontable.out
@@ -251,6 +251,29 @@ SELECT * FROM JSON_TABLE(jsonb '{"d1": "foo"}', '$'
(1 row)
-- JSON_TABLE: Test backward parsing
+CREATE VIEW jsonb_table_view1 AS
+SELECT * FROM
+ JSON_TABLE(
+ jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
CREATE VIEW jsonb_table_view2 AS
SELECT * FROM
JSON_TABLE(
@@ -300,6 +323,44 @@ SELECT * FROM
ia int[] PATH '$',
ta text[] PATH '$',
jba jsonb[] PATH '$'));
+\sv jsonb_table_view1
+CREATE OR REPLACE VIEW public.jsonb_table_view1 AS
+ SELECT id,
+ a1,
+ b1,
+ a11,
+ a21,
+ a22
+ FROM JSON_TABLE(
+ 'null'::jsonb, '$[*]' AS json_table_path_0
+ PASSING
+ 1 + 2 AS a,
+ '"foo"'::json AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1
+ COLUMNS (
+ a1 integer PATH '$."a1"',
+ b1 text PATH '$."b1"',
+ NESTED PATH '$[*]' AS "p1 1"
+ COLUMNS (
+ a11 text PATH '$."a11"'
+ )
+ ),
+ NESTED PATH '$[2]' AS p2
+ COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1"
+ COLUMNS (
+ a21 text PATH '$."a21"'
+ ),
+ NESTED PATH '$[*]' AS p22
+ COLUMNS (
+ a22 text PATH '$."a22"'
+ )
+ )
+ )
+ PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR
+ )
\sv jsonb_table_view2
CREATE OR REPLACE VIEW public.jsonb_table_view2 AS
SELECT "int",
@@ -321,6 +382,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view2 AS
"numeric" numeric PATH '$',
domain jsonb_test_domain PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view3
CREATE OR REPLACE VIEW public.jsonb_table_view3 AS
@@ -341,6 +403,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view3 AS
jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES,
jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view4
CREATE OR REPLACE VIEW public.jsonb_table_view4 AS
@@ -359,6 +422,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view4 AS
aaa integer PATH '$."aaa"',
aaa1 integer PATH '$."aaa"'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view5
CREATE OR REPLACE VIEW public.jsonb_table_view5 AS
@@ -375,6 +439,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view5 AS
exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR,
exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
\sv jsonb_table_view6
CREATE OR REPLACE VIEW public.jsonb_table_view6 AS
@@ -397,45 +462,54 @@ CREATE OR REPLACE VIEW public.jsonb_table_view6 AS
ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES,
jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view1;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Table Function Scan on "json_table"
+ Output: "json_table".id, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR)
+(3 rows)
+
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view2;
- QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".domain
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS ("int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS ("int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view3;
- QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jst text FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jst text FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsc character(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsv character varying(4) FORMAT JSON PATH '$' WITHOUT WRAPPER KEEP QUOTES) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view4;
- QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (jsb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsbq jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (jsb jsonb PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsbq jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view5;
- QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".exists1, "json_table".exists2, "json_table".exists3
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view6;
- QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".ia, "json_table".ta, "json_table".jba
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js2 json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER KEEP QUOTES, jsb2q jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, ia integer[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (js2 json PATH '$' WITHOUT WRAPPER KEEP QUOTES, jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER KEEP QUOTES, jsb2q jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES, ia integer[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES, jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES) PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
-- JSON_TABLE() with alias
@@ -448,11 +522,11 @@ SELECT * FROM
"int" int PATH '$',
"text" text PATH '$'
)) json_table_func;
- QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table" json_table_func
Output: id, "int", text
- Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, "int" integer PATH '$', text text PATH '$'))
+ Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, "int" integer PATH '$', text text PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)
(3 rows)
EXPLAIN (COSTS OFF, FORMAT JSON, VERBOSE)
@@ -464,24 +538,25 @@ SELECT * FROM
"int" int PATH '$',
"text" text PATH '$'
)) json_table_func;
- QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [ +
- { +
- "Plan": { +
- "Node Type": "Table Function Scan", +
- "Parallel Aware": false, +
- "Async Capable": false, +
- "Table Function Name": "json_table", +
- "Alias": "json_table_func", +
- "Disabled": false, +
- "Output": ["id", "\"int\"", "text"], +
- "Table Function Call": "JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '\"foo\"'::jsonb AS \"b c\" COLUMNS (id FOR ORDINALITY, \"int\" integer PATH '$', text text PATH '$'))"+
- } +
- } +
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [ +
+ { +
+ "Plan": { +
+ "Node Type": "Table Function Scan", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Table Function Name": "json_table", +
+ "Alias": "json_table_func", +
+ "Disabled": false, +
+ "Output": ["id", "\"int\"", "text"], +
+ "Table Function Call": "JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_0 PASSING 3 AS a, '\"foo\"'::jsonb AS \"b c\" COLUMNS (id FOR ORDINALITY, \"int\" integer PATH '$', text text PATH '$') PLAN (json_table_path_0) EMPTY ARRAY ON ERROR)"+
+ } +
+ } +
]
(1 row)
+DROP VIEW jsonb_table_view1;
DROP VIEW jsonb_table_view2;
DROP VIEW jsonb_table_view3;
DROP VIEW jsonb_table_view4;
@@ -525,6 +600,30 @@ FROM
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
ON true;
ERROR: invalid input syntax for type integer: "err"
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+ERROR: no SQL/JSON item found for specified path of column "a"
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: jsonpath member accessor can only be applied to an object
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+ERROR: no SQL/JSON item found for specified path of column "a"
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 2
+(1 row)
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+ a
+---
+ 1
+(1 row)
+
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
ERROR: no SQL/JSON item found for specified path of column "a"
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON ERROR) ERROR ON ERROR) jt;
@@ -712,8 +811,44 @@ LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '...
SELECT * FROM JSON_TABLE('{"a": [{"b": "1"}, {"b": "2"}]}', '$' COLUMNS (b json path '$.a[*].b' ERROR ON ERROR));
ERROR: JSON path expression for column "b" must return single item when no wrapper is requested
HINT: Use the WITH WRAPPER clause to wrap SQL/JSON items into an array.
--- JSON_TABLE: nested paths
--- Duplicate path names
+-- JSON_TABLE: nested paths and plans
+-- Should fail (JSON_TABLE columns must contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' -- AS <path name> required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: jsonb '[]', '$' -- AS <path name> required here
+ ^
+DETAIL: JSON_TABLE path must contain explicit AS pathname specification if explicit PLAN clause is used
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS <path name> required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+ foo
+-----
+
+(1 row)
+
+-- Should fail (column names must be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
+ERROR: duplicate JSON_TABLE column or path name: a
+LINE 4: a int
+ ^
SELECT * FROM JSON_TABLE(
jsonb '[]', '$' AS a
COLUMNS (
@@ -774,6 +909,194 @@ SELECT * FROM JSON_TABLE(
ERROR: duplicate JSON_TABLE column or path name: a
LINE 10: NESTED PATH '$' AS a
^
+-- JSON_TABLE: plan validation
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+ERROR: invalid JSON_TABLE plan
+LINE 12: PLAN (p1)
+ ^
+DETAIL: PATH name mismatch: expected p0 but p1 is given.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p1 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 4: NESTED PATH '$' AS p1 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p1 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+ERROR: invalid JSON_TABLE plan clause
+LINE 12: PLAN (p0 UNION p1 UNION p11)
+ ^
+DETAIL: Expected INNER or OUTER.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 8: NESTED PATH '$' AS p2 COLUMNS (
+ ^
+DETAIL: PLAN clause for nested path p2 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 5: NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ ^
+DETAIL: PLAN clause for nested path p11 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE plan clause
+LINE 12: PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+ ^
+DETAIL: PLAN clause contains some extra or duplicate sibling nodes.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 6: NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ^
+DETAIL: PLAN clause for nested path p12 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+ERROR: invalid JSON_TABLE specification
+LINE 9: NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ ^
+DETAIL: PLAN clause for nested path p21 was not found.
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+ bar | foo | baz
+-----+-----+-----
+(0 rows)
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
+ERROR: invalid JSON_TABLE expression
+LINE 2: jsonb 'null', 'strict $[*]' -- without root path name
+ ^
+DETAIL: JSON_TABLE path must contain explicit AS pathname specification if explicit PLAN clause is used
-- JSON_TABLE: plan execution
CREATE TEMP TABLE jsonb_table_test (js jsonb);
INSERT INTO jsonb_table_test
@@ -785,6 +1108,89 @@ VALUES (
{"x": "4", "b": [1, 2], "c": 123}
]'
);
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-- default plans
+create or replace view outer_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt
+);
+NOTICE: view "outer_union" will be a temporary view
+DETAIL: It depends on temporary table jsonb_table_test.
+create or replace view outer_cross as (
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt
+);
+NOTICE: view "outer_cross" will be a temporary view
+DETAIL: It depends on temporary table jsonb_table_test.
+create or replace view inner_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, union)
+ ) jt
+);
+NOTICE: view "inner_union" will be a temporary view
+DETAIL: It depends on temporary table jsonb_table_test.
+create or replace view inner_cross as (
select
jt.*
from
@@ -794,25 +1200,603 @@ from
columns (
n for ordinality,
a int path 'lax $.a' default -1 on empty,
- nested path 'strict $.b[*]' as pb columns (b_id for ordinality, b int path '$' ),
- nested path 'strict $.c[*]' as pc columns (c_id for ordinality, c int path '$' )
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
)
+ plan default (inner, cross)
+ ) jt
+);
+NOTICE: view "inner_cross" will be a temporary view
+DETAIL: It depends on temporary table jsonb_table_test.
+select * from inner_cross;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+select * from outer_cross;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+select * from inner_union;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+select * from outer_union;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(11 rows)
+
+-----------------------------------------------------
+--inner_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
) jt;
- n | a | b_id | b | c_id | c
----+----+------+---+------+----
- 1 | 1 | | | |
- 2 | 2 | 1 | 1 | |
- 2 | 2 | 2 | 2 | |
- 2 | 2 | 3 | 3 | |
- 2 | 2 | | | 1 | 10
- 2 | 2 | | | 2 |
- 2 | 2 | | | 3 | 20
- 3 | 3 | 1 | 1 | |
- 3 | 3 | 2 | 2 | |
- 4 | -1 | 1 | 1 | |
- 4 | -1 | 2 | 2 | |
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 | 0
+ 2 | 2 | 3 | 20
+(9 rows)
+
+--outer_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 0 | 10
+ 2 | 2 | 0 | 0
+ 2 | 2 | 0 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 2 | 2 | 0 | 10
+ 2 | 2 | 0 | 0
+ 2 | 2 | 0 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+--inner_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 0 |
+ 2 | 2 | 2 |
+ 2 | 2 | 0 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 0 |
+ 3 | 3 | 2 |
+ 4 | -1 | 0 |
+ 4 | -1 | 2 |
+(10 rows)
+
+--outer_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 0 |
+ 2 | 2 | 2 |
+ 2 | 2 | 0 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 0 |
+ 3 | 3 | 2 |
+ 4 | -1 | 0 |
+ 4 | -1 | 2 |
+(11 rows)
+
+----------------------------------------
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(9 rows)
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 | 0
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 | 0
+ 2 | 2 | 2 | 20
+(6 rows)
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | | 10
+ 2 | 2 | | 0
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(9 rows)
+
+SELECT * from JSON_TABLE(jsonb '[1, 2, 3,null, 3]', '$[*]? (@ == 3)' PASSING 3 as x COLUMNS(xx int path '$'));
+ xx
+----
+ 3
+ 3
+(2 rows)
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 3 |
+ 2 | 2 | | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(5 rows)
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ -- nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(9 rows)
+
+-- specific plan (p outer (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb union pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
(11 rows)
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+ n | a | c | b
+---+----+----+---
+ 1 | 1 | |
+ 2 | 2 | 10 |
+ 2 | 2 | |
+ 2 | 2 | 20 |
+ 2 | 2 | | 1
+ 2 | 2 | | 2
+ 2 | 2 | | 3
+ 3 | 3 | | 1
+ 3 | 3 | | 2
+ 4 | -1 | | 1
+ 4 | -1 | | 2
+(11 rows)
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 2 | 2 | 1 |
+ 2 | 2 | 2 |
+ 2 | 2 | 3 |
+ 2 | 2 | | 10
+ 2 | 2 | |
+ 2 | 2 | | 20
+ 3 | 3 | 1 |
+ 3 | 3 | 2 |
+ 4 | -1 | 1 |
+ 4 | -1 | 2 |
+(10 rows)
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+---+---+----
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+(9 rows)
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+ n | a | b | c
+---+----+---+----
+ 1 | 1 | |
+ 2 | 2 | 1 | 10
+ 2 | 2 | 1 |
+ 2 | 2 | 1 | 20
+ 2 | 2 | 2 | 10
+ 2 | 2 | 2 |
+ 2 | 2 | 2 | 20
+ 2 | 2 | 3 | 10
+ 2 | 2 | 3 |
+ 2 | 2 | 3 | 20
+ 3 | 3 | |
+ 4 | -1 | |
+(12 rows)
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (jsonb
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+ n | a | b | b1 | c | c1 | b
+---+---+--------------+-----+------+----+-----
+ 1 | 1 | [1, 10] | 1 | 1 | | 101
+ 1 | 1 | [1, 10] | 1 | null | | 101
+ 1 | 1 | [1, 10] | 1 | 2 | | 101
+ 1 | 1 | [1, 10] | 10 | 1 | | 110
+ 1 | 1 | [1, 10] | 10 | null | | 110
+ 1 | 1 | [1, 10] | 10 | 2 | | 110
+ 1 | 1 | [2] | 2 | 1 | | 102
+ 1 | 1 | [2] | 2 | null | | 102
+ 1 | 1 | [2] | 2 | 2 | | 102
+ 1 | 1 | [3, 30, 300] | 3 | 1 | | 103
+ 1 | 1 | [3, 30, 300] | 3 | null | | 103
+ 1 | 1 | [3, 30, 300] | 3 | 2 | | 103
+ 1 | 1 | [3, 30, 300] | 30 | 1 | | 130
+ 1 | 1 | [3, 30, 300] | 30 | null | | 130
+ 1 | 1 | [3, 30, 300] | 30 | 2 | | 130
+ 1 | 1 | [3, 30, 300] | 300 | 1 | | 400
+ 1 | 1 | [3, 30, 300] | 300 | null | | 400
+ 1 | 1 | [3, 30, 300] | 300 | 2 | | 400
+ 2 | 2 | | | | |
+ 3 | | | | | |
+(20 rows)
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+ x | y | y | z
+---+---+--------------+---
+ 2 | 1 | [1, 2, 3] | 1
+ 2 | 1 | [1, 2, 3] | 2
+ 2 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [1, 2, 3] | 1
+ 3 | 1 | [1, 2, 3] | 2
+ 3 | 1 | [1, 2, 3] | 3
+ 3 | 1 | [2, 3, 4, 5] | 2
+ 3 | 1 | [2, 3, 4, 5] | 3
+ 3 | 1 | [2, 3, 4, 5] | 4
+ 3 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [1, 2, 3] | 1
+ 4 | 1 | [1, 2, 3] | 2
+ 4 | 1 | [1, 2, 3] | 3
+ 4 | 1 | [2, 3, 4, 5] | 2
+ 4 | 1 | [2, 3, 4, 5] | 3
+ 4 | 1 | [2, 3, 4, 5] | 4
+ 4 | 1 | [2, 3, 4, 5] | 5
+ 4 | 1 | [3, 4, 5, 6] | 3
+ 4 | 1 | [3, 4, 5, 6] | 4
+ 4 | 1 | [3, 4, 5, 6] | 5
+ 4 | 1 | [3, 4, 5, 6] | 6
+ 2 | 2 | [1, 2, 3] | 2
+ 2 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [1, 2, 3] | 2
+ 3 | 2 | [1, 2, 3] | 3
+ 3 | 2 | [2, 3, 4, 5] | 2
+ 3 | 2 | [2, 3, 4, 5] | 3
+ 3 | 2 | [2, 3, 4, 5] | 4
+ 3 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [1, 2, 3] | 2
+ 4 | 2 | [1, 2, 3] | 3
+ 4 | 2 | [2, 3, 4, 5] | 2
+ 4 | 2 | [2, 3, 4, 5] | 3
+ 4 | 2 | [2, 3, 4, 5] | 4
+ 4 | 2 | [2, 3, 4, 5] | 5
+ 4 | 2 | [3, 4, 5, 6] | 3
+ 4 | 2 | [3, 4, 5, 6] | 4
+ 4 | 2 | [3, 4, 5, 6] | 5
+ 4 | 2 | [3, 4, 5, 6] | 6
+ 2 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [1, 2, 3] | 3
+ 3 | 3 | [2, 3, 4, 5] | 3
+ 3 | 3 | [2, 3, 4, 5] | 4
+ 3 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [1, 2, 3] | 3
+ 4 | 3 | [2, 3, 4, 5] | 3
+ 4 | 3 | [2, 3, 4, 5] | 4
+ 4 | 3 | [2, 3, 4, 5] | 5
+ 4 | 3 | [3, 4, 5, 6] | 3
+ 4 | 3 | [3, 4, 5, 6] | 4
+ 4 | 3 | [3, 4, 5, 6] | 5
+ 4 | 3 | [3, 4, 5, 6] | 6
+(52 rows)
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+ y
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
+ERROR: only string constants are supported in JSON_TABLE path specification
+LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '...
+ ^
-- PASSING arguments are passed to nested paths and their columns' paths
SELECT *
FROM
@@ -912,6 +1896,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view_nested AS
)
)
)
+ PLAN (json_table_path_0 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) EMPTY ARRAY ON ERROR
)
DROP VIEW jsonb_table_view_nested;
CREATE TABLE s (js jsonb);
@@ -1103,6 +2088,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view7 AS
)
)
)
+ PLAN (c1 OUTER ((((json_table_path_0 OUTER z22) UNION json_table_path_1) UNION (json_table_path_2 OUTER z1)) UNION (json_table_path_3 OUTER z21))) EMPTY ARRAY ON ERROR
) sub
DROP VIEW jsonb_table_view7;
DROP TABLE s;
@@ -1144,6 +2130,7 @@ CREATE OR REPLACE VIEW public.json_table_view8 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
CREATE VIEW json_table_view9 AS SELECT * from JSON_TABLE('"a"', '$' COLUMNS (a text PATH '$') ERROR ON ERROR);
\sv json_table_view9;
@@ -1152,8 +2139,9 @@ CREATE OR REPLACE VIEW public.json_table_view9 AS
FROM JSON_TABLE(
'"a"'::text, '$' AS json_table_path_0
COLUMNS (
- a text PATH '$'
- ) ERROR ON ERROR
+ a text PATH '$' NULL ON EMPTY
+ )
+ PLAN (json_table_path_0) ERROR ON ERROR
)
DROP VIEW json_table_view8, json_table_view9;
-- Test JSON_TABLE() deparsing -- don't emit default ON ERROR behavior
@@ -1166,6 +2154,7 @@ CREATE OR REPLACE VIEW public.json_table_view8 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
CREATE VIEW json_table_view9 AS SELECT * from JSON_TABLE('"a"', '$' COLUMNS (a text PATH '$') EMPTY ARRAY ON ERROR);
\sv json_table_view9;
@@ -1176,5 +2165,6 @@ CREATE OR REPLACE VIEW public.json_table_view9 AS
COLUMNS (
a text PATH '$'
)
+ PLAN (json_table_path_0) EMPTY ARRAY ON ERROR
)
DROP VIEW json_table_view8, json_table_view9;
diff --git a/src/test/regress/sql/sqljson_jsontable.sql b/src/test/regress/sql/sqljson_jsontable.sql
index 154eea79c76..d0c88ec6d10 100644
--- a/src/test/regress/sql/sqljson_jsontable.sql
+++ b/src/test/regress/sql/sqljson_jsontable.sql
@@ -134,6 +134,30 @@ SELECT * FROM JSON_TABLE(jsonb '{"d1": "foo"}', '$'
-- JSON_TABLE: Test backward parsing
+CREATE VIEW jsonb_table_view1 AS
+SELECT * FROM
+ JSON_TABLE(
+ jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
+ COLUMNS (
+ id FOR ORDINALITY,
+ NESTED PATH '$[1]' AS p1 COLUMNS (
+ a1 int,
+ NESTED PATH '$[*]' AS "p1 1" COLUMNS (
+ a11 text
+ ),
+ b1 text
+ ),
+ NESTED PATH '$[2]' AS p2 COLUMNS (
+ NESTED PATH '$[*]' AS "p2:1" COLUMNS (
+ a21 text
+ ),
+ NESTED PATH '$[*]' AS p22 COLUMNS (
+ a22 text
+ )
+ )
+ )
+ );
+
CREATE VIEW jsonb_table_view2 AS
SELECT * FROM
JSON_TABLE(
@@ -188,12 +212,14 @@ SELECT * FROM
ta text[] PATH '$',
jba jsonb[] PATH '$'));
+\sv jsonb_table_view1
\sv jsonb_table_view2
\sv jsonb_table_view3
\sv jsonb_table_view4
\sv jsonb_table_view5
\sv jsonb_table_view6
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view1;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view2;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view3;
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view4;
@@ -221,6 +247,7 @@ SELECT * FROM
"text" text PATH '$'
)) json_table_func;
+DROP VIEW jsonb_table_view1;
DROP VIEW jsonb_table_view2;
DROP VIEW jsonb_table_view3;
DROP VIEW jsonb_table_view4;
@@ -253,6 +280,14 @@ FROM
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
ON true;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
+
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
+
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON ERROR) ERROR ON ERROR) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
@@ -323,9 +358,36 @@ SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int)
-- JsonPathQuery() error message mentioning column name
SELECT * FROM JSON_TABLE('{"a": [{"b": "1"}, {"b": "2"}]}', '$' COLUMNS (b json path '$.a[*].b' ERROR ON ERROR));
--- JSON_TABLE: nested paths
+-- JSON_TABLE: nested paths and plans
+
+-- Should fail (JSON_TABLE columns must contain explicit AS path
+-- specifications if explicit PLAN clause is used)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' -- AS <path name> required here
+ COLUMNS (
+ foo int PATH '$'
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS path1
+ COLUMNS (
+ NESTED PATH '$' COLUMNS ( -- AS <path name> required here
+ foo int PATH '$'
+ )
+ )
+ PLAN DEFAULT (UNION)
+) jt;
+
+-- Should fail (column names must be distinct)
+SELECT * FROM JSON_TABLE(
+ jsonb '[]', '$' AS a
+ COLUMNS (
+ a int
+ )
+) jt;
--- Duplicate path names
SELECT * FROM JSON_TABLE(
jsonb '[]', '$' AS a
COLUMNS (
@@ -376,6 +438,161 @@ SELECT * FROM JSON_TABLE(
)
) jt;
+-- JSON_TABLE: plan validation
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p1)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER p3)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 UNION p1 UNION p11)
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p13))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER (p1 CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 UNION p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER p11) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', '$[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' AS p0
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)))
+) jt;
+
+SELECT * FROM JSON_TABLE(
+ jsonb 'null', 'strict $[*]' -- without root path name
+ COLUMNS (
+ NESTED PATH '$' AS p1 COLUMNS (
+ NESTED PATH '$' AS p11 COLUMNS ( foo int ),
+ NESTED PATH '$' AS p12 COLUMNS ( bar int )
+ ),
+ NESTED PATH '$' AS p2 COLUMNS (
+ NESTED PATH '$' AS p21 COLUMNS ( baz int )
+ )
+ )
+ PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
+) jt;
-- JSON_TABLE: plan execution
@@ -391,6 +608,73 @@ VALUES (
]'
);
+-- unspecified plan (outer, union)
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ ) jt;
+
+-- default plans
+
+create or replace view outer_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt
+);
+
+create or replace view outer_cross as (
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (outer, cross)
+ ) jt
+);
+
+create or replace view inner_union as (
+select jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, union)
+ ) jt
+);
+
+create or replace view inner_cross as (
select
jt.*
from
@@ -400,11 +684,280 @@ from
columns (
n for ordinality,
a int path 'lax $.a' default -1 on empty,
- nested path 'strict $.b[*]' as pb columns (b_id for ordinality, b int path '$' ),
- nested path 'strict $.c[*]' as pc columns (c_id for ordinality, c int path '$' )
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan default (inner, cross)
+ ) jt
+);
+
+
+select * from inner_cross;
+select * from outer_cross;
+select * from inner_union;
+select * from outer_union;
+-----------------------------------------------------
+--inner_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+
+--outer_cross;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
)
+ plan default (outer, cross)
) jt;
+--inner_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+
+--outer_union;
+select jt.* from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$?(@ == $"x")' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, union)
+ ) jt;
+
+----------------------------------------
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (outer, cross)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, cross)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ != 3)' as pb columns ( b int path '$' default 0 on empty),
+ -- nested path 'strict $.b[*] ' as pb columns ( b int path '$' default 0 on empty),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$?(@ == $"x" * 5 || @ == $"y" )' default 0 on empty)
+ )
+ plan default (inner, union)
+ ) jt;
+
+SELECT * from JSON_TABLE(jsonb '[1, 2, 3,null, 3]', '$[*]? (@ == 3)' PASSING 3 as x COLUMNS(xx int path '$'));
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+
+select jt.* from jsonb_table_test jtt, json_table(
+ jtt.js,'strict $[*]' as p passing 2 as x, 20 as y, 3 as z
+ columns (
+ n for ordinality,
+ a int path 'lax $.a ' default -1 on empty,
+ -- nested path 'strict $.b[*] ?(@ == 3)' as pb columns ( b int path '$'),
+ nested path 'strict $.b[*]' as pb columns ( b int path '$'),
+ nested path 'strict $.c[*] ? (@ == 20)' as pc columns ( c int path '$')
+ )
+ plan default (outer, union)
+ ) jt;
+
+-- specific plan (p outer (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb union pc))
+ ) jt;
+
+-- specific plan (p outer (pc union pb))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pc union pb))
+ ) jt;
+
+-- specific plan (p inner (pb union pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb union pc))
+ ) jt;
+
+-- specific plan (p inner (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p inner (pb cross pc))
+ ) jt;
+
+-- specific plan (p outer (pb cross pc))
+select
+ jt.*
+from
+ jsonb_table_test jtt,
+ json_table (
+ jtt.js,'strict $[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on empty,
+ nested path 'strict $.b[*]' as pb columns ( b int path '$' ),
+ nested path 'strict $.c[*]' as pc columns ( c int path '$' )
+ )
+ plan (p outer (pb cross pc))
+ ) jt;
+
+
+select
+ jt.*, b1 + 100 as b
+from
+ json_table (jsonb
+ '[
+ {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]},
+ {"a": 2, "b": [10, 20], "c": [1, null, 2]},
+ {"x": "3", "b": [11, 22, 33, 44]}
+ ]',
+ '$[*]' as p
+ columns (
+ n for ordinality,
+ a int path 'lax $.a' default -1 on error,
+ nested path 'strict $.b[*]' as pb columns (
+ b text format json path '$',
+ nested path 'strict $[*]' as pb1 columns (
+ b1 int path '$'
+ )
+ ),
+ nested path 'strict $.c[*]' as pc columns (
+ c text format json path '$',
+ nested path 'strict $[*]' as pc1 columns (
+ c1 int path '$'
+ )
+ )
+ )
+ --plan default(outer, cross)
+ plan(p outer ((pb inner pb1) cross (pc outer pc1)))
+ ) jt;
+
+-- Should succeed (JSON arguments are passed to root and nested paths)
+SELECT *
+FROM
+ generate_series(1, 4) x,
+ generate_series(1, 3) y,
+ JSON_TABLE(jsonb
+ '[[1,2,3],[2,3,4,5],[3,4,5,6]]',
+ 'strict $[*] ? (@[*] < $x)'
+ PASSING x AS x, y AS y
+ COLUMNS (
+ y text FORMAT JSON PATH '$',
+ NESTED PATH 'strict $[*] ? (@ >= $y)'
+ COLUMNS (
+ z int PATH '$'
+ )
+ )
+ ) jt;
+
+-- Should fail (JSON arguments are not passed to column paths)
+SELECT *
+FROM JSON_TABLE(
+ jsonb '[1,2,3]',
+ '$[*] ? (@ < $x)'
+ PASSING 10 AS x
+ COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
+ ) jt;
+
+-- Should fail (not supported)
+SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
+
-- PASSING arguments are passed to nested paths and their columns' paths
SELECT *
--
2.43.0
v21-0001-JSON-TABLE-PLAN-clause.patchapplication/octet-stream; name=v21-0001-JSON-TABLE-PLAN-clause.patchDownload
From 18a80ba29961dd8ac3705686167520af2f45cc8f Mon Sep 17 00:00:00 2001
From: Nikita Malakhov <n.malakhov@postgrespro.ru>
Date: Mon, 22 Dec 2025 13:51:12 +0300
Subject: [PATCH] JSON_TABLE PLAN clause part 1/2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This patch adds the PLAN clause to JSON_TABLE expression allowing user
to specify how data from nested paths are joined, allowing considerable
freedom in shaping the tabular output of JSON_TABLE.
PLAN DEFAULT allows user to specify global strategies when dealing
with sibling or child nested paths. This is often sufficient
to achieve the necessary goal, and is considerably simpler than the
full PLAN clause, which allows the user to specify the strategy to be
used for each named nested path.
Code was taken from v45-0001-JSON_TABLE.patch from
https://www.postgresql.org/message-id/CA%2BHiwqE1gcPkQhBko%2BUbvVvAtRBaLfOpmHbFrK79pW_5F51Oww%40mail.gmail.com
Original patch was modified according to recent changes in SQL/JSON
JSON_TABLE, and invalid PLAN test results found while adapting
original patch to these changes.
This patch [part 1] includes all changes - grammar, plan parsing
and JSON_TABLE columns transformation.
Author: Nikita Glukhov
Author: Teodor Sigaev
Author: Oleg Bartunov
Author: Alexander Korotkov
Author: Andrew Dunstan
Author: Amit Langote
Author: Anton A. Melnikov
Author: Nikita Malakhov
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
---
src/backend/nodes/makefuncs.c | 54 ++++
src/backend/parser/gram.y | 101 ++++++-
src/backend/parser/parse_jsontable.c | 393 +++++++++++++++++++++-----
src/backend/utils/adt/jsonpath_exec.c | 167 ++++++++---
src/backend/utils/adt/ruleutils.c | 53 +++-
src/include/nodes/makefuncs.h | 5 +
src/include/nodes/parsenodes.h | 43 +++
src/include/nodes/primnodes.h | 2 +
src/tools/pgindent/typedefs.list | 7 +
9 files changed, 713 insertions(+), 112 deletions(-)
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index e2d9e9be41a..5349c0e042b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -963,6 +963,60 @@ makeJsonBehavior(JsonBehaviorType btype, Node *expr, int location)
return behavior;
}
+/*
+ * makeJsonTableDefaultPlan -
+ * creates a JsonTablePlanSpec node to represent a "default" JSON_TABLE plan
+ * with given join strategy
+ */
+Node *
+makeJsonTableDefaultPlan(JsonTablePlanJoinType join_type, int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_DEFAULT;
+ n->join_type = join_type;
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonTableSimplePlan -
+ * creates a JsonTablePlanSpec node to represent a "simple" JSON_TABLE plan
+ * for given PATH
+ */
+Node *
+makeJsonTableSimplePlan(char *pathname, int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_SIMPLE;
+ n->pathname = pathname;
+ n->location = location;
+
+ return (Node *) n;
+}
+
+/*
+ * makeJsonTableJoinedPlan -
+ * creates a JsonTablePlanSpec node to represent join between the given
+ * pair of plans
+ */
+Node *
+makeJsonTableJoinedPlan(JsonTablePlanJoinType type, Node *plan1, Node *plan2,
+ int location)
+{
+ JsonTablePlanSpec *n = makeNode(JsonTablePlanSpec);
+
+ n->plan_type = JSTP_JOINED;
+ n->join_type = type;
+ n->plan1 = castNode(JsonTablePlanSpec, plan1);
+ n->plan2 = castNode(JsonTablePlanSpec, plan2);
+ n->location = location;
+
+ return (Node *) n;
+}
+
/*
* makeJsonKeyValue -
* creates a JsonKeyValue node
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 28f4e11e30f..0a0f27c8145 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -666,6 +666,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_table
json_table_column_definition
json_table_column_path_clause_opt
+ json_table_plan_clause_opt
+ json_table_plan
+ json_table_plan_simple
+ json_table_plan_outer
+ json_table_plan_inner
+ json_table_plan_union
+ json_table_plan_cross
+ json_table_plan_primary
%type <list> json_name_and_value_list
json_value_expr_list
json_array_aggregate_order_by_clause_opt
@@ -677,6 +685,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> json_behavior_type
json_predicate_type_constraint
json_quotes_clause_opt
+ json_table_default_plan_choices
+ json_table_default_plan_inner_outer
+ json_table_default_plan_union_cross
json_wrapper_behavior
%type <boolean> json_key_uniqueness_constraint_opt
json_object_constructor_null_clause_opt
@@ -14483,6 +14494,7 @@ json_table:
json_value_expr ',' a_expr json_table_path_name_opt
json_passing_clause_opt
COLUMNS '(' json_table_column_definition_list ')'
+ json_table_plan_clause_opt
json_on_error_clause_opt
')'
{
@@ -14494,13 +14506,15 @@ json_table:
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"),
+ 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->planspec = (JsonTablePlanSpec *) $12;
+ n->on_error = (JsonBehavior *) $13;
n->location = @1;
$$ = (Node *) n;
}
@@ -14592,8 +14606,7 @@ json_table_column_definition:
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
- n->pathspec = (JsonTablePathSpec *)
- makeJsonTablePathSpec($3, NULL, @3, -1);
+ n->pathspec = makeJsonTablePathSpec($3, NULL, @3, -1);
n->columns = $6;
n->location = @1;
$$ = (Node *) n;
@@ -14604,8 +14617,7 @@ json_table_column_definition:
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
- n->pathspec = (JsonTablePathSpec *)
- makeJsonTablePathSpec($3, $5, @3, @5);
+ n->pathspec = makeJsonTablePathSpec($3, $5, @3, @5);
n->columns = $8;
n->location = @1;
$$ = (Node *) n;
@@ -14624,6 +14636,83 @@ json_table_column_path_clause_opt:
{ $$ = NULL; }
;
+json_table_plan_clause_opt:
+ PLAN '(' json_table_plan ')'
+ { $$ = $3; }
+ | PLAN DEFAULT '(' json_table_default_plan_choices ')'
+ { $$ = makeJsonTableDefaultPlan($4, @1); }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
+json_table_plan:
+ json_table_plan_simple
+ | json_table_plan_outer
+ | json_table_plan_inner
+ | json_table_plan_union
+ | json_table_plan_cross
+ ;
+
+json_table_plan_simple:
+ name
+ { $$ = makeJsonTableSimplePlan($1, @1); }
+ ;
+
+json_table_plan_outer:
+ json_table_plan_simple OUTER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_OUTER, $1, $3, @1); }
+ ;
+
+json_table_plan_inner:
+ json_table_plan_simple INNER_P json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_INNER, $1, $3, @1); }
+ ;
+
+json_table_plan_union:
+ json_table_plan_primary UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_UNION, $1, $3, @1); }
+ | json_table_plan_union UNION json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_UNION, $1, $3, @1); }
+ ;
+
+json_table_plan_cross:
+ json_table_plan_primary CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_CROSS, $1, $3, @1); }
+ | json_table_plan_cross CROSS json_table_plan_primary
+ { $$ = makeJsonTableJoinedPlan(JSTP_JOIN_CROSS, $1, $3, @1); }
+ ;
+
+json_table_plan_primary:
+ json_table_plan_simple
+ { $$ = $1; }
+ | '(' json_table_plan ')'
+ {
+ castNode(JsonTablePlanSpec, $2)->location = @1;
+ $$ = $2;
+ }
+ ;
+
+json_table_default_plan_choices:
+ json_table_default_plan_inner_outer
+ { $$ = $1 | JSTP_JOIN_UNION; }
+ | json_table_default_plan_union_cross
+ { $$ = $1 | JSTP_JOIN_OUTER; }
+ | json_table_default_plan_inner_outer ',' json_table_default_plan_union_cross
+ { $$ = $1 | $3; }
+ | json_table_default_plan_union_cross ',' json_table_default_plan_inner_outer
+ { $$ = $1 | $3; }
+ ;
+
+json_table_default_plan_inner_outer:
+ INNER_P { $$ = JSTP_JOIN_INNER; }
+ | OUTER_P { $$ = JSTP_JOIN_OUTER; }
+ ;
+
+json_table_default_plan_union_cross:
+ UNION { $$ = JSTP_JOIN_UNION; }
+ | CROSS { $$ = JSTP_JOIN_CROSS; }
+ ;
+
/*****************************************************************************
*
* Type syntax
diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c
index 13d533b83f3..7d7c595c819 100644
--- a/src/backend/parser/parse_jsontable.c
+++ b/src/backend/parser/parse_jsontable.c
@@ -39,17 +39,22 @@ typedef struct JsonTableParseContext
} JsonTableParseContext;
static JsonTablePlan *transformJsonTableColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *planspec,
List *columns,
- List *passingArgs,
- JsonTablePathSpec *pathspec);
+ List *passing_Args,
+ JsonTablePathSpec * pathspec);
static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt,
- List *passingArgs,
+ JsonTablePlanSpec *plan,
+ List *passing_Args,
List *columns);
static JsonFuncExpr *transformJsonTableColumn(JsonTableColumn *jtc,
Node *contextItemExpr,
- List *passingArgs);
+ List *passingArgs,
+ bool errorOnError);
static bool isCompositeType(Oid typid);
-static JsonTablePlan *makeJsonTablePathScan(JsonTablePathSpec *pathspec,
+static JsonTablePlan *makeJsonTablePathScan(JsonTableParseContext *cxt,
+ JsonTablePathSpec *pathspec,
+ JsonTablePlanSpec *planspec,
bool errorOnError,
int colMin, int colMax,
JsonTablePlan *childplan);
@@ -57,8 +62,14 @@ static void CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
List *columns);
static bool LookupPathOrColumnName(JsonTableParseContext *cxt, char *name);
static char *generateJsonTablePathName(JsonTableParseContext *cxt);
-static JsonTablePlan *makeJsonTableSiblingJoin(JsonTablePlan *lplan,
+static void validateJsonTableChildPlan(ParseState *pstate,
+ JsonTablePlanSpec *plan,
+ List *columns);
+static JsonTablePlan *makeJsonTableSiblingJoin(bool cross,
+ JsonTablePlan *lplan,
JsonTablePlan *rplan);
+static void
+appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs);
/*
* transformJsonTable -
@@ -76,6 +87,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
TableFunc *tf;
JsonFuncExpr *jfe;
JsonExpr *je;
+ JsonTablePlanSpec *plan = jt->planspec;
JsonTablePathSpec *rootPathSpec = jt->pathspec;
bool is_lateral;
JsonTableParseContext cxt = {pstate};
@@ -94,8 +106,21 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
parser_errposition(pstate, jt->on_error->location));
cxt.pathNameId = 0;
+
if (rootPathSpec->name == NULL)
+ {
+ if (jt->planspec != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE expression"),
+ errdetail("JSON_TABLE path must contain"
+ " explicit AS pathname specification if"
+ " explicit PLAN clause is used"),
+ parser_errposition(pstate, rootPathSpec->location)));
+
rootPathSpec->name = generateJsonTablePathName(&cxt);
+ }
+
cxt.pathNames = list_make1(rootPathSpec->name);
CheckDuplicateColumnOrPathNames(&cxt, jt->columns);
@@ -135,7 +160,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt)
*/
cxt.jt = jt;
cxt.tf = tf;
- tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns,
+ tf->plan = (Node *) transformJsonTableColumns(&cxt, plan, jt->columns,
jt->passing,
rootPathSpec);
@@ -246,25 +271,110 @@ generateJsonTablePathName(JsonTableParseContext *cxt)
* their type/collation information to cxt->tf.
*/
static JsonTablePlan *
-transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
- List *passingArgs,
+transformJsonTableColumns(JsonTableParseContext *cxt,
+ JsonTablePlanSpec *planspec,
+ List *columns,
+ List *passing_Args,
JsonTablePathSpec *pathspec)
{
- ParseState *pstate = cxt->pstate;
JsonTable *jt = cxt->jt;
TableFunc *tf = cxt->tf;
- ListCell *col;
- bool ordinality_found = false;
+ JsonTablePathScan *scan;
+ JsonTablePlanSpec *childPlanSpec;
+ bool defaultPlan = planspec == NULL ||
+ planspec->plan_type == JSTP_DEFAULT;
bool errorOnError = jt->on_error &&
jt->on_error->btype == JSON_BEHAVIOR_ERROR;
- Oid contextItemTypid = exprType(tf->docexpr);
int colMin,
colMax;
- JsonTablePlan *childplan;
+ JsonTablePlan *childplan = NULL;
/* Start of column range */
colMin = list_length(tf->colvalexprs);
+ if (defaultPlan)
+ childPlanSpec = planspec;
+ else
+ {
+ /* validate parent and child plans */
+ JsonTablePlanSpec *parentPlanSpec;
+
+ if (planspec->plan_type == JSTP_JOINED)
+ {
+ if (planspec->join_type != JSTP_JOIN_INNER &&
+ planspec->join_type != JSTP_JOIN_OUTER)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("Expected INNER or OUTER."),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ parentPlanSpec = planspec->plan1;
+ childPlanSpec = planspec->plan2;
+
+ Assert(parentPlanSpec->plan_type != JSTP_JOINED);
+ Assert(parentPlanSpec->pathname);
+ }
+ else
+ {
+ parentPlanSpec = planspec;
+ childPlanSpec = NULL;
+ }
+
+ if (strcmp(parentPlanSpec->pathname, pathspec->name) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan"),
+ errdetail("PATH name mismatch: expected %s but %s is given.",
+ pathspec->name, parentPlanSpec->pathname),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ validateJsonTableChildPlan(cxt->pstate, childPlanSpec, columns);
+ }
+
+ appendJsonTableColumns(cxt, columns, passing_Args);
+
+ /* End of column range. */
+ if (list_length(tf->colvalexprs) == colMin)
+ {
+ /* No columns in this Scan beside the nested ones. */
+ colMax = colMin = -1;
+ }
+ else
+ colMax = list_length(tf->colvalexprs) - 1;
+
+ if (childPlanSpec || defaultPlan)
+ {
+ /* transform recursively nested columns */
+ childplan = transformJsonTableNestedColumns(cxt, childPlanSpec,
+ columns, passing_Args);
+ }
+
+ /* transform only non-nested columns */
+ scan = (JsonTablePathScan *) makeJsonTablePathScan(cxt, pathspec,
+ planspec,
+ errorOnError,
+ colMin,
+ colMax,
+ childplan);
+
+ return (JsonTablePlan *) scan;
+}
+
+/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
+static void
+appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs)
+{
+ ListCell *col;
+ ParseState *pstate = cxt->pstate;
+ JsonTable *jt = cxt->jt;
+ TableFunc *tf = cxt->tf;
+ bool ordinality_found = false;
+ JsonBehavior *on_error = jt->on_error;
+ bool errorOnError = on_error &&
+ on_error->btype == JSON_BEHAVIOR_ERROR;
+ Oid contextItemTypid = exprType(tf->docexpr);
+
foreach(col, columns)
{
JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
@@ -273,12 +383,9 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
Oid typcoll = InvalidOid;
Node *colexpr;
- if (rawc->coltype != JTC_NESTED)
- {
- Assert(rawc->name);
+ if (rawc->name)
tf->colnames = lappend(tf->colnames,
makeString(pstrdup(rawc->name)));
- }
/*
* Determine the type and typmod for the new column. FOR ORDINALITY
@@ -324,7 +431,7 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
param->typeMod = -1;
jfe = transformJsonTableColumn(rawc, (Node *) param,
- passingArgs);
+ passingArgs, errorOnError);
colexpr = transformExpr(pstate, (Node *) jfe,
EXPR_KIND_FROM_FUNCTION);
@@ -349,22 +456,6 @@ transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
tf->colcollations = lappend_oid(tf->colcollations, typcoll);
tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
}
-
- /* End of column range. */
- if (list_length(tf->colvalexprs) == colMin)
- {
- /* No columns in this Scan beside the nested ones. */
- colMax = colMin = -1;
- }
- else
- colMax = list_length(tf->colvalexprs) - 1;
-
- /* Recursively transform nested columns */
- childplan = transformJsonTableNestedColumns(cxt, passingArgs, columns);
-
- /* Create a "parent" scan responsible for all columns handled above. */
- return makeJsonTablePathScan(pathspec, errorOnError, colMin, colMax,
- childplan);
}
/*
@@ -395,7 +486,7 @@ isCompositeType(Oid typid)
*/
static JsonFuncExpr *
transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
- List *passingArgs)
+ List *passingArgs, bool errorOnError)
{
Node *pathspec;
JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
@@ -437,6 +528,8 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
jfexpr->output->returning->format = jtc->format;
jfexpr->on_empty = jtc->on_empty;
jfexpr->on_error = jtc->on_error;
+ if (jfexpr->on_error == NULL && errorOnError)
+ jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL, -1);
jfexpr->quotes = jtc->quotes;
jfexpr->wrapper = jtc->wrapper;
jfexpr->location = jtc->location;
@@ -444,49 +537,126 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
return jfexpr;
}
+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->pathspec->name &&
+ !strcmp(jtc->pathspec->name, pathname))
+ return jtc;
+ }
+
+ return NULL;
+}
/*
* Recursively transform nested columns and create child plan(s) that will be
* used to evaluate their row patterns.
+ *
+ * Default plan is transformed into a cross/union join of its nested columns.
+ * Simple and outer/inner plans are transformed into a JsonTablePlan by
+ * finding and transforming corresponding nested column.
+ * Sibling plans are recursively transformed into a JsonTableSiblingJoin.
*/
static JsonTablePlan *
transformJsonTableNestedColumns(JsonTableParseContext *cxt,
- List *passingArgs,
- List *columns)
+ JsonTablePlanSpec *planspec,
+ List *columns,
+ List *passingArgs)
{
- JsonTablePlan *plan = NULL;
- ListCell *lc;
+ JsonTableColumn *jtc = NULL;
- /*
- * If there are multiple NESTED COLUMNS clauses in 'columns', their
- * respective plans will be combined using a "sibling join" plan, which
- * effectively does a UNION of the sets of rows coming from each nested
- * plan.
- */
- foreach(lc, columns)
+ if (!planspec || planspec->plan_type == JSTP_DEFAULT)
{
- JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
- JsonTablePlan *nested;
+ /* unspecified or default plan */
+ JsonTablePlan *plan = NULL;
+ ListCell *lc;
+ bool cross = planspec && (planspec->join_type & JSTP_JOIN_CROSS);
- if (jtc->coltype != JTC_NESTED)
- continue;
+ /*
+ * If there are multiple NESTED COLUMNS clauses in 'columns', their
+ * respective plans will be combined using a "sibling join" plan, which
+ * effectively does a UNION of the sets of rows coming from each nested
+ * plan.
+ */
+ foreach(lc, columns)
+ {
+ JsonTableColumn *col = castNode(JsonTableColumn, lfirst(lc));
+ JsonTablePlan *nested;
- if (jtc->pathspec->name == NULL)
- jtc->pathspec->name = generateJsonTablePathName(cxt);
+ if (col->coltype != JTC_NESTED)
+ continue;
- nested = transformJsonTableColumns(cxt, jtc->columns, passingArgs,
- jtc->pathspec);
+ if (col->pathspec->name == NULL)
+ {
+ col->pathspec->name = generateJsonTablePathName(cxt);
+ }
- if (plan)
- plan = makeJsonTableSiblingJoin(plan, nested);
+ nested = transformJsonTableColumns(cxt, planspec, col->columns,
+ passingArgs,
+ col->pathspec);
+
+ /* Join nested plan with previous sibling nested plans. */
+ if (plan)
+ plan = makeJsonTableSiblingJoin(cross, plan, nested);
+ else
+ plan = nested;
+ }
+
+ return plan;
+ }
+ else if (planspec->plan_type == JSTP_SIMPLE)
+ {
+ jtc = findNestedJsonTableColumn(columns, planspec->pathname);
+ }
+ else if (planspec->plan_type == JSTP_JOINED)
+ {
+ if (planspec->join_type == JSTP_JOIN_INNER ||
+ planspec->join_type == JSTP_JOIN_OUTER)
+ {
+ Assert(planspec->plan1->plan_type == JSTP_SIMPLE);
+ jtc = findNestedJsonTableColumn(columns, planspec->plan1->pathname);
+ }
else
- plan = nested;
+ {
+ JsonTablePlan *lplan = transformJsonTableNestedColumns(cxt,
+ planspec->plan1,
+ columns,
+ passingArgs);
+ JsonTablePlan *rplan = transformJsonTableNestedColumns(cxt,
+ planspec->plan2,
+ columns,
+ passingArgs);
+
+ return makeJsonTableSiblingJoin(planspec->join_type == JSTP_JOIN_CROSS,
+ lplan, rplan);
+ }
}
+ else
+ elog(ERROR, "invalid JSON_TABLE plan type %d", planspec->plan_type);
- return plan;
+ if (!jtc)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("PATH name was %s not found in nested columns list.",
+ planspec->pathname),
+ parser_errposition(cxt->pstate, planspec->location)));
+
+ return transformJsonTableColumns(cxt, planspec, jtc->columns,
+ passingArgs,
+ jtc->pathspec);
}
/*
- * Create a JsonTablePlan for given path and ON ERROR behavior.
+ * 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.
*
* colMin and colMin give the range of columns computed by this scan in the
* global flat list of column expressions that will be passed to the
@@ -494,7 +664,9 @@ transformJsonTableNestedColumns(JsonTableParseContext *cxt,
* thus computed by 'childplan'.
*/
static JsonTablePlan *
-makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
+makeJsonTablePathScan(JsonTableParseContext *cxt, JsonTablePathSpec *pathspec,
+ JsonTablePlanSpec *planspec,
+ bool errorOnError,
int colMin, int colMax,
JsonTablePlan *childplan)
{
@@ -513,11 +685,18 @@ makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
scan->path = makeJsonTablePath(value, pathspec->name);
scan->errorOnError = errorOnError;
- scan->child = childplan;
+ scan->child = NULL;
+ if(childplan)
+ scan->child = childplan;
scan->colMin = colMin;
scan->colMax = colMax;
+ if (scan->child)
+ scan->outerJoin = planspec == NULL ||
+ (planspec->join_type & JSTP_JOIN_OUTER);
+ /* else: default plan case, no children found */
+
return (JsonTablePlan *) scan;
}
@@ -529,13 +708,101 @@ makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
* sets of rows from 'lplan' and 'rplan'.
*/
static JsonTablePlan *
-makeJsonTableSiblingJoin(JsonTablePlan *lplan, JsonTablePlan *rplan)
+makeJsonTableSiblingJoin(bool cross, JsonTablePlan *lplan, JsonTablePlan *rplan)
{
JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
join->plan.type = T_JsonTableSiblingJoin;
join->lplan = lplan;
join->rplan = rplan;
+ join->cross = cross;
return (JsonTablePlan *) join;
}
+
+/* Collect sibling path names from plan to the specified list. */
+static void
+collectSiblingPathsInJsonTablePlan(JsonTablePlanSpec *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 == JSTP_JOIN_INNER ||
+ plan->join_type == JSTP_JOIN_OUTER)
+ {
+ Assert(plan->plan1->plan_type == JSTP_SIMPLE);
+ *paths = lappend(*paths, plan->plan1->pathname);
+ }
+ else if (plan->join_type == JSTP_JOIN_CROSS ||
+ plan->join_type == JSTP_JOIN_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, JsonTablePlanSpec *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->pathspec->name == NULL)
+ 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->pathspec->name, lfirst(lc2))))
+ break;
+ }
+
+ if (!found)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE specification"),
+ errdetail("PLAN clause for nested path %s was not found.",
+ jtc->pathspec->name),
+ parser_errposition(pstate, jtc->location));
+
+ nchildren++;
+ }
+ }
+
+ if (list_length(siblings) > nchildren)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid JSON_TABLE plan clause"),
+ errdetail("PLAN clause contains some extra or duplicate sibling nodes."),
+ parser_errposition(pstate, plan ? plan->location : -1));
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index fc0e05e8789..333f27af43d 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -213,6 +213,13 @@ typedef struct JsonTablePlanState
/* Parent plan, if this is a nested plan */
struct JsonTablePlanState *parent;
+
+ /**/
+ bool cross;
+ bool outerJoin;
+ bool advanceNested;
+ bool advanceRight;
+ bool reset;
} JsonTablePlanState;
/* Random number to identify JsonTableExecContext for sanity checking */
@@ -359,13 +366,13 @@ static JsonTablePlanState *JsonTableInitPlan(JsonTableExecContext *cxt,
MemoryContext mcxt);
static void JsonTableSetDocument(TableFuncScanState *state, Datum value);
static void JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item);
+static void JsonTableRescan(JsonTablePlanState *planstate);
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 JsonTablePlanScanNextRow(JsonTablePlanState *planstate);
static void JsonTableResetNestedPlan(JsonTablePlanState *planstate);
-static bool JsonTablePlanJoinNextRow(JsonTablePlanState *planstate);
static bool JsonTablePlanNextRow(JsonTablePlanState *planstate);
const TableFuncRoutine JsonbTableRoutine =
@@ -4206,6 +4213,7 @@ JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
JsonTablePathScan *scan = (JsonTablePathScan *) plan;
int i;
+ planstate->outerJoin = scan->outerJoin;
planstate->path = DatumGetJsonPathP(scan->path->value->constvalue);
planstate->args = args;
planstate->mcxt = AllocSetContextCreate(mcxt, "JsonTableExecContext",
@@ -4225,6 +4233,8 @@ JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
{
JsonTableSiblingJoin *join = (JsonTableSiblingJoin *) plan;
+ planstate->cross = join->cross;
+
planstate->left = JsonTableInitPlan(cxt, join->lplan, parentstate,
args, mcxt);
planstate->right = JsonTableInitPlan(cxt, join->rplan, parentstate,
@@ -4279,11 +4289,7 @@ JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
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;
+ JsonTableRescan(planstate);
}
/*
@@ -4294,16 +4300,89 @@ JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
static bool
JsonTablePlanNextRow(JsonTablePlanState *planstate)
{
- if (IsA(planstate->plan, JsonTablePathScan))
- return JsonTablePlanScanNextRow(planstate);
- else if (IsA(planstate->plan, JsonTableSiblingJoin))
- return JsonTablePlanJoinNextRow(planstate);
+ if (IsA(planstate->plan, JsonTableSiblingJoin))
+ {
+ if (planstate->advanceRight)
+ {
+ /* fetch next inner row */
+ if (JsonTablePlanNextRow(planstate->right))
+ return true;
+
+ /* inner rows are exhausted */
+ if (planstate->cross)
+ planstate->advanceRight = false; /* next outer row */
+ else
+ return false; /* end of scan */
+ }
+
+ while (!planstate->advanceRight)
+ {
+ /* fetch next outer row */
+ bool more = JsonTablePlanNextRow(planstate->left);
+
+ if (planstate->cross)
+ {
+ if (!more)
+ return false; /* end of scan */
+
+ JsonTableRescan(planstate->right);
+
+ if (!JsonTablePlanNextRow(planstate->right))
+ continue; /* next outer row */
+
+ planstate->advanceRight = true; /* next inner row */
+ }
+ else if (!more)
+ {
+ if (!JsonTablePlanNextRow(planstate->right))
+ return false; /* end of scan */
+
+ planstate->advanceRight = true; /* next inner row */
+ }
+
+ break;
+ }
+ }
else
- elog(ERROR, "invalid JsonTablePlan %d", (int) planstate->plan->type);
+ {
+ /* reset context item if requested */
+ if (planstate->reset)
+ {
+ JsonTablePlanState *parent = planstate->parent;
- Assert(false);
- /* Appease compiler */
- return false;
+ Assert(parent != NULL && !parent->current.isnull);
+ JsonTableResetRowPattern(planstate, parent->current.value);
+ planstate->reset = false;
+ }
+
+ if (planstate->advanceNested)
+ {
+ /* fetch next nested row */
+ planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
+ if (planstate->advanceNested)
+ return true;
+ }
+
+ for (;;)
+ {
+ if (!JsonTablePlanScanNextRow(planstate))
+ return false;
+
+ if (planstate->nested == NULL)
+ break;
+
+ JsonTableResetNestedPlan(planstate->nested);
+ planstate->advanceNested = JsonTablePlanNextRow(planstate->nested);
+
+ if (!planstate->advanceNested && !planstate->outerJoin)
+ continue;
+
+ if (planstate->advanceNested || planstate->nested)
+ break;
+ }
+ }
+
+ return true;
}
/*
@@ -4389,47 +4468,28 @@ JsonTableResetNestedPlan(JsonTablePlanState *planstate)
{
JsonTablePlanState *parent = planstate->parent;
- if (!parent->current.isnull)
- JsonTableResetRowPattern(planstate, parent->current.value);
+ planstate->reset = true;
+ planstate->advanceNested = false;
+
+ if (planstate->nested)
+ JsonTableResetNestedPlan(planstate->nested);
/*
* If this plan itself has a child nested plan, it will be reset when
* the caller calls JsonTablePlanNextRow() on this plan.
*/
+ if (!parent->current.isnull)
+ JsonTableResetRowPattern(planstate, parent->current.value);
+
}
else if (IsA(planstate->plan, JsonTableSiblingJoin))
{
JsonTableResetNestedPlan(planstate->left);
JsonTableResetNestedPlan(planstate->right);
+ planstate->advanceRight = false;
}
}
-/*
- * Fetch the next row from a JsonTableSiblingJoin.
- *
- * This is essentially a UNION between the rows from left and right siblings.
- */
-static bool
-JsonTablePlanJoinNextRow(JsonTablePlanState *planstate)
-{
-
- /* Fetch row from left sibling. */
- if (!JsonTablePlanNextRow(planstate->left))
- {
- /*
- * Left sibling ran out of rows, so start fetching from the right
- * sibling.
- */
- if (!JsonTablePlanNextRow(planstate->right))
- {
- /* Right sibling ran out of row, so there are more rows. */
- return false;
- }
- }
-
- return true;
-}
-
/*
* JsonTableFetchRow
* Prepare the next "current" row for upcoming GetValue calls.
@@ -4494,3 +4554,26 @@ JsonTableGetValue(TableFuncScanState *state, int colnum,
return result;
}
+
+/* Recursively reset planstate and its child nodes */
+static void
+JsonTableRescan(JsonTablePlanState *planstate)
+{
+ if (IsA(planstate->plan, JsonTablePathScan))
+ {
+ /* 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;
+
+ if (planstate->nested)
+ JsonTableRescan(planstate->nested);
+ }
+ else if (IsA(planstate, JsonTableSiblingJoin))
+ {
+ JsonTableRescan(planstate->left);
+ JsonTableRescan(planstate->right);
+ planstate->advanceRight = false;
+ }
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9f85eb86da1..6dbb34ce321 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -12102,6 +12102,49 @@ get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
}
}
+/*
+ * get_json_table_plan - Parse back a JSON_TABLE plan
+ */
+static void
+get_json_table_plan(TableFunc *tf, JsonTablePlan *plan, deparse_context *context,
+ bool parenthesize)
+{
+ if (parenthesize)
+ appendStringInfoChar(context->buf, '(');
+
+ if (IsA(plan, JsonTablePathScan))
+ {
+ JsonTablePathScan *s = castNode(JsonTablePathScan, plan);
+
+ appendStringInfoString(context->buf, quote_identifier(s->path->name));
+
+ if (s->child)
+ {
+ appendStringInfoString(context->buf,
+ s->outerJoin ? " OUTER " : " INNER ");
+ get_json_table_plan(tf, s->child, context,
+ IsA(s->child, JsonTableSiblingJoin));
+ }
+ }
+ else if (IsA(plan, JsonTableSiblingJoin))
+ {
+ JsonTableSiblingJoin *j = (JsonTableSiblingJoin *) plan;
+
+ get_json_table_plan(tf, j->lplan, context,
+ IsA(j->lplan, JsonTableSiblingJoin) ||
+ castNode(JsonTablePathScan, j->lplan)->child);
+
+ appendStringInfoString(context->buf, j->cross ? " CROSS " : " UNION ");
+
+ get_json_table_plan(tf, j->rplan, context,
+ IsA(j->rplan, JsonTableSiblingJoin) ||
+ castNode(JsonTablePathScan, j->rplan)->child);
+ }
+
+ if (parenthesize)
+ appendStringInfoChar(context->buf, ')');
+}
+
/*
* get_json_table_columns - Parse back JSON_TABLE columns
*/
@@ -12111,6 +12154,7 @@ get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
bool showimplicit)
{
StringInfo buf = context->buf;
+ JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
ListCell *lc_colname;
ListCell *lc_coltype;
ListCell *lc_coltypmod;
@@ -12190,6 +12234,9 @@ get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
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);
@@ -12267,7 +12314,11 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
get_json_table_columns(tf, castNode(JsonTablePathScan, tf->plan), context,
showimplicit);
- if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY)
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "PLAN ", 0, 0, 0);
+ get_json_table_plan(tf, (JsonTablePlan *)root, context, true);
+
+ if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY)
get_json_behavior(jexpr->on_error, context, "ERROR");
if (PRETTY_INDENT(context))
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 5473ce9a288..17d761ef142 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -124,5 +124,10 @@ extern JsonTablePath *makeJsonTablePath(Const *pathvalue, char *pathname);
extern JsonTablePathSpec *makeJsonTablePathSpec(char *string, char *name,
int string_location,
int name_location);
+extern Node *makeJsonTableDefaultPlan(JsonTablePlanJoinType join_type,
+ int location);
+extern Node *makeJsonTableSimplePlan(char *pathname, int location);
+extern Node *makeJsonTableJoinedPlan(JsonTablePlanJoinType type,
+ Node *plan1, Node *plan2, int location);
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index bc7adba4a0f..04fac3173f2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1906,6 +1906,48 @@ typedef struct JsonTablePathSpec
ParseLoc location; /* location of 'string' */
} JsonTablePathSpec;
+/*
+ * JsonTablePlanType -
+ * flags for JSON_TABLE plan node types representation
+ */
+typedef enum JsonTablePlanType
+{
+ JSTP_DEFAULT,
+ JSTP_SIMPLE,
+ JSTP_JOINED,
+} JsonTablePlanType;
+
+/*
+ * JsonTablePlanJoinType -
+ * JSON_TABLE join types for JSTP_JOINED plans
+ */
+typedef enum JsonTablePlanJoinType
+{
+ JSTP_JOIN_INNER = 0x01,
+ JSTP_JOIN_OUTER = 0x02,
+ JSTP_JOIN_CROSS = 0x04,
+ JSTP_JOIN_UNION = 0x08,
+} JsonTablePlanJoinType;
+
+/*
+ * JsonTablePlanSpec -
+ * untransformed representation of JSON_TABLE's PLAN clause
+ */
+typedef struct JsonTablePlanSpec
+{
+ NodeTag type;
+
+ JsonTablePlanType plan_type; /* plan type */
+ JsonTablePlanJoinType join_type; /* join type (for joined plan only) */
+ char *pathname; /* path name (for simple plan only) */
+
+ /* For joined plans */
+ struct JsonTablePlanSpec *plan1; /* first joined plan */
+ struct JsonTablePlanSpec *plan2; /* second joined plan */
+
+ ParseLoc location; /* token location, or -1 if unknown */
+} JsonTablePlanSpec;
+
/*
* JsonTable -
* untransformed representation of JSON_TABLE
@@ -1917,6 +1959,7 @@ typedef struct JsonTable
JsonTablePathSpec *pathspec; /* JSON path specification */
List *passing; /* list of PASSING clause arguments, if any */
List *columns; /* list of JsonTableColumn */
+ JsonTablePlanSpec *planspec; /* join plan, if specified */
JsonBehavior *on_error; /* ON ERROR behavior */
Alias *alias; /* table alias in FROM clause */
bool lateral; /* does it have LATERAL prefix? */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..d235709b484 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1930,6 +1930,7 @@ typedef struct JsonTablePathScan
/* Plan(s) for nested columns, if any. */
JsonTablePlan *child;
+ bool outerJoin; /* outer or inner join for nested columns? */
/*
* 0-based index in TableFunc.colvalexprs of the 1st and the last column
@@ -1951,6 +1952,7 @@ typedef struct JsonTableSiblingJoin
JsonTablePlan *lplan;
JsonTablePlan *rplan;
+ bool cross;
} JsonTableSiblingJoin;
/* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 04845d5e680..f8c2e216d92 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1426,6 +1426,7 @@ JsonPathKeyword
JsonPathParseItem
JsonPathParseResult
JsonPathPredicateCallback
+JsonPathSpec
JsonPathString
JsonPathVariable
JsonQuotes
@@ -1441,9 +1442,15 @@ JsonTableParseContext
JsonTablePath
JsonTablePathScan
JsonTablePathSpec
+JsonTableJoinState
JsonTablePlan
JsonTablePlanRowSource
+JsonTablePlanSpec
JsonTablePlanState
+JsonTablePlanStateType
+JsonTablePlanJoinType
+JsonTablePlanType
+JsonTableScanState
JsonTableSiblingJoin
JsonTokenType
JsonTransformStringValuesAction
--
2.43.0