xmltable-15.patch
text/x-patch
Patch
Format: unified
| File | + | − |
|---|---|---|
| doc/src/sgml/func.sgml | 327 | 49 |
| src/backend/executor/execQual.c | 341 | 1 |
| src/backend/executor/execTuples.c | 42 | 0 |
| src/backend/nodes/copyfuncs.c | 66 | 0 |
| src/backend/nodes/equalfuncs.c | 51 | 0 |
| src/backend/nodes/nodeFuncs.c | 101 | 0 |
| src/backend/nodes/outfuncs.c | 51 | 0 |
| src/backend/nodes/readfuncs.c | 42 | 0 |
| src/backend/optimizer/util/clauses.c | 33 | 0 |
| src/backend/parser/gram.y | 178 | 6 |
| src/backend/parser/parse_coerce.c | 26 | 7 |
| src/backend/parser/parse_expr.c | 163 | 0 |
| src/backend/parser/parse_target.c | 7 | 0 |
| src/backend/utils/adt/ruleutils.c | 100 | 0 |
| src/backend/utils/adt/xml.c | 591 | 0 |
| src/backend/utils/fmgr/funcapi.c | 13 | 0 |
| src/include/executor/executor.h | 1 | 0 |
| src/include/funcapi.h | 0 | 1 |
| src/include/nodes/execnodes.h | 31 | 0 |
| src/include/nodes/nodes.h | 4 | 0 |
| src/include/nodes/parsenodes.h | 21 | 0 |
| src/include/nodes/primnodes.h | 40 | 0 |
| src/include/parser/kwlist.h | 3 | 0 |
| src/include/parser/parse_coerce.h | 4 | 0 |
| src/include/utils/xml.h | 2 | 0 |
| src/test/regress/expected/xml_1.out | 321 | 0 |
| src/test/regress/expected/xml_2.out | 412 | 0 |
| src/test/regress/expected/xml.out | 413 | 0 |
| src/test/regress/sql/xml.sql | 170 | 0 |
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2e64cc4..5bb1027 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10324,50 +10324,53 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
<sect2 id="functions-xml-processing">
<title>Processing XML</title>
- <indexterm>
- <primary>XPath</primary>
- </indexterm>
-
<para>
To process values of data type <type>xml</type>, PostgreSQL offers
- the functions <function>xpath</function> and
+ the functions <function>xpath</function>,
<function>xpath_exists</function>, which evaluate XPath 1.0
- expressions.
+ expressions, and the <function>XMLTABLE</function> predicate.
</para>
+ <sect3 id="functions-xml-processing-xpath">
+ <title><literal>xpath</literal></title>
+
+ <indexterm>
+ <primary>XPath</primary>
+ </indexterm>
+
<synopsis>
<function>xpath</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
</synopsis>
- <para>
- The function <function>xpath</function> evaluates the XPath
- expression <replaceable>xpath</replaceable> (a <type>text</> value)
- against the XML value
- <replaceable>xml</replaceable>. It returns an array of XML values
- corresponding to the node set produced by the XPath expression.
- If the XPath expression returns a scalar value rather than a node set,
- a single-element array is returned.
- </para>
+ <para>
+ The function <function>xpath</function> evaluates the XPath
+ expression <replaceable>xpath</replaceable> (a <type>text</> value)
+ against the XML value
+ <replaceable>xml</replaceable>. It returns an array of XML values
+ corresponding to the node set produced by the XPath expression.
+ If the XPath expression returns a scalar value rather than a node set,
+ a single-element array is returned.
+ </para>
- <para>
- The second argument must be a well formed XML document. In particular,
- it must have a single root node element.
- </para>
+ <para>
+ The second argument must be a well formed XML document. In particular,
+ it must have a single root node element.
+ </para>
- <para>
- The optional third argument of the function is an array of namespace
- mappings. This array should be a two-dimensional <type>text</> array with
- the length of the second axis being equal to 2 (i.e., it should be an
- array of arrays, each of which consists of exactly 2 elements).
- The first element of each array entry is the namespace name (alias), the
- second the namespace URI. It is not required that aliases provided in
- this array be the same as those being used in the XML document itself (in
- other words, both in the XML document and in the <function>xpath</function>
- function context, aliases are <emphasis>local</>).
- </para>
+ <para>
+ The optional third argument of the function is an array of namespace
+ mappings. This array should be a two-dimensional <type>text</> array with
+ the length of the second axis being equal to 2 (i.e., it should be an
+ array of arrays, each of which consists of exactly 2 elements).
+ The first element of each array entry is the namespace name (alias), the
+ second the namespace URI. It is not required that aliases provided in
+ this array be the same as those being used in the XML document itself (in
+ other words, both in the XML document and in the <function>xpath</function>
+ function context, aliases are <emphasis>local</>).
+ </para>
- <para>
- Example:
+ <para>
+ Example:
<screen><![CDATA[
SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10377,10 +10380,10 @@ SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
{test}
(1 row)
]]></screen>
- </para>
+ </para>
- <para>
- To deal with default (anonymous) namespaces, do something like this:
+ <para>
+ To deal with default (anonymous) namespaces, do something like this:
<screen><![CDATA[
SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
ARRAY[ARRAY['mydefns', 'http://example.com']]);
@@ -10390,27 +10393,31 @@ SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a
{test}
(1 row)
]]></screen>
- </para>
+ </para>
+ </sect3>
- <indexterm>
- <primary>xpath_exists</primary>
- </indexterm>
+ <sect3 id="functions-xml-processing-xpath-exists">
+ <title><literal>xpath_exists</literal></title>
+
+ <indexterm>
+ <primary>xpath_exists</primary>
+ </indexterm>
<synopsis>
<function>xpath_exists</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
</synopsis>
- <para>
- The function <function>xpath_exists</function> is a specialized form
- of the <function>xpath</function> function. Instead of returning the
- individual XML values that satisfy the XPath, this function returns a
- Boolean indicating whether the query was satisfied or not. This
- function is equivalent to the standard <literal>XMLEXISTS</> predicate,
- except that it also offers support for a namespace mapping argument.
- </para>
+ <para>
+ The function <function>xpath_exists</function> is a specialized form
+ of the <function>xpath</function> function. Instead of returning the
+ individual XML values that satisfy the XPath, this function returns a
+ Boolean indicating whether the query was satisfied or not. This
+ function is equivalent to the standard <literal>XMLEXISTS</> predicate,
+ except that it also offers support for a namespace mapping argument.
+ </para>
- <para>
- Example:
+ <para>
+ Example:
<screen><![CDATA[
SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10420,7 +10427,278 @@ 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>
+
+<synopsis>
+<function>xmltable</function>(
+ <optional>xmlnamespaces(<replaceable>namespace uri</replaceable> AS <replaceable>namespace name</replaceable><optional>, ...</optional>)</optional>
+ <replaceable>rowexpr</replaceable> PASSING <optional>BY REF</optional> <replaceable>xml</replaceable> <optional>BY REF</optional>
+ <optional>COLUMNS <replaceable>name</replaceable> <replaceable>type</replaceable>
+ <optional>PATH <replaceable>columnexpr</replaceable></optional>
+ <optional>DEFAULT <replaceable>expr</replaceable></optional>
+ <optional>NOT NULL | NULL</optional> <optional>, ...</optional></optional>
+)
+</synopsis>
+
+ <para>
+ The <function>xmltable</function> produces a table based on the
+ passed XML value, an xpath filter to extract rows, and an
+ optional set of column definitions.
+ </para>
+
+ <para>
+ For XML document:
+ <screen><![CDATA[
+<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>
+]]></screen>
+
+ the following query produces the result:
+
+<screen><![CDATA[
+SELECT xmltable.*
+ FROM (SELECT data FROM xmldata) x,
+ LATERAL xmltable('//ROWS/ROW'
+ PASSING data
+ COLUMNS id int PATH '@id',
+ country_name text PATH 'COUNTRY_NAME',
+ country_id text PATH 'COUNTRY_ID',
+ region_id int PATH 'REGION_ID',
+ size float PATH 'SIZE[@unit = "km"]/text()',
+ unit text PATH 'SIZE/@unit',
+ premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+ id | country_name | country_id | region_id | size | unit | premier_name
+----+--------------+------------+-----------+------+------+---------------
+ 1 | Australia | AU | 3 | | | not specified
+ 2 | China | CN | 3 | | | not specified
+ 3 | HongKong | HK | 3 | | | not specified
+ 4 | India | IN | 3 | | | not specified
+ 5 | Japan | JP | 3 | | | Sinzo Abe
+ 6 | Singapore | SG | 3 | 791 | km | not specified
+]]></screen>
+ </para>
+
+ <para>
+ The optional <literal>xmlnamespaces</> clause is a comma-separated list of
+ namespaces of the form <replaceable>namespace-URI</> <literal>AS</>
+ <replaceable>namespace-name</>. It specifies the XML namespaces used in
+ the document and their aliases. The default namespace is not supported
+ yet.
+
+<screen><![CDATA[
+SELECT *
+ FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS ns)
+ '/ns:rows/ns:row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+ COLUMNS a int PATH 'a');
+
+ a
+----
+ 10
+]]></screen>
</para>
+
+ <para>
+ The required <replaceable>rowexpr</> 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>
+
+ <note>
+ <para>
+ The SQL/XML standard specifies the the
+ <function>xmltable</function> construct should take an XQuery
+ expression as the first argument, but PostgreSQL currently only
+ supports XPath, which is a subset of XQuery.
+ </para>
+ </note>
+
+ <para>
+ The <literal>PASSING</> <replaceable>xml</> clause 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. Per the SQL/XML standard, the first
+ <literal>BY REF</literal> is required, the second is optional.
+ Passing <literal>BY VALUE</> is not supported. Multiple
+ comma-separated terms in the PASSING clause are not supported.
+ <literal>AS</> aliases are not supported. The argument must be
+ a well-formed XML document; fragments/forests are not accepted.
+ </para>
+
+ <para>
+ The optional <literal>COLUMNS</literal> clause specifies the list
+ of colums in the generated table. If the whole
+ <literal>COLUMNS</> list is omitted, then the result set is a
+ single column where each row is a field of <literal>xml</> type
+ containing the data matched by the <replaceable>rowexpr</>.
+ Otherwise each entry describes a single column. See the syntax
+ summary above for the format. The column name and type are
+ required; the path and not-null specifier are optional.
+ <screen><![CDATA[
+SELECT *
+ FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y' AS ns)
+ '/ns:rows/ns:row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>');
+
+ xmltable
+------------------------------
+ <a xmlns="http://x.y">10</a>
+]]></screen>
+ </para>
+
+ <note>
+ <para>
+ Column names and types are identifiers so unlike normal
+ function arguments they may not be specified as bind parameters,
+ column references, or expressions. The path and default are
+ ordinary values.
+ </para>
+ </note>
+
+ <para>
+ The <literal>PATH</> for a column is an xpath expression that is
+ evaluated for each row, relative to the result of the
+ <replaceable>rowexpr</>, to find the value of the column.
+ If no <literal>PATH</> is given then the column name is used as an
+ implicit path.
+ </para>
+
+ <para>
+ The text body of the XML matched by the <literal>PATH</> 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:
+ <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--x--> bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+element
+-----------------
+ a1aa2a bbbbcccc
+ (1 row)
+]]></programlisting>
+ 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>
+ A <literal>PATH</> expression that matches multiple top-level elements
+ results in an error:
+ <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>1</element><element>2</element></root>' COLUMNS element text);
+ERROR: more than one value returned by column XPath expression
+]]></programlisting>
+ This applies to <literal>text()</> nodes too, so avoid using explicit text nodes
+ in your path expressions unless this behaviour is desired:
+ <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a</element></root>' COLUMNS element text PATH 'element/text()');
+ERROR: more than one value returned by column XPath expression
+]]></programlisting>
+ If the <literal>PATH</> matches an empty tag the result is an empty string
+ (not <literal>NULL</>). Any <literal>xsi:nil</> attributes are ignored.
+ </para>
+
+ <para>
+ The five predefined XML entities <literal>'apos;</>,
+ <literal>'quot;</>, <literal>'amp;</>, <literal>'lt;</>
+ and <literal>'gt;</> are expanded to their corresponding characters
+ <literal>'</>, <literal>"</>, <literal>&</>, <literal><</>
+ and <literal>></> on processing. If the output column format is
+ <type>xml</> then the characters <literal><</>, <literal>></> and
+ <literal>&</> are converted to XML entities, but <literal>"</> and
+ <literal>'</> are left unchanged. This means that <literal>'apos;</>
+ and <literal>'quot;</> entities in XML input get expanded in xml
+ output columns. There is no way to prevent their expansion.
+ </para>
+
+ <para>
+ If the path expression does not match for a given row but a
+ <literal>DEFAULT</> expression is specified, the resulting
+ default value is used. If no <literal>DEFAULT</> is given the
+ field will be <literal>NULL</>. Unlike normal queries, it is possible for
+ a <literal>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 xpath expression
+ for a <literal>NOT NULL</> column does not match anything and there
+ is no <literal>DEFAULT</> or the <literal>DEFAULT</> also evaluated
+ to null then the function terminates with an ERROR.
+ </para>
+
+ <important>
+ <para>
+ Unlike normal PostgreSQL functions, the <literal>PATH</> and
+ <literal>DEFAULT</> arguments to <function>xmltable</> are not evaluated
+ to a simple value before calling the function. <literal>PATH</>
+ expressions are normally evaluated <emphasis>exactly once per input row
+ </emphasis>, and <literal>DEFAULT</> expressions 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
+ <literal>DEFAULT</> expressions, your <literal>PATH</> expressions may
+ depend on other parts of the XML document, etc.
+ </para>
+ </important>
+
+ <para>
+ A column marked with the
+ <literal>FOR ORDINALITY</literal> keyword will be populated with
+ row numbers that match the order in which the the output rows appeared
+ in the original input XML document. Only one column should be
+ marked <literal>FOR ORDINALITY</literal>.
+ </para>
+
+ <para>
+ Only XPath query language is supported. PostgreSQL doesn't support XQuery
+ language. Then the syntax of <function>xmltable</function> doesn't
+ allow to use XQuery related functionality - the name of xml expression
+ (clause <literal>AS</literal>) is not allowed, and only one xml expression
+ should be passed to <function>xmltable</function> function as parameter.
+ </para>
+ </sect3>
</sect2>
<sect2 id="functions-xml-mapping">
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 743e7d6..3bedd8d 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -43,6 +43,7 @@
#include "catalog/pg_type.h"
#include "executor/execdebug.h"
#include "executor/nodeSubplan.h"
+#include "executor/tableexpr.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -189,7 +190,12 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
-
+static Datum ExecEvalTableExpr(TableExprState * tstate,
+ ExprContext *econtext,
+ bool *isnull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExprProtected(TableExprState * tstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
/* ----------------------------------------------------------------
* ExecEvalExpr routines
@@ -4500,6 +4506,236 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
return 0; /* keep compiler quiet */
}
+/* ----------------------------------------------------------------
+ * ExecEvalTableExpr
+ *
+ * This is envelop of ExecEvalTableExprProtected() function.
+ *
+ * This function ensures releasing all TableBuilder context and related
+ * memory context, when ExecEvalTableExprProtected fails on exception.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState * tstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ /* Ensure releasing context every exception */
+ Datum result;
+
+ PG_TRY();
+ {
+ result = ExecEvalTableExprProtected(tstate, econtext, isNull, isDone);
+ }
+ PG_CATCH();
+ {
+ if (tstate->builderCxt != NULL)
+ {
+ tstate->builder->DestroyContext(tstate->builderCxt);
+ tstate->builderCxt = NULL;
+ }
+
+ MemoryContextDelete(tstate->per_rowset_memory);
+ tstate->per_rowset_memory = NULL;
+
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return result;
+}
+
+/*
+ *ExecEvalTableExprProtected
+ *
+ * Evaluate a TableExpr node. This function may leak libxml memory, so it
+ * needs to be protected from exceptions.
+ *
+ * This function creates a TableExprBuilder object and applies all necessary
+ * settings; one tuple is returned per call.
+ *
+ * Is possible to go over this
+ * cycle more times per query - when LATERAL JOIN is used. On the end
+ * of cycle, the TableExprBuilder object is destroyed, state pointer
+ * to this object is cleaned, and related memory context is resetted.
+ * New call starts new cycle.
+ */
+static Datum
+ExecEvalTableExprProtected(TableExprState * tstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ TupleDesc tupdesc;
+ Datum result;
+ int i;
+ Datum value;
+ bool isnull;
+ const TableExprBuilder *builder;
+ void *builderCxt;
+
+ tupdesc = tstate->tupdesc;
+ builder = tstate->builder;
+ builderCxt = tstate->builderCxt;
+
+ /*
+ * First time around? Set up our calling context and evaluate the
+ * document expression.
+ */
+ if (builderCxt == NULL)
+ {
+ ListCell *ns;
+
+ builderCxt = builder->CreateContext(tupdesc,
+ tstate->in_functions,
+ tstate->typioparams,
+ tstate->per_rowset_memory);
+ tstate->builderCxt = builderCxt;
+
+ /* Evaluate document expression first */
+ value = ExecEvalExpr(tstate->doc_expr, econtext, &isnull, NULL);
+ if (isnull)
+ {
+ *isDone = ExprSingleResult;
+ *isNull = true;
+ return (Datum) 0;
+ }
+
+ /*
+ * The content can be bigger document and transformation to cstring
+ * can be expensive. The table builder is better place for this task -
+ * pass value as Datum.
+ */
+ builder->SetContent(builderCxt, value);
+
+ /* Evaluate namespace specifications */
+ foreach(ns, tstate->namespaces)
+ {
+ Node *n = (Node *) lfirst(ns);
+ ExprState *expr;
+ char *ns_name;
+ char *ns_uri;
+
+ if (IsA(n, NamedArgExpr))
+ {
+ NamedArgExpr *na = (NamedArgExpr *) n;
+
+ expr = (ExprState *) na->arg;
+ ns_name = na->name;
+ }
+ else
+ {
+ expr = (ExprState *) n;
+ ns_name = NULL;
+ }
+
+ value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("namespace uri must not be null")));
+ ns_uri = TextDatumGetCString(value);
+
+ builder->AddNS(builderCxt, ns_name, ns_uri);
+ }
+
+ /* Evaluate row path filter */
+ value = ExecEvalExpr(tstate->row_expr, econtext, &isnull, NULL);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("row filter must not be null")));
+ builder->SetRowFilter(builderCxt, TextDatumGetCString(value));
+
+ /* Evaluate column paths */
+ for (i = 0; i < tstate->ncols; i++)
+ {
+ char *col_filter;
+
+ if (tstate->col_expr[i] != NULL)
+ {
+ value = ExecEvalExpr(tstate->col_expr[i], econtext, &isnull, NULL);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("column filter for column \"%s\" must not be null",
+ NameStr(tupdesc->attrs[i]->attname))));
+ col_filter = TextDatumGetCString(value);
+ }
+ else
+ col_filter = NameStr(tupdesc->attrs[i]->attname);
+
+ builder->SetColumnFilter(builderCxt, col_filter, i);
+ }
+ }
+
+ /* Now we can prepare result */
+ if (builder->FetchRow(builderCxt))
+ {
+ HeapTuple tuple;
+ HeapTupleHeader dtuple;
+ Datum *values;
+ bool *nulls;
+
+ values = tstate->values;
+ nulls = tstate->nulls;
+
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ if (i + 1 == tstate->for_ordinality_col)
+ {
+ values[i] = Int32GetDatum(tstate->rownum++);
+ nulls[i] = false;
+ }
+ else
+ {
+ values[i] = builder->GetValue(builderCxt, i, &isnull);
+
+ if (isnull && tstate->def_expr[i] != NULL)
+ values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+ if (isnull && tstate->not_null[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null is not allowed in column \"%s\"",
+ NameStr(tupdesc->attrs[i]->attname))));
+ nulls[i] = isnull;
+ }
+ }
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+ memcpy(dtuple, tuple->t_data, tuple->t_len);
+
+ /*
+ * Label the datum with the composite type previously identified
+ */
+ HeapTupleHeaderSetTypeId(dtuple, tupdesc->tdtypeid);
+ HeapTupleHeaderSetTypMod(dtuple, tupdesc->tdtypmod);
+
+ heap_freetuple(tuple);
+
+ *isNull = false;
+ *isDone = ExprMultipleResult;
+
+ result = HeapTupleHeaderGetDatum(dtuple);
+ }
+ else
+ {
+ /* no more rows */
+ builder->DestroyContext(builderCxt);
+ tstate->builderCxt = NULL;
+
+ /* ensure releasing all memory */
+ MemoryContextReset(tstate->per_rowset_memory);
+
+ *isNull = true;
+ *isDone = ExprEndResult;
+
+ result = (Datum) 0;
+ }
+
+ return result;
+}
/*
* ExecEvalExprSwitchContext
@@ -5262,6 +5498,110 @@ ExecInitExpr(Expr *node, PlanState *parent)
/* Don't fall through to the "common" code below */
return (ExprState *) outlist;
}
+ case T_TableExpr:
+ {
+ TableExpr *te = (TableExpr *) node;
+ TableExprState *tstate = makeNode(TableExprState);
+ int ncols;
+ int i;
+
+ tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+ tstate->builderCxt = NULL;
+
+ /* Only XmlTableBuilder is supported currently */
+ tstate->builder = &XmlTableBuilder;
+
+ /* XXX this assumes the anon record type has been registered */
+ tstate->tupdesc =
+ lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+ exprTypmod((Node *) te));
+ Assert(tstate->tupdesc->natts > 0);
+
+ ncols = tstate->tupdesc->natts;
+ tstate->values = palloc0(sizeof(Datum) * ncols);
+ tstate->nulls = palloc(sizeof(bool) * ncols);
+ tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+ tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+ for (i = 0; i < ncols; i++)
+ {
+ Oid in_funcid;
+
+ getTypeInputInfo(tstate->tupdesc->attrs[i]->atttypid,
+ &in_funcid, &tstate->typioparams[i]);
+ fmgr_info(in_funcid, &tstate->in_functions[i]);
+ }
+
+ tstate->row_expr = ExecInitExpr((Expr *) te->row_expr, parent);
+ tstate->doc_expr = ExecInitExpr((Expr *) te->document_expr, parent);
+
+ if (te->columns)
+ {
+ ListCell *col;
+
+ Assert(ncols == list_length(te->columns));
+
+ tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+ tstate->col_expr = palloc0(sizeof(ExprState *) * ncols);
+ tstate->not_null = palloc0(sizeof(bool) * ncols);
+ tstate->ncols = ncols;
+ tstate->rownum = 1;
+
+ i = 0;
+ foreach(col, te->columns)
+ {
+ TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+ if (!tec->for_ordinality)
+ {
+ tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+ parent);
+ tstate->col_expr[i] = ExecInitExpr((Expr *) tec->column_expr,
+ parent);
+ tstate->not_null[i] = tec->is_not_null;
+ }
+ else
+ tstate->for_ordinality_col = i + 1;
+
+ i++;
+ }
+ }
+
+ if (te->namespaces)
+ {
+ List *preparedlist = NIL;
+ ListCell *ns;
+
+ foreach(ns, te->namespaces)
+ {
+ Node *n = (Node *) lfirst(ns);
+
+ if (IsA(n, NamedArgExpr))
+ {
+ NamedArgExpr *na = (NamedArgExpr *) n;
+ NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+ nax->name = na->name;
+ nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+ nax->location = na->location;
+ preparedlist = lappend(preparedlist, nax);
+ }
+ else
+ preparedlist = lappend(preparedlist,
+ ExecInitExpr((Expr *) n, parent));
+ }
+ tstate->namespaces = preparedlist;
+ }
+ else
+ tstate->namespaces = NIL;
+
+ tstate->per_rowset_memory = AllocSetContextCreate(CurrentMemoryContext,
+ "XmlTable per rowgroup context",
+ ALLOCSET_DEFAULT_SIZES);
+
+ state = (ExprState *) tstate;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..8c722ed 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,48 @@ ExecTypeFromExprList(List *exprList)
}
/*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+ TupleDesc typeInfo;
+
+ if (te->columns != NIL)
+ {
+ ListCell *col;
+ int i = 1;
+
+ typeInfo = CreateTemplateTupleDesc(list_length(te->columns), false);
+
+ foreach(col, te->columns)
+ {
+ TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+ TupleDescInitEntry(typeInfo,
+ (AttrNumber) i,
+ pstrdup(tec->colname),
+ tec->typid,
+ tec->typmod,
+ 0);
+ i++;
+ }
+ }
+ else
+ {
+ typeInfo = CreateTemplateTupleDesc(1, false);
+ TupleDescInitEntry(typeInfo, (AttrNumber) 1,
+ "xmltable", XMLOID, -1, 0);
+ }
+
+ /* XXX OK to do this? looks a bit out of place ... */
+ assign_record_type_typmod(typeInfo);
+
+ return typeInfo;
+}
+
+/*
* ExecTypeSetColNames - set column names in a TupleDesc
*
* Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..0c9e3ed 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
return newnode;
}
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr * from)
+{
+ TableExpr *newnode = makeNode(TableExpr);
+
+ COPY_NODE_FIELD(row_expr);
+ COPY_NODE_FIELD(document_expr);
+ COPY_NODE_FIELD(columns);
+ COPY_NODE_FIELD(namespaces);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol * from)
+{
+ TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+ COPY_STRING_FIELD(colname);
+ COPY_NODE_FIELD(typeName);
+ COPY_SCALAR_FIELD(for_ordinality);
+ COPY_SCALAR_FIELD(is_not_null);
+ COPY_NODE_FIELD(column_expr);
+ COPY_NODE_FIELD(default_expr);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn * from)
+{
+ TableExprColumn *newnode = makeNode(TableExprColumn);
+
+ COPY_STRING_FIELD(colname);
+ COPY_SCALAR_FIELD(typid);
+ COPY_SCALAR_FIELD(typmod);
+ COPY_SCALAR_FIELD(collation);
+ COPY_SCALAR_FIELD(for_ordinality);
+ COPY_SCALAR_FIELD(is_not_null);
+ COPY_NODE_FIELD(column_expr);
+ COPY_NODE_FIELD(default_expr);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* relation.h copy functions
*
@@ -4600,6 +4657,12 @@ copyObject(const void *from)
case T_OnConflictExpr:
retval = _copyOnConflictExpr(from);
break;
+ case T_TableExpr:
+ retval = _copyTableExpr(from);
+ break;
+ case T_TableExprColumn:
+ retval = _copyTableExprColumn(from);
+ break;
/*
* RELATION NODES
@@ -5104,6 +5167,9 @@ copyObject(const void *from)
case T_TriggerTransition:
retval = _copyTriggerTransition(from);
break;
+ case T_TableExprRawCol:
+ retval = _copyTableExprRawCol(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..b36b755 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2615,6 +2615,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
}
static bool
+_equalTableExprRawCol(const TableExprRawCol * a, const TableExprRawCol * b)
+{
+ COMPARE_STRING_FIELD(colname);
+ COMPARE_NODE_FIELD(typeName);
+ COMPARE_SCALAR_FIELD(for_ordinality);
+ COMPARE_SCALAR_FIELD(is_not_null);
+ COMPARE_NODE_FIELD(column_expr);
+ COMPARE_NODE_FIELD(default_expr);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn * a, const TableExprColumn * b)
+{
+ COMPARE_STRING_FIELD(colname);
+ COMPARE_SCALAR_FIELD(typid);
+ COMPARE_SCALAR_FIELD(typmod);
+ COMPARE_SCALAR_FIELD(collation);
+ COMPARE_SCALAR_FIELD(for_ordinality);
+ COMPARE_SCALAR_FIELD(is_not_null);
+ COMPARE_NODE_FIELD(column_expr);
+ COMPARE_NODE_FIELD(default_expr);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
_equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
{
COMPARE_SCALAR_FIELD(xmloption);
@@ -2645,6 +2675,18 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
return true;
}
+static bool
+_equalTableExpr(const TableExpr * a, const TableExpr * b)
+{
+ COMPARE_NODE_FIELD(row_expr);
+ COMPARE_NODE_FIELD(document_expr);
+ COMPARE_NODE_FIELD(columns);
+ COMPARE_NODE_FIELD(namespaces);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -2910,6 +2952,12 @@ equal(const void *a, const void *b)
case T_JoinExpr:
retval = _equalJoinExpr(a, b);
break;
+ case T_TableExpr:
+ retval = _equalTableExpr(a, b);
+ break;
+ case T_TableExprColumn:
+ retval = _equalTableExprColumn(a, b);
+ break;
/*
* RELATION NODES
@@ -3401,6 +3449,9 @@ equal(const void *a, const void *b)
case T_TriggerTransition:
retval = _equalTriggerTransition(a, b);
break;
+ case T_TableExprRawCol:
+ retval = _equalTableExprRawCol(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..7f30e3c 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -16,6 +16,7 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/execnodes.h"
@@ -23,6 +24,7 @@
#include "nodes/relation.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+#include "utils/typcache.h"
static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +259,12 @@ exprType(const Node *expr)
case T_PlaceHolderVar:
type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_TableExpr:
+ type = RECORDOID;
+ break;
+ case T_TableExprColumn:
+ type = ((const TableExprColumn *) expr)->typid;
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -492,6 +500,19 @@ exprTypmod(const Node *expr)
return ((const SetToDefault *) expr)->typeMod;
case T_PlaceHolderVar:
return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+ case T_TableExpr:
+ {
+ /*
+ * The result of table expression is pseudo-type record.
+ * Generate a blessed tupdesc from the column definitions, and
+ * returns its typmod.
+ */
+ TupleDesc tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+ return tupdesc->tdtypmod;
+ }
+ case T_TableExprColumn:
+ return ((const TableExprColumn *) expr)->typmod;
default:
break;
}
@@ -727,6 +748,8 @@ expression_returns_set_walker(Node *node, void *context)
return false;
if (IsA(node, XmlExpr))
return false;
+ if (IsA(node, TableExpr))
+ return true;
return expression_tree_walker(node, expression_returns_set_walker,
context);
@@ -929,6 +952,12 @@ exprCollation(const Node *expr)
case T_PlaceHolderVar:
coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_TableExpr:
+ coll = InvalidOid; /* result is composite or XML or JSON */
+ break;
+ case T_TableExprColumn:
+ coll = ((const TableExprColumn *) expr)->collation;
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1127,6 +1156,13 @@ exprSetCollation(Node *expr, Oid collation)
case T_CurrentOfExpr:
Assert(!OidIsValid(collation)); /* result is always boolean */
break;
+ case T_TableExpr:
+ Assert(!OidIsValid(collation)); /* result is always composite
+ * or XML, .. */
+ break;
+ case T_TableExprColumn:
+ ((TableExprColumn *) expr)->collation = collation;
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1552,6 +1588,9 @@ exprLocation(const Node *expr)
/* just use nested expr's location */
loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
break;
+ case T_TableExpr:
+ loc = ((const TableExpr *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2211,6 +2250,30 @@ expression_tree_walker(Node *node,
return true;
}
break;
+ case T_TableExpr:
+ {
+ TableExpr *te = (TableExpr *) node;
+
+ if (walker(te->row_expr, context))
+ return true;
+ if (walker(te->document_expr, context))
+ return true;
+ if (walker(te->namespaces, context))
+ return true;
+ if (walker(te->columns, context))
+ return true;
+ }
+ break;
+ case T_TableExprColumn:
+ {
+ TableExprColumn *tec = (TableExprColumn *) node;
+
+ if (walker(tec->column_expr, context))
+ return true;
+ if (walker(tec->default_expr, context))
+ return true;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3007,6 +3070,30 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
+ case T_TableExpr:
+ {
+ TableExpr *te = (TableExpr *) node;
+ TableExpr *newnode;
+
+ FLATCOPY(newnode, te, TableExpr);
+ MUTATE(newnode->row_expr, te->row_expr, Node *);
+ MUTATE(newnode->document_expr, te->document_expr, Node *);
+ MUTATE(newnode->namespaces, te->namespaces, List *);
+ MUTATE(newnode->columns, te->columns, List *);
+ return (Node *) newnode;
+ }
+ break;
+ case T_TableExprColumn:
+ {
+ TableExprColumn *tec = (TableExprColumn *) node;
+ TableExprColumn *newnode;
+
+ FLATCOPY(newnode, tec, TableExprColumn);
+ MUTATE(newnode->column_expr, tec->column_expr, Node *);
+ MUTATE(newnode->default_expr, tec->default_expr, Node *);
+ return (Node *) newnode;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3622,6 +3709,20 @@ raw_expression_tree_walker(Node *node,
break;
case T_CommonTableExpr:
return walker(((CommonTableExpr *) node)->ctequery, context);
+ case T_TableExpr:
+ {
+ TableExpr *te = (TableExpr *) node;
+
+ if (walker(te->row_expr, context))
+ return true;
+ if (walker(te->document_expr, context))
+ return true;
+ if (walker(te->columns, context))
+ return true;
+ if (walker(te->namespaces, context))
+ return true;
+ }
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..3f7166b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
WRITE_NODE_FIELD(exclRelTlist);
}
+static void
+_outTableExpr(StringInfo str, const TableExpr * node)
+{
+ WRITE_NODE_TYPE("TABLEEXPR");
+
+ WRITE_NODE_FIELD(row_expr);
+ WRITE_NODE_FIELD(document_expr);
+ WRITE_NODE_FIELD(columns);
+ WRITE_NODE_FIELD(namespaces);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn * node)
+{
+ WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+ WRITE_STRING_FIELD(colname);
+ WRITE_OID_FIELD(typid);
+ WRITE_INT_FIELD(typmod);
+ WRITE_OID_FIELD(collation);
+ WRITE_BOOL_FIELD(for_ordinality);
+ WRITE_BOOL_FIELD(is_not_null);
+ WRITE_NODE_FIELD(column_expr);
+ WRITE_NODE_FIELD(default_expr);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol * node)
+{
+ WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+ WRITE_STRING_FIELD(colname);
+ WRITE_NODE_FIELD(typeName);
+ WRITE_BOOL_FIELD(for_ordinality);
+ WRITE_BOOL_FIELD(is_not_null);
+ WRITE_NODE_FIELD(column_expr);
+ WRITE_NODE_FIELD(default_expr);
+ WRITE_LOCATION_FIELD(location);
+}
+
/*****************************************************************************
*
* Stuff from relation.h.
@@ -3539,6 +3581,12 @@ outNode(StringInfo str, const void *obj)
case T_XmlExpr:
_outXmlExpr(str, obj);
break;
+ case T_TableExpr:
+ _outTableExpr(str, obj);
+ break;
+ case T_TableExprColumn:
+ _outTableExprColumn(str, obj);
+ break;
case T_NullTest:
_outNullTest(str, obj);
break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
case T_TriggerTransition:
_outTriggerTransition(str, obj);
break;
+ case T_TableExprRawCol:
+ _outTableExprRawCol(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..ed71fc6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,44 @@ _readExtensibleNode(void)
}
/*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+ READ_LOCALS(TableExpr);
+
+ READ_NODE_FIELD(row_expr);
+ READ_NODE_FIELD(document_expr);
+ READ_NODE_FIELD(columns);
+ READ_NODE_FIELD(namespaces);
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+ READ_LOCALS(TableExprColumn);
+
+ READ_STRING_FIELD(colname);
+ READ_OID_FIELD(typid);
+ READ_INT_FIELD(typmod);
+ READ_OID_FIELD(collation);
+ READ_BOOL_FIELD(for_ordinality);
+ READ_BOOL_FIELD(is_not_null);
+ READ_NODE_FIELD(column_expr);
+ READ_NODE_FIELD(default_expr);
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2535,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("TABLEEXPR", 9))
+ return_value = _readTableExprNode();
+ else if (MATCH("TABLEEXPRCOLUMN", 15))
+ return_value = _readTableExprColumnNode();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9af29dd..a93e636 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -829,6 +829,12 @@ expression_returns_set_rows_walker(Node *node, double *count)
*count *= get_func_rows(expr->opfuncid);
}
}
+ if (IsA(node, TableExpr))
+ {
+ /* we have not any method how to estimate it, use default */
+ *count = 1000;
+ return false;
+ }
/* Avoid recursion for some cases that can't return a set */
if (IsA(node, Aggref))
@@ -3598,6 +3604,33 @@ eval_const_expressions_mutator(Node *node,
context);
}
break;
+ case T_TableExprColumn:
+ {
+ TableExprColumn *tec = (TableExprColumn *) node;
+
+ if (tec->column_expr != NULL || tec->default_expr != NULL)
+ {
+ TableExprColumn *newtec = makeNode(TableExprColumn);
+
+ newtec->colname = tec->colname;
+ newtec->typid = tec->typid;
+ newtec->typmod = tec->typmod;
+ newtec->collation = tec->collation;
+ newtec->for_ordinality = tec->for_ordinality;
+ newtec->is_not_null = tec->is_not_null;
+ newtec->column_expr =
+ eval_const_expressions_mutator((Node *) tec->column_expr,
+ context);
+ newtec->default_expr =
+ eval_const_expressions_mutator((Node *) tec->default_expr,
+ context);
+ newtec->location = tec->location;
+
+ return (Node *) newtec;
+ }
+ }
+ break;
+
default:
break;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 367bc2e..6e12570 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -545,6 +545,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <list> XmlTableExprColList XmlTableExprColOptions
+%type <node> XmlTableExprCol
+%type <defelt> XmlTableExprColOption
+%type <boolean> IsNotNull
+%type <list> XmlNamespaceList
+%type <node> XmlNamespace
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -576,10 +583,10 @@ 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
- COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
- CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
- CROSS CSV CUBE CURRENT_P
+ 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
CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -649,8 +656,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
@@ -12578,6 +12585,168 @@ func_expr_common_subexpr:
n->location = @1;
$$ = (Node *)n;
}
+ | XMLTABLE '(' c_expr xmlexists_argument ')'
+ {
+ TableExpr *n = makeNode(TableExpr);
+ n->row_expr = $3;
+ n->document_expr = $4;
+ n->columns = NIL;
+ n->namespaces = NIL;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
+ | XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+ {
+ TableExpr *n = makeNode(TableExpr);
+ n->row_expr = $3;
+ n->document_expr = $4;
+ n->columns = $6;
+ n->namespaces = NIL;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
+ | XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+ c_expr xmlexists_argument ')'
+ {
+ TableExpr *n = makeNode(TableExpr);
+ n->row_expr = $8;
+ n->document_expr = $9;
+ n->columns = NIL;
+ n->namespaces = $5;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
+ | XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+ c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+ {
+ TableExpr *n = makeNode(TableExpr);
+ n->row_expr = $8;
+ n->document_expr = $9;
+ n->columns = $11;
+ n->namespaces = $5;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
+ ;
+
+XmlTableExprColList: XmlTableExprCol { $$ = list_make1($1); }
+ | XmlTableExprColList ',' XmlTableExprCol { $$ = lappend($1, $3); }
+ ;
+
+XmlTableExprCol:
+ ColId Typename IsNotNull
+ {
+ TableExprRawCol *rawc = makeNode(TableExprRawCol);
+
+ rawc->colname = $1;
+ rawc->for_ordinality = false;
+ rawc->typeName = $2;
+ rawc->is_not_null = $3;
+ rawc->column_expr = NULL;
+ rawc->default_expr = NULL;
+ rawc->location = @1;
+
+ $$ = (Node *) rawc;
+ }
+ | ColId Typename XmlTableExprColOptions IsNotNull
+ {
+ TableExprRawCol *rawc = makeNode(TableExprRawCol);
+ ListCell *option;
+
+ rawc->colname = $1;
+ rawc->for_ordinality = false;
+ rawc->typeName = $2;
+ rawc->is_not_null = $4;
+ rawc->column_expr = NULL;
+ rawc->default_expr = NULL;
+ rawc->location = @1;
+
+ foreach(option, $3)
+ {
+ DefElem *defel = (DefElem *) lfirst(option);
+
+ if (strcmp(defel->defname, "default") == 0)
+ {
+ if (rawc->default_expr != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("only one DEFAULT value per column is allowed"),
+ parser_errposition(defel->location)));
+ rawc->default_expr = defel->arg;
+ }
+ else if (strcmp(defel->defname, "path") == 0)
+ {
+ if (rawc->column_expr != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("only one PATH value per column is allowed"),
+ parser_errposition(defel->location)));
+ rawc->column_expr = defel->arg;
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized column option \"%s\"",
+ defel->defname),
+ parser_errposition(defel->location)));
+ }
+ }
+ $$ = (Node *) rawc;
+ }
+ | ColId FOR ORDINALITY
+ {
+ TableExprRawCol *rawc = makeNode(TableExprRawCol);
+
+ rawc->colname = $1;
+ rawc->for_ordinality = true;
+ /* other fields are ignored, initialized by makeNode */
+ rawc->location = @1;
+
+ $$ = (Node *) rawc;
+ }
+ ;
+
+XmlTableExprColOptions: XmlTableExprColOption { $$ = list_make1($1); }
+ | XmlTableExprColOptions XmlTableExprColOption { $$ = lappend($1, $2); }
+ ;
+
+XmlTableExprColOption:
+ IDENT b_expr
+ {
+ $$ = makeDefElem($1, $2, @1);
+ }
+ | DEFAULT b_expr
+ {
+ $$ = makeDefElem("default", $2, @1);
+ }
+ ;
+
+IsNotNull: NOT NULL_P { $$ = true; }
+ | NULL_P { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+XmlNamespaceList: XmlNamespace { $$ = list_make1($1); }
+ | XmlNamespaceList ',' XmlNamespace { $$ = lappend($1, $3); }
+ ;
+
+XmlNamespace:
+ c_expr AS ColLabel
+ {
+ NamedArgExpr *na = makeNode(NamedArgExpr);
+ na->name = $3;
+ na->arg = (Expr *) $1;
+ na->location = @1;
+ $$ = (Node *) na;
+ }
+ | DEFAULT b_expr
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DEFAULT namespace is not supported"),
+ parser_errposition(@1)));
+ }
;
/*
@@ -13686,6 +13855,7 @@ unreserved_keyword:
| CLASS
| CLOSE
| CLUSTER
+ | COLUMNS
| COMMENT
| COMMENTS
| COMMIT
@@ -13987,10 +14157,12 @@ col_name_keyword:
| XMLELEMENT
| XMLEXISTS
| XMLFOREST
+ | XMLNAMESPACES
| XMLPARSE
| XMLPI
| XMLROOT
| XMLSERIALIZE
+ | XMLTABLE
;
/* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..12f3b36 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1095,9 +1095,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.
*
@@ -1105,9 +1105,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);
@@ -1116,7 +1116,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);
@@ -1143,6 +1143,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
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 17d1cbf..3ea62ed 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
#include "utils/date.h"
#include "utils/lsyscache.h"
#include "utils/timestamp.h"
+#include "utils/typcache.h"
#include "utils/xml.h"
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
List *indirection);
static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr * te);
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
break;
}
+ case T_TableExpr:
+ result = transformTableExpr(pstate, (TableExpr *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2764,6 +2770,163 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
return (Node *) newc;
}
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr* te)
+{
+ TableExpr *newte = makeNode(TableExpr);
+ const char *constructName;
+ Oid exprType;
+
+ /* Currently only XMLTABLE is supported */
+ constructName = "XMLTABLE";
+ exprType = XMLOID;
+
+ /* TableExpr is SRF, check target SRF usage */
+ check_srf_call_placement(pstate, te->location);
+
+ /* Transform the row-generating Xpath expression ... */
+ Assert(te->row_expr != NULL);
+ newte->row_expr = coerce_to_specific_type(pstate,
+ transformExprRecurse(pstate, te->row_expr),
+ TEXTOID,
+ constructName);
+ /* ... and the XML itself */
+ Assert(te->document_expr != NULL);
+ newte->document_expr = coerce_to_specific_type(pstate,
+ transformExprRecurse(pstate, te->document_expr),
+ exprType,
+ constructName);
+
+ /* Columns, if any, also need to be transformed */
+ if (te->columns != NIL)
+ {
+ ListCell *col;
+ bool for_ordinality = false;
+ char **colnames;
+ int i = 0;
+
+ colnames = palloc(sizeof(char *) * list_length(te->columns));
+
+ /*
+ * For each TableExprRawCol in the input TableExpr, generate one
+ * TableExprColumn in the output TableExpr.
+ */
+ foreach(col, te->columns)
+ {
+ TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+ TableExprColumn *newc;
+ int j;
+
+ /* Only one FOR ORDINALITY column is allowed; verify */
+ if (rawc->for_ordinality)
+ {
+ if (for_ordinality)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("only one FOR ORDINALITY column is allowed"),
+ parser_errposition(pstate, rawc->location)));
+ for_ordinality = true;
+ }
+
+ newc = makeNode(TableExprColumn);
+ newc->colname = pstrdup(rawc->colname);
+ newc->for_ordinality = rawc->for_ordinality;
+ newc->is_not_null = rawc->is_not_null;
+ newc->location = rawc->location;
+
+ /*
+ * 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)
+ {
+ newc->typid = INT4OID;
+ newc->typmod = -1;
+ }
+ else
+ typenameTypeIdAndMod(pstate, rawc->typeName,
+ &newc->typid, &newc->typmod);
+
+ /* Transform the PATH and DEFAULT expressions */
+ if (rawc->column_expr)
+ newc->column_expr = coerce_to_specific_type(pstate,
+ transformExprRecurse(pstate, rawc->column_expr),
+ TEXTOID,
+ constructName);
+ if (rawc->default_expr)
+ newc->default_expr = coerce_to_specific_type_typmod(pstate,
+ transformExprRecurse(pstate, rawc->default_expr),
+ newc->typid, newc->typmod,
+ constructName);
+
+
+ newte->columns = lappend(newte->columns, newc);
+
+ /* make sure column names are unique */
+ for (j = 0; j < i; j++)
+ if (strcmp(colnames[j], rawc->colname) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("the column name \"%s\" is not unique",
+ rawc->colname),
+ parser_errposition(pstate, rawc->location)));
+ colnames[i] = rawc->colname;
+
+ i++;
+ }
+
+ pfree(colnames);
+ }
+ else
+ newte->columns = NIL;
+
+ /* Namespaces, if any, also need to be transformed */
+ if (te->namespaces != NIL)
+ {
+ List *transformlist = NIL;
+ ListCell *ns;
+ int nnames = 0;
+ char **nsnames;
+
+ nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+ foreach(ns, te->namespaces)
+ {
+ Node *n = (Node *) lfirst(ns);
+ NamedArgExpr *na = (NamedArgExpr *) n;
+ int i;
+
+ Assert(IsA(na, NamedArgExpr));
+
+ /* make sure namespace names are unique */
+ for (i = 0; i < nnames; i++)
+ if (strcmp(nsnames[i], na->name) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("the namespace \"%s\" is not unique",
+ na->name),
+ parser_errposition(pstate, na->location)));
+ nsnames[nnames++] = na->name;
+
+ na->arg = (Expr *) coerce_to_specific_type(pstate,
+ transformExprRecurse(pstate, (Node *) na->arg),
+ TEXTOID,
+ constructName);
+
+ transformlist = lappend(transformlist, n);
+ }
+ newte->namespaces = transformlist;
+ pfree(nsnames);
+ }
+ else
+ newte->namespaces = NIL;
+
+ newte->location = te->location;
+
+ return (Node *) newte;
+}
+
/*
* Transform a "row compare-op row" construct
*
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index d440dec..828ae9b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,13 @@ FigureColnameInternal(Node *node, char **name)
case T_XmlSerialize:
*name = "xmlserialize";
return 2;
+ case T_TableExpr:
+ /*
+ * Make TableExpr act like a regular function. Only
+ * XMLTABLE expr is supported in this moment.
+ */
+ *name = "xmltable";
+ return 2;
default:
break;
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..d2e72e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
+#include "parser/parse_type.h"
#include "parser/parser.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
@@ -8306,6 +8307,105 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_TableExpr:
+ {
+ TableExpr *te = (TableExpr *) node;
+
+ /*
+ * Deparse TableExpr - the is only one TableExpr producer,
+ * the function XMLTABLE.
+ */
+
+ /* c_expr shoud be closed in brackets */
+ appendStringInfoString(buf, "XMLTABLE(");
+
+ if (te->namespaces != NIL)
+ {
+ ListCell *ns;
+ bool first = true;
+
+ appendStringInfoString(buf, "XMLNAMESPACES(");
+ foreach(ns, te->namespaces)
+ {
+ Node *n = (Node *) lfirst(ns);
+
+ if (!first)
+ appendStringInfoString(buf, ", ");
+ else
+ first = false;
+
+ if (IsA(n, NamedArgExpr))
+ {
+ NamedArgExpr *na = (NamedArgExpr *) n;
+
+ get_rule_expr((Node *) na->arg, context, true);
+ appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+ }
+ else
+ {
+ appendStringInfoString(buf, "DEFAULT ");
+ get_rule_expr(n, context, true);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ }
+
+ appendStringInfoChar(buf, '(');
+ get_rule_expr((Node *) te->row_expr, context, true);
+ appendStringInfoString(buf, ") PASSING (");
+ get_rule_expr((Node *) te->document_expr, context, true);
+ appendStringInfoChar(buf, ')');
+
+ if (te->columns != NIL)
+ {
+ ListCell *col;
+ bool first = true;
+
+ appendStringInfoString(buf, " COLUMNS ");
+
+ foreach(col, te->columns)
+ {
+ TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+ if (!first)
+ appendStringInfoString(buf, ", ");
+ else
+ first = false;
+
+ appendStringInfoString(buf, quote_identifier(tec->colname));
+ appendStringInfoChar(buf, ' ');
+
+ if (!tec->for_ordinality)
+ {
+ appendStringInfoString(buf,
+ format_type_with_typemod(tec->typid, tec->typmod));
+
+ if (tec->default_expr != NULL)
+ {
+ appendStringInfoString(buf, " DEFAULT (");
+ get_rule_expr((Node *) tec->default_expr, context, true);
+ appendStringInfoChar(buf, ')');
+ }
+ if (tec->column_expr != NULL)
+ {
+ appendStringInfoString(buf, " PATH (");
+ get_rule_expr((Node *) tec->column_expr, context, true);
+ appendStringInfoChar(buf, ')');
+ }
+ if (tec->is_not_null)
+ appendStringInfoString(buf, " NOT NULL");
+ }
+ else
+ {
+ appendStringInfoString(buf, "FOR ORDINALITY");
+ }
+ }
+ }
+
+ appendStringInfoChar(buf, ')');
+ }
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..21466f0 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -73,6 +73,7 @@
#include "commands/dbcommands.h"
#include "executor/executor.h"
#include "executor/spi.h"
+#include "executor/tableexpr.h"
#include "fmgr.h"
#include "lib/stringinfo.h"
#include "libpq/pqformat.h"
@@ -165,6 +166,29 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
char *tablename, bool nulls, bool tableforest,
const char *targetns, bool top_level);
+static void *XmlTableCreateContext(TupleDesc tupdesc,
+ FmgrInfo *in_functions, Oid *typioparms,
+ MemoryContext mcxt);
+static void XmlTableSetContent(void *tcontext, Datum value);
+static void XmlTableAddNS(void *tcontext, char *name, char *uri);
+static void XmlTableSetRowPath(void *tcontext, char *path);
+static void XmlTableSetColumnPath(void *tcontext, char *path, int colnum);
+static bool XmlTableFetchRow(void *tcontext);
+static Datum XmlTableGetValue(void *tcontext, int colnum, bool *isnull);
+static void XmlTableDestroyContext(void *tcontext);
+
+const TableExprBuilder XmlTableBuilder =
+{
+ XmlTableCreateContext,
+ XmlTableSetContent,
+ XmlTableAddNS,
+ XmlTableSetRowPath,
+ XmlTableSetColumnPath,
+ XmlTableFetchRow,
+ XmlTableGetValue,
+ XmlTableDestroyContext
+};
+
#define NO_XML_SUPPORT() \
ereport(ERROR, \
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4089,570 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
return 0;
#endif /* not USE_LIBXML */
}
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Functions for XmlTableBuilder
+ *
+ */
+typedef struct XmlTableContext
+{
+ TupleDesc tupdesc;
+ MemoryContext per_rowset_memory;
+ char *def_namespace_name;
+ FmgrInfo *in_functions;
+ Oid *typioparams;
+ long int rc;
+ PgXmlErrorContext *xmlerrcxt;
+ xmlParserCtxtPtr ctxt;
+ xmlDocPtr doc;
+ xmlXPathContextPtr xpathcxt;
+ xmlXPathCompExprPtr xpathcomp;
+ xmlXPathObjectPtr xpathobj;
+ xmlXPathCompExprPtr *xpathscomp;
+} XmlTableContext;
+
+/*
+ * Convert XML node to cstring (dump subtree in case of element,
+ * return value otherwise)
+ */
+static char *
+xml_xmlnodetostr(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt)
+{
+ char *result;
+
+ if (cur->type == XML_ELEMENT_NODE)
+ {
+ xmlBufferPtr buf;
+ xmlNodePtr cur_copy;
+
+ buf = xmlBufferCreate();
+
+ /*
+ * The result of xmlNodeDump() won't contain namespace definitions
+ * from parent nodes, but xmlCopyNode() duplicates a node along with
+ * its required namespace definitions.
+ */
+ cur_copy = xmlCopyNode(cur, 1);
+
+ if (cur_copy == NULL)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+ "could not copy node");
+
+ PG_TRY();
+ {
+ xmlNodeDump(buf, NULL, cur_copy, 0, 1);
+ result = pstrdup((const char *) xmlBufferContent(buf));
+ }
+ PG_CATCH();
+ {
+ xmlFreeNode(cur_copy);
+ xmlBufferFree(buf);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ xmlFreeNode(cur_copy);
+ xmlBufferFree(buf);
+ }
+ else
+ {
+ xmlChar *str;
+
+ str = xmlXPathCastNodeToString(cur);
+ PG_TRY();
+ {
+ /* Here we rely on XML having the same representation as TEXT */
+ char *escaped = escape_xml((char *) str);
+
+ result = escaped;
+ }
+ PG_CATCH();
+ {
+ xmlFree(str);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ xmlFree(str);
+ }
+
+ return result;
+}
+
+/*
+ * Secure cstring for usage in libxml2
+ */
+static xmlChar *
+to_xmlstr(char *str, size_t len)
+{
+ xmlChar *result;
+
+ result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+ memcpy(result, str, len);
+ result[len] = '\0';
+
+ return result;
+}
+#endif
+
+/*
+ * Create XmlTableContext for XmlTable builder. Initialize XML parser.
+ */
+static void *
+XmlTableCreateContext(TupleDesc tupdesc,
+ FmgrInfo *in_functions, Oid *typioparams,
+ MemoryContext per_rowset_memory)
+{
+#ifdef USE_LIBXML
+ MemoryContext oldcxt;
+ XmlTableContext *result = NULL;
+ PgXmlErrorContext *xmlerrcxt = NULL;
+ volatile xmlParserCtxtPtr ctxt = NULL;
+
+ oldcxt = MemoryContextSwitchTo(per_rowset_memory);
+
+ result = palloc0(sizeof(struct XmlTableContext));
+ result->tupdesc = tupdesc;
+ result->per_rowset_memory = per_rowset_memory;
+ result->in_functions = in_functions;
+ result->typioparams = typioparams;
+ result->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * tupdesc->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();
+
+ result->xmlerrcxt = xmlerrcxt;
+ result->ctxt = ctxt;
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+#else
+ NO_XML_SUPPORT();
+#endif /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(void *tcontext, Datum value)
+{
+#ifdef USE_LIBXML
+ XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+ xmltype *xmlval = DatumGetXmlP(value);
+ xmlChar *xstr;
+ int length;
+ volatile xmlDocPtr doc = NULL;
+ volatile xmlXPathContextPtr xpathcxt = NULL;
+ MemoryContext oldcxt;
+
+ length = VARSIZE(xmlval) - VARHDRSZ;
+
+ oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+
+ xstr = to_xmlstr(VARDATA(xmlval), 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;
+
+ MemoryContextSwitchTo(oldcxt);
+#else
+ NO_XML_SUPPORT();
+#endif /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableAddNS - add namespace. use fake name when the name
+ * is null, and store this name as default namespace.
+ */
+static void
+XmlTableAddNS(void *tcontext, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+ XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+ if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+ to_xmlstr(name, strlen(name)),
+ to_xmlstr(uri, strlen(uri))))
+ xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+ "could not set XML namespace");
+#else
+ NO_XML_SUPPORT();
+#endif /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowPath
+ */
+static void
+XmlTableSetRowPath(void *tcontext, char *path)
+{
+#ifdef USE_LIBXML
+ XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+ xmlChar *xstr;
+ MemoryContext oldcxt;
+
+ if (path == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_EXCEPTION),
+ errmsg("row path filter must not be empty string")));
+
+ oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+ xstr = to_xmlstr(path, strlen(path));
+
+ xtCxt->xpathcomp = xmlXPathCompile(xstr);
+ if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+ xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+ "invalid XPath expression");
+
+ MemoryContextSwitchTo(oldcxt);
+#else
+ NO_XML_SUPPORT();
+#endif /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetColumnPath
+ */
+static void
+XmlTableSetColumnPath(void *tcontext, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+ XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+ MemoryContext oldcxt;
+ xmlChar *xstr;
+
+ if (path == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_EXCEPTION),
+ errmsg("column path filter must not be empty string")));
+
+ oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+ xstr = to_xmlstr(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");
+
+ MemoryContextSwitchTo(oldcxt);
+#else
+ NO_XML_SUPPORT();
+#endif /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableFetchRow - returns true when is possible to read
+ * values from XML document.
+ *
+ * First call evaluates row path over content.
+ * Next step and at other calls increment row counter and
+ * compare it with number values in result set.
+ */
+static bool
+XmlTableFetchRow(void *tcontext)
+{
+#ifdef USE_LIBXML
+ XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+ /*
+ * 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)
+ {
+ MemoryContext oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+
+ 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->rc = 0;
+
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ if (xtCxt->xpathobj->type == XPATH_NODESET)
+ {
+ if (xtCxt->xpathobj->nodesetval != NULL)
+ {
+ if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+ return true;
+ }
+ }
+
+ return false;
+#else
+ NO_XML_SUPPORT();
+#endif /* not USE_LIBXML */
+
+ return false;
+}
+
+/*
+ * Apply column path on current node and returns result.
+ * Allows multivalue when expected type is XML.
+ * When COLUMNS part was not entered, returns current node.
+ */
+static Datum
+XmlTableGetValue(void *tcontext, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+ XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+ Datum result = (Datum) 0;
+ xmlNodePtr cur;
+ char *cstr = NULL;
+
+ 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);
+
+ cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+ if (xtCxt->xpathscomp[colnum] != NULL)
+ {
+ volatile xmlXPathObjectPtr column_xpathobj = NULL;
+
+ /* fast leaving */
+ if (cur->type != XML_ELEMENT_NODE)
+ elog(ERROR, "unexpected xmlNode type");
+
+ PG_TRY();
+ {
+ /* Set current node as entry point for XPath evaluation */
+ xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+ /* Evaluate column path */
+ column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+ if (column_xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+ xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+ "could not create XPath object");
+
+ if (column_xpathobj->type == XPATH_NODESET)
+ {
+ int count;
+
+ if (column_xpathobj->nodesetval != NULL)
+ count = column_xpathobj->nodesetval->nodeNr;
+ else
+ count = 0;
+
+ if (count > 0)
+ {
+ Oid targettypid = xtCxt->tupdesc->attrs[colnum]->atttypid;
+
+ if (count == 1)
+ {
+ if (targettypid != XMLOID)
+ {
+ xmlChar *str;
+
+ str = xmlNodeListGetString(xtCxt->doc,
+ column_xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+ 1);
+
+ if (str != NULL)
+ {
+ PG_TRY();
+ {
+ /* Copy string to PostgreSQL controlled memory */
+ cstr = pstrdup((char *) str);
+ }
+ PG_CATCH();
+ {
+ xmlFree(str);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ xmlFree(str);
+ }
+ else
+ {
+ /* Return empty string when tag is empty */
+ cstr = pstrdup("");
+ }
+ }
+ else
+ {
+ /* simple case, result is one value */
+ cstr = xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[0],
+ xtCxt->xmlerrcxt);
+ }
+ }
+ else
+ {
+ StringInfoData str;
+ int i;
+
+ /*
+ * When result is not one value, then we can work with
+ * concated values. But it requires XML as expected
+ * type.
+ */
+ if (targettypid != XMLOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_CARDINALITY_VIOLATION),
+ errmsg("more than one value returned by column XPath expression")));
+
+ /* Concate serialized values */
+ initStringInfo(&str);
+ for (i = 0; i < count; i++)
+ {
+ appendStringInfoString(&str,
+ xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+ xtCxt->xmlerrcxt));
+ }
+ cstr = str.data;
+ }
+
+ result = InputFunctionCall(&xtCxt->in_functions[colnum],
+ cstr,
+ xtCxt->typioparams[colnum],
+ xtCxt->tupdesc->attrs[colnum]->atttypmod);
+ *isnull = false;
+ }
+ else
+ *isnull = true;
+ }
+ else if (column_xpathobj->type == XPATH_STRING)
+ {
+ result = InputFunctionCall(&xtCxt->in_functions[colnum],
+ (char *) column_xpathobj->stringval,
+ xtCxt->typioparams[colnum],
+ xtCxt->tupdesc->attrs[colnum]->atttypmod);
+ *isnull = false;
+ }
+ else
+ elog(ERROR, "unexpected XPath object type");
+
+ xmlXPathFreeObject(column_xpathobj);
+ column_xpathobj = NULL;
+ }
+ PG_CATCH();
+ {
+ if (column_xpathobj != NULL)
+ xmlXPathFreeObject(column_xpathobj);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ }
+ else
+ {
+ /*
+ * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+ * serialized current node.
+ */
+ cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+ result = InputFunctionCall(&xtCxt->in_functions[0],
+ cstr,
+ xtCxt->typioparams[0],
+ -1); /* target type is XML always */
+ *isnull = false;
+ }
+
+ if (cstr != NULL)
+ pfree(cstr);
+
+ return result;
+#else
+ NO_XML_SUPPORT();
+#endif /* not USE_LIBXML */
+}
+
+/*
+ * Release all libxml2 memory and related resources
+ */
+static void
+XmlTableDestroyContext(void *tcontext)
+{
+#ifdef USE_LIBXML
+ XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+ /* Propagate context related error context to libxml2 */
+ xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+ if (xtCxt->xpathscomp != NULL)
+ {
+ int i;
+
+ for (i = 0; i < xtCxt->tupdesc->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);
+
+#else
+ NO_XML_SUPPORT();
+#endif /* not USE_LIBXML */
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
NULL,
resultTypeId,
resultTupleDesc);
+ else if (expr && IsA(expr, TableExpr))
+ {
+ Oid typid = exprType(expr);
+ int32 typmod = exprTypmod(expr);
+
+ if (resultTypeId)
+ *resultTypeId = typid;
+
+ if (resultTupleDesc)
+ *resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+ return TYPEFUNC_COMPOSITE;
+ }
else
{
/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c6d69bc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -272,6 +272,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
Datum proargnames);
extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
-
/*----------
* Support to ease writing functions returning composite types
*
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..8d6b5cb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -17,6 +17,7 @@
#include "access/genam.h"
#include "access/heapam.h"
#include "executor/instrument.h"
+#include "executor/tableexpr.h"
#include "lib/pairingheap.h"
#include "nodes/params.h"
#include "nodes/plannodes.h"
@@ -1011,6 +1012,36 @@ typedef struct DomainConstraintState
ExprState *check_expr; /* for CHECK, a boolean expression */
} DomainConstraintState;
+/* ----------------
+ * TableExprState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableExprState
+{
+ ExprState xprstate;
+ List *namespaces; /* list of prepared ResTarget fields */
+ TupleDesc tupdesc; /* cache */
+ int ncols; /* number of declared columns */
+ int for_ordinality_col; /* number of oridinality column,
+ * started by 1 */
+ int rownum; /* row counter - for ordinality column */
+ ExprState *row_expr; /* row xpath expression */
+ ExprState *doc_expr; /* processed data */
+ ExprState **def_expr; /* array of expressions for default value */
+ ExprState **col_expr; /* array of expressions for path value */
+ bool *not_null; /* for any column info if NULL is allowed or
+ * not */
+ Datum *values; /* prealloc buffer */
+ bool *nulls; /* prealloc buffer */
+ FmgrInfo *in_functions; /* array of infunction for any column */
+ Oid *typioparams; /* array of typIOParam for any column */
+ const TableExprBuilder *builder; /* pointers to builder functions */
+ void *builderCxt; /* data for content builder */
+
+ MemoryContext per_rowset_memory;
+} TableExprState;
/* ----------------------------------------------------------------
* Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b6e5151 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
T_FromExpr,
T_OnConflictExpr,
T_IntoClause,
+ T_TableExpr,
+ T_TableExprColumn,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
T_NullTestState,
T_CoerceToDomainState,
T_DomainConstraintState,
+ T_TableExprState,
/*
* TAGS FOR PLANNER NODES (relation.h)
@@ -454,6 +457,7 @@ typedef enum NodeTag
T_CommonTableExpr,
T_RoleSpec,
T_TriggerTransition,
+ T_TableExprRawCol,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..2608562 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,27 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/*
+ * TableExprRawCol - raw column definition in table-expression functions
+ * like XMLTABLE.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions.
+ *
+ * If for_ordinality is true (FOR ORDINALITY), then the column is an int4
+ * column and the rest of the fields are ignored.
+ */
+typedef struct TableExprRawCol
+{
+ NodeTag type;
+ char *colname; /* name of generated column */
+ TypeName *typeName; /* type name of generated column */
+ bool for_ordinality;
+ bool is_not_null;
+ Node *column_expr; /* column filter expression */
+ Node *default_expr; /* column default value expr */
+ int location;
+} TableExprRawCol;
/****************************************************************************
* Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..91054b1 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,44 @@ typedef struct OnConflictExpr
List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */
} OnConflictExpr;
+/*----------
+ * TableExpr - holds data for table-expression functions like XMLTABLE
+ *
+ *----------
+ */
+typedef struct TableExpr
+{
+ NodeTag type;
+ Node *row_expr; /* row filter expression */
+ Node *document_expr; /* processed data */
+ List *columns; /* columns definitions */
+ List *namespaces; /* list of namespaces */
+ int location;
+} TableExpr;
+
+/*----------
+ * TableExprColumn - a column definition in table-expression functions.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions
+ * and just doesn't make sense here.
+ *
+ * Raw form of this node is TableExprRawCol node - different in
+ * typeName field. After transformation we use typid, typmod.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+ NodeTag type;
+ char *colname; /* name of generated column */
+ Oid typid; /* typid of generated column */
+ int32 typmod;
+ Oid collation;
+ bool for_ordinality;
+ bool is_not_null;
+ Node *column_expr; /* column filter expression */
+ Node *default_expr; /* column default value expr */
+ int location;
+} TableExprColumn;
+
#endif /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..de4b8b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,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)
@@ -441,10 +442,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)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -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);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..1229b76 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,6 @@ extern int xmlbinary; /* XmlBinaryType, but int for guc enum */
extern int xmloption; /* XmlOptionType, but int for guc enum */
+extern const TableExprBuilder XmlTableBuilder;
+
#endif /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..bdbfc03 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,416 @@ 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> </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 without columns
+SELECT * FROM XMLTABLE('/rows/row' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable
+-------------
+ <row> +
+ <a>10</a>+
+ <a>20</a>+
+ </row>
+(1 row)
+
+SELECT XMLTABLE('/rows/row' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable
+-------------
+ ("<row> +
+ <a>10</a>+
+ <a>20</a>+
+ </row>")
+(1 row)
+
+-- 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)) "xmltable"(id, _id, country_name, country_id, region_id, size, unit, premier_name)
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+ QUERY PLAN
+-----------------------------------
+ Nested Loop
+ -> Seq Scan on xmldata
+ -> 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
+ -> 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
+ 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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+ ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable
+----------
+ 10
+ 20
+(2 rows)
+
+-- 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('/ROWS/ROW' PASSING data) FROM xmldata;
+ xmltable
+------------------------------------------------------------------
+ ("<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>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+ xmltable
+------------------------------------------------------------------
+ ("<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>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+ xmltable
+------------------------------------------------------------------
+ ("<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>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ 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 "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>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent text);
+ ent
+-----
+ '
+ "
+ &
+ <
+ >
+(5 rows)
+
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent xml);
+ ent
+------------------
+ <ent>'</ent>
+ <ent>"</ent>
+ <ent>&</ent>
+ <ent><</ent>
+ <ent>></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
+ -> 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
+ 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)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT 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') FROM xmldata;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Seq Scan on public.xmldata
+ Output: XMLTABLE(('/ROWS/ROW'::text) PASSING (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))
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (xmltable).* FROM
+ (SELECT 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') FROM xmldata) s;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Subquery Scan on s
+ Output: (s."xmltable").id, (s."xmltable")._id, (s."xmltable").country_name, (s."xmltable").country_id, (s."xmltable").region_id, (s."xmltable").size, (s."xmltable").unit, (s."xmltable").premier_name
+ -> Seq Scan on public.xmldata
+ Output: 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))
+(4 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d702703..0b720a7 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,324 @@ 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 without columns
+SELECT * FROM XMLTABLE('/rows/row' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ERROR: unsupported XML feature
+LINE 1: SELECT * FROM XMLTABLE('/rows/row' PASSING '<rows><row><a>10...
+ ^
+DETAIL: This functionality requires the server to be built with libxml support.
+HINT: You need to rebuild PostgreSQL using --with-libxml.
+SELECT XMLTABLE('/rows/row' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ERROR: unsupported XML feature
+LINE 1: SELECT XMLTABLE('/rows/row' PASSING '<rows><row><a>10</a><a>...
+ ^
+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)) "xmltable"(id, _id, country_name, country_id, region_id, size, unit, premier_name)
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+ QUERY PLAN
+-----------------------------------
+ Nested Loop
+ -> Seq Scan on xmldata
+ -> 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
+ -> 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
+ 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: DEFAULT namespace is not supported
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+ ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ERROR: unsupported XML feature
+LINE 1: ...LECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><ro...
+ ^
+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('/ROWS/ROW' PASSING data) FROM xmldata;
+ xmltable
+----------
+(0 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+ xmltable
+----------
+(0 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+ xmltable
+----------
+(0 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID
+--------------+-----------
+(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>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent text);
+ERROR: unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</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>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent xml);
+ERROR: unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</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
+ -> 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
+ 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)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT 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') FROM xmldata;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Seq Scan on public.xmldata
+ Output: XMLTABLE(('/ROWS/ROW'::text) PASSING (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))
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (xmltable).* FROM
+ (SELECT 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') FROM xmldata) s;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Subquery Scan on s
+ Output: (s."xmltable").id, (s."xmltable")._id, (s."xmltable").country_name, (s."xmltable").country_id, (s."xmltable").region_id, (s."xmltable").size, (s."xmltable").unit, (s."xmltable").premier_name
+ -> Seq Scan on public.xmldata
+ Output: 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))
+(4 rows)
+
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ 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> </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 without columns
+SELECT * FROM XMLTABLE('/rows/row' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable
+-------------
+ <row> +
+ <a>10</a>+
+ <a>20</a>+
+ </row>
+(1 row)
+
+SELECT XMLTABLE('/rows/row' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable
+-------------
+ ("<row> +
+ <a>10</a>+
+ <a>20</a>+
+ </row>")
+(1 row)
+
+-- 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)) "xmltable"(id, _id, country_name, country_id, region_id, size, unit, premier_name)
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+ QUERY PLAN
+-----------------------------------
+ Nested Loop
+ -> Seq Scan on xmldata
+ -> 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
+ -> 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
+ 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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+ ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable
+----------
+ 10
+ 20
+(2 rows)
+
+-- 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('/ROWS/ROW' PASSING data) FROM xmldata;
+ xmltable
+------------------------------------------------------------------
+ ("<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>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+ xmltable
+------------------------------------------------------------------
+ ("<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>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+ xmltable
+------------------------------------------------------------------
+ ("<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>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ 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 "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>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent text);
+ ent
+-----
+ '
+ "
+ &
+ <
+ >
+(5 rows)
+
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent xml);
+ ent
+------------------
+ <ent>'</ent>
+ <ent>"</ent>
+ <ent>&</ent>
+ <ent><</ent>
+ <ent>></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
+ -> 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
+ 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)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT 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') FROM xmldata;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Seq Scan on public.xmldata
+ Output: XMLTABLE(('/ROWS/ROW'::text) PASSING (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))
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (xmltable).* FROM
+ (SELECT 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') FROM xmldata) s;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Subquery Scan on s
+ Output: (s."xmltable").id, (s."xmltable")._id, (s."xmltable").country_name, (s."xmltable").country_id, (s."xmltable").region_id, (s."xmltable").size, (s."xmltable").unit, (s."xmltable").premier_name
+ -> Seq Scan on public.xmldata
+ Output: 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))
+(4 rows)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30..11d7402 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,173 @@ 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> </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 without columns
+SELECT * FROM XMLTABLE('/rows/row' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+SELECT XMLTABLE('/rows/row' PASSING '<rows><row><a>10</a><a>20</a></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');
+
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+
+-- 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('/ROWS/ROW' PASSING data) FROM xmldata;
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+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>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></ent></a></x>' COLUMNS ent text);
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>'</ent></a><a><ent>"</ent></a><a><ent>&</ent></a><a><ent><</ent></a><a><ent>></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');
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT 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') FROM xmldata;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (xmltable).* FROM
+ (SELECT 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') FROM xmldata) s;