JSON_TABLE
authorAndrew Dunstan <[email protected]>
Mon, 4 Apr 2022 19:36:03 +0000 (15:36 -0400)
committerAndrew Dunstan <[email protected]>
Mon, 4 Apr 2022 20:03:47 +0000 (16:03 -0400)
This feature allows jsonb data to be treated as a table and thus used in
a FROM clause like other tabular data. Data can be selected from the
jsonb using jsonpath expressions, and hoisted out of nested structures
in the jsonb to form multiple rows, more or less like an outer join.

Nikita Glukhov

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

Discussion: https://postgr.es/m/7e2cb85d-24cf-4abb-30a5-1a33715959bd@postgrespro.ru

31 files changed:
src/backend/commands/explain.c
src/backend/executor/execExpr.c
src/backend/executor/execExprInterp.c
src/backend/executor/nodeTableFuncscan.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/parser/Makefile
src/backend/parser/gram.y
src/backend/parser/parse_clause.c
src/backend/parser/parse_expr.c
src/backend/parser/parse_jsontable.c [new file with mode: 0644]
src/backend/parser/parse_relation.c
src/backend/parser/parse_target.c
src/backend/utils/adt/jsonpath_exec.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/misc/queryjumble.c
src/include/executor/execExpr.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/nodes/primnodes.h
src/include/parser/kwlist.h
src/include/parser/parse_clause.h
src/include/utils/jsonpath.h
src/test/regress/expected/json_sqljson.out
src/test/regress/expected/jsonb_sqljson.out
src/test/regress/sql/json_sqljson.sql
src/test/regress/sql/jsonb_sqljson.sql
src/tools/pgindent/typedefs.list

index cb13227db1f557d40ddcb3855909f44bce557559..1e5701b8eba8ed7cd402a8ad4e892fadcb163160 100644 (file)
@@ -3796,7 +3796,13 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
                        break;
                case T_TableFuncScan:
                        Assert(rte->rtekind == RTE_TABLEFUNC);
-                       objectname = "xmltable";
+                       if (rte->tablefunc)
+                               if (rte->tablefunc->functype == TFT_XMLTABLE)
+                                       objectname = "xmltable";
+                               else /* Must be TFT_JSON_TABLE */
+                                       objectname = "json_table";
+                       else
+                               objectname = NULL;
                        objecttag = "Table Function Name";
                        break;
                case T_ValuesScan:
index d4d3850ec7c8230331e8ec7d29cea910cfe7d6ae..38b94c0276745b638cf6bd68449f4ebd39f2e374 100644 (file)
@@ -2635,6 +2635,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
                                        var->typmod = exprTypmod((Node *) argexpr);
                                        var->estate = ExecInitExpr(argexpr, state->parent);
                                        var->econtext = NULL;
+                                       var->mcxt = NULL;
                                        var->evaluated = false;
                                        var->value = (Datum) 0;
                                        var->isnull = true;
index 7d4253d970dae3a669d2d5b7cc47f99543246165..7094e7e3f6fc86394f6b803231e63f977632fc7e 100644 (file)
@@ -4602,6 +4602,7 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
 
                case JSON_BEHAVIOR_NULL:
                case JSON_BEHAVIOR_UNKNOWN:
+               case JSON_BEHAVIOR_EMPTY:
                        *is_null = true;
                        return (Datum) 0;
 
@@ -4694,8 +4695,14 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
 
        if (!var->evaluated)
        {
+               MemoryContext oldcxt = var->mcxt ?
+                       MemoryContextSwitchTo(var->mcxt) : NULL;
+
                var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
                var->evaluated = true;
+
+               if (oldcxt)
+                       MemoryContextSwitchTo(oldcxt);
        }
 
        if (var->isnull)
@@ -4843,6 +4850,7 @@ ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
        PG_CATCH();
        {
                ErrorData  *edata;
+               int                     ecategory;
 
                /* Save error info in oldcontext */
                MemoryContextSwitchTo(oldcontext);
@@ -4854,8 +4862,10 @@ ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
                MemoryContextSwitchTo(oldcontext);
                CurrentResourceOwner = oldowner;
 
-               if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) !=
-                       ERRCODE_DATA_EXCEPTION)
+               ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode);
+
+               if (ecategory != ERRCODE_DATA_EXCEPTION &&      /* jsonpath and other data errors */
+                       ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION)    /* domain errors */
                        ReThrowError(edata);
 
                res = (Datum) 0;
@@ -4981,6 +4991,10 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
                                break;
                        }
 
+               case JSON_TABLE_OP:
+                       *resnull = false;
+                       return item;
+
                default:
                        elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
                        return (Datum) 0;
index 0db4ed0c2feca9827b3c5ca3aaea6a9223b0054b..691c3e28cef3e9061a856cf554b7867e5750ce50 100644 (file)
@@ -28,6 +28,7 @@
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
 #include "utils/builtins.h"
+#include "utils/jsonpath.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/xml.h"
@@ -161,8 +162,9 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
        scanstate->ss.ps.qual =
                ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps);
 
-       /* Only XMLTABLE is supported currently */
-       scanstate->routine = &XmlTableRoutine;
+       /* Only XMLTABLE and JSON_TABLE are supported currently */
+       scanstate->routine =
+               tf->functype == TFT_XMLTABLE ? &XmlTableRoutine : &JsonbTableRoutine;
 
        scanstate->perTableCxt =
                AllocSetContextCreate(CurrentMemoryContext,
@@ -381,14 +383,17 @@ tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc)
                routine->SetNamespace(tstate, ns_name, ns_uri);
        }
 
-       /* Install the row filter expression into the table builder context */
-       value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
-       if (isnull)
-               ereport(ERROR,
-                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("row filter expression must not be null")));
+       if (routine->SetRowFilter)
+       {
+               /* Install the row filter expression into the table builder context */
+               value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
+               if (isnull)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                        errmsg("row filter expression must not be null")));
 
