]> granicus.if.org Git - postgresql/commitdiff
Support XMLTABLE query expression
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Wed, 8 Mar 2017 15:39:37 +0000 (12:39 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Wed, 8 Mar 2017 15:40:26 +0000 (12:40 -0300)
XMLTABLE is defined by the SQL/XML standard as a feature that allows
turning XML-formatted data into relational form, so that it can be used
as a <table primary> in the FROM clause of a query.

This new construct provides significant simplicity and performance
benefit for XML data processing; what in a client-side custom
implementation was reported to take 20 minutes can be executed in 400ms
using XMLTABLE.  (The same functionality was said to take 10 seconds
using nested PostgreSQL XPath function calls, and 5 seconds using
XMLReader under PL/Python).

The implemented syntax deviates slightly from what the standard
requires.  First, the standard indicates that the PASSING clause is
optional and that multiple XML input documents may be given to it; we
make it mandatory and accept a single document only.  Second, we don't
currently support a default namespace to be specified.

This implementation relies on a new executor node based on a hardcoded
method table.  (Because the grammar is fixed, there is no extensibility
in the current approach; further constructs can be implemented on top of
this such as JSON_TABLE, but they require changes to core code.)

Author: Pavel Stehule, Álvaro Herrera
Extensively reviewed by: Craig Ringer
Discussion: https://postgr.es/m/CAFj8pRAgfzMD-LoSmnMGybD0WsEznLHWap8DO79+-GTRAPR4qA@mail.gmail.com

52 files changed:
contrib/pg_stat_statements/pg_stat_statements.c
doc/src/sgml/func.sgml
src/backend/commands/explain.c
src/backend/executor/Makefile
src/backend/executor/execAmi.c
src/backend/executor/execProcnode.c
src/backend/executor/nodeTableFuncscan.c [new file with mode: 0644]
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/makefuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/print.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/initsplan.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/plan/subselect.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/util/pathnode.c
src/backend/optimizer/util/plancat.c
src/backend/optimizer/util/relnode.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/parse_clause.c
src/backend/parser/parse_coerce.c
src/backend/parser/parse_relation.c
src/backend/parser/parse_target.c
src/backend/rewrite/rewriteHandler.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/adt/xml.c
src/include/catalog/catversion.h
src/include/executor/nodeTableFuncscan.h [new file with mode: 0644]
src/include/executor/tablefunc.h [new file with mode: 0644]
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/nodes/plannodes.h
src/include/nodes/primnodes.h
src/include/optimizer/cost.h
src/include/optimizer/pathnode.h
src/include/parser/kwlist.h
src/include/parser/parse_coerce.h
src/include/parser/parse_relation.h
src/include/utils/xml.h
src/test/regress/expected/xml.out
src/test/regress/expected/xml_1.out
src/test/regress/expected/xml_2.out
src/test/regress/sql/xml.sql

index 62dec8768a5c30a6e191d4bbde14511019f7a721..221ac98d4aa6f4eea668a91aa1e2c4300dafc926 100644 (file)
@@ -2400,6 +2400,9 @@ JumbleRangeTable(pgssJumbleState *jstate, List *rtable)
                        case RTE_FUNCTION:
                                JumbleExpr(jstate, (Node *) rte->functions);
                                break;
+                       case RTE_TABLEFUNC:
+                               JumbleExpr(jstate, (Node *) rte->tablefunc);
+                               break;
                        case RTE_VALUES:
                                JumbleExpr(jstate, (Node *) rte->values_lists);
                                break;
@@ -2868,6 +2871,15 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
                                JumbleExpr(jstate, rtfunc->funcexpr);
                        }
                        break;
+               case T_TableFunc:
+                       {
+                               TableFunc       *tablefunc = (TableFunc *) node;
+
+                               JumbleExpr(jstate, tablefunc->docexpr);
+                               JumbleExpr(jstate, tablefunc->rowexpr);
+                               JumbleExpr(jstate, (Node *) tablefunc->colexprs);
+                       }
+                       break;
                case T_TableSampleClause:
                        {
                                TableSampleClause *tsc = (TableSampleClause *) node;
index 9e084adc1aca9cf09df5586e125d34eec33b78b9..583b3b241aba0d6cf8d104db1652cf8f2fbadb91 100644 (file)
@@ -10332,7 +10332,8 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
     To process values of data type <type>xml</type>, PostgreSQL offers
     the functions <function>xpath</function> and
     <function>xpath_exists</function>, which evaluate XPath 1.0
-    expressions.
+    expressions, and the <function>XMLTABLE</function>
+    table function.
    </para>
 
    <sect3 id="functions-xml-processing-xpath">
@@ -10430,6 +10431,206 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
 --------------
  t
 (1 row)
+]]></screen>
+    </para>
+   </sect3>
+
+   <sect3 id="functions-xml-processing-xmltable">
+    <title><literal>xmltable</literal></title>
+
+    <indexterm>
+     <primary>xmltable</primary>
+    </indexterm>
+
+    <indexterm zone="functions-xml-processing-xmltable">
+     <primary>table function</primary>
+     <secondary>XMLTABLE</secondary>
+    </indexterm>
+
+<synopsis>
+<function>xmltable</function>( <optional>XMLNAMESPACES(<replaceable>namespace uri</replaceable> AS <replaceable>namespace name</replaceable><optional>, ...</optional>)</optional>
+          <replaceable>row_expression</replaceable> PASSING <optional>BY REF</optional> <replaceable>document_expression</replaceable> <optional>BY REF</optional>
+          COLUMNS <replaceable>name</replaceable> { <replaceable>type</replaceable> <optional>PATH <replaceable>column_expression</replaceable></optional> <optional>DEFAULT <replaceable>default_expression</replaceable></optional> <optional>NOT NULL | NULL</optional>
+                        | FOR ORDINALITY }
+                   <optional>, ...</optional>
+)
+</synopsis>
+
+    <para>
+     The <function>xmltable</function> function produces a table based
+     on the given XML value, an XPath filter to extract rows, and an
+     optional set of column definitions.
+    </para>
+
+    <para>
+     The optional <literal>XMLNAMESPACES</> clause is a comma-separated
+     list of namespaces.  It specifies the XML namespaces used in
+     the document and their aliases. A default namespace specification
+     is not currently supported.
+    </para>
+
+    <para>
+     The required <replaceable>row_expression</> argument is an XPath
+     expression that is evaluated against the supplied XML document to
+     obtain an ordered sequence of XML nodes. This sequence is what
+     <function>xmltable</> transforms into output rows.
+    </para>
+
+    <para>
+     <replaceable>document_expression</> provides the XML document to
+     operate on.
+     The <literal>BY REF</literal> clauses have no effect in PostgreSQL,
+     but are allowed for SQL conformance and compatibility with other
+     implementations.
+     The argument must be a well-formed XML document; fragments/forests
+     are not accepted.
+    </para>
+
+    <para>
+     The mandatory <literal>COLUMNS</literal> clause specifies the list
+     of columns in the output table.
+     If the <literal>COLUMNS</> clause is omitted, the rows in the result
+     set contain a single column of type <literal>xml</> containing the
+     data matched by <replaceable>row_expression</>.
+     If <literal>COLUMNS</literal> is specified, each entry describes a
+     single column.
+     See the syntax summary above for the format.
+     The column name and type are required; the path, default and
+     nullability clauses are optional.
+    </para>
+
+    <para>
+     A column marked <literal>FOR ORDINALITY</literal> will be populated
+     with row numbers matching the order in which the
+     output rows appeared in the original input XML document.
+     At most one column may be marked <literal>FOR ORDINALITY</literal>.
+    </para>
+
+    <para>
+     The <literal>column_expression</> for a column is an XPath expression
+     that is evaluated for each row, relative to the result of the
+     <replaceable>row_expression</>, to find the value of the column.
+     If no <literal>column_expression</> is given, then the column name
+     is used as an implicit path.
+    </para>
+
+    <para>
+     If a column's XPath expression returns multiple elements, an error
+     is raised.
+     If the expression matches an empty tag, the result is an
+     empty string (not <literal>NULL</>).
+     Any <literal>xsi:nil</> attributes are ignored.
+    </para>
+
+    <para>
+     The text body of the XML matched by the <replaceable>column_expression</>
+     is used as the column value. Multiple <literal>text()</literal> nodes
+     within an element are concatenated in order. Any child elements,
+     processing instructions, and comments are ignored, but the text contents
+     of child elements are concatenated to the result.
+     Note that the whitespace-only <literal>text()</> node between two non-text
+     elements is preserved, and that leading whitespace on a <literal>text()</>
+     node is not flattened.
+    </para>
+
+    <para>
+     If the path expression does not match for a given row but
+     <replaceable>default_expression</> is specified, the value resulting
+     from evaluating that expression is used.
+     If no <literal>DEFAULT</> clause is given for the column,
+     the field will be set to <literal>NULL</>.
+     It is possible for a <replaceable>default_expression</> to reference
+     the value of output columns that appear prior to it in the column list,
+     so the default of one column may be based on the value of another
+     column.
+    </para>
+
+    <para>
+     Columns may be marked <literal>NOT NULL</>. If the
+     <replaceable>column_expression</> for a <literal>NOT NULL</> column
+     does not match anything and there is no <literal>DEFAULT</> or the
+     <replaceable>default_expression</> also evaluates to null, an error
+     is reported.
+    </para>
+
+    <para>
+     Unlike regular PostgreSQL functions, <replaceable>column_expression</>
+     and <replaceable>default_expression</> are not evaluated to a simple
+     value before calling the function.
+     <replaceable>column_expression</> is normally evaluated
+     exactly once per input row, and <replaceable>default_expression</>
+     is evaluated each time a default is needed for a field.
+     If the expression qualifies as stable or immutable the repeat
+     evaluation may be skipped.
+     Effectively <function>xmltable</> behaves more like a subquery than a
+     function call.
+     This means that you can usefully use volatile functions like
+     <function>nextval</> in <replaceable>default_expression</>, and
+     <replaceable>column_expression</> may depend on other parts of the
+     XML document.
+    </para>
+
+    <para>
+     Examples:
+  <screen><![CDATA[
+CREATE TABLE xmldata AS SELECT
+xml $$
+<ROWS>
+  <ROW id="1">
+    <COUNTRY_ID>AU</COUNTRY_ID>
+    <COUNTRY_NAME>Australia</COUNTRY_NAME>
+  </ROW>
+  <ROW id="5">
+    <COUNTRY_ID>JP</COUNTRY_ID>
+    <COUNTRY_NAME>Japan</COUNTRY_NAME>
+    <PREMIER_NAME>Shinzo Abe</PREMIER_NAME>
+    <SIZE unit="sq_mi">145935</SIZE>
+  </ROW>
+  <ROW id="6">
+    <COUNTRY_ID>SG</COUNTRY_ID>
+    <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+    <SIZE unit="sq_km">697</SIZE>
+  </ROW>
+</ROWS>
+$$ AS data;
+
+SELECT xmltable.*
+  FROM xmldata,
+       XMLTABLE('//ROWS/ROW'
+                PASSING data
+                COLUMNS id int PATH '@id',
+                        ordinality FOR ORDINALITY,
+                        "COUNTRY_NAME" text,
+                        country_id text PATH 'COUNTRY_ID',
+                        size_sq_km float PATH 'SIZE[@unit = "sq_km"]',
+                        size_other text PATH
+                             'concat(SIZE[@unit!="sq_km"], " ", SIZE[@unit!="sq_km"]/@unit)',
+                        premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified') ;
+
+ id | ordinality | COUNTRY_NAME | country_id | size_sq_km |  size_other  | premier_name  
+----+------------+--------------+------------+------------+--------------+---------------
+  1 |          1 | Australia    | AU         |            |              | not specified
+  5 |          2 | Japan        | JP         |            | 145935 sq_mi | Shinzo Abe
+  6 |          3 | Singapore    | SG         |        697 |              | not specified
+]]></screen>
+
+     The following example shows concatenation of multiple text() nodes,
+     usage of the column name as XPath filter, and the treatment of whitespace,
+     XML comments and processing instructions:
+
+  <screen><![CDATA[
+CREATE TABLE xmlelements AS SELECT
+xml $$
+  <root>
+   <element>  Hello<!-- xyxxz -->2a2<?aaaaa?> <!--x-->  bbb<x>xxx</x>CC  </element>
+  </root>
+$$ AS data;
+
+SELECT xmltable.*
+  FROM xmlelements, XMLTABLE('/root' PASSING data COLUMNS element text);
+       element        
+----------------------
+   Hello2a2   bbbCC  
 ]]></screen>
     </para>
    </sect3>
index c9e0a3e42d20339143736833da92c117cd76aa16..7a8d36c8db106abfc390e7a82322daec645cc337 100644 (file)
@@ -781,6 +781,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
                case T_TidScan:
                case T_SubqueryScan:
                case T_FunctionScan:
+               case T_TableFuncScan:
                case T_ValuesScan:
                case T_CteScan:
                case T_WorkTableScan:
@@ -926,6 +927,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
                case T_FunctionScan:
                        pname = sname = "Function Scan";
                        break;
+               case T_TableFuncScan:
+                       pname = sname = "Table Function Scan";
+                       break;
                case T_ValuesScan:
                        pname = sname = "Values Scan";
                        break;
@@ -1103,6 +1107,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
                case T_TidScan:
                case T_SubqueryScan:
                case T_FunctionScan:
+               case T_TableFuncScan:
                case T_ValuesScan:
                case T_CteScan:
                case T_WorkTableScan:
@@ -1416,6 +1421,20 @@ ExplainNode(PlanState *planstate, List *ancestors,
                                show_instrumentation_count("Rows Removed by Filter", 1,
                                                                                   planstate, es);
                        break;
+               case T_TableFuncScan:
+                       if (es->verbose)
+                       {
+                               TableFunc  *tablefunc = ((TableFuncScan *) plan)->tablefunc;
+
+                               show_expression((Node *) tablefunc,
+                                                               "Table Function Call", planstate, ancestors,
+                                                               es->verbose, es);
+                       }
+                       show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+                       if (plan->qual)
+                               show_instrumentation_count("Rows Removed by Filter", 1,
+                                                                                  planstate, es);
+                       break;
                case T_TidScan:
                        {
                                /*
@@ -2593,6 +2612,11 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
                                objecttag = "Function Name";
                        }
                        break;
+               case T_TableFuncScan:
+                       Assert(rte->rtekind == RTE_TABLEFUNC);
+                       objectname = "xmltable";
+                       objecttag = "Table Function Name";
+                       break;
                case T_ValuesScan:
                        Assert(rte->rtekind == RTE_VALUES);
                        break;
index 2a2b7eb9bd94c141eb721e7bdc5dfffe4fe2e240..a9893c2b2208e624c0a3ae70134366cac3db0e83 100644 (file)
@@ -26,6 +26,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \
        nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
        nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
        nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
-       nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o
+       nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \
+       nodeTableFuncscan.o
 
 include $(top_srcdir)/src/backend/common.mk
index d3802079f5e70b66b57f696028deff790a5d28e8..5d59f95a916891d83d8b77e4533e728b86b9543c 100644 (file)
@@ -48,6 +48,7 @@
 #include "executor/nodeSort.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
+#include "executor/nodeTableFuncscan.h"
 #include "executor/nodeTidscan.h"
 #include "executor/nodeUnique.h"
 #include "executor/nodeValuesscan.h"
@@ -198,6 +199,10 @@ ExecReScan(PlanState *node)
                        ExecReScanFunctionScan((FunctionScanState *) node);
                        break;
 
+               case T_TableFuncScanState:
+                       ExecReScanTableFuncScan((TableFuncScanState *) node);
+                       break;
+
                case T_ValuesScanState:
                        ExecReScanValuesScan((ValuesScanState *) node);
                        break;
@@ -564,6 +569,7 @@ ExecMaterializesOutput(NodeTag plantype)
        {
                case T_Material:
                case T_FunctionScan:
+               case T_TableFuncScan:
                case T_CteScan:
                case T_WorkTableScan:
                case T_Sort:
index ef6f35a5a0b0f7f717d0b9bc8f026e650cde005b..468f50e6a6b6fa6b151db9b9b7bc454ba2433e87 100644 (file)
 #include "executor/nodeSort.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
+#include "executor/nodeTableFuncscan.h"
 #include "executor/nodeTidscan.h"
 #include "executor/nodeUnique.h"
 #include "executor/nodeValuesscan.h"
@@ -239,6 +240,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
                                                                                                                estate, eflags);
                        break;
 
+               case T_TableFuncScan:
+                       result = (PlanState *) ExecInitTableFuncScan((TableFuncScan *) node,
+                                                                                                                estate, eflags);
+                       break;
+
                case T_ValuesScan:
                        result = (PlanState *) ExecInitValuesScan((ValuesScan *) node,
                                                                                                          estate, eflags);
@@ -459,6 +465,10 @@ ExecProcNode(PlanState *node)
                        result = ExecFunctionScan((FunctionScanState *) node);
                        break;
 
+               case T_TableFuncScanState:
+                       result = ExecTableFuncScan((TableFuncScanState *) node);
+                       break;
+
                case T_ValuesScanState:
                        result = ExecValuesScan((ValuesScanState *) node);
                        break;
@@ -715,6 +725,10 @@ ExecEndNode(PlanState *node)
                        ExecEndFunctionScan((FunctionScanState *) node);
                        break;
 
+               case T_TableFuncScanState:
+                       ExecEndTableFuncScan((TableFuncScanState *) node);
+                       break;
+
                case T_ValuesScanState:
                        ExecEndValuesScan((ValuesScanState *) node);
                        break;
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
new file mode 100644 (file)
index 0000000..628f1ba
--- /dev/null
@@ -0,0 +1,502 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeTableFuncscan.c
+ *       Support routines for scanning RangeTableFunc (XMLTABLE like functions).
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       src/backend/executor/nodeTableFuncscan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * INTERFACE ROUTINES
+ *             ExecTableFuncscan               scans a function.
+ *             ExecFunctionNext                retrieve next tuple in sequential order.
+ *             ExecInitTableFuncscan   creates and initializes a TableFuncscan node.
+ *             ExecEndTableFuncscan            releases any storage allocated.
+ *             ExecReScanTableFuncscan rescans the function
+ */
+#include "postgres.h"
+
+#include "nodes/execnodes.h"
+#include "executor/executor.h"
+#include "executor/nodeTableFuncscan.h"
+#include "executor/tablefunc.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/xml.h"
+
+
+static TupleTableSlot *TableFuncNext(TableFuncScanState *node);
+static bool TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot);
+
+static void tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext);
+static void tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc);
+static void tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext);
+
+/* ----------------------------------------------------------------
+ *                                             Scan Support
+ * ----------------------------------------------------------------
+ */
+/* ----------------------------------------------------------------
+ *             TableFuncNext
+ *
+ *             This is a workhorse for ExecTableFuncscan
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+TableFuncNext(TableFuncScanState *node)
+{
+       TupleTableSlot *scanslot;
+
+       scanslot = node->ss.ss_ScanTupleSlot;
+
+       /*
+        * If first time through, read all tuples from function and put them in a
+        * tuplestore. Subsequent calls just fetch tuples from tuplestore.
+        */
+       if (node->tupstore == NULL)
+               tfuncFetchRows(node, node->ss.ps.ps_ExprContext);
+
+       /*
+        * Get the next tuple from tuplestore.
+        */
+       (void) tuplestore_gettupleslot(node->tupstore,
+                                                                  true,
+                                                                  false,
+                                                                  scanslot);
+       return scanslot;
+}
+
+/*
+ * TableFuncRecheck -- access method routine to recheck a tuple in EvalPlanQual
+ */
+static bool
+TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot)
+{
+       /* nothing to check */
+       return true;
+}
+
+/* ----------------------------------------------------------------
+ *             ExecTableFuncscan(node)
+ *
+ *             Scans the function sequentially and returns the next qualifying
+ *             tuple.
+ *             We call the ExecScan() routine and pass it the appropriate
+ *             access method functions.
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecTableFuncScan(TableFuncScanState *node)
+{
+       return ExecScan(&node->ss,
+                                       (ExecScanAccessMtd) TableFuncNext,
+                                       (ExecScanRecheckMtd) TableFuncRecheck);
+}
+
+/* ----------------------------------------------------------------
+ *             ExecInitTableFuncscan
+ * ----------------------------------------------------------------
+ */
+TableFuncScanState *
+ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
+{
+       TableFuncScanState *scanstate;
+       TableFunc  *tf = node->tablefunc;
+       TupleDesc       tupdesc;
+       int                     i;
+
+       /* check for unsupported flags */
+       Assert(!(eflags & EXEC_FLAG_MARK));
+
+       /*
+        * TableFuncscan should not have any children.
+        */
+       Assert(outerPlan(node) == NULL);
+       Assert(innerPlan(node) == NULL);
+
+       /*
+        * create new ScanState for node
+        */
+       scanstate = makeNode(TableFuncScanState);
+       scanstate->ss.ps.plan = (Plan *) node;
+       scanstate->ss.ps.state = estate;
+
+       /*
+        * Miscellaneous initialization
+        *
+        * create expression context for node
+        */
+       ExecAssignExprContext(estate, &scanstate->ss.ps);
+
+       /*
+        * initialize child expressions
+        */
+       scanstate->ss.ps.targetlist = (List *)
+               ExecInitExpr((Expr *) node->scan.plan.targetlist,
+                                        (PlanState *) scanstate);
+       scanstate->ss.ps.qual = (List *)
+               ExecInitExpr((Expr *) node->scan.plan.qual,
+                                        (PlanState *) scanstate);
+
+       /*
+        * tuple table initialization
+        */
+       ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
+       ExecInitScanTupleSlot(estate, &scanstate->ss);
+
+       /*
+        * initialize source tuple type
+        */
+       tupdesc = BuildDescFromLists(tf->colnames,
+                                                                tf->coltypes,
+                                                                tf->coltypmods,
+                                                                tf->colcollations);
+
+       ExecAssignScanType(&scanstate->ss, tupdesc);
+
+       /*
+        * Initialize result tuple type and projection info.
+        */
+       ExecAssignResultTypeFromTL(&scanstate->ss.ps);
+       ExecAssignScanProjectionInfo(&scanstate->ss);
+
+       /* Only XMLTABLE is supported currently */
+       scanstate->routine = &XmlTableRoutine;
+
+       scanstate->perValueCxt =
+               AllocSetContextCreate(CurrentMemoryContext,
+                                                         "TableFunc per value context",
+                                                         ALLOCSET_DEFAULT_SIZES);
+       scanstate->opaque = NULL;       /* initialized at runtime */
+
+       scanstate->ns_names = tf->ns_names;
+
+       scanstate->ns_uris = (List *)
+               ExecInitExpr((Expr *) tf->ns_uris, (PlanState *) scanstate);
+       scanstate->docexpr =
+               ExecInitExpr((Expr *) tf->docexpr, (PlanState *) scanstate);
+       scanstate->rowexpr =
+               ExecInitExpr((Expr *) tf->rowexpr, (PlanState *) scanstate);
+       scanstate->colexprs = (List *)
+               ExecInitExpr((Expr *) tf->colexprs, (PlanState *) scanstate);
+       scanstate->coldefexprs = (List *)
+               ExecInitExpr((Expr *) tf->coldefexprs, (PlanState *) scanstate);
+
+       scanstate->notnulls = tf->notnulls;
+
+       /* these are allocated now and initialized later */
+       scanstate->in_functions = palloc(sizeof(FmgrInfo) * tupdesc->natts);
+       scanstate->typioparams = palloc(sizeof(Oid) * tupdesc->natts);
+
+       /*
+        * Fill in the necessary fmgr infos.
+        */
+       for (i = 0; i < tupdesc->natts; i++)
+       {
+               Oid                     in_funcid;
+
+               getTypeInputInfo(tupdesc->attrs[i]->atttypid,
+                                                &in_funcid, &scanstate->typioparams[i]);
+               fmgr_info(in_funcid, &scanstate->in_functions[i]);
+       }
+
+       return scanstate;
+}
+
+/* ----------------------------------------------------------------
+ *             ExecEndTableFuncscan
+ *
+ *             frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndTableFuncScan(TableFuncScanState *node)
+{
+       /*
+        * Free the exprcontext
+        */
+       ExecFreeExprContext(&node->ss.ps);
+
+       /*
+        * clean out the tuple table
+        */
+       ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+       ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+       /*
+        * Release tuplestore resources
+        */
+       if (node->tupstore != NULL)
+               tuplestore_end(node->tupstore);
+       node->tupstore = NULL;
+}
+
+/* ----------------------------------------------------------------
+ *             ExecReScanTableFuncscan
+ *
+ *             Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+void
+ExecReScanTableFuncScan(TableFuncScanState *node)
+{
+       Bitmapset  *chgparam = node->ss.ps.chgParam;
+
+       ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+       ExecScanReScan(&node->ss);
+
+       /*
+        * Recompute when parameters are changed.
+        */
+       if (chgparam)
+       {
+               if (node->tupstore != NULL)
+               {
+                       tuplestore_end(node->tupstore);
+                       node->tupstore = NULL;
+               }
+       }
+
+       if (node->tupstore != NULL)
+               tuplestore_rescan(node->tupstore);
+}
+
+/* ----------------------------------------------------------------
+ *             tfuncFetchRows
+ *
+ *             Read rows from a TableFunc producer
+ * ----------------------------------------------------------------
+ */
+static void
+tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext)
+{
+       const TableFuncRoutine *routine = tstate->routine;
+       MemoryContext oldcxt;
+       Datum           value;
+       bool            isnull;
+
+       Assert(tstate->opaque == NULL);
+
+       /* build tuplestore for the result */
+       oldcxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+       tstate->tupstore = tuplestore_begin_heap(false, false, work_mem);
+
+       PG_TRY();
+       {
+               routine->InitOpaque(tstate,
+                                                       tstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor->natts);
+
+               /*
+                * If evaluating the document expression returns NULL, the table
+                * expression is empty and we return immediately.
+                */
+               value = ExecEvalExpr(tstate->docexpr, econtext, &isnull);
+
+               if (!isnull)
+               {
+                       /* otherwise, pass the document value to the table builder */
+                       tfuncInitialize(tstate, econtext, value);
+
+                       /* initialize ordinality counter */
+                       tstate->ordinal = 1;
+
+                       /* Load all rows into the tuplestore, and we're done */
+                       tfuncLoadRows(tstate, econtext);
+               }
+       }
+       PG_CATCH();
+       {
+               if (tstate->opaque != NULL)
+                       routine->DestroyOpaque(tstate);
+               PG_RE_THROW();
+       }
+       PG_END_TRY();
+
+       /* return to original memory context, and clean up */
+       MemoryContextSwitchTo(oldcxt);
+
+       if (tstate->opaque != NULL)
+       {
+               routine->DestroyOpaque(tstate);
+               tstate->opaque = NULL;
+       }
+
+       return;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc)
+{
+       const TableFuncRoutine *routine = tstate->routine;
+       TupleDesc       tupdesc;
+       ListCell   *lc1,
+                          *lc2;
+       bool            isnull;
+       int                     colno;
+       Datum           value;
+       int                     ordinalitycol =
+               ((TableFuncScan *) (tstate->ss.ps.plan))->tablefunc->ordinalitycol;
+
+       /*
+        * Install the document as a possibly-toasted Datum into the tablefunc
+        * context.
+        */
+       routine->SetDocument(tstate, doc);
+
+       /* Evaluate namespace specifications */
+       forboth(lc1, tstate->ns_uris, lc2, tstate->ns_names)
+       {
+               ExprState  *expr = (ExprState *) lfirst(lc1);
+               char       *ns_name = strVal(lfirst(lc2));
+               char       *ns_uri;
+
+               value = ExecEvalExpr((ExprState *) expr, econtext, &isnull);
+               if (isnull)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                        errmsg("namespace URI must not be null")));
+               ns_uri = TextDatumGetCString(value);
+
+               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")));
+
+       routine->SetRowFilter(tstate, TextDatumGetCString(value));
+
+       /*
+        * Install the column filter expressions into the table builder context.
+        * If an expression is given, use that; otherwise the column name itself
+        * is the column filter.
+        */
+       colno = 0;
+       tupdesc = tstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+       foreach(lc1, tstate->colexprs)
+       {
+               char       *colfilter;
+
+               if (colno != ordinalitycol)
+               {
+                       ExprState  *colexpr = lfirst(lc1);
+
+                       if (colexpr != NULL)
+                       {
+                               value = ExecEvalExpr(colexpr, econtext, &isnull);
+                               if (isnull)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                                errmsg("column filter expression must not be null"),
+                                                        errdetail("Filter for column \"%s\" is null.",
+                                                                 NameStr(tupdesc->attrs[colno]->attname))));
+                               colfilter = TextDatumGetCString(value);
+                       }
+                       else
+                               colfilter = NameStr(tupdesc->attrs[colno]->attname);
+
+                       routine->SetColumnFilter(tstate, colfilter, colno);
+               }
+
+               colno++;
+       }
+}
+
+/*
+ * Load all the rows from the TableFunc table builder into a tuplestore.
+ */
+static void
+tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext)
+{
+       const TableFuncRoutine *routine = tstate->routine;
+       TupleTableSlot *slot = tstate->ss.ss_ScanTupleSlot;
+       TupleDesc       tupdesc = slot->tts_tupleDescriptor;
+       Datum      *values = slot->tts_values;
+       bool       *nulls = slot->tts_isnull;
+       int                     natts = tupdesc->natts;
+       MemoryContext oldcxt;
+       int                     ordinalitycol;
+
+       ordinalitycol =
+               ((TableFuncScan *) (tstate->ss.ps.plan))->tablefunc->ordinalitycol;
+       oldcxt = MemoryContextSwitchTo(tstate->perValueCxt);
+
+       /*
+        * Keep requesting rows from the table builder until there aren't any.
+        */
+       while (routine->FetchRow(tstate))
+       {
+               ListCell   *cell = list_head(tstate->coldefexprs);
+               int                     colno;
+
+               ExecClearTuple(tstate->ss.ss_ScanTupleSlot);
+
+               /*
+                * Obtain the value of each column for this row, installing them into the
+                * slot; then add the tuple to the tuplestore.
+                */
+               for (colno = 0; colno < natts; colno++)
+               {
+                       if (colno == ordinalitycol)
+                       {
+                               /* Fast path for ordinality column */
+                               values[colno] = Int32GetDatum(tstate->ordinal++);
+                               nulls[colno] = false;
+                       }
+                       else
+                       {
+                               bool    isnull;
+
+                               values[colno] = routine->GetValue(tstate,
+                                                                                                 colno,
+                                                                                                 tupdesc->attrs[colno]->atttypid,
+                                                                                                 tupdesc->attrs[colno]->atttypmod,
+                                                                                                 &isnull);
+
+                               /* No value?  Evaluate and apply the default, if any */
+                               if (isnull && cell != NULL)
+                               {
+                                       ExprState  *coldefexpr = (ExprState *) lfirst(cell);
+
+                                       if (coldefexpr != NULL)
+                                               values[colno] = ExecEvalExpr(coldefexpr, econtext,
+                                                                                                        &isnull);
+                               }
+
+                               /* Verify a possible NOT NULL constraint */
+                               if (isnull && bms_is_member(colno, tstate->notnulls))
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                                        errmsg("null is not allowed in column \"%s\"",
+                                                                       NameStr(tupdesc->attrs[colno]->attname))));
+
+                               nulls[colno] = isnull;
+                       }
+
+                       /* advance list of default expressions */
+                       if (cell != NULL)
+                               cell = lnext(cell);
+               }
+
+               tuplestore_putvalues(tstate->tupstore, tupdesc, values, nulls);
+
+               MemoryContextReset(tstate->perValueCxt);
+       }
+
+       MemoryContextSwitchTo(oldcxt);
+}
index bb2a8a358656c0a79e8057068650fb3e10651e47..b3eac06c50ee664ec225d43e48b8f6d16792a95b 100644 (file)
@@ -587,6 +587,27 @@ _copyFunctionScan(const FunctionScan *from)
        return newnode;
 }
 
