diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index e2d9e9be41a6..5349c0e042b8 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 3c4268b271a4..4ca6e1e607cc 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 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 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 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 13d533b83f37..7d7c595c8197 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 dbab24737ef1..ada5d62412bd 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 467b08198b83..e31a2903543b 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 5473ce9a288a..17d761ef142d 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 4610fc61293b..3c596ce1599d 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 7d3b4198f266..79ead41d5d00 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/test/regress/expected/sqljson_jsontable.out b/src/test/regress/expected/sqljson_jsontable.out index 458c5aaa5b06..6678704958c6 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 required here + COLUMNS ( + foo int PATH '$' + ) + PLAN DEFAULT (UNION) +) jt; +ERROR: invalid JSON_TABLE expression +LINE 2: jsonb '[]', '$' -- AS 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 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 154eea79c768..d0c88ec6d10c 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 required here + COLUMNS ( + foo int PATH '$' + ) + PLAN DEFAULT (UNION) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' AS path1 + COLUMNS ( + NESTED PATH '$' COLUMNS ( -- AS 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 e5879e00dffe..76544a817d02 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1404,6 +1404,7 @@ JsonPathKeyword JsonPathParseItem JsonPathParseResult JsonPathPredicateCallback +JsonPathSpec JsonPathString JsonPathVariable JsonQuotes @@ -1419,9 +1420,15 @@ JsonTableParseContext JsonTablePath JsonTablePathScan JsonTablePathSpec +JsonTableJoinState JsonTablePlan JsonTablePlanRowSource +JsonTablePlanSpec JsonTablePlanState +JsonTablePlanStateType +JsonTablePlanJoinType +JsonTablePlanType +JsonTableScanState JsonTableSiblingJoin JsonTokenType JsonTransformStringValuesAction