-       routine->SetRowFilter(tstate, TextDatumGetCString(value));
+               routine->SetRowFilter(tstate, TextDatumGetCString(value));
+       }
 
        /*
         * Install the column filter expressions into the table builder context.
index 11c016495e3e94b87b61795b5dcf3b1791872e99..1a74122f139f57aaa6ac10dcfd93e4b100ceafec 100644 (file)
@@ -1394,6 +1394,7 @@ _copyTableFunc(const TableFunc *from)
 {
        TableFunc  *newnode = makeNode(TableFunc);
 
+       COPY_SCALAR_FIELD(functype);
        COPY_NODE_FIELD(ns_uris);
        COPY_NODE_FIELD(ns_names);
        COPY_NODE_FIELD(docexpr);
@@ -1404,7 +1405,9 @@ _copyTableFunc(const TableFunc *from)
        COPY_NODE_FIELD(colcollations);
        COPY_NODE_FIELD(colexprs);
        COPY_NODE_FIELD(coldefexprs);
+       COPY_NODE_FIELD(colvalexprs);
        COPY_BITMAPSET_FIELD(notnulls);
+       COPY_NODE_FIELD(plan);
        COPY_SCALAR_FIELD(ordinalitycol);
        COPY_LOCATION_FIELD(location);
 
@@ -2683,6 +2686,76 @@ _copyJsonArgument(const JsonArgument *from)
        return newnode;
 }
 
+/*
+ * _copyJsonTable
+ */
+static JsonTable *
+_copyJsonTable(const JsonTable *from)
+{
+       JsonTable *newnode = makeNode(JsonTable);
+
+       COPY_NODE_FIELD(common);
+       COPY_NODE_FIELD(columns);
+       COPY_NODE_FIELD(on_error);
+       COPY_NODE_FIELD(alias);
+       COPY_SCALAR_FIELD(location);
+
+       return newnode;
+}
+
+/*
+ * _copyJsonTableColumn
+ */
+static JsonTableColumn *
+_copyJsonTableColumn(const JsonTableColumn *from)
+{
+       JsonTableColumn *newnode = makeNode(JsonTableColumn);
+
+       COPY_SCALAR_FIELD(coltype);
+       COPY_STRING_FIELD(name);
+       COPY_NODE_FIELD(typeName);
+       COPY_STRING_FIELD(pathspec);
+       COPY_SCALAR_FIELD(format);
+       COPY_SCALAR_FIELD(wrapper);
+       COPY_SCALAR_FIELD(omit_quotes);
+       COPY_NODE_FIELD(columns);
+       COPY_NODE_FIELD(on_empty);
+       COPY_NODE_FIELD(on_error);
+       COPY_SCALAR_FIELD(location);
+
+       return newnode;
+}
+
+/*
+ * _copyJsonTableParent
+ */
+static JsonTableParent *
+_copyJsonTableParent(const JsonTableParent *from)
+{
+       JsonTableParent *newnode = makeNode(JsonTableParent);
+
+       COPY_NODE_FIELD(path);
+       COPY_NODE_FIELD(child);
+       COPY_SCALAR_FIELD(colMin);
+       COPY_SCALAR_FIELD(colMax);
+
+       return newnode;
+}
+
+/*
+ * _copyJsonTableSibling
+ */
+static JsonTableSibling *
+_copyJsonTableSibling(const JsonTableSibling *from)
+{
+       JsonTableSibling *newnode = makeNode(JsonTableSibling);
+
+       COPY_NODE_FIELD(larg);
+       COPY_NODE_FIELD(rarg);
+
+       return newnode;
+}
+
 /* ****************************************************************
  *                                             pathnodes.h copy functions
  *
@@ -5850,6 +5923,18 @@ copyObjectImpl(const void *from)
                case T_JsonItemCoercions:
                        retval = _copyJsonItemCoercions(from);
                        break;
+               case T_JsonTable:
+                       retval = _copyJsonTable(from);
+                       break;
+               case T_JsonTableColumn:
+                       retval = _copyJsonTableColumn(from);
+                       break;
+               case T_JsonTableParent:
+                       retval = _copyJsonTableParent(from);
+                       break;
+               case T_JsonTableSibling:
+                       retval = _copyJsonTableSibling(from);
+                       break;
 
                        /*
                         * RELATION NODES
index 722dbe6a0d853fe82f603415aef9a0ac384827d2..5c21850c9755415db9b134c1836be87790f8a4aa 100644 (file)
@@ -127,6 +127,7 @@ _equalRangeVar(const RangeVar *a, const RangeVar *b)
 static bool
 _equalTableFunc(const TableFunc *a, const TableFunc *b)
 {
+       COMPARE_SCALAR_FIELD(functype);
        COMPARE_NODE_FIELD(ns_uris);
        COMPARE_NODE_FIELD(ns_names);
        COMPARE_NODE_FIELD(docexpr);
@@ -137,13 +138,65 @@ _equalTableFunc(const TableFunc *a, const TableFunc *b)
        COMPARE_NODE_FIELD(colcollations);
        COMPARE_NODE_FIELD(colexprs);
        COMPARE_NODE_FIELD(coldefexprs);
+       COMPARE_NODE_FIELD(colvalexprs);
        COMPARE_BITMAPSET_FIELD(notnulls);
+       COMPARE_NODE_FIELD(plan);
        COMPARE_SCALAR_FIELD(ordinalitycol);
        COMPARE_LOCATION_FIELD(location);
 
        return true;
 }
 
+static bool
+_equalJsonTable(const JsonTable *a, const JsonTable *b)
+{
+       COMPARE_NODE_FIELD(common);
+       COMPARE_NODE_FIELD(columns);
+       COMPARE_NODE_FIELD(on_error);
+       COMPARE_NODE_FIELD(alias);
+       COMPARE_SCALAR_FIELD(location);
+
+       return true;
+}
+
+static bool
+_equalJsonTableColumn(const JsonTableColumn *a, const JsonTableColumn *b)
+{
+       COMPARE_SCALAR_FIELD(coltype);
+       COMPARE_STRING_FIELD(name);
+       COMPARE_NODE_FIELD(typeName);
+       COMPARE_STRING_FIELD(pathspec);
+       COMPARE_SCALAR_FIELD(format);
+       COMPARE_SCALAR_FIELD(wrapper);
+       COMPARE_SCALAR_FIELD(omit_quotes);
+       COMPARE_NODE_FIELD(columns);
+       COMPARE_NODE_FIELD(on_empty);
+       COMPARE_NODE_FIELD(on_error);
+       COMPARE_SCALAR_FIELD(location);
+
+       return true;
+}
+
+static bool
+_equalJsonTableParent(const JsonTableParent *a, const JsonTableParent *b)
+{
+       COMPARE_NODE_FIELD(path);
+       COMPARE_NODE_FIELD(child);
+       COMPARE_SCALAR_FIELD(colMin);
+       COMPARE_SCALAR_FIELD(colMax);
+
+       return true;
+}
+
+static bool
+_equalJsonTableSibling(const JsonTableSibling *a, const JsonTableSibling *b)
+{
+       COMPARE_NODE_FIELD(larg);
+       COMPARE_NODE_FIELD(rarg);
+
+       return true;
+}
+
 static bool
 _equalIntoClause(const IntoClause *a, const IntoClause *b)
 {
@@ -3719,6 +3772,12 @@ equal(const void *a, const void *b)
                case T_JsonItemCoercions:
                        retval = _equalJsonItemCoercions(a, b);
                        break;
+               case T_JsonTableParent:
+                       retval = _equalJsonTableParent(a, b);
+                       break;
+               case T_JsonTableSibling:
+                       retval = _equalJsonTableSibling(a, b);
+                       break;
 
                        /*
                         * RELATION NODES
@@ -4341,6 +4400,12 @@ equal(const void *a, const void *b)
                case T_JsonArgument:
                        retval = _equalJsonArgument(a, b);
                        break;
+               case T_JsonTable:
+                       retval = _equalJsonTable(a, b);
+                       break;
+               case T_JsonTableColumn:
+                       retval = _equalJsonTableColumn(a, b);
+                       break;
 
                default:
                        elog(ERROR, "unrecognized node type: %d",
index a094317bfc1c1d1b9ef235402fcd3690c5109e99..4ae5e5d4dd6f305cb92d0f9690e88cb641cd4299 100644 (file)
@@ -2466,6 +2466,8 @@ expression_tree_walker(Node *node,
                                        return true;
                                if (walker(tf->coldefexprs, context))
                                        return true;
+                               if (walker(tf->colvalexprs, context))
+                                       return true;
                        }
                        break;
                case T_JsonValueExpr:
@@ -3513,6 +3515,7 @@ expression_tree_mutator(Node *node,
                                MUTATE(newnode->rowexpr, tf->rowexpr, Node *);
                                MUTATE(newnode->colexprs, tf->colexprs, List *);
                                MUTATE(newnode->coldefexprs, tf->coldefexprs, List *);
+                               MUTATE(newnode->colvalexprs, tf->colvalexprs, List *);
                                return (Node *) newnode;
                        }
                        break;
@@ -4530,6 +4533,30 @@ raw_expression_tree_walker(Node *node,
                                        return true;
                        }
                        break;
+               case T_JsonTable:
+                       {
+                               JsonTable  *jt = (JsonTable *) node;
+
+                               if (walker(jt->common, context))
+                                       return true;
+                               if (walker(jt->columns, context))
+                                       return true;
+                       }
+                       break;
+               case T_JsonTableColumn:
+                       {
+                               JsonTableColumn  *jtc = (JsonTableColumn *) node;
+
+                               if (walker(jtc->typeName, context))
+                                       return true;
+                               if (walker(jtc->on_empty, context))
+                                       return true;
+                               if (walker(jtc->on_error, context))
+                                       return true;
+                               if (jtc->coltype == JTC_NESTED && walker(jtc->columns, context))
+                                       return true;
+                       }
+                       break;
                default:
                        elog(ERROR, "unrecognized node type: %d",
                                 (int) nodeTag(node));
index 6e39590730a4a224345ee64ba844303c99e4fc6e..213396f99928a203ea8f787c2ef20d36d91e27de 100644 (file)
@@ -1092,6 +1092,7 @@ _outTableFunc(StringInfo str, const TableFunc *node)
 {
        WRITE_NODE_TYPE("TABLEFUNC");
 
+       WRITE_ENUM_FIELD(functype, TableFuncType);
        WRITE_NODE_FIELD(ns_uris);
        WRITE_NODE_FIELD(ns_names);
        WRITE_NODE_FIELD(docexpr);
@@ -1102,7 +1103,9 @@ _outTableFunc(StringInfo str, const TableFunc *node)
        WRITE_NODE_FIELD(colcollations);
        WRITE_NODE_FIELD(colexprs);
        WRITE_NODE_FIELD(coldefexprs);
+       WRITE_NODE_FIELD(colvalexprs);
        WRITE_BITMAPSET_FIELD(notnulls);
+       WRITE_NODE_FIELD(plan);
        WRITE_INT_FIELD(ordinalitycol);
        WRITE_LOCATION_FIELD(location);
 }
@@ -1866,6 +1869,26 @@ _outJsonItemCoercions(StringInfo str, const JsonItemCoercions *node)
        WRITE_NODE_FIELD(composite);
 }
 
+static void
+_outJsonTableParent(StringInfo str, const JsonTableParent *node)
+{
+       WRITE_NODE_TYPE("JSONTABPNODE");
+
+       WRITE_NODE_FIELD(path);
+       WRITE_NODE_FIELD(child);
+       WRITE_INT_FIELD(colMin);
+       WRITE_INT_FIELD(colMax);
+}
+
+static void
+_outJsonTableSibling(StringInfo str, const JsonTableSibling *node)
+{
+       WRITE_NODE_TYPE("JSONTABSNODE");
+
+       WRITE_NODE_FIELD(larg);
+       WRITE_NODE_FIELD(rarg);
+}
+
 /*****************************************************************************
  *
  *     Stuff from pathnodes.h.
@@ -4714,6 +4737,12 @@ outNode(StringInfo str, const void *obj)
                        case T_JsonItemCoercions:
                                _outJsonItemCoercions(str, obj);
                                break;
+                       case T_JsonTableParent:
+                               _outJsonTableParent(str, obj);
+                               break;
+                       case T_JsonTableSibling:
+                               _outJsonTableSibling(str, obj);
+                               break;
 
                        default:
 
index c94b2561f05b9a179234c76c817835e13e1f0d19..19e257684cd85c551cd7fff4b716c099b32413ce 100644 (file)
@@ -571,6 +571,7 @@ _readTableFunc(void)
 {
        READ_LOCALS(TableFunc);
 
+       READ_ENUM_FIELD(functype, TableFuncType);
        READ_NODE_FIELD(ns_uris);
        READ_NODE_FIELD(ns_names);
        READ_NODE_FIELD(docexpr);
@@ -581,7 +582,9 @@ _readTableFunc(void)
        READ_NODE_FIELD(colcollations);
        READ_NODE_FIELD(colexprs);
        READ_NODE_FIELD(coldefexprs);
+       READ_NODE_FIELD(colvalexprs);
        READ_BITMAPSET_FIELD(notnulls);
+       READ_NODE_FIELD(plan);
        READ_INT_FIELD(ordinalitycol);
        READ_LOCATION_FIELD(location);
 
@@ -1532,6 +1535,30 @@ _readJsonExpr(void)
        READ_DONE();
 }
 
+static JsonTableParent *
+_readJsonTableParent(void)
+{
+       READ_LOCALS(JsonTableParent);
+
+       READ_NODE_FIELD(path);
+       READ_NODE_FIELD(child);
+       READ_INT_FIELD(colMin);
+       READ_INT_FIELD(colMax);
+
+       READ_DONE();
+}
+
+static JsonTableSibling *
+_readJsonTableSibling(void)
+{
+       READ_LOCALS(JsonTableSibling);
+
+       READ_NODE_FIELD(larg);
+       READ_NODE_FIELD(rarg);
+
+       READ_DONE();
+}
+
 /*
  * _readJsonCoercion
  */