+/*
+ * _copyTableFuncScan
+ */
+static TableFuncScan *
+_copyTableFuncScan(const TableFuncScan *from)
+{
+       TableFuncScan *newnode = makeNode(TableFuncScan);
+
+       /*
+        * copy node superclass fields
+        */
+       CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+       /*
+        * copy remainder of node
+        */
+       COPY_NODE_FIELD(tablefunc);
+
+       return newnode;
+}
+
 /*
  * _copyValuesScan
  */
@@ -1138,6 +1159,31 @@ _copyRangeVar(const RangeVar *from)
        return newnode;
 }
 
+/*
+ * _copyTableFunc
+ */
+static TableFunc *
+_copyTableFunc(const TableFunc *from)
+{
+       TableFunc  *newnode = makeNode(TableFunc);
+
+       COPY_NODE_FIELD(ns_names);
+       COPY_NODE_FIELD(ns_uris);
+       COPY_NODE_FIELD(docexpr);
+       COPY_NODE_FIELD(rowexpr);
+       COPY_NODE_FIELD(colnames);
+       COPY_NODE_FIELD(coltypes);
+       COPY_NODE_FIELD(coltypmods);
+       COPY_NODE_FIELD(colcollations);
+       COPY_NODE_FIELD(colexprs);
+       COPY_NODE_FIELD(coldefexprs);
+       COPY_BITMAPSET_FIELD(notnulls);
+       COPY_SCALAR_FIELD(ordinalitycol);
+       COPY_LOCATION_FIELD(location);
+
+       return newnode;
+}
+
 /*
  * _copyIntoClause
  */
@@ -2169,6 +2215,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
        COPY_NODE_FIELD(joinaliasvars);
        COPY_NODE_FIELD(functions);
        COPY_SCALAR_FIELD(funcordinality);
+       COPY_NODE_FIELD(tablefunc);
        COPY_NODE_FIELD(values_lists);
        COPY_STRING_FIELD(ctename);
        COPY_SCALAR_FIELD(ctelevelsup);
@@ -2590,6 +2637,38 @@ _copyRangeTableSample(const RangeTableSample *from)
        return newnode;
 }
 
+static RangeTableFunc *
+_copyRangeTableFunc(const RangeTableFunc *from)
+{
+       RangeTableFunc *newnode = makeNode(RangeTableFunc);
+
+       COPY_SCALAR_FIELD(lateral);
+       COPY_NODE_FIELD(docexpr);
+       COPY_NODE_FIELD(rowexpr);
+       COPY_NODE_FIELD(namespaces);
+       COPY_NODE_FIELD(columns);
+       COPY_NODE_FIELD(alias);
+       COPY_LOCATION_FIELD(location);
+
+       return newnode;
+}
+
+static RangeTableFuncCol *
+_copyRangeTableFuncCol(const RangeTableFuncCol *from)
+{
+       RangeTableFuncCol *newnode = makeNode(RangeTableFuncCol);
+
+       COPY_STRING_FIELD(colname);
+       COPY_NODE_FIELD(typeName);
+       COPY_SCALAR_FIELD(for_ordinality);
+       COPY_SCALAR_FIELD(is_not_null);
+       COPY_NODE_FIELD(colexpr);
+       COPY_NODE_FIELD(coldefexpr);
+       COPY_LOCATION_FIELD(location);
+
+       return newnode;
+}
+
 static TypeCast *
 _copyTypeCast(const TypeCast *from)
 {
@@ -4540,6 +4619,9 @@ copyObject(const void *from)
                case T_FunctionScan:
                        retval = _copyFunctionScan(from);
                        break;
+               case T_TableFuncScan:
+                       retval = _copyTableFuncScan(from);
+                       break;
                case T_ValuesScan:
                        retval = _copyValuesScan(from);
                        break;
@@ -4616,6 +4698,9 @@ copyObject(const void *from)
                case T_RangeVar:
                        retval = _copyRangeVar(from);
                        break;
+               case T_TableFunc:
+                       retval = _copyTableFunc(from);
+                       break;
                case T_IntoClause:
                        retval = _copyIntoClause(from);
                        break;
@@ -5210,6 +5295,12 @@ copyObject(const void *from)
                case T_RangeTableSample:
                        retval = _copyRangeTableSample(from);
                        break;
+               case T_RangeTableFunc:
+                       retval = _copyRangeTableFunc(from);
+                       break;
+               case T_RangeTableFuncCol:
+                       retval = _copyRangeTableFuncCol(from);
+                       break;
                case T_TypeName:
                        retval = _copyTypeName(from);
                        break;
index 9fa83b9453a5d55f4d797cf52cf9212cff6d66bb..54e9c983a0f416f66f6db5ffcb392440706d9294 100644 (file)
@@ -116,6 +116,26 @@ _equalRangeVar(const RangeVar *a, const RangeVar *b)
        return true;
 }
 