@@ -3194,6 +3221,10 @@ parseNodeString(void)
                return_value = _readJsonCoercion();
        else if (MATCH("JSONITEMCOERCIONS", 17))
                return_value = _readJsonItemCoercions();
+       else if (MATCH("JSONTABPNODE", 12))
+               return_value = _readJsonTableParent();
+       else if (MATCH("JSONTABSNODE", 12))
+               return_value = _readJsonTableSibling();
        else
        {
                elog(ERROR, "badly formatted node string \"%.32s\"...", token);
index 9f1c4022bbe00176d747150d6f7840ac91b1afc6..f4c0cc7f101bf00ef4df86ffc3baf184ecf4016a 100644 (file)
@@ -23,6 +23,7 @@ OBJS = \
        parse_enr.o \
        parse_expr.o \
        parse_func.o \
+       parse_jsontable.o \
        parse_merge.o \
        parse_node.o \
        parse_oper.o \
index e5a3c528aad6ba42cc2e8430340452f618cff140..13fa5bea87a1c33c3c3a853cb32450f98a7ed6b1 100644 (file)
@@ -676,15 +676,25 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
                                        json_object_aggregate_constructor
                                        json_array_aggregate_constructor
                                        json_path_specification
+                                       json_table
+                                       json_table_column_definition
+                                       json_table_ordinality_column_definition
+                                       json_table_regular_column_definition
+                                       json_table_formatted_column_definition
+                                       json_table_exists_column_definition
+                                       json_table_nested_columns
 
 %type <list>           json_name_and_value_list
                                        json_value_expr_list
                                        json_array_aggregate_order_by_clause_opt
                                        json_arguments
                                        json_passing_clause_opt
+                                       json_table_columns_clause
+                                       json_table_column_definition_list
 
 %type <str>                    json_table_path_name
                                        json_as_path_name_clause_opt
+                                       json_table_column_path_specification_clause_opt
 
 %type <ival>           json_encoding
                                        json_encoding_clause_opt
@@ -698,6 +708,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
                                        json_behavior_true
                                        json_behavior_false
                                        json_behavior_unknown
+                                       json_behavior_empty
                                        json_behavior_empty_array
                                        json_behavior_empty_object
                                        json_behavior_default
@@ -705,6 +716,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
                                        json_query_behavior
                                        json_exists_error_behavior
                                        json_exists_error_clause_opt
+                                       json_table_error_behavior
+                                       json_table_error_clause_opt
 
 %type <on_behavior> json_value_on_behavior_clause_opt
                                        json_query_on_behavior_clause_opt
@@ -779,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
        INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
        JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG
-       JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_VALUE
+       JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_TABLE JSON_VALUE
 
        KEY KEYS KEEP
 
@@ -790,8 +803,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
        MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
        MINUTE_P MINVALUE MODE MONTH_P MOVE
 
-       NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE
-       NORMALIZE NORMALIZED
+       NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD NO
+       NONE NORMALIZE NORMALIZED
        NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
        NULLS_P NUMERIC
 
@@ -799,7 +812,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
        ORDER ORDINALITY OTHERS OUT_P OUTER_P
        OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-       PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+       PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS POLICY
        POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
        PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -901,7 +914,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  */
 %nonassoc      UNBOUNDED               /* ideally would have same precedence as IDENT */
 %nonassoc      ERROR_P EMPTY_P DEFAULT ABSENT /* JSON error/empty behavior */
-%nonassoc      FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON
+%nonassoc      FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON COLUMNS
 %nonassoc      IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
 %left          Op OPERATOR             /* multi-character ops and user-defined operators */
 %left          '+' '-'
@@ -926,6 +939,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  */
 %left          JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
 
+%nonassoc      json_table_column
+%nonassoc      NESTED
+%left          PATH
+
 %nonassoc      empty_json_unique
 %left          WITHOUT WITH_LA_UNIQUE
 
@@ -12697,6 +12714,19 @@ table_ref:     relation_expr opt_alias_clause
                                        $2->alias = $4;
                                        $$ = (Node *) $2;
                                }
+                       | json_table opt_alias_clause
+                               {
+                                       JsonTable *jt = castNode(JsonTable, $1);
+                                       jt->alias = $2;
+                                       $$ = (Node *) jt;
+                               }
+                       | LATERAL_P json_table opt_alias_clause
+                               {
+                                       JsonTable *jt = castNode(JsonTable, $2);
+                                       jt->alias = $3;
+                                       jt->lateral = true;
+                                       $$ = (Node *) jt;
+                               }
                ;
 
 
@@ -13248,6 +13278,8 @@ xmltable_column_option_el:
                                { $$ = makeDefElem("is_not_null", (Node *) makeBoolean(true), @1); }
                        | NULL_P
                                { $$ = makeDefElem("is_not_null", (Node *) makeBoolean(false), @1); }
+                       | PATH b_expr
+                               { $$ = makeDefElem("path", $2, @1); }
                ;
 
 xml_namespace_list:
@@ -15774,6 +15806,10 @@ json_behavior_unknown:
                        UNKNOWN         { $$ = makeJsonBehavior(JSON_BEHAVIOR_UNKNOWN, NULL); }
                ;
 
+json_behavior_empty:
+                       EMPTY_P         { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); }
+               ;
+
 json_behavior_empty_array:
                        EMPTY_P ARRAY   { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); }
                        /* non-standard, for Oracle compatibility only */
@@ -15888,6 +15924,153 @@ json_query_on_behavior_clause_opt:
                                                                        { $$.on_empty = NULL; $$.on_error = NULL; }
                ;
 
+json_table:
+                       JSON_TABLE '('
+                               json_api_common_syntax
+                               json_table_columns_clause
+                               json_table_error_clause_opt
+                       ')'
+                               {
+                                       JsonTable *n = makeNode(JsonTable);
+                                       n->common = (JsonCommon *) $3;
+                                       n->columns = $4;
+                                       n->on_error = $5;
+                                       n->location = @1;
+                                       $$ = (Node *) n;
+                               }
+               ;
+
+json_table_columns_clause:
+                       COLUMNS '('     json_table_column_definition_list ')' { $$ = $3; }
+               ;
+
+json_table_column_definition_list:
+                       json_table_column_definition
+                               { $$ = list_make1($1); }
+                       | json_table_column_definition_list ',' json_table_column_definition
+                               { $$ = lappend($1, $3); }
+               ;
+
+json_table_column_definition:
+                       json_table_ordinality_column_definition         %prec json_table_column
+                       | json_table_regular_column_definition          %prec json_table_column
+                       | json_table_formatted_column_definition        %prec json_table_column
+                       | json_table_exists_column_definition           %prec json_table_column
+                       | json_table_nested_columns
+               ;
+
+json_table_ordinality_column_definition:
+                       ColId FOR ORDINALITY
+                               {
+                                       JsonTableColumn *n = makeNode(JsonTableColumn);
+                                       n->coltype = JTC_FOR_ORDINALITY;
+                                       n->name = $1;
+                                       n->location = @1;
+                                       $$ = (Node *) n;
+                               }
+               ;
+
+json_table_regular_column_definition:
+                       ColId Typename
+                       json_table_column_path_specification_clause_opt
+                       json_wrapper_clause_opt
+                       json_quotes_clause_opt
+                       json_value_on_behavior_clause_opt
+                               {
+                                       JsonTableColumn *n = makeNode(JsonTableColumn);
+                                       n->coltype = JTC_REGULAR;
+                                       n->name = $1;
+                                       n->typeName = $2;
+                                       n->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+                                       n->wrapper = $4; /* JSW_NONE */
+                                       n->omit_quotes = $5; /* false */
+                                       n->pathspec = $3;
+                                       n->on_empty = $6.on_empty;
+                                       n->on_error = $6.on_error;
+                                       n->location = @1;
+                                       $$ = (Node *) n;
+                               }
+               ;
+
+json_table_exists_column_definition:
+                       ColId Typename
+                       EXISTS json_table_column_path_specification_clause_opt
+                       json_exists_error_clause_opt
+                               {
+                                       JsonTableColumn *n = makeNode(JsonTableColumn);
+                                       n->coltype = JTC_EXISTS;
+                                       n->name = $1;
+                                       n->typeName = $2;
+                                       n->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+                                       n->wrapper = JSW_NONE;
+                                       n->omit_quotes = false;
+                                       n->pathspec = $4;
+                                       n->on_empty = NULL;
+                                       n->on_error = $5;
+                                       n->location = @1;
+                                       $$ = (Node *) n;
+                               }
+               ;
+
+json_table_error_behavior:
+                       json_behavior_error
+                       | json_behavior_empty
+               ;
+
+json_table_error_clause_opt:
+                       json_table_error_behavior ON ERROR_P    { $$ = $1; }
+                       | /* EMPTY */                                                   { $$ = NULL; }
+               ;
+
+json_table_column_path_specification_clause_opt:
+                       PATH Sconst                                                             { $$ = $2; }
+                       | /* EMPTY */ %prec json_table_column   { $$ = NULL; }
+               ;
+
+json_table_formatted_column_definition:
+                       ColId Typename FORMAT json_representation
+                       json_table_column_path_specification_clause_opt
+                       json_wrapper_clause_opt
+                       json_quotes_clause_opt
+                       json_query_on_behavior_clause_opt
+                               {
+                                       JsonTableColumn *n = makeNode(JsonTableColumn);
+                                       n->coltype = JTC_FORMATTED;
+                                       n->name = $1;
+                                       n->typeName = $2;
+                                       n->format = castNode(JsonFormat, $4);
+                                       n->pathspec = $5;
+                                       n->wrapper = $6;
+                                       if (n->wrapper != JSW_NONE && $7 != JS_QUOTES_UNSPEC)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                errmsg("SQL/JSON QUOTES behavior must not be specified when WITH WRAPPER is used"),
+                                                                parser_errposition(@7)));
+                                       n->omit_quotes = $7 == JS_QUOTES_OMIT;
+                                       n->on_empty = $8.on_empty;
+                                       n->on_error = $8.on_error;
+                                       n->location = @1;
+                                       $$ = (Node *) n;
+                               }
+               ;
+
+json_table_nested_columns:
+                       NESTED path_opt Sconst json_table_columns_clause
+                               {
+                                       JsonTableColumn *n = makeNode(JsonTableColumn);
+                                       n->coltype = JTC_NESTED;
+                                       n->pathspec = $3;
+                                       n->columns = $4;
+                                       n->location = @1;
+                                       $$ = (Node *) n;
+                               }
+               ;
+
+path_opt:
+                       PATH                                                                    { }
+                       | /* EMPTY */                                                   { }
+               ;
+
 json_returning_clause_opt:
                        RETURNING Typename
                                {
@@ -16733,6 +16916,7 @@ unreserved_keyword:
                        | MOVE
                        | NAME_P
                        | NAMES
+                       | NESTED
                        | NEW
                        | NEXT
                        | NFC
@@ -16766,6 +16950,7 @@ unreserved_keyword:
                        | PARTITION
                        | PASSING
                        | PASSWORD
+                       | PATH
                        | PLANS
                        | POLICY
                        | PRECEDING
@@ -16929,6 +17114,7 @@ col_name_keyword:
                        | JSON_QUERY
                        | JSON_SCALAR
                        | JSON_SERIALIZE
+                       | JSON_TABLE
                        | JSON_VALUE
                        | LEAST
                        | NATIONAL
@@ -17296,6 +17482,7 @@ bare_label_keyword:
                        | JSON_QUERY
                        | JSON_SCALAR
                        | JSON_SERIALIZE
+                       | JSON_TABLE
                        | JSON_VALUE
                        | KEEP
                        | KEY
@@ -17335,6 +17522,7 @@ bare_label_keyword:
                        | NATIONAL
                        | NATURAL
                        | NCHAR
+                       | NESTED
                        | NEW
                        | NEXT
                        | NFC
@@ -17378,6 +17566,7 @@ bare_label_keyword:
                        | PARTITION
                        | PASSING
                        | PASSWORD
+                       | PATH
                        | PLACING
                        | PLANS
                        | POLICY
index d8b14ba7cde48a523b500ca8215f44574c5cbda2..dafde68b2079a9ca06b047b4a3b8153854e29a4c 100644 (file)
@@ -696,7 +696,9 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
        char      **names;
        int                     colno;
 
-       /* Currently only XMLTABLE is supported */
+       /* Currently only XMLTABLE and JSON_TABLE are supported */
+
+       tf->functype = TFT_XMLTABLE;
        constructName = "XMLTABLE";
        docType = XMLOID;
 
@@ -1100,13 +1102,17 @@ transformFromClauseItem(ParseState *pstate, Node *n,
                rtr->rtindex = nsitem->p_rtindex;
                return (Node *) rtr;
        }
-       else if (IsA(n, RangeTableFunc))
+       else if (IsA(n, RangeTableFunc) || IsA(n, JsonTable))
        {
                /* table function is like a plain relation */
                RangeTblRef *rtr;
                ParseNamespaceItem *nsitem;
 
-               nsitem = transformRangeTableFunc(pstate, (RangeTableFunc *) n);
+               if (IsA(n, RangeTableFunc))
+                       nsitem = transformRangeTableFunc(pstate, (RangeTableFunc *) n);
+               else
+                       nsitem = transformJsonTable(pstate, (JsonTable *) n);
+
                *top_nsitem = nsitem;
                *namespace = list_make1(nsitem);
                rtr = makeNode(RangeTblRef);
index 911f355460b18e87e52b5d3a1e32e539722d3821..b6a2482f23a66c32355459fed2bad6bb461adede 100644 (file)
@@ -4093,7 +4093,7 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
        Node       *pathspec;
        JsonFormatType format;
 
-       if (func->common->pathname)
+       if (func->common->pathname && func->op != JSON_TABLE_OP)
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 errmsg("JSON_TABLE path name is not allowed here"),
@@ -4131,14 +4131,19 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
        transformJsonPassingArgs(pstate, format, func->common->passing,
                                                         &jsexpr->passing_values, &jsexpr->passing_names);
 
-       if (func->op != JSON_EXISTS_OP)
+       if (func->op != JSON_EXISTS_OP && func->op != JSON_TABLE_OP)
                jsexpr->on_empty = transformJsonBehavior(pstate, func->on_empty,
                                                                                                 JSON_BEHAVIOR_NULL);
 
-       jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
-                                                                                        func->op == JSON_EXISTS_OP ?
-                                                                                        JSON_BEHAVIOR_FALSE :
-                                                                                        JSON_BEHAVIOR_NULL);
+       if (func->op == JSON_EXISTS_OP)
+               jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
+                                                                                                JSON_BEHAVIOR_FALSE);
+       else if (func->op == JSON_TABLE_OP)
+               jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
+                                                                                                JSON_BEHAVIOR_EMPTY);
+       else
+               jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
+                                                                                                JSON_BEHAVIOR_NULL);
 
        return jsexpr;
 }
@@ -4439,6 +4444,21 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
                                        jsexpr->result_coercion->expr = NULL;
                        }
                        break;
+
+               case JSON_TABLE_OP:
+                       jsexpr->returning = makeNode(JsonReturning);
+                       jsexpr->returning->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+                       jsexpr->returning->typid = exprType(contextItemExpr);
+                       jsexpr->returning->typmod = -1;
+
+                       if (jsexpr->returning->typid != JSONBOID)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                errmsg("JSON_TABLE() is not yet implemented for json type"),
+                                                errhint("Try casting the argument to jsonb"),
+                                                parser_errposition(pstate, func->location)));
+
+                       break;
        }
 
        if (exprType(contextItemExpr) != JSONBOID)
diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c
new file mode 100644 (file)
index 0000000..dd75a40
--- /dev/null
@@ -0,0 +1,466 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_jsontable.c
+ *       parsing of JSON_TABLE
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       src/backend/parser/parse_jsontable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_type.h"
+#include "utils/builtins.h"
+#include "utils/json.h"
+#include "utils/lsyscache.h"
+
+/* Context for JSON_TABLE transformation */
+typedef struct JsonTableContext
+{
+       ParseState *pstate;                             /* parsing state */
+       JsonTable  *table;                              /* untransformed node */
+       TableFunc  *tablefunc;                  /* transformed node     */
+       List       *pathNames;                  /* list of all path and columns names */
+       Oid                     contextItemTypid;       /* type oid of context item (json/jsonb) */
+} JsonTableContext;
+
+static JsonTableParent * transformJsonTableColumns(JsonTableContext *cxt,
+                                                                                                          List *columns,
+                                                                                                          char *pathSpec,
+                                                                                                          int location);
+
+static Node *
+makeStringConst(char *str, int location)
+{
+       A_Const *n = makeNode(A_Const);
+
+       n->val.node.type = T_String;
+       n->val.sval.sval = str;
+       n->location = location;
+
+       return (Node *)n;
+}
+
+/*
+ * Transform JSON_TABLE column
+ *   - regular column into JSON_VALUE()
+ *   - FORMAT JSON column into JSON_QUERY()
+ *   - EXISTS column into JSON_EXISTS()
+ */
+static Node *
+transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
+                                                List *passingArgs, bool errorOnError)
+{
+       JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
+       JsonCommon *common = makeNode(JsonCommon);
+       JsonOutput *output = makeNode(JsonOutput);
+       JsonPathSpec pathspec;
+       JsonFormat *default_format;
+
+       jfexpr->op =
+               jtc->coltype == JTC_REGULAR ? JSON_VALUE_OP :
+               jtc->coltype == JTC_EXISTS ? JSON_EXISTS_OP : JSON_QUERY_OP;
+       jfexpr->common = common;
+       jfexpr->output = output;
+       jfexpr->on_empty = jtc->on_empty;
+       jfexpr->on_error = jtc->on_error;
+       if (!jfexpr->on_error && errorOnError)
+               jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL);
+       jfexpr->omit_quotes = jtc->omit_quotes;
+       jfexpr->wrapper = jtc->wrapper;
+       jfexpr->location = jtc->location;
+
+       output->typeName = jtc->typeName;
+       output->returning = makeNode(JsonReturning);
+       output->returning->format = jtc->format;
+
+       default_format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+
+       common->pathname = NULL;
+       common->expr = makeJsonValueExpr((Expr *) contextItemExpr, default_format);
+       common->passing = passingArgs;
+
+       if (jtc->pathspec)
+               pathspec = jtc->pathspec;
+       else
+       {
+               /* Construct default path as '$."column_name"' */
+               StringInfoData path;
+
+               initStringInfo(&path);
+
+               appendStringInfoString(&path, "$.");
+               escape_json(&path, jtc->name);
+
+               pathspec = path.data;
+       }
+
+       common->pathspec = makeStringConst(pathspec, -1);
+
+       return (Node *) jfexpr;
+}
+
+static bool
+isJsonTablePathNameDuplicate(JsonTableContext *cxt, const char *pathname)
+{
+       ListCell *lc;
+
+       foreach(lc, cxt->pathNames)
+       {
+               if (!strcmp(pathname, (const char *) lfirst(lc)))
+                       return true;
+       }
+
+       return false;
+}
+
+/* Register the column name in the path name list. */
+static void
+registerJsonTableColumn(JsonTableContext *cxt, char *colname)
+{
+       if (isJsonTablePathNameDuplicate(cxt, colname))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DUPLICATE_ALIAS),
+                                errmsg("duplicate JSON_TABLE column name: %s", colname),
+                                errhint("JSON_TABLE column names must be distinct from one another")));
+
+       cxt->pathNames = lappend(cxt->pathNames, colname);
+}
+
+/* Recursively register all nested column names in the path name list. */
+static void
+registerAllJsonTableColumns(JsonTableContext *cxt, List *columns)
+{
+       ListCell   *lc;
+
+       foreach(lc, columns)
+       {
+               JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
+
+               if (jtc->coltype == JTC_NESTED)
+                       registerAllJsonTableColumns(cxt, jtc->columns);
+               else
+                       registerJsonTableColumn(cxt, jtc->name);
+       }
+}
+
+static Node *
+transformNestedJsonTableColumn(JsonTableContext *cxt, JsonTableColumn *jtc)
+{
+       JsonTableParent *node;
+
+       node = transformJsonTableColumns(cxt, jtc->columns, jtc->pathspec,
+                                                                        jtc->location);
+
+       return (Node *) node;
+}
+
+static Node *
+makeJsonTableSiblingJoin(Node *lnode, Node *rnode)
+{
+       JsonTableSibling *join = makeNode(JsonTableSibling);
+
+       join->larg = lnode;
+       join->rarg = rnode;
+
+       return (Node *) join;
+}
+
+/*
+ * Recursively transform child (nested) JSON_TABLE columns.
+ *
+ * Child columns are transformed into a binary tree of union-joined
+ * JsonTableSiblings.
+ */
+static Node *
+transformJsonTableChildColumns(JsonTableContext *cxt, List *columns)
+{
+       Node       *res = NULL;
+       ListCell   *lc;
+
+       /* transform all nested columns into union join */
+       foreach(lc, columns)
+       {
+               JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
+               Node       *node;
+
+               if (jtc->coltype != JTC_NESTED)
+                       continue;
+
+               node = transformNestedJsonTableColumn(cxt, jtc);
+
+               /* join transformed node with previous sibling nodes */
+               res = res ? makeJsonTableSiblingJoin(res, node) : node;
+       }
+
+       return res;
+}
+
+/* Check whether type is json/jsonb, array, or record. */
+static bool
+typeIsComposite(Oid typid)
+{
+       char typtype;
+
+       if (typid == JSONOID ||
+               typid == JSONBOID ||
+               typid == RECORDOID ||
+               type_is_array(typid))
+               return true;
+
+       typtype = get_typtype(typid);
+
+       if (typtype ==  TYPTYPE_COMPOSITE)
+               return true;
+
+       if (typtype == TYPTYPE_DOMAIN)
+               return typeIsComposite(getBaseType(typid));
+
+       return false;
+}
+
+/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
+static void
+appendJsonTableColumns(JsonTableContext *cxt, List *columns)
+{
+       ListCell   *col;
+       ParseState *pstate = cxt->pstate;
+       JsonTable  *jt = cxt->table;
+       TableFunc  *tf = cxt->tablefunc;
+       bool            errorOnError = jt->on_error &&
+                                                          jt->on_error->btype == JSON_BEHAVIOR_ERROR;
+
+       foreach(col, columns)
+       {
+               JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
+               Oid                     typid;
+               int32           typmod;
+               Node       *colexpr;
+
+               if (rawc->name)
+               {
+                       /* make sure column names are unique */
+                       ListCell *colname;
+
+                       foreach(colname, tf->colnames)
+                               if (!strcmp((const char *) colname, rawc->name))
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("column name \"%s\" is not unique",
+                                                                       rawc->name),
+                                                        parser_errposition(pstate, rawc->location)));
+
+                       tf->colnames = lappend(tf->colnames,
+                                                                  makeString(pstrdup(rawc->name)));
+               }
+
+               /*
+                * Determine the type and typmod for the new column. FOR
+                * ORDINALITY columns are INTEGER by standard; the others are
+                * user-specified.
+                */
+               switch (rawc->coltype)
+               {
+                       case JTC_FOR_ORDINALITY:
+                               colexpr = NULL;
+                               typid = INT4OID;
+                               typmod = -1;
+                               break;
+
+                       case JTC_REGULAR:
+                               typenameTypeIdAndMod(pstate, rawc->typeName, &typid, &typmod);
+
+                               /*
+                                * Use implicit FORMAT JSON for composite types (arrays and
+                                * records)
+                                */
+                               if (typeIsComposite(typid))
+                                       rawc->coltype = JTC_FORMATTED;
+                               else if (rawc->wrapper != JSW_NONE)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("cannot use WITH WRAPPER clause with scalar columns"),
+                                                        parser_errposition(pstate, rawc->location)));
+                               else if (rawc->omit_quotes)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("cannot use OMIT QUOTES clause with scalar columns"),
+                                                        parser_errposition(pstate, rawc->location)));
+
+                               /* FALLTHROUGH */
+                       case JTC_EXISTS:
+                       case JTC_FORMATTED:
+                               {
+                                       Node       *je;
+                                       CaseTestExpr *param = makeNode(CaseTestExpr);
+
+                                       param->collation = InvalidOid;
+                                       param->typeId = cxt->contextItemTypid;
+                                       param->typeMod = -1;
+
+                                       je = transformJsonTableColumn(rawc, (Node *) param,
+                                                                                                 NIL, errorOnError);
+
+                                       colexpr = transformExpr(pstate, je, EXPR_KIND_FROM_FUNCTION);
+                                       assign_expr_collations(pstate, colexpr);
+
+                                       typid = exprType(colexpr);
+                                       typmod = exprTypmod(colexpr);
+                                       break;
+                               }
+
+                       case JTC_NESTED:
+                               continue;
+
+                       default:
+                               elog(ERROR, "unknown JSON_TABLE column type: %d", rawc->coltype);
+                               break;
+               }
+
+               tf->coltypes = lappend_oid(tf->coltypes, typid);
+               tf->coltypmods = lappend_int(tf->coltypmods, typmod);
+               tf->colcollations = lappend_oid(tf->colcollations,
+                                                                               type_is_collatable(typid)
+                                                                                       ? DEFAULT_COLLATION_OID
+                                                                                       : InvalidOid);
+               tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
+       }
+}
+
+/*
+ * Create transformed JSON_TABLE parent plan node by appending all non-nested
+ * columns to the TableFunc node and remembering their indices in the
+ * colvalexprs list.
+ */
+static JsonTableParent *
+makeParentJsonTableNode(JsonTableContext *cxt, char *pathSpec, List *columns)
+{
+       JsonTableParent *node = makeNode(JsonTableParent);
+
+       node->path = makeConst(JSONPATHOID, -1, InvalidOid, -1,
+                                                  DirectFunctionCall1(jsonpath_in,
+                                                                                          CStringGetDatum(pathSpec)),
+                                                  false, false);
+
+       /* save start of column range */
+       node->colMin = list_length(cxt->tablefunc->colvalexprs);
+
+       appendJsonTableColumns(cxt, columns);
+
+       /* save end of column range */
+       node->colMax = list_length(cxt->tablefunc->colvalexprs) - 1;
+
+       node->errorOnError =
+               cxt->table->on_error &&
+               cxt->table->on_error->btype == JSON_BEHAVIOR_ERROR;
+
+       return node;
+}
+
+static JsonTableParent *
+transformJsonTableColumns(JsonTableContext *cxt, List *columns, char *pathSpec,
+                                                 int location)
+{
+       JsonTableParent *node;
+
+       /* transform only non-nested columns */
+       node = makeParentJsonTableNode(cxt, pathSpec, columns);
+
+       /* transform recursively nested columns */
+       node->child = transformJsonTableChildColumns(cxt, columns);
+
+       return node;
+}
+
+/*
+ * transformJsonTable -
+ *                     Transform a raw JsonTable into TableFunc.
+ *
+ * Transform the document-generating expression, the row-generating expression,
+ * the column-generating expressions, and the default value expressions.
+ */
+ParseNamespaceItem *
+transformJsonTable(ParseState *pstate, JsonTable *jt)
+{
+       JsonTableContext cxt;
+       TableFunc  *tf = makeNode(TableFunc);
+       JsonFuncExpr *jfe = makeNode(JsonFuncExpr);
+       JsonCommon *jscommon;
+       char       *rootPath;
+       bool            is_lateral;
+
+       cxt.pstate = pstate;
+       cxt.table = jt;
+       cxt.tablefunc = tf;
+       cxt.pathNames = NIL;
+
+       registerAllJsonTableColumns(&cxt, jt->columns);
+
+       jscommon = copyObject(jt->common);
+       jscommon->pathspec = makeStringConst(pstrdup("$"), -1);
+
+       jfe->op = JSON_TABLE_OP;
+       jfe->common = jscommon;
+       jfe->on_error = jt->on_error;
+       jfe->location = jt->common->location;
+
+       /*
+        * We make lateral_only names of this level visible, whether or not the
+        * RangeTableFunc is explicitly marked LATERAL.  This is needed for SQL
+        * spec compliance and seems useful on convenience grounds for all
+        * functions in FROM.
+        *
+        * (LATERAL can't nest within a single pstate level, so we don't need
+        * save/restore logic here.)
+        */
+       Assert(!pstate->p_lateral_active);
+       pstate->p_lateral_active = true;
+
+       tf->functype = TFT_JSON_TABLE;
+       tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION);
+
+       cxt.contextItemTypid = exprType(tf->docexpr);
+
+       if (!IsA(jt->common->pathspec, A_Const) ||
+               castNode(A_Const, jt->common->pathspec)->val.node.type != T_String)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("only string constants supported in JSON_TABLE path specification"),
+                                parser_errposition(pstate,
+                                                                       exprLocation(jt->common->pathspec))));
+
+       rootPath = castNode(A_Const, jt->common->pathspec)->val.sval.sval;
+
+       tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns, rootPath,
+                                                                                                 jt->common->location);
+
+       tf->ordinalitycol = -1;         /* undefine ordinality column number */
+       tf->location = jt->location;
+
+       pstate->p_lateral_active = false;
+
+       /*
+        * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
+        * there are any lateral cross-references in it.
+        */
+       is_lateral = jt->lateral || contain_vars_of_level((Node *) tf, 0);
+
+       return addRangeTableEntryForTableFunc(pstate,
+                                                                                 tf, jt->alias, is_lateral, true);
+}
index 7efa5f15d72204bbde7f95e5810de405bc7f39cc..5448cb01fa7c789238287de769c7ee81b64c0870 100644 (file)
@@ -1989,7 +1989,8 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
                                                           bool inFromCl)
 {
        RangeTblEntry *rte = makeNode(RangeTblEntry);
-       char       *refname = alias ? alias->aliasname : pstrdup("xmltable");
+       char       *refname = alias ? alias->aliasname :
+               pstrdup(tf->functype == TFT_XMLTABLE ? "xmltable" : "json_table");
        Alias      *eref;
        int                     numaliases;
 
index 31c576cfec5e17197e1632be08176ca14bdee56e..2a1d44b813b01b661dde438050d781a3cd6c9575 100644 (file)
@@ -1993,6 +1993,9 @@ FigureColnameInternal(Node *node, char **name)
                                case JSON_EXISTS_OP:
                                        *name = "json_exists";
                                        return 2;
+                               case JSON_TABLE_OP:
+                                       *name = "json_table";
+                                       return 2;
                        }
                        break;
                default:
index 7811fa31e07d6718024cd673b03428f485dc0c07..c55b3aae027d8b909e5fd42732e1fc22c191e502 100644 (file)
 
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
+#include "executor/execExpr.h"
 #include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -74,6 +76,8 @@
 #include "utils/guc.h"
 #include "utils/json.h"
 #include "utils/jsonpath.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/timestamp.h"
 #include "utils/varlena.h"
 
@@ -155,6 +159,57 @@ typedef struct JsonValueListIterator
        ListCell   *next;
 } JsonValueListIterator;
 
+/* Structures for JSON_TABLE execution  */
+typedef struct JsonTableScanState JsonTableScanState;
+typedef struct JsonTableJoinState JsonTableJoinState;
+
+struct JsonTableScanState
+{
+       JsonTableScanState *parent;
+       JsonTableJoinState *nested;
+       MemoryContext mcxt;
+       JsonPath   *path;
+       List       *args;
+       JsonValueList found;
+       JsonValueListIterator iter;
+       Datum           current;
+       int                     ordinal;
+       bool            currentIsNull;
+       bool            errorOnError;
+       bool            advanceNested;
+       bool            reset;
+};
+
+struct JsonTableJoinState
+{
+       union
+       {
+               struct
+               {
+                       JsonTableJoinState *left;
+                       JsonTableJoinState *right;
+                       bool            advanceRight;
+               }                       join;
+               JsonTableScanState scan;
+       }                       u;
+       bool            is_join;
+};
+
+/* random number to identify JsonTableContext */
+#define JSON_TABLE_CONTEXT_MAGIC       418352867
+
+typedef struct JsonTableContext
+{
+       int                     magic;
+       struct
+       {
+               ExprState  *expr;
+               JsonTableScanState *scan;
+       }                  *colexprs;
+       JsonTableScanState root;
+       bool            empty;
+} JsonTableContext;
+
 /* strict/lax flags is decomposed into four [un]wrap/error flags */
 #define jspStrictAbsenseOfErrors(cxt)  (!(cxt)->laxMode)
 #define jspAutoUnwrap(cxt)                             ((cxt)->laxMode)
@@ -245,6 +300,7 @@ static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
                                                                                JsonPathItem *jsp, JsonbValue *jb, int32 *index);
 static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt,
                                                                                JsonbValue *jbv, int32 id);
+static void JsonValueListClear(JsonValueList *jvl);
 static void JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv);
 static int     JsonValueListLength(const JsonValueList *jvl);
 static bool JsonValueListIsEmpty(JsonValueList *jvl);
@@ -262,6 +318,12 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
 static int     compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
                                                        bool useTz, bool *have_error);
 