+static bool
+_equalTableFunc(const TableFunc *a, const TableFunc *b)
+{
+       COMPARE_NODE_FIELD(ns_names);
+       COMPARE_NODE_FIELD(ns_uris);
+       COMPARE_NODE_FIELD(docexpr);
+       COMPARE_NODE_FIELD(rowexpr);
+       COMPARE_NODE_FIELD(colnames);
+       COMPARE_NODE_FIELD(coltypes);
+       COMPARE_NODE_FIELD(coltypes);
+       COMPARE_NODE_FIELD(colcollations);
+       COMPARE_NODE_FIELD(colexprs);
+       COMPARE_NODE_FIELD(coldefexprs);
+       COMPARE_BITMAPSET_FIELD(notnulls);
+       COMPARE_SCALAR_FIELD(ordinalitycol);
+       COMPARE_LOCATION_FIELD(location);
+
+       return true;
+}
+
 static bool
 _equalIntoClause(const IntoClause *a, const IntoClause *b)
 {
@@ -2419,6 +2439,36 @@ _equalRangeTableSample(const RangeTableSample *a, const RangeTableSample *b)
        return true;
 }
 
+static bool
+_equalRangeTableFunc(const RangeTableFunc *a, const RangeTableFunc *b)
+{
+       COMPARE_SCALAR_FIELD(lateral);
+       COMPARE_NODE_FIELD(docexpr);
+       COMPARE_NODE_FIELD(rowexpr);
+       COMPARE_NODE_FIELD(namespaces);
+       COMPARE_NODE_FIELD(columns);
+       COMPARE_NODE_FIELD(alias);
+       COMPARE_LOCATION_FIELD(location);
+
+       return true;
+}
+
+static bool
+_equalRangeTableFuncCol(const RangeTableFuncCol *a, const RangeTableFuncCol *b)
+{
+       COMPARE_STRING_FIELD(colname);
+       COMPARE_NODE_FIELD(typeName);
+       COMPARE_SCALAR_FIELD(for_ordinality);
+       COMPARE_NODE_FIELD(typeName);
+       COMPARE_SCALAR_FIELD(is_not_null);
+       COMPARE_NODE_FIELD(colexpr);
+       COMPARE_NODE_FIELD(coldefexpr);
+       COMPARE_LOCATION_FIELD(location);
+
+       return true;
+}
+
+
 static bool
 _equalIndexElem(const IndexElem *a, const IndexElem *b)
 {
@@ -2521,6 +2571,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
        COMPARE_SCALAR_FIELD(jointype);
        COMPARE_NODE_FIELD(joinaliasvars);
        COMPARE_NODE_FIELD(functions);
+       COMPARE_NODE_FIELD(tablefunc);
        COMPARE_SCALAR_FIELD(funcordinality);
        COMPARE_NODE_FIELD(values_lists);
        COMPARE_STRING_FIELD(ctename);
@@ -2887,6 +2938,9 @@ equal(const void *a, const void *b)
                case T_RangeVar:
                        retval = _equalRangeVar(a, b);
                        break;
+               case T_TableFunc:
+                       retval = _equalTableFunc(a, b);
+                       break;
                case T_IntoClause:
                        retval = _equalIntoClause(a, b);
                        break;
@@ -3468,6 +3522,12 @@ equal(const void *a, const void *b)
                case T_RangeTableSample:
                        retval = _equalRangeTableSample(a, b);
                        break;
+               case T_RangeTableFunc:
+                       retval = _equalRangeTableFunc(a, b);
+                       break;
+               case T_RangeTableFuncCol:
+                       retval = _equalRangeTableFuncCol(a, b);
+                       break;
                case T_TypeName:
                        retval = _equalTypeName(a, b);
                        break;
index 7586cce56a0243fcd453d5112d2d837b077605d3..e88d82f3b013055aa63118f3a9df38d0962777d4 100644 (file)
@@ -210,10 +210,10 @@ makeWholeRowVar(RangeTblEntry *rte,
                default:
 
                        /*
-                        * RTE is a join, subselect, or VALUES.  We represent this as a
-                        * whole-row Var of RECORD type. (Note that in most cases the Var
-                        * will be expanded to a RowExpr during planning, but that is not
-                        * our concern here.)
+                        * RTE is a join, subselect, tablefunc, or VALUES.  We represent
+                        * this as a whole-row Var of RECORD type. (Note that in most
+                        * cases the Var will be expanded to a RowExpr during planning,
+                        * but that is not our concern here.)
                         */
                        result = makeVar(varno,
                                                         InvalidAttrNumber,
index 71b24a079cddf6659cdae4c1a2a723fafcd7f9d8..6e52eb7231d73b3258da57f072f053a2dac9ff0b 100644 (file)
@@ -1212,6 +1212,9 @@ exprLocation(const Node *expr)
                case T_RangeVar:
                        loc = ((const RangeVar *) expr)->location;
                        break;
+               case T_TableFunc:
+                       loc = ((const TableFunc *) expr)->location;
+                       break;
                case T_Var:
                        loc = ((const Var *) expr)->location;
                        break;
@@ -2211,6 +2214,22 @@ expression_tree_walker(Node *node,
                                        return true;
                        }
                        break;
+               case T_TableFunc:
+                       {
+                               TableFunc  *tf = (TableFunc *) node;
+
+                               if (walker(tf->ns_uris, context))
+                                       return true;
+                               if (walker(tf->docexpr, context))
+                                       return true;
+                               if (walker(tf->rowexpr, context))
+                                       return true;
+                               if (walker(tf->colexprs, context))
+                                       return true;
+                               if (walker(tf->coldefexprs, context))
+                                       return true;
+                       }
+                       break;
                default:
                        elog(ERROR, "unrecognized node type: %d",
                                 (int) nodeTag(node));
@@ -2318,6 +2337,10 @@ range_table_walker(List *rtable,
                                if (walker(rte->functions, context))
                                        return true;
                                break;
+                       case RTE_TABLEFUNC:
+                               if (walker(rte->tablefunc, context))
+                                       return true;
+                               break;
                        case RTE_VALUES:
                                if (walker(rte->values_lists, context))
                                        return true;
@@ -3007,6 +3030,20 @@ expression_tree_mutator(Node *node,
                                return (Node *) newnode;
                        }
                        break;
+               case T_TableFunc:
+                       {
+                               TableFunc  *tf = (TableFunc *) node;
+                               TableFunc  *newnode;
+
+                               FLATCOPY(newnode, tf, TableFunc);
+                               MUTATE(newnode->ns_uris, tf->ns_uris, List *);
+                               MUTATE(newnode->docexpr, tf->docexpr, Node *);
+                               MUTATE(newnode->rowexpr, tf->rowexpr, Node *);
+                               MUTATE(newnode->colexprs, tf->colexprs, List *);
+                               MUTATE(newnode->coldefexprs, tf->coldefexprs, List *);
+                               return (Node *) newnode;
+                       }
+                       break;
                default:
                        elog(ERROR, "unrecognized node type: %d",
                                 (int) nodeTag(node));
@@ -3124,6 +3161,9 @@ range_table_mutator(List *rtable,
                        case RTE_FUNCTION:
                                MUTATE(newrte->functions, rte->functions, List *);
                                break;
+                       case RTE_TABLEFUNC:
+                               MUTATE(newrte->tablefunc, rte->tablefunc, TableFunc *);
+                               break;
                        case RTE_VALUES:
                                MUTATE(newrte->values_lists, rte->values_lists, List *);
                                break;
@@ -3548,6 +3588,32 @@ raw_expression_tree_walker(Node *node,
                                        return true;
                        }
                        break;
+               case T_RangeTableFunc:
+                       {
+                               RangeTableFunc *rtf = (RangeTableFunc *) node;
+
+                               if (walker(rtf->docexpr, context))
+                                       return true;
+                               if (walker(rtf->rowexpr, context))
+                                       return true;
+                               if (walker(rtf->namespaces, context))
+                                       return true;
+                               if (walker(rtf->columns, context))
+                                       return true;
+                               if (walker(rtf->alias, context))
+                                       return true;
+                       }
+                       break;
+               case T_RangeTableFuncCol:
+                       {
+                               RangeTableFuncCol *rtfc = (RangeTableFuncCol *) node;
+
+                               if (walker(rtfc->colexpr, context))
+                                       return true;
+                               if (walker(rtfc->coldefexpr, context))
+                                       return true;
+                       }
+                       break;
                case T_TypeName:
                        {
                                TypeName   *tn = (TypeName *) node;
index b3802b4428fd3e21ebb8affbcc0f9e6fe6dbe8dc..d4297d11b1890b4a2769ea33e3b6ded089dfa085 100644 (file)
@@ -565,6 +565,16 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
        WRITE_BOOL_FIELD(funcordinality);
 }
 
+static void
+_outTableFuncScan(StringInfo str, const TableFuncScan *node)
+{
+       WRITE_NODE_TYPE("TABLEFUNCSCAN");
+
+       _outScanInfo(str, (const Scan *) node);
+
+       WRITE_NODE_FIELD(tablefunc);
+}
+
 static void
 _outValuesScan(StringInfo str, const ValuesScan *node)
 {
@@ -955,6 +965,26 @@ _outRangeVar(StringInfo str, const RangeVar *node)
        WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outTableFunc(StringInfo str, const TableFunc *node)
+{
+       WRITE_NODE_TYPE("TABLEFUNC");
+
+       WRITE_NODE_FIELD(ns_names);
+       WRITE_NODE_FIELD(ns_uris);
+       WRITE_NODE_FIELD(docexpr);
+       WRITE_NODE_FIELD(rowexpr);
+       WRITE_NODE_FIELD(colnames);
+       WRITE_NODE_FIELD(coltypes);
+       WRITE_NODE_FIELD(coltypmods);
+       WRITE_NODE_FIELD(colcollations);
+       WRITE_NODE_FIELD(colexprs);
+       WRITE_NODE_FIELD(coldefexprs);
+       WRITE_BITMAPSET_FIELD(notnulls);
+       WRITE_INT_FIELD(ordinalitycol);
+       WRITE_LOCATION_FIELD(location);
+}
+
 static void
 _outIntoClause(StringInfo str, const IntoClause *node)
 {
@@ -2869,6 +2899,9 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
                        WRITE_NODE_FIELD(functions);
                        WRITE_BOOL_FIELD(funcordinality);
                        break;
+               case RTE_TABLEFUNC:
+                       WRITE_NODE_FIELD(tablefunc);
+                       break;
                case RTE_VALUES:
                        WRITE_NODE_FIELD(values_lists);
                        WRITE_NODE_FIELD(coltypes);
@@ -3191,6 +3224,34 @@ _outRangeTableSample(StringInfo str, const RangeTableSample *node)
        WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outRangeTableFunc(StringInfo str, const RangeTableFunc *node)
+{
+       WRITE_NODE_TYPE("RANGETABLEFUNC");
+
+       WRITE_BOOL_FIELD(lateral);
+       WRITE_NODE_FIELD(docexpr);
+       WRITE_NODE_FIELD(rowexpr);
+       WRITE_NODE_FIELD(namespaces);
+       WRITE_NODE_FIELD(columns);
+       WRITE_NODE_FIELD(alias);
+       WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outRangeTableFuncCol(StringInfo str, const RangeTableFuncCol *node)
+{
+       WRITE_NODE_TYPE("RANGETABLEFUNCCOL");
+
+       WRITE_STRING_FIELD(colname);
+       WRITE_NODE_FIELD(typeName);
+       WRITE_BOOL_FIELD(for_ordinality);
+       WRITE_BOOL_FIELD(is_not_null);
+       WRITE_NODE_FIELD(colexpr);
+       WRITE_NODE_FIELD(coldefexpr);
+       WRITE_LOCATION_FIELD(location);
+}
+
 static void
 _outConstraint(StringInfo str, const Constraint *node)
 {
@@ -3440,6 +3501,9 @@ outNode(StringInfo str, const void *obj)
                        case T_FunctionScan:
                                _outFunctionScan(str, obj);
                                break;
+                       case T_TableFuncScan:
+                               _outTableFuncScan(str, obj);
+                               break;
                        case T_ValuesScan:
                                _outValuesScan(str, obj);
                                break;
@@ -3512,6 +3576,9 @@ outNode(StringInfo str, const void *obj)
                        case T_RangeVar:
                                _outRangeVar(str, obj);
                                break;
+                       case T_TableFunc:
+                               _outTableFunc(str, obj);
+                               break;
                        case T_IntoClause:
                                _outIntoClause(str, obj);
                                break;
@@ -3922,6 +3989,12 @@ outNode(StringInfo str, const void *obj)
                        case T_RangeTableSample:
                                _outRangeTableSample(str, obj);
                                break;
+                       case T_RangeTableFunc:
+                               _outRangeTableFunc(str, obj);
+                               break;
+                       case T_RangeTableFuncCol:
+                               _outRangeTableFuncCol(str, obj);
+                               break;
                        case T_Constraint:
                                _outConstraint(str, obj);
                                break;
index 926f226f34c5c4801cdfc41f4fe84788dab762b4..dfb8bfa8034f44baaa3bf290c44fd3d00352d3ff 100644 (file)
@@ -279,6 +279,10 @@ print_rt(const List *rtable)
                                printf("%d\t%s\t[rangefunction]",
                                           i, rte->eref->aliasname);
                                break;
+                       case RTE_TABLEFUNC:
+                               printf("%d\t%s\t[table function]",
+                                          i, rte->eref->aliasname);
+                               break;
                        case RTE_VALUES:
                                printf("%d\t%s\t[values list]",
                                           i, rte->eref->aliasname);
index 05bf2e93e6b34bf6f02ce57508dbf9e891d0aaa3..b02d9fa24693562f689c5a5c45211194b80ec5dd 100644 (file)
@@ -458,6 +458,31 @@ _readRangeVar(void)
        READ_DONE();
 }
 
+/*
+ * _readTableFunc
+ */
+static TableFunc *
+_readTableFunc(void)
+{
+       READ_LOCALS(TableFunc);
+
+       READ_NODE_FIELD(ns_names);
+       READ_NODE_FIELD(ns_uris);
+       READ_NODE_FIELD(docexpr);
+       READ_NODE_FIELD(rowexpr);
+       READ_NODE_FIELD(colnames);
+       READ_NODE_FIELD(coltypes);
+       READ_NODE_FIELD(coltypmods);
+       READ_NODE_FIELD(colcollations);
+       READ_NODE_FIELD(colexprs);
+       READ_NODE_FIELD(coldefexprs);
+       READ_BITMAPSET_FIELD(notnulls);
+       READ_INT_FIELD(ordinalitycol);
+       READ_LOCATION_FIELD(location);
+
+       READ_DONE();
+}
+
 static IntoClause *
 _readIntoClause(void)
 {
@@ -1313,6 +1338,9 @@ _readRangeTblEntry(void)
                        READ_NODE_FIELD(functions);
                        READ_BOOL_FIELD(funcordinality);
                        break;
+               case RTE_TABLEFUNC:
+                       READ_NODE_FIELD(tablefunc);
+                       break;
                case RTE_VALUES:
                        READ_NODE_FIELD(values_lists);
                        READ_NODE_FIELD(coltypes);
@@ -1798,6 +1826,21 @@ _readValuesScan(void)
        READ_DONE();
 }
 
+/*
+ * _readTableFuncScan
+ */
+static TableFuncScan *
+_readTableFuncScan(void)
+{
+       READ_LOCALS(TableFuncScan);
+
+       ReadCommonScan(&local_node->scan);
+
+       READ_NODE_FIELD(tablefunc);
+
+       READ_DONE();
+}
+
 /*
  * _readCteScan
  */
@@ -2356,6 +2399,8 @@ parseNodeString(void)
                return_value = _readRangeVar();
        else if (MATCH("INTOCLAUSE", 10))
                return_value = _readIntoClause();
+       else if (MATCH("TABLEFUNC", 9))
+               return_value = _readTableFunc();
        else if (MATCH("VAR", 3))
                return_value = _readVar();
        else if (MATCH("CONST", 5))
@@ -2498,6 +2543,8 @@ parseNodeString(void)
                return_value = _readFunctionScan();
        else if (MATCH("VALUESSCAN", 10))
                return_value = _readValuesScan();
+       else if (MATCH("TABLEFUNCSCAN", 13))
+               return_value = _readTableFuncScan();
        else if (MATCH("CTESCAN", 7))
                return_value = _readCteScan();
        else if (MATCH("WORKTABLESCAN", 13))
index 87a3faff09db2ff0537ed30728677fdc0338161a..932c84c949befb99a0014c46bc651230ea7e1762 100644 (file)
@@ -106,6 +106,8 @@ static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
                                          RangeTblEntry *rte);
 static void set_values_pathlist(PlannerInfo *root, RelOptInfo *rel,
                                        RangeTblEntry *rte);
+static void set_tablefunc_pathlist(PlannerInfo *root, RelOptInfo *rel,
+                                          RangeTblEntry *rte);
 static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
                                 RangeTblEntry *rte);
 static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
@@ -365,6 +367,9 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
                        case RTE_FUNCTION:
                                set_function_size_estimates(root, rel);
                                break;
+                       case RTE_TABLEFUNC:
+                               set_tablefunc_size_estimates(root, rel);
+                               break;
                        case RTE_VALUES:
                                set_values_size_estimates(root, rel);
                                break;
@@ -437,6 +442,10 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
                                /* RangeFunction */
                                set_function_pathlist(root, rel, rte);
                                break;
+                       case RTE_TABLEFUNC:
+                               /* Table Function */
+                               set_tablefunc_pathlist(root, rel, rte);
+                               break;
                        case RTE_VALUES:
                                /* Values list */
                                set_values_pathlist(root, rel, rte);
@@ -599,6 +608,10 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
                                return;
                        break;
 
+               case RTE_TABLEFUNC:
+                       /* not parallel safe */
+                       return;
+
                case RTE_VALUES:
                        /* Check for parallel-restricted functions. */
                        if (!is_parallel_safe(root, (Node *) rte->values_lists))
@@ -1932,6 +1945,27 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
        add_path(rel, create_valuesscan_path(root, rel, required_outer));
 }
 
+/*
+ * set_tablefunc_pathlist
+ *             Build the (single) access path for a table func RTE
+ */
+static void
+set_tablefunc_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
+{
+       Relids          required_outer;
+
+       /*
+        * We don't support pushing join clauses into the quals of a tablefunc
+        * scan, but it could still have required parameterization due to LATERAL
+        * refs in the function expression.
+        */
+       required_outer = rel->lateral_relids;
+
+       /* Generate appropriate path */
+       add_path(rel, create_tablefuncscan_path(root, rel,
+                                                                                       required_outer));
+}
+
 /*
  * set_cte_pathlist
  *             Build the (single) access path for a non-self-reference CTE RTE
@@ -3032,6 +3066,9 @@ print_path(PlannerInfo *root, Path *path, int indent)
                                case T_FunctionScan:
                                        ptype = "FunctionScan";
                                        break;
+                               case T_TableFuncScan:
+                                       ptype = "TableFuncScan";
+                                       break;
                                case T_ValuesScan:
                                        ptype = "ValuesScan";
                                        break;
index c138f57ebb4416cd8ff723be95aeaee67799baf2..3eaed5af7a56a38e6400fe13b3efc4c777e2719a 100644 (file)
@@ -1277,6 +1277,62 @@ cost_functionscan(Path *path, PlannerInfo *root,
        path->total_cost = startup_cost + run_cost;
 }
 
+/*
+ * cost_tablefuncscan
+ *       Determines and returns the cost of scanning a table function.
+ *
+ * 'baserel' is the relation to be scanned
+ * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
+ */
+void
+cost_tablefuncscan(Path *path, PlannerInfo *root,
+                                  RelOptInfo *baserel, ParamPathInfo *param_info)
+{
+       Cost            startup_cost = 0;
+       Cost            run_cost = 0;
+       QualCost        qpqual_cost;
+       Cost            cpu_per_tuple;
+       RangeTblEntry *rte;
+       QualCost        exprcost;
+
+       /* Should only be applied to base relations that are functions */
+       Assert(baserel->relid > 0);
+       rte = planner_rt_fetch(baserel->relid, root);
+       Assert(rte->rtekind == RTE_TABLEFUNC);
+
+       /* Mark the path with the correct row estimate */
+       if (param_info)
+               path->rows = param_info->ppi_rows;
+       else
+               path->rows = baserel->rows;
+
+       /*
+        * Estimate costs of executing the table func expression(s).
+        *
+        * XXX in principle we ought to charge tuplestore spill costs if the
+        * number of rows is large.  However, given how phony our rowcount
+        * estimates for tablefuncs tend to be, there's not a lot of point in that
+        * refinement right now.
+        */
+       cost_qual_eval_node(&exprcost, (Node *) rte->tablefunc, root);
+
+       startup_cost += exprcost.startup + exprcost.per_tuple;
+
+       /* Add scanning CPU costs */
+       get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
+
+       startup_cost += qpqual_cost.startup;
+       cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
+       run_cost += cpu_per_tuple * baserel->tuples;
+
+       /* tlist eval costs are paid per output row, not per tuple scanned */
+       startup_cost += path->pathtarget->cost.startup;
+       run_cost += path->pathtarget->cost.per_tuple * path->rows;
+
+       path->startup_cost = startup_cost;
+       path->total_cost = startup_cost + run_cost;
+}
+
 /*
  * cost_valuesscan
  *       Determines and returns the cost of scanning a VALUES RTE.
@@ -4421,6 +4477,31 @@ set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel)
        set_baserel_size_estimates(root, rel);
 }
 
+/*
+ * set_function_size_estimates
+ *             Set the size estimates for a base relation that is a function call.
+ *
+ * The rel's targetlist and restrictinfo list must have been constructed
+ * already.
+ *
+ * We set the same fields as set_tablefunc_size_estimates.
+ */
+void
+set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel)
+{
+       RangeTblEntry *rte;
+
+       /* Should only be applied to base relations that are functions */
+       Assert(rel->relid > 0);
+       rte = planner_rt_fetch(rel->relid, root);
+       Assert(rte->rtekind == RTE_TABLEFUNC);
+
+       rel->tuples = 100;
+
+       /* Now estimate number of output rows, etc */
+       set_baserel_size_estimates(root, rel);
+}
+
 /*
  * set_values_size_estimates
  *             Set the size estimates for a base relation that is a values list.
index 1e953b40d6f5dfafa53f4f1f851e6145608fbf07..f1c7f609c0a706d1225304fbc129c80dad0ba703 100644 (file)
@@ -134,6 +134,8 @@ static FunctionScan *create_functionscan_plan(PlannerInfo *root, Path *best_path
                                                 List *tlist, List *scan_clauses);
 static ValuesScan *create_valuesscan_plan(PlannerInfo *root, Path *best_path,
                                           List *tlist, List *scan_clauses);
+static TableFuncScan *create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
+                                                 List *tlist, List *scan_clauses);
 static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
                                        List *tlist, List *scan_clauses);
 static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
@@ -190,6 +192,8 @@ static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
                                  Index scanrelid, List *functions, bool funcordinality);
 static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
                                Index scanrelid, List *values_lists);
+static TableFuncScan *make_tablefuncscan(List *qptlist, List *qpqual,
+                                  Index scanrelid, TableFunc *tablefunc);
 static CteScan *make_ctescan(List *qptlist, List *qpqual,
                         Index scanrelid, int ctePlanId, int cteParam);
 static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
@@ -355,6 +359,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
                case T_TidScan:
                case T_SubqueryScan:
                case T_FunctionScan:
+               case T_TableFuncScan:
                case T_ValuesScan:
                case T_CteScan:
                case T_WorkTableScan:
@@ -635,6 +640,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
                                                                                                         scan_clauses);
                        break;
 
+               case T_TableFuncScan:
+                       plan = (Plan *) create_tablefuncscan_plan(root,
+                                                                                                         best_path,
+                                                                                                         tlist,
+                                                                                                         scan_clauses);
+                       break;
+
                case T_ValuesScan:
                        plan = (Plan *) create_valuesscan_plan(root,
                                                                                                   best_path,
@@ -749,11 +761,12 @@ use_physical_tlist(PlannerInfo *root, Path *path, int flags)
 
        /*
         * We can do this for real relation scans, subquery scans, function scans,
-        * values scans, and CTE scans (but not for, eg, joins).
+        * tablefunc scans, values scans, and CTE scans (but not for, eg, joins).
         */
        if (rel->rtekind != RTE_RELATION &&
                rel->rtekind != RTE_SUBQUERY &&
                rel->rtekind != RTE_FUNCTION &&
+               rel->rtekind != RTE_TABLEFUNC &&
                rel->rtekind != RTE_VALUES &&
                rel->rtekind != RTE_CTE)
                return false;
@@ -3014,6 +3027,49 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
        return scan_plan;
 }
 
+/*
+ * create_tablefuncscan_plan
+ *      Returns a tablefuncscan plan for the base relation scanned by 'best_path'
+ *      with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+static TableFuncScan *
+create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
+                                                 List *tlist, List *scan_clauses)
+{
+       TableFuncScan *scan_plan;
+       Index           scan_relid = best_path->parent->relid;
+       RangeTblEntry *rte;
+       TableFunc  *tablefunc;
+
+       /* it should be a function base rel... */
+       Assert(scan_relid > 0);
+       rte = planner_rt_fetch(scan_relid, root);
+       Assert(rte->rtekind == RTE_TABLEFUNC);
+       tablefunc = rte->tablefunc;
+
+       /* Sort clauses into best execution order */
+       scan_clauses = order_qual_clauses(root, scan_clauses);
+
+       /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+       scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+       /* Replace any outer-relation variables with nestloop params */
+       if (best_path->param_info)
+       {
+               scan_clauses = (List *)
+                       replace_nestloop_params(root, (Node *) scan_clauses);
+               /* The function expressions could contain nestloop params, too */
+               tablefunc = (TableFunc *) replace_nestloop_params(root, (Node *) tablefunc);
+       }
+
+       scan_plan = make_tablefuncscan(tlist, scan_clauses, scan_relid,
+                                                                  tablefunc);
+
+       copy_generic_path_info(&scan_plan->scan.plan, best_path);
+
+       return scan_plan;
+}
+
 /*
  * create_valuesscan_plan
  *      Returns a valuesscan plan for the base relation scanned by 'best_path'
@@ -4909,6 +4965,25 @@ make_functionscan(List *qptlist,
        return node;
 }
 
+static TableFuncScan *
+make_tablefuncscan(List *qptlist,
+                                  List *qpqual,
+                                  Index scanrelid,
+                                  TableFunc *tablefunc)
+{
+       TableFuncScan *node = makeNode(TableFuncScan);
+       Plan       *plan = &node->scan.plan;
+
+       plan->targetlist = qptlist;
+       plan->qual = qpqual;
+       plan->lefttree = NULL;
+       plan->righttree = NULL;
+       node->scan.scanrelid = scanrelid;
+       node->tablefunc = tablefunc;
+
+       return node;
+}
+
 static ValuesScan *
 make_valuesscan(List *qptlist,
                                List *qpqual,
index c170e9614f60a8e4814c1753d8c8e0d42081e9ee..b4ac224a7a8a6c52d1052ee40acf01657661c0ed 100644 (file)
@@ -335,6 +335,8 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex)
                vars = pull_vars_of_level((Node *) rte->subquery, 1);
        else if (rte->rtekind == RTE_FUNCTION)
                vars = pull_vars_of_level((Node *) rte->functions, 0);
+       else if (rte->rtekind == RTE_TABLEFUNC)
+               vars = pull_vars_of_level((Node *) rte->tablefunc, 0);
        else if (rte->rtekind == RTE_VALUES)
                vars = pull_vars_of_level((Node *) rte->values_lists, 0);
        else
index ca0ae7883e795b6845930b1bf53a8206cbe041d3..1636a69dba49328a926ef7f3c1582e907ce7d131 100644 (file)
@@ -69,17 +69,19 @@ create_upper_paths_hook_type create_upper_paths_hook = NULL;
 
 
 /* Expression kind codes for preprocess_expression */
-#define EXPRKIND_QUAL                  0
-#define EXPRKIND_TARGET                        1
-#define EXPRKIND_RTFUNC                        2
-#define EXPRKIND_RTFUNC_LATERAL 3
-#define EXPRKIND_VALUES                        4
-#define EXPRKIND_VALUES_LATERAL 5
-#define EXPRKIND_LIMIT                 6
-#define EXPRKIND_APPINFO               7
-#define EXPRKIND_PHV                   8
-#define EXPRKIND_TABLESAMPLE   9
-#define EXPRKIND_ARBITER_ELEM  10
+#define EXPRKIND_QUAL                          0
+#define EXPRKIND_TARGET                                1
+#define EXPRKIND_RTFUNC                                2
+#define EXPRKIND_RTFUNC_LATERAL        3
+#define EXPRKIND_VALUES                                4
+#define EXPRKIND_VALUES_LATERAL        5
+#define EXPRKIND_LIMIT                         6
+#define EXPRKIND_APPINFO                       7
+#define EXPRKIND_PHV                           8
+#define EXPRKIND_TABLESAMPLE           9
+#define EXPRKIND_ARBITER_ELEM          10
+#define EXPRKIND_TABLEFUNC                     11
+#define EXPRKIND_TABLEFUNC_LATERAL     12
 
 /* Passthrough data for standard_qp_callback */
 typedef struct
@@ -685,7 +687,15 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
                {
                        /* Preprocess the function expression(s) fully */
                        kind = rte->lateral ? EXPRKIND_RTFUNC_LATERAL : EXPRKIND_RTFUNC;
-                       rte->functions = (List *) preprocess_expression(root, (Node *) rte->functions, kind);
+                       rte->functions = (List *)
+                               preprocess_expression(root, (Node *) rte->functions, kind);
+               }
+               else if (rte->rtekind == RTE_TABLEFUNC)
+               {
+                       /* Preprocess the function expression(s) fully */
+                       kind = rte->lateral ? EXPRKIND_TABLEFUNC_LATERAL : EXPRKIND_TABLEFUNC;
+                       rte->tablefunc = (TableFunc *)
+                               preprocess_expression(root, (Node *) rte->tablefunc, kind);
                }
                else if (rte->rtekind == RTE_VALUES)
                {
@@ -844,7 +854,8 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
        if (root->hasJoinRTEs &&
                !(kind == EXPRKIND_RTFUNC ||
                  kind == EXPRKIND_VALUES ||
-                 kind == EXPRKIND_TABLESAMPLE))
+                 kind == EXPRKIND_TABLESAMPLE ||
+                 kind == EXPRKIND_TABLEFUNC))
                expr = flatten_join_alias_vars(root, expr);
 
        /*
index 07ddbcf37e9e46f5b452d89545f0b14247fd2348..3d2c12433d3d52282fd6a397c2737af0bd9d09fe 100644 (file)
@@ -393,6 +393,7 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
        newrte->subquery = NULL;
        newrte->joinaliasvars = NIL;
        newrte->functions = NIL;
+       newrte->tablefunc = NULL;
        newrte->values_lists = NIL;
        newrte->coltypes = NIL;
        newrte->coltypmods = NIL;
@@ -553,6 +554,19 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
                                        fix_scan_list(root, splan->functions, rtoffset);
                        }
                        break;
+               case T_TableFuncScan:
+                       {
+                               TableFuncScan *splan = (TableFuncScan *) plan;
+
+                               splan->scan.scanrelid += rtoffset;
+                               splan->scan.plan.targetlist =
+                                       fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+                               splan->scan.plan.qual =
+                                       fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+                               splan->tablefunc = (TableFunc *)
+                                       fix_scan_expr(root, (Node *) splan->tablefunc, rtoffset);
+                       }
+                       break;
                case T_ValuesScan:
                        {
                                ValuesScan *splan = (ValuesScan *) plan;
index 3eb2bb749e4a2c981e19101564fd26052ffcc406..da9a84be3d76bbbbc5c403fbfbd5094d6b873dcf 100644 (file)
@@ -2421,6 +2421,12 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
                        }
                        break;
 
+               case T_TableFuncScan:
+                       finalize_primnode((Node *) ((TableFuncScan *) plan)->tablefunc,
+                                                         &context);
+                       context.paramids = bms_add_members(context.paramids, scan_params);
+                       break;
+
                case T_ValuesScan:
                        finalize_primnode((Node *) ((ValuesScan *) plan)->values_lists,
                                                          &context);
index 6c6ac8dc0aa27a26edfc9dfb6f623badea93ae9d..048815d7b07d4f8968b362140bc6b171e2fd6413 100644 (file)
@@ -1118,6 +1118,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
                                case RTE_SUBQUERY:
                                case RTE_FUNCTION:
                                case RTE_VALUES:
+                               case RTE_TABLEFUNC:
                                        child_rte->lateral = true;
                                        break;
                                case RTE_JOIN:
@@ -1964,6 +1965,11 @@ replace_vars_in_jointree(Node *jtnode,
                                                        pullup_replace_vars((Node *) rte->functions,
                                                                                                context);
                                                break;
+                                       case RTE_TABLEFUNC:
+                                               rte->tablefunc = (TableFunc *)
+                                                       pullup_replace_vars((Node *) rte->tablefunc,
+                                                                                               context);
+                                               break;
                                        case RTE_VALUES:
                                                rte->values_lists = (List *)
                                                        pullup_replace_vars((Node *) rte->values_lists,
index 324829690d28521e34d15292d124a16e6f3b961d..86aee2f8ec37cc1e44b23dfb0ad9a1806613afd7 100644 (file)
@@ -1750,6 +1750,32 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
        return pathnode;
 }
 
+/*
+ * create_tablefuncscan_path
+ *       Creates a path corresponding to a sequential scan of a table function,
+ *       returning the pathnode.
+ */
+Path *
+create_tablefuncscan_path(PlannerInfo *root, RelOptInfo *rel,
+                                                 Relids required_outer)
+{
+       Path       *pathnode = makeNode(Path);
+
+       pathnode->pathtype = T_TableFuncScan;
+       pathnode->parent = rel;
+       pathnode->pathtarget = rel->reltarget;
+       pathnode->param_info = get_baserel_parampathinfo(root, rel,
+                                                                                                        required_outer);
+       pathnode->parallel_aware = false;
+       pathnode->parallel_safe = rel->consider_parallel;
+       pathnode->parallel_workers = 0;
+       pathnode->pathkeys = NIL;       /* result is always unordered */
+
+       cost_tablefuncscan(pathnode, root, rel, pathnode->param_info);
+
+       return pathnode;
+}
+
 /*
  * create_valuesscan_path
  *       Creates a path corresponding to a scan of a VALUES list,
index 4ed27054a11d49bd6e29ed09d5a2b606f211ad88..463f806467827f77181c69eca5bf5604edb72d38 100644 (file)
@@ -1381,8 +1381,9 @@ relation_excluded_by_constraints(PlannerInfo *root,
  * dropped cols.
  *
  * We also support building a "physical" tlist for subqueries, functions,
- * values lists, and CTEs, since the same optimization can occur in
- * SubqueryScan, FunctionScan, ValuesScan, CteScan, and WorkTableScan nodes.
+ * values lists, table expressions and CTEs, since the same optimization can
+ * occur in SubqueryScan, FunctionScan, ValuesScan, CteScan, TableFunc
+ * and WorkTableScan nodes.
  */
 List *
 build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
@@ -1454,6 +1455,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
                        break;
 
                case RTE_FUNCTION:
+               case RTE_TABLEFUNC:
                case RTE_VALUES:
                case RTE_CTE:
                        /* Not all of these can have dropped cols, but share code anyway */
index adc1db94f4142473d931dec12715b01d257f94df..caf8291e106d4bf287709827fd6cae83eed5f822 100644 (file)
@@ -150,12 +150,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
                        break;
                case RTE_SUBQUERY:
                case RTE_FUNCTION:
+               case RTE_TABLEFUNC:
                case RTE_VALUES:
                case RTE_CTE:
 
                        /*
-                        * Subquery, function, or values list --- set up attr range and
-                        * arrays
+                        * Subquery, function, tablefunc, or values list --- set up attr
+                        * range and arrays
                         *
                         * Note: 0 is included in range to support whole-row Vars
                         */
index 796b5c9a5f951172a835c1350ea3e28185c0b0f9..3571e50aea4372875d366930440f872841295340 100644 (file)
@@ -2772,6 +2772,15 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
                                                                                LCS_asString(lc->strength)),
                                                         parser_errposition(pstate, thisrel->location)));
                                                        break;