+
+static JsonTableJoinState *JsonTableInitPlanState(JsonTableContext *cxt,
+                                                                       Node *plan, JsonTableScanState *parent);
+static bool JsonTableNextRow(JsonTableScanState *scan);
+
+
 /****************** User interface to JsonPath executor ********************/
 
 /*
@@ -2458,6 +2520,13 @@ setBaseObject(JsonPathExecContext *cxt, JsonbValue *jbv, int32 id)
        return baseObject;
 }
 
+static void
+JsonValueListClear(JsonValueList *jvl)
+{
+       jvl->singleton = NULL;
+       jvl->list = NULL;
+}
+
 static void
 JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
 {
@@ -3067,3 +3136,370 @@ JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonbValue *res)
                                                        "casted to supported jsonpath types.")));
        }
 }
+
+/************************ JSON_TABLE functions ***************************/
+
+/*
+ * Returns private data from executor state. Ensure validity by check with
+ * MAGIC number.
+ */
+static inline JsonTableContext *
+GetJsonTableContext(TableFuncScanState *state, const char *fname)
+{
+       JsonTableContext *result;
+
+       if (!IsA(state, TableFuncScanState))
+               elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+       result = (JsonTableContext *) state->opaque;
+       if (result->magic != JSON_TABLE_CONTEXT_MAGIC)
+               elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+
+       return result;
+}
+
+/* Recursively initialize JSON_TABLE scan state */
+static void
+JsonTableInitScanState(JsonTableContext *cxt, JsonTableScanState *scan,
+                                          JsonTableParent *node, JsonTableScanState *parent,
+                                          List *args, MemoryContext mcxt)
+{
+       int                     i;
+
+       scan->parent = parent;
+       scan->errorOnError = node->errorOnError;
+       scan->path = DatumGetJsonPathP(node->path->constvalue);
+       scan->args = args;
+       scan->mcxt = AllocSetContextCreate(mcxt, "JsonTableContext",
+                                                                          ALLOCSET_DEFAULT_SIZES);
+       scan->nested = node->child ?
+               JsonTableInitPlanState(cxt, node->child, scan) : NULL;
+       scan->current = PointerGetDatum(NULL);
+       scan->currentIsNull = true;
+
+       for (i = node->colMin; i <= node->colMax; i++)
+               cxt->colexprs[i].scan = scan;
+}
+
+/* Recursively initialize JSON_TABLE scan state */
+static JsonTableJoinState *
+JsonTableInitPlanState(JsonTableContext *cxt, Node *plan,
+                                          JsonTableScanState *parent)
+{
+       JsonTableJoinState *state = palloc0(sizeof(*state));
+
+       if (IsA(plan, JsonTableSibling))
+       {
+               JsonTableSibling *join = castNode(JsonTableSibling, plan);
+
+               state->is_join = true;
+               state->u.join.left = JsonTableInitPlanState(cxt, join->larg, parent);
+               state->u.join.right = JsonTableInitPlanState(cxt, join->rarg, parent);
+       }
+       else
+       {
+               JsonTableParent *node = castNode(JsonTableParent, plan);
+
+               state->is_join = false;
+
+               JsonTableInitScanState(cxt, &state->u.scan, node, parent,
+                                                          parent->args, parent->mcxt);
+       }
+
+       return state;
+}
+
+/*
+ * JsonTableInitOpaque
+ *             Fill in TableFuncScanState->opaque for JsonTable processor
+ */
+static void
+JsonTableInitOpaque(TableFuncScanState *state, int natts)
+{
+       JsonTableContext *cxt;
+       PlanState  *ps = &state->ss.ps;
+       TableFuncScan  *tfs = castNode(TableFuncScan, ps->plan);
+       TableFunc  *tf = tfs->tablefunc;
+       JsonExpr   *ci = castNode(JsonExpr, tf->docexpr);
+       JsonTableParent *root = castNode(JsonTableParent, tf->plan);
+       List       *args = NIL;
+       ListCell   *lc;
+       int                     i;
+
+       cxt = palloc0(sizeof(JsonTableContext));
+       cxt->magic = JSON_TABLE_CONTEXT_MAGIC;
+
+       if (ci->passing_values)
+       {
+               ListCell   *exprlc;
+               ListCell   *namelc;
+
+               forboth(exprlc, ci->passing_values,
+                               namelc, ci->passing_names)
+               {
+                       Expr       *expr = (Expr *) lfirst(exprlc);
+                       String     *name = lfirst_node(String, namelc);
+                       JsonPathVariableEvalContext *var = palloc(sizeof(*var));
+
+                       var->name = pstrdup(name->sval);
+                       var->typid = exprType((Node *) expr);
+                       var->typmod = exprTypmod((Node *) expr);
+                       var->estate = ExecInitExpr(expr, ps);
+                       var->econtext = ps->ps_ExprContext;
+                       var->mcxt = CurrentMemoryContext;
+                       var->evaluated = false;
+                       var->value = (Datum) 0;
+                       var->isnull = true;
+
+                       args = lappend(args, var);
+               }
+       }
+
+       cxt->colexprs = palloc(sizeof(*cxt->colexprs) *
+                                                  list_length(tf->colvalexprs));
+
+       JsonTableInitScanState(cxt, &cxt->root, root, NULL, args,
+                                                  CurrentMemoryContext);
+
+       i = 0;
+
+       foreach(lc, tf->colvalexprs)
+       {
+               Expr       *expr = lfirst(lc);
+
+               cxt->colexprs[i].expr =
+                       ExecInitExprWithCaseValue(expr, ps,
+                                                                         &cxt->colexprs[i].scan->current,
+                                                                         &cxt->colexprs[i].scan->currentIsNull);
+
+               i++;
+       }
+
+       state->opaque = cxt;
+}
+
+/* Reset scan iterator to the beginning of the item list */
+static void
+JsonTableRescan(JsonTableScanState *scan)
+{
+       JsonValueListInitIterator(&scan->found, &scan->iter);
+       scan->current = PointerGetDatum(NULL);
+       scan->currentIsNull = true;
+       scan->advanceNested = false;
+       scan->ordinal = 0;
+}
+
+/* Reset context item of a scan, execute JSON path and reset a scan */
+static void
+JsonTableResetContextItem(JsonTableScanState *scan, Datum item)
+{
+       MemoryContext oldcxt;
+       JsonPathExecResult res;
+       Jsonb           *js = (Jsonb *) DatumGetJsonbP(item);
+
+       JsonValueListClear(&scan->found);
+
+       MemoryContextResetOnly(scan->mcxt);
+
+       oldcxt = MemoryContextSwitchTo(scan->mcxt);
+
+       res = executeJsonPath(scan->path, scan->args, EvalJsonPathVar, js,
+                                                 scan->errorOnError, &scan->found, false /* FIXME */);
+
+       MemoryContextSwitchTo(oldcxt);
+
+       if (jperIsError(res))
+       {
+               Assert(!scan->errorOnError);
+               JsonValueListClear(&scan->found);       /* EMPTY ON ERROR case */
+       }
+
+       JsonTableRescan(scan);
+}
+
+/*
+ * JsonTableSetDocument
+ *             Install the input document
+ */
+static void
+JsonTableSetDocument(TableFuncScanState *state, Datum value)
+{
+       JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableSetDocument");
+
+       JsonTableResetContextItem(&cxt->root, value);
+}
+
+/*
+ * Fetch next row from a union joined scan.
+ *
+ * Returns false at the end of a scan, true otherwise.
+ */
+static bool
+JsonTableNextJoinRow(JsonTableJoinState *state)
+{
+       if (!state->is_join)
+               return JsonTableNextRow(&state->u.scan);
+
+       if (!state->u.join.advanceRight)
+       {
+               /* fetch next outer row */
+               if (JsonTableNextJoinRow(state->u.join.left))
+                       return true;
+
+               state->u.join.advanceRight = true;      /* next inner row */
+       }
+
+       /* fetch next inner row */
+       return JsonTableNextJoinRow(state->u.join.right);
+}
+
+/* Recursively set 'reset' flag of scan and its child nodes */
+static void
+JsonTableJoinReset(JsonTableJoinState *state)
+{
+       if (state->is_join)
+       {
+               JsonTableJoinReset(state->u.join.left);
+               JsonTableJoinReset(state->u.join.right);
+               state->u.join.advanceRight = false;
+       }
+       else
+       {
+               state->u.scan.reset = true;
+               state->u.scan.advanceNested = false;
+
+               if (state->u.scan.nested)
+                       JsonTableJoinReset(state->u.scan.nested);
+       }
+}
+
+/*
+ * Fetch next row from a simple scan with outer joined nested subscans.
+ *
+ * Returns false at the end of a scan, true otherwise.
+ */
+static bool
+JsonTableNextRow(JsonTableScanState *scan)
+{
+       JsonbValue *jbv;
+       MemoryContext oldcxt;
+
+       /* reset context item if requested */
+       if (scan->reset)
+       {
+               Assert(!scan->parent->currentIsNull);
+               JsonTableResetContextItem(scan, scan->parent->current);
+               scan->reset = false;
+       }
+
+       if (scan->advanceNested)
+       {
+               /* fetch next nested row */
+               if (JsonTableNextJoinRow(scan->nested))
+                       return true;
+
+               scan->advanceNested = false;
+       }
+
+       /* fetch next row */
+       jbv = JsonValueListNext(&scan->found, &scan->iter);
+
+       if (!jbv)
+       {
+               scan->current = PointerGetDatum(NULL);
+               scan->currentIsNull = true;
+               return false;   /* end of scan */
+       }
+
+       /* set current row item */
+       oldcxt = MemoryContextSwitchTo(scan->mcxt);
+       scan->current = JsonbPGetDatum(JsonbValueToJsonb(jbv));
+       scan->currentIsNull = false;
+       MemoryContextSwitchTo(oldcxt);
+
+       scan->ordinal++;
+
+       if (scan->nested)
+       {
+               JsonTableJoinReset(scan->nested);
+               scan->advanceNested = JsonTableNextJoinRow(scan->nested);
+       }
+
+       return true;
+}
+
+/*
+ * JsonTableFetchRow
+ *             Prepare the next "current" tuple for upcoming GetValue calls.
+ *             Returns FALSE if the row-filter expression returned no more rows.
+ */
+static bool
+JsonTableFetchRow(TableFuncScanState *state)
+{
+       JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableFetchRow");
+
+       if (cxt->empty)
+               return false;
+
+       return JsonTableNextRow(&cxt->root);
+}
+
+/*
+ * JsonTableGetValue
+ *             Return the value for column number 'colnum' for the current row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
+ */
+static Datum
+JsonTableGetValue(TableFuncScanState *state, int colnum,
+                                 Oid typid, int32 typmod, bool *isnull)
+{
+       JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableGetValue");
+       ExprContext *econtext = state->ss.ps.ps_ExprContext;
+       ExprState  *estate = cxt->colexprs[colnum].expr;
+       JsonTableScanState *scan = cxt->colexprs[colnum].scan;
+       Datum           result;
+
+       if (scan->currentIsNull) /* NULL from outer/union join */
+       {
+               result = (Datum) 0;
+               *isnull = true;
+       }
+       else if (estate)        /* regular column */
+       {
+               result = ExecEvalExpr(estate, econtext, isnull);
+       }
+       else
+       {
+               result = Int32GetDatum(scan->ordinal);  /* ordinality column */
+               *isnull = false;
+       }
+
+       return result;
+}
+
+/*
+ * JsonTableDestroyOpaque
+ */
+static void
+JsonTableDestroyOpaque(TableFuncScanState *state)
+{
+       JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableDestroyOpaque");
+
+       /* not valid anymore */
+       cxt->magic = 0;
+
+       state->opaque = NULL;
+}
+
+const TableFuncRoutine JsonbTableRoutine =
+{
+       JsonTableInitOpaque,
+       JsonTableSetDocument,
+       NULL,
+       NULL,
+       NULL,
+       JsonTableFetchRow,
+       JsonTableGetValue,
+       JsonTableDestroyOpaque
+};
index 4458d2ff90a533e546183246916120995e996817..e6173a9db42a8b8791b12fa2f73f64632720de34 100644 (file)
@@ -503,6 +503,8 @@ static char *flatten_reloptions(Oid relid);
 static void get_reloptions(StringInfo buf, Datum reloptions);
 static void get_json_path_spec(Node *path_spec, deparse_context *context,
                                                           bool showimplicit);