+                                               case RTE_TABLEFUNC:
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                       /*------
+                                                         translator: %s is a SQL row locking clause such as FOR UPDATE */
+                                                       errmsg("%s cannot be applied to a table function",
+                                                                  LCS_asString(lc->strength)),
+                                                        parser_errposition(pstate, thisrel->location)));
+                                                       break;
                                                case RTE_VALUES:
                                                        ereport(ERROR,
                                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
index bb55e1c95ccad9175aa07edf547597a73f550933..e7acc2d9a2304943d83939d05d99b5dbdbf979a2 100644 (file)
@@ -464,7 +464,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <defelt> def_elem reloption_elem old_aggr_elem operator_def_elem
 %type <node>   def_arg columnElem where_clause where_or_current_clause
                                a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
-                               columnref in_expr having_clause func_table array_expr
+                               columnref in_expr having_clause func_table xmltable array_expr
                                ExclusionWhereClause
 %type <list>   rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality
@@ -550,6 +550,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>   xmlexists_argument
 %type <ival>   document_or_content
 %type <boolean> xml_whitespace_option
+%type <list>   xmltable_column_list xmltable_column_option_list
+%type <node>   xmltable_column_el
+%type <defelt> xmltable_column_option_el
+%type <list>   xml_namespace_list
+%type <target> xml_namespace_el
 
 %type <node>   func_application func_expr_common_subexpr
 %type <node>   func_expr func_expr_windowless
@@ -607,7 +612,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
        CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
        CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
-       CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
+       CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
        COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
        CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
        CROSS CSV CUBE CURRENT_P
@@ -681,8 +686,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
        WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
-       XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
-       XMLPI XMLROOT XMLSERIALIZE
+       XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
+       XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
 
        YEAR_P YES_P
 
@@ -11187,6 +11192,19 @@ table_ref:     relation_expr opt_alias_clause
                                        n->coldeflist = lsecond($3);
                                        $$ = (Node *) n;
                                }
+                       | xmltable opt_alias_clause
+                               {
+                                       RangeTableFunc *n = (RangeTableFunc *) $1;
+                                       n->alias = $2;
+                                       $$ = (Node *) n;
+                               }
+                       | LATERAL_P xmltable opt_alias_clause
+                               {
+                                       RangeTableFunc *n = (RangeTableFunc *) $2;
+                                       n->lateral = true;
+                                       n->alias = $3;
+                                       $$ = (Node *) n;
+                               }
                        | select_with_parens opt_alias_clause
                                {
                                        RangeSubselect *n = makeNode(RangeSubselect);
@@ -11626,6 +11644,166 @@ TableFuncElement:     ColId Typename opt_collate_clause
                                }
                ;
 
+/*
+ * XMLTABLE
+ */
+xmltable:
+                       XMLTABLE '(' c_expr xmlexists_argument COLUMNS xmltable_column_list ')'
+                               {
+                                       RangeTableFunc *n = makeNode(RangeTableFunc);
+                                       n->rowexpr = $3;
+                                       n->docexpr = $4;
+                                       n->columns = $6;
+                                       n->namespaces = NIL;
+                                       n->location = @1;
+                                       $$ = (Node *)n;
+                               }
+                       | XMLTABLE '(' XMLNAMESPACES '(' xml_namespace_list ')' ','
+                               c_expr xmlexists_argument COLUMNS xmltable_column_list ')'
+                               {
+                                       RangeTableFunc *n = makeNode(RangeTableFunc);
+                                       n->rowexpr = $8;
+                                       n->docexpr = $9;
+                                       n->columns = $11;
+                                       n->namespaces = $5;
+                                       n->location = @1;
+                                       $$ = (Node *)n;
+                               }
+               ;
+
+xmltable_column_list: xmltable_column_el                                       { $$ = list_make1($1); }
+                       | xmltable_column_list ',' xmltable_column_el   { $$ = lappend($1, $3); }
+               ;
+
+xmltable_column_el:
+                       ColId Typename
+                               {
+                                       RangeTableFuncCol          *fc = makeNode(RangeTableFuncCol);
+
+                                       fc->colname = $1;
+                                       fc->for_ordinality = false;
+                                       fc->typeName = $2;
+                                       fc->is_not_null = false;
+                                       fc->colexpr = NULL;
+                                       fc->coldefexpr = NULL;
+                                       fc->location = @1;
+
+                                       $$ = (Node *) fc;
+                               }
+                       | ColId Typename xmltable_column_option_list
+                               {
+                                       RangeTableFuncCol          *fc = makeNode(RangeTableFuncCol);
+                                       ListCell                   *option;
+                                       bool                            nullability_seen = false;
+
+                                       fc->colname = $1;
+                                       fc->typeName = $2;
+                                       fc->for_ordinality = false;
+                                       fc->is_not_null = false;
+                                       fc->colexpr = NULL;
+                                       fc->coldefexpr = NULL;
+                                       fc->location = @1;
+
+                                       foreach(option, $3)
+                                       {
+                                               DefElem   *defel = (DefElem *) lfirst(option);
+
+                                               if (strcmp(defel->defname, "default") == 0)
+                                               {
+                                                       if (fc->coldefexpr != NULL)
+                                                               ereport(ERROR,
+                                                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                                errmsg("only one DEFAULT value is allowed"),
+                                                                                parser_errposition(defel->location)));
+                                                       fc->coldefexpr = defel->arg;
+                                               }
+                                               else if (strcmp(defel->defname, "path") == 0)
+                                               {
+                                                       if (fc->colexpr != NULL)
+                                                               ereport(ERROR,
+                                                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                                errmsg("only one PATH value per column is allowed"),
+                                                                                parser_errposition(defel->location)));
+                                                       fc->colexpr = defel->arg;
+                                               }
+                                               else if (strcmp(defel->defname, "is_not_null") == 0)
+                                               {
+                                                       if (nullability_seen)
+                                                               ereport(ERROR,
+                                                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                                errmsg("conflicting or redundant NULL / NOT NULL declarations for column \"%s\"", fc->colname),
+                                                                                parser_errposition(defel->location)));
+                                                       fc->is_not_null = intVal(defel->arg);
+                                                       nullability_seen = true;
+                                               }
+                                               else
+                                               {
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                        errmsg("unrecognized column option \"%s\"",
+                                                                                       defel->defname),
+                                                                        parser_errposition(defel->location)));
+                                               }
+                                       }
+                                       $$ = (Node *) fc;
+                               }
+                       | ColId FOR ORDINALITY
+                               {
+                                       RangeTableFuncCol          *fc = makeNode(RangeTableFuncCol);
+
+                                       fc->colname = $1;
+                                       fc->for_ordinality = true;
+                                       /* other fields are ignored, initialized by makeNode */
+                                       fc->location = @1;
+
+                                       $$ = (Node *) fc;
+                               }
+               ;
+
+xmltable_column_option_list:
+                       xmltable_column_option_el
+                               { $$ = list_make1($1); }
+                       | xmltable_column_option_list xmltable_column_option_el
+                               { $$ = lappend($1, $2); }
+               ;
+
+xmltable_column_option_el:
+                       IDENT b_expr
+                               { $$ = makeDefElem($1, $2, @1); }
+                       | DEFAULT b_expr
+                               { $$ = makeDefElem("default", $2, @1); }
+                       | NOT NULL_P
+                               { $$ = makeDefElem("is_not_null", (Node *) makeInteger(true), @1); }
+                       | NULL_P
+                               { $$ = makeDefElem("is_not_null", (Node *) makeInteger(false), @1); }
+               ;
+
+xml_namespace_list:
+                       xml_namespace_el
+                               { $$ = list_make1($1); }
+                       | xml_namespace_list ',' xml_namespace_el
+                               { $$ = lappend($1, $3); }
+               ;
+
+xml_namespace_el:
+                       b_expr AS ColLabel
+                               {
+                                       $$ = makeNode(ResTarget);
+                                       $$->name = $3;
+                                       $$->indirection = NIL;
+                                       $$->val = $1;
+                                       $$->location = @1;
+                               }
+                       | DEFAULT b_expr
+                               {
+                                       $$ = makeNode(ResTarget);
+                                       $$->name = NULL;
+                                       $$->indirection = NIL;
+                                       $$->val = $2;
+                                       $$->location = @1;
+                               }
+               ;
+
 /*****************************************************************************
  *
  *     Type syntax
@@ -14205,6 +14383,7 @@ unreserved_keyword:
                        | CLASS
                        | CLOSE
                        | CLUSTER
+                       | COLUMNS
                        | COMMENT
                        | COMMENTS
                        | COMMIT
@@ -14510,10 +14689,12 @@ col_name_keyword:
                        | XMLELEMENT
                        | XMLEXISTS
                        | XMLFOREST
+                       | XMLNAMESPACES
                        | XMLPARSE
                        | XMLPI
                        | XMLROOT
                        | XMLSERIALIZE
+                       | XMLTABLE
                ;
 
 /* Type/function identifier --- keywords that can be type or function names.
index b5eae56006d2dec14319dae120014f0c4c22f499..47ca685b568be2a596e5766a11066678f26442c4 100644 (file)
@@ -22,6 +22,7 @@
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_constraint_fn.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
@@ -65,6 +66,8 @@ static RangeTblEntry *transformRangeSubselect(ParseState *pstate,
                                                RangeSubselect *r);
 static RangeTblEntry *transformRangeFunction(ParseState *pstate,
                                           RangeFunction *r);
+static RangeTblEntry *transformRangeTableFunc(ParseState *pstate,
+                                          RangeTableFunc *t);
 static TableSampleClause *transformRangeTableSample(ParseState *pstate,
                                                  RangeTableSample *rts);
 static Node *transformFromClauseItem(ParseState *pstate, Node *n,
@@ -692,6 +695,229 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
        return rte;
 }
 
+/*
+ * transformRangeTableFunc -
+ *                     Transform a raw RangeTableFunc into TableFunc.
+ *
+ * Transform the namespace clauses, the document-generating expression, the
+ * row-generating expression, the column-generating expressions, and the
+ * default value expressions.
+ */
+static RangeTblEntry *
+transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
+{
+       TableFunc  *tf = makeNode(TableFunc);
+       const char *constructName;
+       Oid                     docType;
+       RangeTblEntry *rte;
+       bool            is_lateral;
+       ListCell   *col;
+       char      **names;
+       int                     colno;
+
+       /* Currently only XMLTABLE is supported */
+       constructName = "XMLTABLE";
+       docType = XMLOID;
+
+       /*
+        * 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;
+
+       /* Transform and apply typecast to the row-generating expression ... */
+       Assert(rtf->rowexpr != NULL);
+       tf->rowexpr = coerce_to_specific_type(pstate,
+                                                                                 transformExpr(pstate, rtf->rowexpr, EXPR_KIND_FROM_FUNCTION),
+                                                                                 TEXTOID,
+                                                                                 constructName);
+       assign_expr_collations(pstate, tf->rowexpr);
+
+       /* ... and to the document itself */
+       Assert(rtf->docexpr != NULL);
+       tf->docexpr = coerce_to_specific_type(pstate,
+                                                                                 transformExpr(pstate, rtf->docexpr, EXPR_KIND_FROM_FUNCTION),
+                                                                                 docType,
+                                                                                 constructName);
+       assign_expr_collations(pstate, tf->docexpr);
+
+       /* undef ordinality column number */
+       tf->ordinalitycol = -1;
+
+
+       names = palloc(sizeof(char *) * list_length(rtf->columns));
+
+       colno = 0;
+       foreach(col, rtf->columns)
+       {
+               RangeTableFuncCol *rawc = (RangeTableFuncCol *) lfirst(col);
+               Oid                     typid;
+               int32           typmod;
+               Node       *colexpr;
+               Node       *coldefexpr;
+               int                     j;
+
+               tf->colnames = lappend(tf->colnames,
+                                                          makeString(pstrdup(rawc->colname)));
+
+               /*
+                * Determine the type and typmod for the new column. FOR
+                * ORDINALITY columns are INTEGER per spec; the others are
+                * user-specified.
+                */
+               if (rawc->for_ordinality)
+               {
+                       if (tf->ordinalitycol != -1)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("only one FOR ORDINALITY column is allowed"),
+                                                parser_errposition(pstate, rawc->location)));
+
+                       typid = INT4OID;
+                       typmod = -1;
+                       tf->ordinalitycol = colno;
+               }
+               else
+               {
+                       if (rawc->typeName->setof)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                                errmsg("column \"%s\" cannot be declared SETOF",
+                                                               rawc->colname),
+                                                parser_errposition(pstate, rawc->location)));
+
+                       typenameTypeIdAndMod(pstate, rawc->typeName,
+                                                                &typid, &typmod);
+               }
+
+               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);
+
+               /* Transform the PATH and DEFAULT expressions */
+               if (rawc->colexpr)
+               {
+                       colexpr = coerce_to_specific_type(pstate,
+                                                                                         transformExpr(pstate, rawc->colexpr,
+                                                                                                                       EXPR_KIND_FROM_FUNCTION),
+                                                                                         TEXTOID,
+                                                                                         constructName);
+                       assign_expr_collations(pstate, colexpr);
+               }
+               else
+                       colexpr = NULL;
+
+               if (rawc->coldefexpr)
+               {
+                       coldefexpr = coerce_to_specific_type_typmod(pstate,
+                                                                                                               transformExpr(pstate, rawc->coldefexpr,
+                                                                                                                                         EXPR_KIND_FROM_FUNCTION),
+                                                                                                               typid, typmod,
+                                                                                                               constructName);
+                       assign_expr_collations(pstate, coldefexpr);
+               }
+               else
+                       coldefexpr = NULL;
+
+               tf->colexprs = lappend(tf->colexprs, colexpr);
+               tf->coldefexprs = lappend(tf->coldefexprs, coldefexpr);
+
+               if (rawc->is_not_null)
+                       tf->notnulls = bms_add_member(tf->notnulls, colno);
+
+               /* make sure column names are unique */
+               for (j = 0; j < colno; j++)
+                       if (strcmp(names[j], rawc->colname) == 0)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("column name \"%s\" is not unique",
+                                                               rawc->colname),
+                                                parser_errposition(pstate, rawc->location)));
+               names[colno] = rawc->colname;
+
+               colno++;
+       }
+       pfree(names);
+
+       /* Namespaces, if any, also need to be transformed */
+       if (rtf->namespaces != NIL)
+       {
+               ListCell   *ns;
+               ListCell   *lc2;
+               List       *ns_uris = NIL;
+               List       *ns_names = NIL;
+               bool            default_ns_seen = false;
+
+               foreach(ns, rtf->namespaces)
+               {
+                       ResTarget  *r = (ResTarget *) lfirst(ns);
+                       Node       *ns_uri;
+
+                       Assert(IsA(r, ResTarget));
+                       ns_uri = transformExpr(pstate, r->val, EXPR_KIND_FROM_FUNCTION);
+                       ns_uri = coerce_to_specific_type(pstate, ns_uri,
+                                                                                        TEXTOID, constructName);
+                       assign_expr_collations(pstate, ns_uri);
+                       ns_uris = lappend(ns_uris, ns_uri);
+
+                       /* Verify consistency of name list: no dupes, only one DEFAULT */
+                       if (r->name != NULL)
+                       {
+                               foreach(lc2, ns_names)
+                               {
+                                       char       *name = strVal(lfirst(lc2));
+
+                                       if (name == NULL)
+                                               continue;
+                                       if (strcmp(name, r->name) == 0)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                errmsg("namespace name \"%s\" is not unique",
+                                                                               name),
+                                                                parser_errposition(pstate, r->location)));
+                               }
+                       }
+                       else
+                       {
+                               if (default_ns_seen)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("only one default namespace is allowed"),
+                                                        parser_errposition(pstate, r->location)));
+                               default_ns_seen = true;
+                       }
+
+                       /* Note the string may be NULL */
+                       ns_names = lappend(ns_names, makeString(r->name));
+               }
+
+               tf->ns_uris = ns_uris;
+               tf->ns_names = ns_names;
+       }
+
+       tf->location = rtf->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 = rtf->lateral || contain_vars_of_level((Node *) tf, 0);
+
+       rte = addRangeTableEntryForTableFunc(pstate,
+                                                                                tf, rtf->alias, is_lateral, true);
+
+       return rte;
+}
+
 /*
  * transformRangeTableSample --- transform a TABLESAMPLE clause
  *
@@ -795,7 +1021,6 @@ transformRangeTableSample(ParseState *pstate, RangeTableSample *rts)
        return tablesample;
 }
 
-
 /*
  * transformFromClauseItem -
  *       Transform a FROM-clause item, adding any required entries to the
@@ -891,6 +1116,24 @@ transformFromClauseItem(ParseState *pstate, Node *n,
                rtr->rtindex = rtindex;
                return (Node *) rtr;
        }
+       else if (IsA(n, RangeTableFunc))
+       {
+               /* table function is like a plain relation */
+               RangeTblRef *rtr;
+               RangeTblEntry *rte;
+               int                     rtindex;
+
+               rte = transformRangeTableFunc(pstate, (RangeTableFunc *) n);
+               /* assume new rte is at end */
+               rtindex = list_length(pstate->p_rtable);
+               Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
+               *top_rte = rte;
+               *top_rti = rtindex;
+               *namespace = list_make1(makeDefaultNSItem(rte));
+               rtr = makeNode(RangeTblRef);
+               rtr->rtindex = rtindex;
+               return (Node *) rtr;
+       }
        else if (IsA(n, RangeTableSample))
        {
                /* TABLESAMPLE clause (wrapping some other valid FROM node) */
index 2a2ac32157314e62e5e86bbf19e08e6755422c96..2c3f3cd9ce7ca5a3d678b5571ee126ffef812458 100644 (file)
@@ -1096,9 +1096,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *             Coerce an argument of a construct that requires a specific data type.
- *             Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *             Coerce an argument of a construct that requires a specific data type,
+ *             with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1106,9 +1106,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-                                               Oid targetTypeId,
-                                               const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+                                                          Oid targetTypeId, int32 targetTypmod,
+                                                          const char *constructName)
 {
        Oid                     inputTypeId = exprType(node);
 
@@ -1117,7 +1117,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
                Node       *newnode;
 
                newnode = coerce_to_target_type(pstate, node, inputTypeId,
-                                                                               targetTypeId, -1,
+                                                                               targetTypeId, targetTypmod,
                                                                                COERCION_ASSIGNMENT,
                                                                                COERCE_IMPLICIT_CAST,
                                                                                -1);
@@ -1144,6 +1144,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
        return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *             Coerce an argument of a construct that requires a specific data type.
+ *             Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+                                               Oid targetTypeId,
+                                               const char *constructName)
+{
+       return coerce_to_specific_type_typmod(pstate, node,
+                                                                                 targetTypeId, -1,
+                                                                                 constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
index cf69533b53d230ee0277e906a0f17389b17bd2ed..2eea258d28d4218249264485084508b8af0c51a0 100644 (file)
@@ -1627,6 +1627,69 @@ addRangeTableEntryForFunction(ParseState *pstate,
        return rte;
 }
 
+/*
+ * Add an entry for a table function to the pstate's range table (p_rtable).
+ *
+ * This is much like addRangeTableEntry() except that it makes a tablefunc RTE.
+ */
+RangeTblEntry *
+addRangeTableEntryForTableFunc(ParseState *pstate,
+                                                          TableFunc *tf,
+                                                          Alias *alias,
+                                                          bool lateral,
+                                                          bool inFromCl)
+{
+       RangeTblEntry *rte = makeNode(RangeTblEntry);
+       char       *refname = alias ? alias->aliasname : pstrdup("xmltable");
+       Alias      *eref;
+       int                     numaliases;
+
+       Assert(pstate != NULL);
+
+       rte->rtekind = RTE_TABLEFUNC;
+       rte->relid = InvalidOid;
+       rte->subquery = NULL;
+       rte->tablefunc = tf;
+       rte->coltypes = tf->coltypes;
+       rte->coltypmods = tf->coltypmods;
+       rte->colcollations = tf->colcollations;
+       rte->alias = alias;
+
+       eref = alias ? copyObject(alias) : makeAlias(refname, NIL);
+       numaliases = list_length(eref->colnames);
+
+       /* fill in any unspecified alias columns */
+       if (numaliases < list_length(tf->colnames))
+               eref->colnames = list_concat(eref->colnames,
+                                                                  list_copy_tail(tf->colnames, numaliases));
+
+       rte->eref = eref;
+
+       /*
+        * Set flags and access permissions.
+        *
+        * Tablefuncs are never checked for access rights (at least, not by the
+        * RTE permissions mechanism).
+        */
+       rte->lateral = lateral;
+       rte->inh = false;                       /* never true for tablefunc RTEs */
+       rte->inFromCl = inFromCl;
+
+       rte->requiredPerms = 0;
+       rte->checkAsUser = InvalidOid;
+       rte->selectedCols = NULL;
+       rte->insertedCols = NULL;
+       rte->updatedCols = NULL;
+
+       /*
+        * Add completed RTE to pstate's range table list, but not to join list
+        * nor namespace --- caller must do that if appropriate.
+        */
+       pstate->p_rtable = lappend(pstate->p_rtable, rte);
+
+       return rte;
+}
+
 /*
  * Add an entry for a VALUES list to the pstate's range table (p_rtable).
  *
@@ -2226,10 +2289,11 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
                                }
                        }
                        break;
+               case RTE_TABLEFUNC:
                case RTE_VALUES:
                case RTE_CTE:
                        {
-                               /* Values or CTE RTE */
+                               /* Tablefunc, Values or CTE RTE */
                                ListCell   *aliasp_item = list_head(rte->eref->colnames);
                                ListCell   *lct;
                                ListCell   *lcm;
@@ -2638,10 +2702,14 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
                                *varcollid = exprCollation(aliasvar);
                        }
                        break;
+               case RTE_TABLEFUNC:
                case RTE_VALUES:
                case RTE_CTE:
                        {
-                               /* VALUES or CTE RTE --- get type info from lists in the RTE */
+                               /*
+                                * tablefunc, VALUES or CTE RTE --- get type info from lists
+                                * in the RTE
+                                */
                                Assert(attnum > 0 && attnum <= list_length(rte->coltypes));
                                *vartype = list_nth_oid(rte->coltypes, attnum - 1);
                                *vartypmod = list_nth_int(rte->coltypmods, attnum - 1);
@@ -2684,9 +2752,14 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
                        }
                        break;
                case RTE_SUBQUERY:
+               case RTE_TABLEFUNC:
                case RTE_VALUES:
                case RTE_CTE:
-                       /* Subselect, Values, CTE RTEs never have dropped columns */
+
+                       /*
+                        * Subselect, Table Functions, Values, CTE RTEs never have dropped
+                        * columns
+                        */
                        result = false;
                        break;
                case RTE_JOIN:
index 2576e312390b65272dc673ed62a322f24624b7ba..3b84140a9bef6d504a369687a35b286209dbaf18 100644 (file)
@@ -396,6 +396,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
                        break;
                case RTE_FUNCTION:
                case RTE_VALUES:
+               case RTE_TABLEFUNC:
                        /* not a simple relation, leave it unmarked */
                        break;
                case RTE_CTE:
@@ -1557,6 +1558,12 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
                         * its result columns as RECORD, which is not allowed.
                         */
                        break;
+               case RTE_TABLEFUNC:
+
+                       /*
+                        * Table function cannot have columns with RECORD type.
+                        */
+                       break;
                case RTE_CTE:
                        /* CTE reference: examine subquery's output expr */
                        if (!rte->self_reference)
index 20bbb3748459bb2442569d4a89c7eca500651666..354e5d0462005cd5e0d70bcd08f63e51d0ea3c6b 100644 (file)
@@ -433,6 +433,10 @@ rewriteRuleAction(Query *parsetree,
                                        sub_action->hasSubLinks =
                                                checkExprHasSubLink((Node *) rte->functions);
                                        break;
+                               case RTE_TABLEFUNC:
+                                       sub_action->hasSubLinks =
+                                               checkExprHasSubLink((Node *) rte->tablefunc);
+                                       break;
                                case RTE_VALUES:
                                        sub_action->hasSubLinks =
                                                checkExprHasSubLink((Node *) rte->values_lists);
index cf79f4126e45a7fa0b4c629ae5f1fcc3e20a7e64..3c697f3c0dfa527cf22e828d5b14a4d447507a29 100644 (file)
@@ -429,6 +429,8 @@ static void get_const_expr(Const *constval, deparse_context *context,
 static void get_const_collation(Const *constval, deparse_context *context);
 static void simple_quote_literal(StringInfo buf, const char *val);
 static void get_sublink_expr(SubLink *sublink, deparse_context *context);
+static void get_tablefunc(TableFunc *tf, deparse_context *context,
+                         bool showimplicit);
 static void get_from_clause(Query *query, const char *prefix,
                                deparse_context *context);
 static void get_from_clause_item(Node *jtnode, Query *query,
@@ -3642,14 +3644,17 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
         * are different from the underlying "real" names.  For a function RTE,
         * always emit a complete column alias list; this is to protect against
         * possible instability of the default column names (eg, from altering
-        * parameter names).  For other RTE types, print if we changed anything OR
-        * if there were user-written column aliases (since the latter would be
-        * part of the underlying "reality").
+        * parameter names).  For tablefunc RTEs, we never print aliases, because
+        * the column names are part of the clause itself.  For other RTE types,
+        * print if we changed anything OR if there were user-written column
+        * aliases (since the latter would be part of the underlying "reality").
         */
        if (rte->rtekind == RTE_RELATION)
                colinfo->printaliases = changed_any;
        else if (rte->rtekind == RTE_FUNCTION)
                colinfo->printaliases = true;
+       else if (rte->rtekind == RTE_TABLEFUNC)
+               colinfo->printaliases = false;
        else if (rte->alias && rte->alias->colnames != NIL)
                colinfo->printaliases = true;
        else
@@ -6726,6 +6731,7 @@ get_name_for_var_field(Var *var, int fieldno,
                        /* else fall through to inspect the expression */
                        break;
                case RTE_FUNCTION:
+               case RTE_TABLEFUNC:
 
                        /*
                         * We couldn't get here unless a function is declared with one of
@@ -8562,6 +8568,10 @@ get_rule_expr(Node *node, deparse_context *context,
                        }
                        break;
 
+               case T_TableFunc:
+                       get_tablefunc((TableFunc *) node, context, showimplicit);
+                       break;
+
                default:
                        elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
                        break;
@@ -9297,6 +9307,120 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
 }
 
 
+/* ----------
+ * get_tablefunc                       - Parse back a table function
+ * ----------
+ */
+static void
+get_tablefunc(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)
+       {
+               ListCell   *lc1,
+                                  *lc2;
+               bool            first = true;
+
+               appendStringInfoString(buf, "XMLNAMESPACES (");
+               forboth(lc1, tf->ns_uris, lc2, tf->ns_names)
+               {
+                       Node       *expr = (Node *) lfirst(lc1);
+                       char       *name = strVal(lfirst(lc2));
+
+                       if (!first)
+                               appendStringInfoString(buf, ", ");
+                       else
+                               first = false;
+
+                       if (name != NULL)
+                       {
+                               get_rule_expr(expr, context, showimplicit);
+                               appendStringInfo(buf, " AS %s", name);
+                       }
+                       else
+                       {
+                               appendStringInfoString(buf, "DEFAULT ");
+                               get_rule_expr(expr, context, showimplicit);
+                       }
+               }
+               appendStringInfoString(buf, "), ");
+       }
+
+       appendStringInfoChar(buf, '(');
+       get_rule_expr((Node *) tf->rowexpr, context, showimplicit);
+       appendStringInfoString(buf, ") PASSING (");
+       get_rule_expr((Node *) tf->docexpr, context, showimplicit);
+       appendStringInfoChar(buf, ')');
+
+       if (tf->colexprs != NIL)
+       {
+               ListCell   *l1;
+               ListCell   *l2;
+               ListCell   *l3;
+               ListCell   *l4;
+               ListCell   *l5;
+               int                     colnum = 0;
+
+               l2 = list_head(tf->coltypes);
+               l3 = list_head(tf->coltypmods);
+               l4 = list_head(tf->colexprs);
+               l5 = list_head(tf->coldefexprs);
+
+               appendStringInfoString(buf, " COLUMNS ");
+               foreach(l1, tf->colnames)
+               {
+                       char       *colname = strVal(lfirst(l1));
+                       Oid                     typid;
+                       int32           typmod;
+                       Node       *colexpr;
+                       Node       *coldefexpr;
+                       bool            ordinality = tf->ordinalitycol == colnum;
+                       bool            notnull = bms_is_member(colnum, tf->notnulls);
+
+                       typid = lfirst_oid(l2);
+                       l2 = lnext(l2);
+                       typmod = lfirst_int(l3);
+                       l3 = lnext(l3);
+                       colexpr = (Node *) lfirst(l4);
+                       l4 = lnext(l4);
+                       coldefexpr = (Node *) lfirst(l5);
+                       l5 = lnext(l5);
+
+                       if (colnum > 0)
+                               appendStringInfoString(buf, ", ");
+                       colnum++;
+
+                       appendStringInfo(buf, "%s %s", quote_identifier(colname),
+                                                        ordinality ? "FOR ORDINALITY" :
+                                                        format_type_with_typemod(typid, typmod));
+                       if (ordinality)
+                               continue;
+
+                       if (coldefexpr != NULL)
+                       {
+                               appendStringInfoString(buf, " DEFAULT (");
+                               get_rule_expr((Node *) coldefexpr, context, showimplicit);
+                               appendStringInfoChar(buf, ')');
+                       }
+                       if (colexpr != NULL)
+                       {
+                               appendStringInfoString(buf, " PATH (");
+                               get_rule_expr((Node *) colexpr, context, showimplicit);
+                               appendStringInfoChar(buf, ')');
+                       }
+                       if (notnull)
+                               appendStringInfoString(buf, " NOT NULL");
+               }
+       }
+
+       appendStringInfoChar(buf, ')');
+}
+
 /* ----------
  * get_from_clause                     - Parse back a FROM clause
  *
@@ -9530,6 +9654,9 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
                                if (rte->funcordinality)
                                        appendStringInfoString(buf, " WITH ORDINALITY");
                                break;
+                       case RTE_TABLEFUNC:
+                               get_tablefunc(rte->tablefunc, context, true);
+                               break;
                        case RTE_VALUES:
                                /* Values list RTE */
                                appendStringInfoChar(buf, '(');
index e8bce3b806d127b19c9b0be06da7d9764cdda900..f2e5224fc3f18eabad53553d78f18e8760acac99 100644 (file)
@@ -73,6 +73,7 @@
 #include "commands/dbcommands.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "executor/tablefunc.h"
 #include "fmgr.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
@@ -145,6 +146,7 @@ static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
 static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
                                           ArrayBuildState *astate,
                                           PgXmlErrorContext *xmlerrcxt);
+static xmlChar *pg_xmlCharStrndup(char *str, size_t len);
 #endif   /* USE_LIBXML */
 
 static StringInfo query_to_xml_internal(const char *query, char *tablename,
@@ -165,6 +167,49 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
                                                  char *tablename, bool nulls, bool tableforest,
                                                  const char *targetns, bool top_level);
 
+/* XMLTABLE support */
+#ifdef USE_LIBXML
+/* random number to identify XmlTableContext */
+#define XMLTABLE_CONTEXT_MAGIC 46922182
+typedef struct XmlTableBuilderData
+{
+       int                     magic;
+       int                     natts;
+       long int        row_count;
+       PgXmlErrorContext *xmlerrcxt;
+       xmlParserCtxtPtr ctxt;
+       xmlDocPtr       doc;
+       xmlXPathContextPtr xpathcxt;
+       xmlXPathCompExprPtr xpathcomp;
+       xmlXPathObjectPtr xpathobj;
+       xmlXPathCompExprPtr *xpathscomp;
+} XmlTableBuilderData;
+#endif
+
+static void XmlTableInitOpaque(struct TableFuncScanState *state, int natts);
+static void XmlTableSetDocument(struct TableFuncScanState *state, Datum value);
+static void XmlTableSetNamespace(struct TableFuncScanState *state, char *name,
+                                        char *uri);
+static void XmlTableSetRowFilter(struct TableFuncScanState *state, char *path);
+static void XmlTableSetColumnFilter(struct TableFuncScanState *state,
+                                               char *path, int colnum);
+static bool XmlTableFetchRow(struct TableFuncScanState *state);
+static Datum XmlTableGetValue(struct TableFuncScanState *state, int colnum,
+                                Oid typid, int32 typmod, bool *isnull);
+static void XmlTableDestroyOpaque(struct TableFuncScanState *state);
+
+const TableFuncRoutine XmlTableRoutine =
+{
+       XmlTableInitOpaque,
+       XmlTableSetDocument,
+       XmlTableSetNamespace,
+       XmlTableSetRowFilter,
+       XmlTableSetColumnFilter,
+       XmlTableFetchRow,
+       XmlTableGetValue,
+       XmlTableDestroyOpaque
+};
+
 #define NO_XML_SUPPORT() \
        ereport(ERROR, \
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -1113,6 +1158,19 @@ xml_pnstrdup(const xmlChar *str, size_t len)
        return result;
 }
 
+/* Ditto, except input is char* */
+static xmlChar *
+pg_xmlCharStrndup(char *str, size_t len)
+{
+       xmlChar    *result;
+
+       result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+       memcpy(result, str, len);
+       result[len] = '\0';
+
+       return result;
+}
+
 /*
  * str is the null-terminated input string.  Remaining arguments are
  * output arguments; each can be NULL if value is not wanted.
@@ -3811,13 +3869,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
                                (errcode(ERRCODE_DATA_EXCEPTION),
                                 errmsg("empty XPath expression")));
 
-       string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
-       memcpy(string, datastr, len);
-       string[len] = '\0';
-
-       xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
-       memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
-       xpath_expr[xpath_len] = '\0';
+       string = pg_xmlCharStrndup(datastr, len);
+       xpath_expr = pg_xmlCharStrndup(VARDATA(xpath_expr_text), xpath_len);
 
        xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
 
@@ -4065,3 +4118,494 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
        return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Returns private data from executor state. Ensure validity by check with
+ * MAGIC number.
+ */
+static inline XmlTableBuilderData *
+GetXmlTableBuilderPrivateData(TableFuncScanState *state, const char *fname)
+{
+       XmlTableBuilderData *result;
+
+       if (!IsA(state, TableFuncScanState))
+               elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+       result = (XmlTableBuilderData *) state->opaque;
+       if (result->magic != XMLTABLE_CONTEXT_MAGIC)
+               elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+
+       return result;
+}
+#endif
+
+/*
+ * XmlTableInitOpaque
+ *             Fill in TableFuncScanState->opaque for XmlTable processor; initialize
+ *             the XML parser.
+ *
+ * Note: Because we call pg_xml_init() here and pg_xml_done() in
+ * XmlTableDestroyOpaque, it is critical for robustness that no other
+ * executor nodes run until this node is processed to completion.  Caller
+ * must execute this to completion (probably filling a tuplestore to exhaust
+ * this node in a single pass) instead of using row-per-call mode.
+ */
+static void
+XmlTableInitOpaque(TableFuncScanState *state, int natts)
+{
+#ifdef USE_LIBXML
+       volatile xmlParserCtxtPtr ctxt = NULL;
+       XmlTableBuilderData *xtCxt;
+       PgXmlErrorContext *xmlerrcxt;
+
+       xtCxt = palloc0(sizeof(XmlTableBuilderData));
+       xtCxt->magic = XMLTABLE_CONTEXT_MAGIC;
+       xtCxt->natts = natts;
+       xtCxt->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * natts);
+
+       xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+       PG_TRY();
+       {
+               xmlInitParser();
+
+               ctxt = xmlNewParserCtxt();
+               if (ctxt == NULL || xmlerrcxt->err_occurred)
+                       xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+                                               "could not allocate parser context");
+       }
+       PG_CATCH();
+       {
+               if (ctxt != NULL)
+                       xmlFreeParserCtxt(ctxt);
+
+               pg_xml_done(xmlerrcxt, true);
+
+               PG_RE_THROW();
+       }
+       PG_END_TRY();
+
+       xtCxt->xmlerrcxt = xmlerrcxt;
+       xtCxt->ctxt = ctxt;
+
+       state->opaque = xtCxt;
+#else
+       NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetDocument
+ *             Install the input document
+ */
+static void
+XmlTableSetDocument(TableFuncScanState *state, Datum value)
+{
+#ifdef USE_LIBXML
+       XmlTableBuilderData *xtCxt;
+       xmltype    *xmlval = DatumGetXmlP(value);
+       char       *str;
+       xmlChar    *xstr;
+       int                     length;
+       volatile xmlDocPtr doc = NULL;
+       volatile xmlXPathContextPtr xpathcxt = NULL;
+
+       xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetDocument");
+
+       /*
+        * Use out function for casting to string (remove encoding property). See
+        * comment in xml_out.
+        */
+       str = xml_out_internal(xmlval, 0);
+
+       length = strlen(str);
+       xstr = pg_xmlCharStrndup(str, length);
+
+       PG_TRY();
+       {
+               doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL, 0);
+               if (doc == NULL || xtCxt->xmlerrcxt->err_occurred)
+                       xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+                                               "could not parse XML document");
+               xpathcxt = xmlXPathNewContext(doc);
+               if (xpathcxt == NULL || xtCxt->xmlerrcxt->err_occurred)
+                       xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+                                               "could not allocate XPath context");
+               xpathcxt->node = xmlDocGetRootElement(doc);
+               if (xpathcxt->node == NULL || xtCxt->xmlerrcxt->err_occurred)
+                       xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+                                               "could not find root XML element");
+       }
+       PG_CATCH();
+       {
+               if (xpathcxt != NULL)
+                       xmlXPathFreeContext(xpathcxt);
+               if (doc != NULL)
+                       xmlFreeDoc(doc);
+
+               PG_RE_THROW();
+       }
+       PG_END_TRY();
+
+       xtCxt->doc = doc;
+       xtCxt->xpathcxt = xpathcxt;
+#else
+       NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetNamespace
+ *             Add a namespace declaration
+ */
+static void
+XmlTableSetNamespace(TableFuncScanState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+       XmlTableBuilderData *xtCxt;
+
+       if (name == NULL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("DEFAULT namespace is not supported")));
+       xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetNamespace");
+
+       if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+                                                  pg_xmlCharStrndup(name, strlen(name)),
+                                                  pg_xmlCharStrndup(uri, strlen(uri))))
+               xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+                                       "could not set XML namespace");
+#else
+       NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowFilter
+ *             Install the row-filter Xpath expression.
+ */
+static void
+XmlTableSetRowFilter(TableFuncScanState *state, char *path)
+{
+#ifdef USE_LIBXML
+       XmlTableBuilderData *xtCxt;
+       xmlChar    *xstr;
+
+       xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetRowFilter");
+
+       if (*path == '\0')
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATA_EXCEPTION),
+                                errmsg("row path filter must not be empty string")));
+
+       xstr = pg_xmlCharStrndup(path, strlen(path));
+
+       xtCxt->xpathcomp = xmlXPathCompile(xstr);
+       if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+               xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_SYNTAX_ERROR,
+                                       "invalid XPath expression");
+#else
+       NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetColumnFilter
+ *             Install the column-filter Xpath expression, for the given column.
+ */
+static void
+XmlTableSetColumnFilter(TableFuncScanState *state, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+       XmlTableBuilderData *xtCxt;
+       xmlChar    *xstr;
+
+       AssertArg(PointerIsValid(path));
+
+       xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetColumnFilter");
+
+       if (*path == '\0')
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATA_EXCEPTION),
+                                errmsg("column path filter must not be empty string")));
+
+       xstr = pg_xmlCharStrndup(path, strlen(path));
+
+       xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+       if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+               xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+                                       "invalid XPath expression");
+#else
+       NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableFetchRow
+ *             Prepare the next "current" tuple for upcoming GetValue calls.
+ *             Returns FALSE if the row-filter expression returned no more rows.
+ */
+static bool
+XmlTableFetchRow(TableFuncScanState *state)
+{
+#ifdef USE_LIBXML
+       XmlTableBuilderData *xtCxt;
+
+       xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableFetchRow");
+
+       /*
+        * XmlTable returns table - set of composite values. The error context, is
+        * used for producement more values, between two calls, there can be
+        * created and used another libxml2 error context. It is libxml2 global
+        * value, so it should be refreshed any time before any libxml2 usage,
+        * that is finished by returning some value.
+        */
+       xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+       if (xtCxt->xpathobj == NULL)
+       {
+               xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+               if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+                       xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+                                               "could not create XPath object");
+
+               xtCxt->row_count = 0;
+       }
+
+       if (xtCxt->xpathobj->type == XPATH_NODESET)
+       {
+               if (xtCxt->xpathobj->nodesetval != NULL)
+               {
+                       if (xtCxt->row_count++ < xtCxt->xpathobj->nodesetval->nodeNr)
+                               return true;
+               }
+       }
+
+       return false;
+#else
+       NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+       return false;
+}
+
+/*
+ * XmlTableGetValue
+ *             Return the value for column number 'colnum' for the current row.  If
+ *             column -1 is requested, return representation of the whole row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
+ */
+static Datum
+XmlTableGetValue(TableFuncScanState *state, int colnum,
+                                Oid typid, int32 typmod, bool *isnull)
+{
+#ifdef USE_LIBXML
+       XmlTableBuilderData *xtCxt;
+       Datum           result = (Datum) 0;
+       xmlNodePtr      cur;
+       char       *cstr = NULL;
+       volatile xmlXPathObjectPtr xpathobj = NULL;
+
+       xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
+
+       Assert(xtCxt->xpathobj &&
+                  xtCxt->xpathobj->type == XPATH_NODESET &&
+                  xtCxt->xpathobj->nodesetval != NULL);
+
+       /* Propagate context related error context to libxml2 */
+       xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+       *isnull = false;
+
+       cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
+
+       Assert(xtCxt->xpathscomp[colnum] != NULL);
+
+       PG_TRY();
+       {
+               /* Set current node as entry point for XPath evaluation */
+               xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+               /* Evaluate column path */
+               xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+               if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+                       xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+                                               "could not create XPath object");
+
+               /*
+                * There are four possible cases, depending on the number of nodes
+                * returned by the XPath expression and the type of the target column:
+                * a) XPath returns no nodes.  b) One node is returned, and column is
+                * of type XML.  c) One node, column type other than XML.  d) Multiple
+                * nodes are returned.
+                */
+               if (xpathobj->type == XPATH_NODESET)
+               {
+                       int                     count = 0;
+
+                       if (xpathobj->nodesetval != NULL)
+                               count = xpathobj->nodesetval->nodeNr;
+
+                       if (xpathobj->nodesetval == NULL || count == 0)
+                       {
+                               *isnull = true;
+                       }
+                       else if (count == 1 && typid == XMLOID)
+                       {
+                               text       *textstr;
+
+                               /* simple case, result is one value */
+                               textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
+                                                                                          xtCxt->xmlerrcxt);
+                               cstr = text_to_cstring(textstr);
+                       }
+                       else if (count == 1)
+                       {
+                               xmlChar    *str;
+
+                               str = xmlNodeListGetString(xtCxt->doc,
+                                                  xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+                                                                                  1);
+
+                               if (str != NULL)
+                               {
+                                       PG_TRY();
+                                       {
+                                               cstr = pstrdup((char *) str);
+                                       }
+                                       PG_CATCH();
+                                       {
+                                               xmlFree(str);
+                                               PG_RE_THROW();
+                                       }
+                                       PG_END_TRY();
+                                       xmlFree(str);
+                               }
+                               else
+                               {
+                                       /*
+                                        * This line ensure mapping of empty tags to PostgreSQL
+                                        * value. Usually we would to map a empty tag to empty
+                                        * string. But this mapping can create empty string when
+                                        * user doesn't expect it - when empty tag is enforced
+                                        * by libxml2 - when user uses a text() function for
+                                        * example.
+                                        */
+                                       cstr = "";
+                               }
+                       }
+                       else
+                       {
+                               StringInfoData str;
+                               int                     i;
+
+                               Assert(count > 1);
+
+                               /*
+                                * When evaluating the XPath expression returns multiple
+                                * nodes, the result is the concatenation of them all. The
+                                * target type must be XML.
+                                */
+                               if (typid != XMLOID)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_CARDINALITY_VIOLATION),
+                                                        errmsg("more than one value returned by column XPath expression")));
+
+                               /* Concatenate serialized values */
+                               initStringInfo(&str);
+                               for (i = 0; i < count; i++)
+                               {
+                                       appendStringInfoText(&str,
+                                          xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
+                                                                                       xtCxt->xmlerrcxt));
+                               }
+                               cstr = str.data;
+                       }
+               }
+               else if (xpathobj->type == XPATH_STRING)
+               {
+                       cstr = (char *) xpathobj->stringval;
+               }
+               else
+                       elog(ERROR, "unexpected XPath object type %u", xpathobj->type);
+
+               /*
+                * By here, either cstr contains the result value, or the isnull flag
+                * has been set.
+                */
+               Assert(cstr || *isnull);
+
+               if (!*isnull)
+                       result = InputFunctionCall(&state->in_functions[colnum],
+                                                                          cstr,
+                                                                          state->typioparams[colnum],
+                                                                          typmod);
+       }
+       PG_CATCH();
+       {
+               if (xpathobj != NULL)
+                       xmlXPathFreeObject(xpathobj);
+               PG_RE_THROW();
+       }
+       PG_END_TRY();
+
+       xmlXPathFreeObject(xpathobj);
+
+       return result;
+#else
+       NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableDestroyOpaque
+ *             Release all libxml2 resources
+ */
+static void
+XmlTableDestroyOpaque(TableFuncScanState *state)
+{
+#ifdef USE_LIBXML
+       XmlTableBuilderData *xtCxt;
+
+       xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableDestroyOpaque");
+
+       /* Propagate context related error context to libxml2 */
+       xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+       if (xtCxt->xpathscomp != NULL)
+       {
+               int                     i;
+
+               for (i = 0; i < xtCxt->natts; i++)
+                       if (xtCxt->xpathscomp[i] != NULL)
+                               xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+       }
+
+       if (xtCxt->xpathobj != NULL)
+               xmlXPathFreeObject(xtCxt->xpathobj);
+       if (xtCxt->xpathcomp != NULL)
+               xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+       if (xtCxt->xpathcxt != NULL)
+               xmlXPathFreeContext(xtCxt->xpathcxt);
+       if (xtCxt->doc != NULL)
+               xmlFreeDoc(xtCxt->doc);
+       if (xtCxt->ctxt != NULL)
+               xmlFreeParserCtxt(xtCxt->ctxt);
+
+       pg_xml_done(xtCxt->xmlerrcxt, true);
+
+       /* not valid anymore */
+       xtCxt->magic = 0;
+       state->opaque = NULL;
+
+#else
+       NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
index 8d98195576834a9b483351b28900df561bf8634a..4c05b30068b295a1fb9abe93f76472412f1feb98 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201703061
+#define CATALOG_VERSION_NO     201703081
 
 #endif
diff --git a/src/include/executor/nodeTableFuncscan.h b/src/include/executor/nodeTableFuncscan.h
new file mode 100644 (file)
index 0000000..529c929
--- /dev/null
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeTableFuncscan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeTableFuncscan.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODETABLEFUNCSCAN_H
+#define NODETABLEFUNCSCAN_H
+
+#include "nodes/execnodes.h"
+
+extern TableFuncScanState *ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecTableFuncScan(TableFuncScanState *node);
+extern void ExecEndTableFuncScan(TableFuncScanState *node);
+extern void ExecReScanTableFuncScan(TableFuncScanState *node);
+
+#endif   /* NODETABLEFUNCSCAN_H */
diff --git a/src/include/executor/tablefunc.h b/src/include/executor/tablefunc.h
new file mode 100644 (file)
index 0000000..89d6381
--- /dev/null
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * tablefunc.h
+ *                             interface for TableFunc executor node
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/tablefunc.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _TABLEFUNC_H
+#define _TABLEFUNC_H
+
+/* Forward-declare this to avoid including execnodes.h here */
+struct TableFuncScanState;
+
+/*
+ * TableFuncRoutine holds function pointers used for generating content of
+ * table-producer functions, such as XMLTABLE.
+ *
+ * InitBuilder initialize table builder private objects.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed
+ * from executor state.
+ *
+ * SetDoc is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * SetNamespace is called to pass namespace declarations from the table
+ * expression.  This function may be NULL if namespaces are not supported by
+ * the table builder.  Namespaces must be given before setting the row and
+ * column filters.  If the name is given as NULL, the entry shall be for the
+ * default namespace.
+ *
+ * SetRowFilter is called do define the row-generating filter, which shall be
+ * used to extract each row from the input document.
+ *
+ * SetColumnFilter is called once for each column, to define the column-
+ * generating filter for the given column.
+ *
+ * FetchRow shall be called repeatedly until it returns that no more rows are
+ * found in the document.  On each invocation it shall set state in the table
+ * builder context such that each subsequent GetValue call returns the values
+ * for the indicated column for the row being processed.
+ *
+ * DestroyBuilder shall release all resources associated with a table builder
+ * context.  It may be called either because all rows have been consumed, or
+ * because an error ocurred while processing the table expression.
+ */
+typedef struct TableFuncRoutine
+{
+       void            (*InitOpaque) (struct TableFuncScanState *state, int natts);
+       void            (*SetDocument) (struct TableFuncScanState *state, Datum value);
+       void            (*SetNamespace) (struct TableFuncScanState *state, char *name,
+                                                                                        char *uri);
+       void            (*SetRowFilter) (struct TableFuncScanState *state, char *path);
+       void            (*SetColumnFilter) (struct TableFuncScanState *state,
+                                                                       char *path, int colnum);
+       bool            (*FetchRow) (struct TableFuncScanState *state);
+       Datum           (*GetValue) (struct TableFuncScanState *state, int colnum,
+                                                        Oid typid, int32 typmod, bool *isnull);
+       void            (*DestroyOpaque) (struct TableFuncScanState *state);
+} TableFuncRoutine;
+
+#endif   /* _TABLEFUNC_H */
index 6332ea0620c928c32414f7f487fc90a6dbf6d51b..2fde67a9c8d9fd605b7b237af9e8abe1a63e365a 100644 (file)
@@ -1584,6 +1584,31 @@ typedef struct ValuesScanState
        int                     curr_idx;
 } ValuesScanState;
 