+static void get_json_table_columns(TableFunc *tf, JsonTableParent *node,
+                                                                  deparse_context *context, bool showimplicit);
 
 #define only_marker(rte)  ((rte)->inh ? "" : "ONLY ")
 
@@ -8516,7 +8518,8 @@ get_json_behavior(JsonBehavior *behavior, deparse_context *context,
 /*
  * get_json_expr_options
  *
- * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS.
+ * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS and
+ * JSON_TABLE columns.
  */
 static void
 get_json_expr_options(JsonExpr *jsexpr, deparse_context *context,
@@ -9763,6 +9766,9 @@ get_rule_expr(Node *node, deparse_context *context,
                                        case JSON_EXISTS_OP:
                                                appendStringInfoString(buf, "JSON_EXISTS(");
                                                break;
+                                       default:
+                                               elog(ERROR, "unexpected JsonExpr type: %d", jexpr->op);
+                                               break;
                                }
 
                                get_rule_expr(jexpr->formatted_expr, context, showimplicit);
@@ -11039,16 +11045,14 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
 
 
 /* ----------
- * get_tablefunc                       - Parse back a table function
+ * get_xmltable                        - Parse back a XMLTABLE function
  * ----------
  */
 static void
-get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
+get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
 {
        StringInfo      buf = context->buf;
 
-       /* XMLTABLE is the only existing implementation.  */
-
        appendStringInfoString(buf, "XMLTABLE(");
 
        if (tf->ns_uris != NIL)
@@ -11139,6 +11143,220 @@ get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
        appendStringInfoChar(buf, ')');
 }
 
+/*
+ * get_json_nested_columns - Parse back nested JSON_TABLE columns
+ */
+static void
+get_json_table_nested_columns(TableFunc *tf, Node *node,
+                                                         deparse_context *context, bool showimplicit,
+                                                         bool needcomma)
+{
+       if (IsA(node, JsonTableSibling))
+       {
+               JsonTableSibling *n = (JsonTableSibling *) node;
+
+               get_json_table_nested_columns(tf, n->larg, context, showimplicit,
+                                                                         needcomma);
+               get_json_table_nested_columns(tf, n->rarg, context, showimplicit, true);
+       }
+       else
+       {
+                JsonTableParent *n = castNode(JsonTableParent, node);
+
+                if (needcomma)
+                        appendStringInfoChar(context->buf, ',');
+
+                appendStringInfoChar(context->buf, ' ');
+                appendContextKeyword(context,  "NESTED PATH ", 0, 0, 0);
+                get_const_expr(n->path, context, -1);
+                get_json_table_columns(tf, n, context, showimplicit);
+       }
+}
+
+/*
+ * get_json_table_columns - Parse back JSON_TABLE columns
+ */
+static void
+get_json_table_columns(TableFunc *tf, JsonTableParent *node,
+                                          deparse_context *context, bool showimplicit)
+{
+       StringInfo      buf = context->buf;
+       JsonExpr   *jexpr = castNode(JsonExpr, tf->docexpr);
+       ListCell   *lc_colname;
+       ListCell   *lc_coltype;
+       ListCell   *lc_coltypmod;
+       ListCell   *lc_colvarexpr;
+       int                     colnum = 0;
+
+       appendStringInfoChar(buf, ' ');
+       appendContextKeyword(context, "COLUMNS (", 0, 0, 0);
+
+       if (PRETTY_INDENT(context))
+               context->indentLevel += PRETTYINDENT_VAR;
+
+       forfour(lc_colname, tf->colnames,
+                       lc_coltype, tf->coltypes,
+                       lc_coltypmod, tf->coltypmods,
+                       lc_colvarexpr, tf->colvalexprs)
+       {
+               char       *colname = strVal(lfirst(lc_colname));
+               JsonExpr   *colexpr;
+               Oid                     typid;
+               int32           typmod;
+               bool            ordinality;
+               JsonBehaviorType default_behavior;
+
+               typid = lfirst_oid(lc_coltype);
+               typmod = lfirst_int(lc_coltypmod);
+               colexpr = castNode(JsonExpr, lfirst(lc_colvarexpr));
+
+               if (colnum < node->colMin)
+               {
+                       colnum++;
+                       continue;
+               }
+
+               if (colnum > node->colMax)
+                       break;
+
+               if (colnum > node->colMin)
+                       appendStringInfoString(buf, ", ");
+
+               colnum++;
+
+               ordinality = !colexpr;
+
+               appendContextKeyword(context, "", 0, 0, 0);
+
+               appendStringInfo(buf, "%s %s", quote_identifier(colname),
+                                                ordinality ? "FOR ORDINALITY" :
+                                                format_type_with_typemod(typid, typmod));
+               if (ordinality)
+                       continue;
+
+               if (colexpr->op == JSON_EXISTS_OP)
+               {
+                       appendStringInfoString(buf, " EXISTS");
+                       default_behavior = JSON_BEHAVIOR_FALSE;
+               }
+               else
+               {
+                       if (colexpr->op == JSON_QUERY_OP)
+                       {
+                               char            typcategory;
+                               bool            typispreferred;
+
+                               get_type_category_preferred(typid, &typcategory, &typispreferred);
+
+                               if (typcategory == TYPCATEGORY_STRING)
+                                       appendStringInfoString(buf,
+                                                                                  colexpr->format->format_type == JS_FORMAT_JSONB ?
+                                                                                  " FORMAT JSONB" : " FORMAT JSON");
+                       }
+
+                       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);
+
+               get_json_expr_options(colexpr, context, default_behavior);
+       }
+
+       if (node->child)
+               get_json_table_nested_columns(tf, node->child, context, showimplicit,
+                                                                         node->colMax >= node->colMin);
+
+       if (PRETTY_INDENT(context))
+               context->indentLevel -= PRETTYINDENT_VAR;
+
+       appendContextKeyword(context, ")", 0, 0, 0);
+}
+
+/* ----------
+ * get_json_table                      - Parse back a JSON_TABLE function
+ * ----------
+ */
+static void
+get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
+{
+       StringInfo      buf = context->buf;
+       JsonExpr   *jexpr = castNode(JsonExpr, tf->docexpr);
+       JsonTableParent *root = castNode(JsonTableParent, tf->plan);
+
+       appendStringInfoString(buf, "JSON_TABLE(");
+
+       if (PRETTY_INDENT(context))
+               context->indentLevel += PRETTYINDENT_VAR;
+
+       appendContextKeyword(context, "", 0, 0, 0);
+
+       get_rule_expr(jexpr->formatted_expr, context, showimplicit);
+
+       appendStringInfoString(buf, ", ");
+
+       get_const_expr(root->path, context, -1);
+
+       if (jexpr->passing_values)
+       {
+               ListCell   *lc1, *lc2;
+              &n