+/* ----------------
+ *             TableFuncScanState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableFuncScanState
+{
+       ScanState       ss;                             /* its first field is NodeTag */
+       ExprState  *docexpr;            /* state for document expression */
+       ExprState  *rowexpr;            /* state for row-generating expression */
+       List       *colexprs;           /* state for column-generating expression */
+       List       *coldefexprs;        /* state for column default expressions */
+       List       *ns_names;           /* list of str nodes with namespace names */
+       List       *ns_uris;            /* list of states of namespace uri exprs */
+       Bitmapset  *notnulls;           /* nullability flag for each output column */
+       void       *opaque;                     /* table builder private space */
+       const struct TableFuncRoutine *routine;         /* table builder methods */
+       FmgrInfo   *in_functions;       /* input function for each column */
+       Oid                *typioparams;        /* typioparam for each column */
+       int64           ordinal;                /* row number to be output next */
+       MemoryContext perValueCxt;      /* short life context for value evaluation */
+       Tuplestorestate *tupstore;      /* output tuple store */
+} TableFuncScanState;
+
 /* ----------------
  *      CteScanState information
  *
index ede7ace76b44fec49af1f3a21b17b9fdbb218d8f..49fa9447556436dfc2868999d528d5fb2d46dd38 100644 (file)
@@ -61,6 +61,7 @@ typedef enum NodeTag
        T_SubqueryScan,
        T_FunctionScan,
        T_ValuesScan,
+       T_TableFuncScan,
        T_CteScan,
        T_WorkTableScan,
        T_ForeignScan,
@@ -109,6 +110,7 @@ typedef enum NodeTag
        T_TidScanState,
        T_SubqueryScanState,
        T_FunctionScanState,
+       T_TableFuncScanState,
        T_ValuesScanState,
        T_CteScanState,
        T_WorkTableScanState,
@@ -135,6 +137,7 @@ typedef enum NodeTag
         */
        T_Alias,
        T_RangeVar,
+       T_TableFunc,
        T_Expr,
        T_Var,
        T_Const,
@@ -439,6 +442,8 @@ typedef enum NodeTag
        T_RangeSubselect,
        T_RangeFunction,
        T_RangeTableSample,
+       T_RangeTableFunc,
+       T_RangeTableFuncCol,
        T_TypeName,
        T_ColumnDef,
        T_IndexElem,
index 956f99830ca0d7e2e4730ed372d0b5756e6c6cb3..a44d2178e1cbda9e05dab4fb4144435cc019f064 100644 (file)
@@ -554,6 +554,39 @@ typedef struct RangeFunction
                                                                 * of function returning RECORD */
 } RangeFunction;
 
+/*
+ * RangeTableFunc - raw form of "table functions" such as XMLTABLE
+ */
+typedef struct RangeTableFunc
+{
+       NodeTag         type;
+       bool            lateral;                /* does it have LATERAL prefix? */
+       Node       *docexpr;            /* document expression */
+       Node       *rowexpr;            /* row generator expression */
+       List       *namespaces;         /* list of namespaces as ResTarget */
+       List       *columns;            /* list of RangeTableFuncCol */
+       Alias      *alias;                      /* table alias & optional column aliases */
+       int                     location;               /* token location, or -1 if unknown */
+} RangeTableFunc;
+
+/*
+ * RangeTableFuncCol - one column in a RangeTableFunc->columns
+ *
+ * If for_ordinality is true (FOR ORDINALITY), then the column is an int4
+ * column and the rest of the fields are ignored.
+ */
+typedef struct RangeTableFuncCol
+{
+       NodeTag         type;
+       char       *colname;            /* name of generated column */
+       TypeName   *typeName;           /* type of generated column */
+       bool            for_ordinality; /* does it have FOR ORDINALITY? */
+       bool            is_not_null;    /* does it have NOT NULL? */
+       Node       *colexpr;            /* column filter expression */
+       Node       *coldefexpr;         /* column default value expression */
+       int                     location;               /* token location, or -1 if unknown */
+} RangeTableFuncCol;
+
 /*
  * RangeTableSample - TABLESAMPLE appearing in a raw FROM clause
  *
@@ -871,6 +904,7 @@ typedef enum RTEKind
        RTE_SUBQUERY,                           /* subquery in FROM */
        RTE_JOIN,                                       /* join */
        RTE_FUNCTION,                           /* function in FROM */
+       RTE_TABLEFUNC,                          /* TableFunc(.., column list) */
        RTE_VALUES,                                     /* VALUES (<exprlist>), (<exprlist>), ... */
        RTE_CTE                                         /* common table expr (WITH list element) */
 } RTEKind;
@@ -931,6 +965,11 @@ typedef struct RangeTblEntry
        List       *functions;          /* list of RangeTblFunction nodes */
        bool            funcordinality; /* is this called WITH ORDINALITY? */
 
+       /*
+        * Fields valid for a TableFunc RTE (else NULL):
+        */
+       TableFunc  *tablefunc;
+
        /*
         * Fields valid for a values RTE (else NIL):
         */
index f72f7a8978a5327aaae5a4231f761e61bf4c14a8..e30ed6aa29b54ce5caae32d26aec7eaeddbc1b84 100644 (file)
@@ -495,6 +495,16 @@ typedef struct ValuesScan
        List       *values_lists;       /* list of expression lists */
 } ValuesScan;
 
+/* ----------------
+ *             TableFunc scan node
+ * ----------------
+ */
+typedef struct TableFuncScan
+{
+       Scan            scan;
+       TableFunc  *tablefunc;          /* table function node */
+} TableFuncScan;
+
 /* ----------------
  *             CteScan node
  * ----------------
index 235bc7509662ed255a8c15e154252e2925560e45..d57b4fab3d9b3042820dd10da4bd2bc6081b2088 100644 (file)
@@ -18,6 +18,7 @@
 #define PRIMNODES_H
 
 #include "access/attnum.h"
+#include "nodes/bitmapset.h"
 #include "nodes/pg_list.h"
 
 
@@ -72,6 +73,27 @@ typedef struct RangeVar
        int                     location;               /* token location, or -1 if unknown */
 } RangeVar;
 
+/*
+ * TableFunc - node for a table function, such as XMLTABLE.
+ */
+typedef struct TableFunc
+{
+       NodeTag         type;
+       List       *ns_uris;            /* list of namespace uri */
+       List       *ns_names;           /* list of namespace names */
+       Node       *docexpr;            /* input document expression */
+       Node       *rowexpr;            /* row filter expression */
+       List       *colnames;           /* column names (list of String) */
+       List       *coltypes;           /* OID list of column type OIDs */
+       List       *coltypmods;         /* integer list of column typmods */
+       List       *colcollations;      /* OID list of column collation OIDs */
+       List       *colexprs;           /* list of column filter expressions */
+       List       *coldefexprs;        /* list of column default expressions */
+       Bitmapset  *notnulls;           /* nullability flag for each output column */
+       int                     ordinalitycol;  /* counts from 0; -1 if none specified */
+       int                     location;               /* token location, or -1 if unknown */
+} TableFunc;
+
 /*
  * IntoClause - target information for SELECT INTO, CREATE TABLE AS, and
  * CREATE MATERIALIZED VIEW
index 72200fa5310e1205445387f7739520f3820971dc..2b386835e3728810925f9a56249a39e3f4920a63 100644 (file)
@@ -89,8 +89,12 @@ extern void cost_subqueryscan(SubqueryScanPath *path, PlannerInfo *root,
                                  RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_functionscan(Path *path, PlannerInfo *root,
                                  RelOptInfo *baserel, ParamPathInfo *param_info);
+extern void cost_tableexprscan(Path *path, PlannerInfo *root,
+                                  RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_valuesscan(Path *path, PlannerInfo *root,
                                RelOptInfo *baserel, ParamPathInfo *param_info);
+extern void cost_tablefuncscan(Path *path, PlannerInfo *root,
+                                  RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_ctescan(Path *path, PlannerInfo *root,
                         RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_recursive_union(Path *runion, Path *nrterm, Path *rterm);
@@ -181,6 +185,7 @@ extern void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel,
                                           double cte_rows);
+extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
 extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
index 53cad247dc4707a42ced7aa5d23860c3ad57b9a1..befe5781416f1a71eea32b5d8befca6aca3f4737 100644 (file)
@@ -82,8 +82,12 @@ extern SubqueryScanPath *create_subqueryscan_path(PlannerInfo *root,
                                                 List *pathkeys, Relids required_outer);
 extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
                                                 List *pathkeys, Relids required_outer);
+extern Path *create_tablexprscan_path(PlannerInfo *root, RelOptInfo *rel,
+                                                List *pathkeys, Relids required_outer);
 extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel,
                                           Relids required_outer);
+extern Path *create_tablefuncscan_path(PlannerInfo *root, RelOptInfo *rel,
+                                                 Relids required_outer);
 extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel,
                                        Relids required_outer);
 extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel,
index 985d6505eceb885f66c01e78e2a4d3ff1fb47137..28c4dab258624f46120d7f0736b4854e83118648 100644 (file)
@@ -82,6 +82,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -446,10 +447,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
index b50992cfd90acbfc5366f1b22b1a28516ad9e7b5..3eed81966dcc34ae0fc25b3d75c88b69d649a486 100644 (file)
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
                                                Oid targetTypeId,
                                                const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+                                                          Oid targetTypeId, int32 targetTypmod,
+                                                          const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
                                                        int coerce_location,
                                                        Node *input_expr);
index cfb2e5b88c0bd02e3fc88d1bd0a16a86a8903821..515c06cfef694400459b5f7b07e01089d32166ec 100644 (file)
@@ -91,6 +91,11 @@ extern RangeTblEntry *addRangeTableEntryForValues(ParseState *pstate,
                                                        Alias *alias,
                                                        bool lateral,
                                                        bool inFromCl);
+extern RangeTblEntry *addRangeTableEntryForTableFunc(ParseState *pstate,
+                                                          TableFunc *tf,
+                                                          Alias *alias,
+                                                          bool lateral,
+                                                          bool inFromCl);
 extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,
                                                  List *colnames,
                                                  JoinType jointype,
index cc1fc390e803069f6830bc28f7d1ef1c390cbbe0..e570b71c04f9da23fb96e9dbf8b016155fccb9d8 100644 (file)
@@ -18,6 +18,7 @@
 #include "fmgr.h"
 #include "nodes/execnodes.h"
 #include "nodes/primnodes.h"
+#include "executor/tablefunc.h"
 
 typedef struct varlena xmltype;
 
@@ -76,4 +77,6 @@ extern int    xmlbinary;                      /* XmlBinaryType, but int for guc enum */
 
 extern int     xmloption;                      /* XmlOptionType, but int for guc enum */
 
+extern const TableFuncRoutine XmlTableRoutine;
+
 #endif   /* XML_H */
index f21e119f1e6468547dcca733ca1a15d22eb34efb..bcc585d427eab1934b4a3193861866ce7cd202fc 100644 (file)
@@ -948,3 +948,507 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>
 (1 row)
 
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+  <COUNTRY_ID>AU</COUNTRY_ID>
+  <COUNTRY_NAME>Australia</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+  <COUNTRY_ID>CN</COUNTRY_ID>
+  <COUNTRY_NAME>China</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+  <COUNTRY_ID>HK</COUNTRY_ID>
+  <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+  <COUNTRY_ID>IN</COUNTRY_ID>
+  <COUNTRY_NAME>India</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+  <COUNTRY_ID>JP</COUNTRY_ID>
+  <COUNTRY_NAME>Japan</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+  <COUNTRY_ID>SG</COUNTRY_ID>
+  <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+-- XMLTABLE with columns
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+CREATE VIEW xmltableview1 AS SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+    "xmltable"._id,
+    "xmltable".country_name,
+    "xmltable".country_id,
+    "xmltable".region_id,
+    "xmltable".size,
+    "xmltable".unit,
+    "xmltable".premier_name
+   FROM ( SELECT xmldata.data
+           FROM xmldata) x,
+    LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+               QUERY PLAN                
+-----------------------------------------
+ Nested Loop
+   ->  Seq Scan on xmldata
+   ->  Table Function Scan on "xmltable"
+(3 rows)
+
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ a  
+----
+ 10
+(1 row)
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+SELECT * FROM xmltableview2;
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  DEFAULT namespace is not supported
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+ ent 
+-----
+ '
+ "
+ &
+ <
+ >
+(5 rows)
+
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+       ent        
+------------------
+ <ent>'</ent>
+ <ent>"</ent>
+ <ent>&amp;</ent>
+ <ent>&lt;</ent>
+ <ent>&gt;</ent>
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ Japan        |         3
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+                                                                                    QUERY PLAN                                                                                    
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+         Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+         Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia      | AU         |         3 |      |      | not specified
+  2 |   2 | China          | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong       | HK         |         3 |      |      | not specified
+  4 |   4 | India          | IN         |         3 |      |      | not specified
+  5 |   5 | Japan          | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore      | SG         |         3 |  791 | km   | not specified
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+ 20 |   1 | Egypt          | EG         |         1 |      |      | not specified
+ 21 |   2 | Sudan          | SD         |         1 |      |      | not specified
+(11 rows)
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+         Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ERROR:  null is not allowed in column "size"
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a  
+----
+ 11
+ 12
+ 13
+ 14
+(4 rows)
+
index d7027030c368e92ba716ae3e2cc27e041477ca22..d3bd8c91d78a6a052c197ed7d8ae7fbed39deea9 100644 (file)
@@ -827,3 +827,478 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
 ERROR:  unsupported XML feature
 DETAIL:  This functionality requires the server to be built with libxml support.
 HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+  <COUNTRY_ID>AU</COUNTRY_ID>
+  <COUNTRY_NAME>Australia</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+  <COUNTRY_ID>CN</COUNTRY_ID>
+  <COUNTRY_NAME>China</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+  <COUNTRY_ID>HK</COUNTRY_ID>
+  <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+  <COUNTRY_ID>IN</COUNTRY_ID>
+  <COUNTRY_NAME>India</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+  <COUNTRY_ID>JP</COUNTRY_ID>
+  <COUNTRY_NAME>Japan</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+  <COUNTRY_ID>SG</COUNTRY_ID>
+  <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmldata VALUES('<ROWS>
+                                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- XMLTABLE with columns
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+CREATE VIEW xmltableview1 AS SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+    "xmltable"._id,
+    "xmltable".country_name,
+    "xmltable".country_id,
+    "xmltable".region_id,
+    "xmltable".size,
+    "xmltable".unit,
+    "xmltable".premier_name
+   FROM ( SELECT xmldata.data
+           FROM xmldata) x,
+    LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+               QUERY PLAN                
+-----------------------------------------
+ Nested Loop
+   ->  Seq Scan on xmldata
+   ->  Table Function Scan on "xmltable"
+(3 rows)
+
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltableview2;
+ERROR:  relation "xmltableview2" does not exist
+LINE 1: SELECT * FROM xmltableview2;
+                      ^
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID | rawdata 
+----+--------------+-----------+---------
+(0 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID | rawdata 
+----+--------------+-----------+---------
+(0 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+ERROR:  unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/root' passing '<root><element>a1a<!...
+                                               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/root' passing '<root><element>a1a<!...
+                                               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+ERROR:  unsupported XML feature
+LINE 1: select * from xmltable('r' passing '<d><r><c><![CDATA[<hello...
+                                           ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+ERROR:  unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</en...
+                                              ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+ERROR:  unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</en...
+                                              ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+(0 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+                                                                                    QUERY PLAN                                                                                    
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+         Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+         Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmldata VALUES('<ROWS>
+                                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmldata VALUES('<ROWS>
+                                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+         Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a 
+---
+(0 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a 
+---
+(0 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a 
+---
+(0 rows)
+
index 530faf5dafbfbbdac6ed0adea1992173849d5011..ff77132803654eb8af02fed849ead873d7444863 100644 (file)
@@ -928,3 +928,507 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>
 (1 row)
 
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+  <COUNTRY_ID>AU</COUNTRY_ID>
+  <COUNTRY_NAME>Australia</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+  <COUNTRY_ID>CN</COUNTRY_ID>
+  <COUNTRY_NAME>China</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+  <COUNTRY_ID>HK</COUNTRY_ID>
+  <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+  <COUNTRY_ID>IN</COUNTRY_ID>
+  <COUNTRY_NAME>India</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+  <COUNTRY_ID>JP</COUNTRY_ID>
+  <COUNTRY_NAME>Japan</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+  <COUNTRY_ID>SG</COUNTRY_ID>
+  <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+-- XMLTABLE with columns
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+CREATE VIEW xmltableview1 AS SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+    "xmltable"._id,
+    "xmltable".country_name,
+    "xmltable".country_id,
+    "xmltable".region_id,
+    "xmltable".size,
+    "xmltable".unit,
+    "xmltable".premier_name
+   FROM ( SELECT xmldata.data
+           FROM xmldata) x,
+    LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+               QUERY PLAN                
+-----------------------------------------
+ Nested Loop
+   ->  Seq Scan on xmldata
+   ->  Table Function Scan on "xmltable"
+(3 rows)
+
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ a  
+----
+ 10
+(1 row)
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+SELECT * FROM xmltableview2;
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  DEFAULT namespace is not supported
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+ ent 
+-----
+ '
+ "
+ &
+ <
+ >
+(5 rows)
+
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+       ent        
+------------------
+ <ent>'</ent>
+ <ent>"</ent>
+ <ent>&amp;</ent>
+ <ent>&lt;</ent>
+ <ent>&gt;</ent>
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ Japan        |         3
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+                                                                                    QUERY PLAN                                                                                    
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+         Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+         Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia      | AU         |         3 |      |      | not specified
+  2 |   2 | China          | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong       | HK         |         3 |      |      | not specified
+  4 |   4 | India          | IN         |         3 |      |      | not specified
+  5 |   5 | Japan          | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore      | SG         |         3 |  791 | km   | not specified
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+ 20 |   1 | Egypt          | EG         |         1 |      |      | not specified
+ 21 |   2 | Sudan          | SD         |         1 |      |      | not specified
+(11 rows)
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  Table Function Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+         Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ERROR:  null is not allowed in column "size"
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a  
+----
+ 11
+ 12
+ 13
+ 14
+(4 rows)
+
index 08a0b30067a88702ef770adcf741e832874515f2..eb4687fb09124729c5bccf5bd603efd54fab3941 100644 (file)
@@ -270,3 +270,291 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/passwd">]><foo>
 SELECT XMLPARSE(DOCUMENT '<!DOCTYPE foo [<!ENTITY c SYSTEM "/etc/no.such.file">]><foo>&c;</foo>');
 -- This might or might not load the requested DTD, but it mustn't throw error.
 SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>');
+
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+  <COUNTRY_ID>AU</COUNTRY_ID>
+  <COUNTRY_NAME>Australia</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+  <COUNTRY_ID>CN</COUNTRY_ID>
+  <COUNTRY_NAME>China</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+  <COUNTRY_ID>HK</COUNTRY_ID>
+  <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+  <COUNTRY_ID>IN</COUNTRY_ID>
+  <COUNTRY_NAME>India</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+  <COUNTRY_ID>JP</COUNTRY_ID>
+  <COUNTRY_NAME>Japan</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+  <COUNTRY_ID>SG</COUNTRY_ID>
+  <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+
+-- XMLTABLE with columns
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+CREATE VIEW xmltableview1 AS SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+SELECT * FROM xmltableview1;
+
+\sv xmltableview1
+
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1;
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+
+SELECT * FROM xmltableview2;
+
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+EXECUTE pp;
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                case when proargtypes <> '' then array_to_string(proargtypes::oid[],',') end as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+
+CREATE TABLE xmltest2(x xml, _path text);
+
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);