xmltable-30.patch

text/x-patch

Filename: xmltable-30.patch
Type: text/x-patch
Part: 0
Message: Re: patch: function xmltable

Patch

Format: unified
File+
doc/src/sgml/func.sgml 272 2
src/backend/executor/execQual.c 349 0
src/backend/nodes/copyfuncs.c 72 0
src/backend/nodes/equalfuncs.c 57 0
src/backend/nodes/nodeFuncs.c 73 0
src/backend/nodes/outfuncs.c 57 0
src/backend/nodes/readfuncs.c 29 0
src/backend/optimizer/util/clauses.c 6 0
src/backend/parser/gram.y 183 6
src/backend/parser/parse_coerce.c 26 7
src/backend/parser/parse_expr.c 217 0
src/backend/parser/parse_target.c 7 0
src/backend/utils/adt/ruleutils.c 119 0
src/backend/utils/adt/xml.c 624 0
src/backend/utils/fmgr/funcapi.c 23 0
src/include/executor/tableexpr.h 65 0
src/include/nodes/execnodes.h 26 0
src/include/nodes/nodes.h 4 0
src/include/nodes/parsenodes.h 31 1
src/include/nodes/primnodes.h 25 0
src/include/parser/kwlist.h 3 0
src/include/parser/parse_coerce.h 4 0
src/include/utils/xml.h 3 0
src/test/regress/expected/xml_1.out 321 0
src/test/regress/expected/xml_2.out 410 0
src/test/regress/expected/xml.out 410 0
src/test/regress/sql/xml.sql 170 0
src/tools/pgindent/typedefs.list 12 0
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9c4b322..6a46418 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10327,9 +10327,9 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
 
    <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">
@@ -10430,6 +10430,276 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
 ]]></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;apos;</>,
+     <literal>&apos;quot;</>, <literal>&apos;amp;</>, <literal>&apos;lt;</>
+     and <literal>&apos;gt;</> are expanded to their corresponding characters
+     <literal>&apos;</>, <literal>&quot;</>, <literal>&amp;</>, <literal>&lt;</>
+     and <literal>&gt;</> on processing. If the output column format is
+     <type>xml</> then the characters <literal>&lt;</>, <literal>&gt;</> and
+     <literal>&amp;</> are converted to XML entities, but <literal>"</> and
+     <literal>'</> are left unchanged. This means that <literal>&apos;apos;</>
+     and <literal>&apos;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 bf007b7..7aa4e2b 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,6 +190,15 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(ExprState *tstate, ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExprFast(ExprState *exprstate, ExprContext *econtext,
+					  bool *isNull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum doc);
+static void ShutdownTableExpr(Datum arg);
 
 
 /* ----------------------------------------------------------------
@@ -4513,6 +4523,306 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ *		Returns a Datum for a table expression (such as XMLTABLE).
+ *
+ * Note: ExecEvalTableExpr is executed only the first time through in a
+ * given plan; it changes the ExprState's function pointer to pass control
+ * directly to ExecEvalTableExprFast after making one-time initialization.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(ExprState *exprstate, ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	TableExprState *tstate = (TableExprState *) exprstate;
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+	MemoryContext oldcxt;
+	Datum		value;
+	TupleDesc	tupdesc = tstate->resultSlot->tts_tupleDescriptor;
+	int			natts = tupdesc->natts;
+	int			i;
+
+	Assert(tstate->opaque == NULL);
+
+	/* fill in the necessary fmgr infos */
+	for (i = 0; i < natts; i++)
+	{
+		Oid			in_funcid;
+
+		getTypeInputInfo(tupdesc->attrs[i]->atttypid,
+						 &in_funcid, &tstate->typioparams[i]);
+		fmgr_info(in_funcid, &tstate->in_functions[i]);
+	}
+
+	/*
+	 * The first time around, create the table builder context and initialize
+	 * it with the document content.
+	 */
+
+	/* Fill in table builder opaque area */
+	oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	routine->InitBuilder(tstate);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Register shutdown callback to clean up tableexpr builder state */
+	RegisterExprContextCallback(econtext, ShutdownTableExpr,
+								PointerGetDatum(tstate));
+
+	/*
+	 * If evaluating the document expression returns NULL, the table
+	 * expression is empty and we return immediately.
+	 */
+	value = ExecEvalExpr(tstate->docexpr, econtext, isNull, NULL);
+	if (*isNull)
+	{
+		*isDone = ExprEndResult;
+		return (Datum) 0;
+	}
+
+	/* otherwise, pass the document value to the table builder */
+	tabexprInitialize(tstate, econtext, value);
+
+	/* skip all of the above on future executions of node */
+	exprstate->evalfunc = ExecEvalTableExprFast;
+
+	/* Fetch and return one row per call from the table builder */
+	result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+
+	return result;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ *		Returns a Datum for a table expression (such as XMLTABLE).
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExprFast(ExprState *exprstate, ExprContext *econtext,
+					  bool *isNull, ExprDoneCond *isDone)
+{
+	TableExprState *tstate = (TableExprState *) exprstate;
+
+	return tabexprFetchRow(tstate, econtext, isNull, isDone);
+}
+
+/*
+ * callback function in case a TableExpr needs to be shut down before
+ * it has been run to completion.
+ *
+ * FIXME do we need to call this in the normal case?
+ */
+static void
+ShutdownTableExpr(Datum arg)
+{
+	TableExprState *tstate = (TableExprState *) arg;
+	const TableExprRoutine *routine = tstate->routine;
+
+	if (tstate->opaque != NULL)
+	{
+		routine->DestroyBuilder(tstate);
+		ExecDropSingleTupleTableSlot(tstate->resultSlot);
+	}
+}
+
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum doc)
+{
+	const TableExprRoutine *routine;
+	TupleDesc	tupdesc;
+	MemoryContext oldcxt;
+	ListCell   *lc1,
+			   *lc2;
+	bool		isnull;
+	int			colno;
+	Datum		value;
+
+	routine = tstate->routine;
+
+	/*
+	 * 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. Evaluate builder function in special memory context
+	 */
+	oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	routine->SetDoc(tstate, doc);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Evaluate namespace specifications */
+	forboth(lc1, tstate->ns_uris, lc2, tstate->ns_names)
+	{
+		ExprState  *expr = (ExprState *) lfirst(lc1);
+		char	   *ns_name = strVal(lfirst(lc2));
+		char	   *ns_uri;
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->SetNamespace(tstate, ns_name, ns_uri);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull, NULL);
+	if (isnull)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("row filter expression must not be null")));
+
+	MemoryContextSwitchTo(tstate->buildercxt);
+	routine->SetRowFilter(tstate, TextDatumGetCString(value));
+	MemoryContextSwitchTo(oldcxt);
+
+	/*
+	 * Install the column filter expressions into the table builder context.
+	 * If an expression is given, use that; otherwise the column name itself
+	 * is the column filter.
+	 */
+	colno = 0;
+	tupdesc = tstate->resultSlot->tts_tupleDescriptor;
+	foreach(lc1, tstate->colexprs)
+	{
+		char	   *colfilter;
+
+		if (tstate->evalcols && colno != tstate->ordinalitycol)
+		{
+			ExprState  *colexpr = lfirst(lc1);
+
+			if (colexpr != NULL)
+			{
+				value = ExecEvalExpr(colexpr, econtext, &isnull, NULL);
+				if (isnull)
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("column filter expression must not be null"),
+							 errdetail("Filter for column \"%s\" is null.",
+								  NameStr(tupdesc->attrs[colno]->attname))));
+				colfilter = TextDatumGetCString(value);
+			}
+			else
+				colfilter = NameStr(tupdesc->attrs[colno]->attname);
+		}
+		else
+			colfilter = NULL;
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->SetColumnFilter(tstate, colfilter, colno);
+		MemoryContextSwitchTo(oldcxt);
+
+		colno++;
+	}
+}
+
+/*
+ * Fetch one more row from a TableExpr table builder.
+ */
+static Datum
+tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone)
+{
+	const TableExprRoutine *routine;
+	MemoryContext oldcxt;
+	Datum		result;
+
+	routine = tstate->routine;
+	oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+
+	/* Prepare one more row */
+	if (routine->FetchRow(tstate))
+	{
+		TupleDesc	tupdesc = tstate->resultSlot->tts_tupleDescriptor;
+		int			natts = tupdesc->natts;
+		Datum	   *values = tstate->resultSlot->tts_values;
+		bool	   *nulls = tstate->resultSlot->tts_isnull;
+		ListCell   *cell = list_head(tstate->coldefexprs);
+		int			colno = 0;
+		bool		isnull;
+
+		ExecClearTuple(tstate->resultSlot);
+
+		for (colno = 0; colno < natts; colno++)
+		{
+			/* take simple result when column is a ordinality column */
+			if (colno == tstate->ordinalitycol)
+			{
+				values[colno] = Int32GetDatum(tstate->rownum++);
+				nulls[colno] = false;
+			}
+			else
+			{
+				/* fetch value from builder */
+				MemoryContextSwitchTo(tstate->buildercxt);
+				values[colno] = routine->GetValue(tstate, colno, &isnull);
+				MemoryContextSwitchTo(oldcxt);
+
+				/* try to use default expr when it is required and possible */
+				if (isnull && cell != NULL)
+				{
+					ExprState  *coldefexpr = (ExprState *) lfirst(cell);
+
+					if (coldefexpr != NULL)
+						values[colno] = ExecEvalExpr(coldefexpr, econtext,
+													 &isnull, NULL);
+				}
+
+				/* the result should not be null? */
+				if (isnull && !bms_is_empty(tstate->notnulls) &&
+					bms_is_member(colno, tstate->notnulls))
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+								  NameStr(tupdesc->attrs[colno]->attname))));
+
+				nulls[colno] = isnull;
+			}
+
+			if (cell != NULL)
+				cell = lnext(cell);
+		}
+
+		ExecStoreVirtualTuple(tstate->resultSlot);
+
+		MemoryContextSwitchTo(oldcxt);
+
+		*isNull = false;
+		*isDone = ExprMultipleResult;
+
+		result = ExecFetchSlotTupleDatum(tstate->resultSlot);
+	}
+	else
+	{
+		/* no more rows */
+		routine->DestroyBuilder(tstate);
+
+		/* make sure all memory is released */
+		ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		MemoryContextReset(tstate->buildercxt);
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	/* push back short life memory context */
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5253,6 +5563,45 @@ ExecInitExpr(Expr *node, PlanState *parent)
 			state = makeNode(ExprState);
 			state->evalfunc = ExecEvalCurrentOfExpr;
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate;
+				TypeFuncClass functypclass;
+				TupleDesc	tupdesc;
+
+				functypclass = get_expr_result_type((Node *) node, NULL, &tupdesc);
+				Assert(functypclass == TYPEFUNC_COMPOSITE);
+				Assert(tupdesc->natts == te->colcount);
+
+				tstate = makeNode(TableExprState);
+				tstate->xprstate.evalfunc = ExecEvalTableExpr;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+				tstate->opaque = NULL;	/* initialized at runtime */
+				tstate->resultSlot = MakeSingleTupleTableSlot(tupdesc);
+				tstate->ns_names = te->ns_names;
+				tstate->ns_uris = (List *) ExecInitExpr((Expr *) te->ns_uris, parent);
+				/* these are allocated now and initialized later */
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * tupdesc->natts);
+				tstate->typioparams = palloc(sizeof(Oid) * tupdesc->natts);
+				tstate->evalcols = te->evalcols;
+				tstate->ordinalitycol = te->ordinalitycol;
+				tstate->rownum = 1;
+				tstate->docexpr = ExecInitExpr((Expr *) te->docexpr, parent);
+				tstate->rowexpr = ExecInitExpr((Expr *) te->rowexpr, parent);
+				tstate->colexprs = (List *) ExecInitExpr((Expr *) te->colexprs, parent);
+				tstate->coldefexprs = (List *) ExecInitExpr((Expr *) te->coldefexprs, parent);
+				tstate->notnulls = te->notnulls;
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		case T_TargetEntry:
 			{
 				TargetEntry *tle = (TargetEntry *) node;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 930f2f1..7a1bc1d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,69 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(ns_names);
+	COPY_NODE_FIELD(ns_uris);
+	COPY_NODE_FIELD(docexpr);
+	COPY_NODE_FIELD(rowexpr);
+	COPY_SCALAR_FIELD(colcount);
+	COPY_NODE_FIELD(colnames);
+	COPY_NODE_FIELD(coltypes);
+	COPY_NODE_FIELD(coltypmods);
+	COPY_NODE_FIELD(colcollations);
+	COPY_NODE_FIELD(colexprs);
+	COPY_NODE_FIELD(coldefexprs);
+	COPY_BITMAPSET_FIELD(notnulls);
+	COPY_SCALAR_FIELD(ordinalitycol);
+	COPY_SCALAR_FIELD(evalcols);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprFunc
+ */
+static TableExprFunc *
+_copyTableExprFunc(const TableExprFunc *from)
+{
+	TableExprFunc *newnode = makeNode(TableExprFunc);
+
+	COPY_NODE_FIELD(docexpr);
+	COPY_NODE_FIELD(rowexpr);
+	COPY_NODE_FIELD(namespaces);
+	COPY_NODE_FIELD(columns);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprFuncCol
+ */
+static TableExprFuncCol *
+_copyTableExprFuncCol(const TableExprFuncCol *from)
+{
+	TableExprFuncCol *newnode = makeNode(TableExprFuncCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(notnull);
+	COPY_NODE_FIELD(colexpr);
+	COPY_NODE_FIELD(coldefexpr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4665,6 +4728,9 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5184,6 +5250,12 @@ copyObject(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_TableExprFunc:
+			retval = _copyTableExprFunc(from);
+			break;
+		case T_TableExprFuncCol:
+			retval = _copyTableExprFuncCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a27e5ed..bbde39c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2617,6 +2617,32 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprFunc(const TableExprFunc *a, const TableExprFunc *b)
+{
+	COMPARE_NODE_FIELD(docexpr);
+	COMPARE_NODE_FIELD(rowexpr);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprFuncCol(const TableExprFuncCol *a, const TableExprFuncCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(notnull);
+	COMPARE_NODE_FIELD(colexpr);
+	COMPARE_NODE_FIELD(coldefexpr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2700,6 +2726,28 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(ns_names);
+	COMPARE_NODE_FIELD(ns_uris);
+	COMPARE_NODE_FIELD(docexpr);
+	COMPARE_NODE_FIELD(rowexpr);
+	COMPARE_SCALAR_FIELD(colcount);
+	COMPARE_NODE_FIELD(colnames);
+	COMPARE_NODE_FIELD(coltypes);
+	COMPARE_NODE_FIELD(coltypes);
+	COMPARE_NODE_FIELD(colcollations);
+	COMPARE_NODE_FIELD(colexprs);
+	COMPARE_NODE_FIELD(coldefexprs);
+	COMPARE_BITMAPSET_FIELD(notnulls);
+	COMPARE_SCALAR_FIELD(ordinalitycol);
+	COMPARE_SCALAR_FIELD(evalcols);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2965,6 +3013,9 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3471,6 +3522,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_TableExprFunc:
+			retval = _equalTableExprFunc(a, b);
+			break;
+		case T_TableExprFuncCol:
+			retval = _equalTableExprFuncCol(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 c2f03b7..45aa563 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -257,6 +257,10 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			/* result is record OID always */
+			type = RECORDOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -727,6 +731,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 +935,9 @@ 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;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1136,10 @@ 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;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1558,6 +1571,12 @@ exprLocation(const Node *expr)
 		case T_PartitionRangeDatum:
 			loc = ((const PartitionRangeDatum *) expr)->location;
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
+		case T_TableExprFunc:
+			loc = ((const TableExprFunc *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2217,6 +2236,22 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->ns_uris, context))
+					return true;
+				if (walker(te->docexpr, context))
+					return true;
+				if (walker(te->rowexpr, context))
+					return true;
+				if (walker(te->colexprs, context))
+					return true;
+				if (walker(te->coldefexprs, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3013,6 +3048,20 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->ns_uris, te->ns_uris, List *);
+				MUTATE(newnode->docexpr, te->docexpr, Node *);
+				MUTATE(newnode->rowexpr, te->rowexpr, Node *);
+				MUTATE(newnode->colexprs, te->colexprs, List *);
+				MUTATE(newnode->coldefexprs, te->coldefexprs, List *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3628,6 +3677,30 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExprFunc:
+			{
+				TableExprFunc *te = (TableExprFunc *) node;
+
+				if (walker(te->docexpr, context))
+					return true;
+				if (walker(te->rowexpr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+			}
+			break;
+		case T_TableExprFuncCol:
+			{
+				TableExprFuncCol *terc = (TableExprFuncCol *) node;
+
+				if (walker(terc->colexpr, context))
+					return true;
+				if (walker(terc->coldefexpr, 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 806d0a9..b08831d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,28 @@ _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(ns_names);
+	WRITE_NODE_FIELD(ns_uris);
+	WRITE_NODE_FIELD(docexpr);
+	WRITE_NODE_FIELD(rowexpr);
+	WRITE_INT_FIELD(colcount);
+	WRITE_NODE_FIELD(colnames);
+	WRITE_NODE_FIELD(coltypes);
+	WRITE_NODE_FIELD(coltypmods);
+	WRITE_NODE_FIELD(colcollations);
+	WRITE_NODE_FIELD(colexprs);
+	WRITE_NODE_FIELD(coldefexprs);
+	WRITE_BITMAPSET_FIELD(notnulls);
+	WRITE_INT_FIELD(ordinalitycol);
+	WRITE_BOOL_FIELD(evalcols);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3323,6 +3345,32 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
 	WRITE_NODE_FIELD(value);
 }
 
+static void
+_outTableExprFunc(StringInfo str, const TableExprFunc *node)
+{
+	WRITE_NODE_TYPE("TABLEXPRFUNC");
+
+	WRITE_NODE_FIELD(docexpr);
+	WRITE_NODE_FIELD(rowexpr);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_NODE_FIELD(columns);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprFuncCol(StringInfo str, const TableExprFuncCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRFUNCCOL");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(notnull);
+	WRITE_NODE_FIELD(colexpr);
+	WRITE_NODE_FIELD(coldefexpr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*
  * outNode -
  *	  converts a Node into ascii string and append it to 'str'
@@ -3584,6 +3632,9 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3922,6 +3973,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionRangeDatum:
 				_outPartitionRangeDatum(str, obj);
 				break;
+			case T_TableExprFunc:
+				_outTableExprFunc(str, obj);
+				break;
+			case T_TableExprFuncCol:
+				_outTableExprFuncCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index dc40d01..60f7bed 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2298,6 +2298,33 @@ _readPartitionRangeDatum(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(ns_names);
+	READ_NODE_FIELD(ns_uris);
+	READ_NODE_FIELD(docexpr);
+	READ_NODE_FIELD(rowexpr);
+	READ_INT_FIELD(colcount);
+	READ_NODE_FIELD(colnames);
+	READ_NODE_FIELD(coltypes);
+	READ_NODE_FIELD(coltypmods);
+	READ_NODE_FIELD(colcollations);
+	READ_NODE_FIELD(colexprs);
+	READ_NODE_FIELD(coldefexprs);
+	READ_BITMAPSET_FIELD(notnulls);
+	READ_INT_FIELD(ordinalitycol);
+	READ_BOOL_FIELD(evalcols);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2533,6 +2560,8 @@ parseNodeString(void)
 		return_value = _readPartitionBoundSpec();
 	else if (MATCH("PARTRANGEDATUM", 14))
 		return_value = _readPartitionRangeDatum();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
 	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 10abdc3..acf9893 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))
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9eef550..4fe09ce 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -534,6 +534,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	xmlexists_argument
 %type <ival>	document_or_content
 %type <boolean> xml_whitespace_option
+%type <list>	xmltable_column_list xmltable_column_opt_list
+%type <node>	xmltable_column_el
+%type <defelt>	xmltable_column_opt_el
+%type <list>	xml_namespace_list
+%type <target>	xml_namespace_el
 
 %type <node>	func_application func_expr_common_subexpr
 %type <node>	func_expr func_expr_windowless
@@ -591,10 +596,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
 
@@ -665,8 +670,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
 
@@ -12355,6 +12360,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 		;
 
+
 /*
  * Restricted expressions
  *
@@ -12922,6 +12928,48 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExprFunc *n = makeNode(TableExprFunc);
+					n->rowexpr = $3;
+					n->docexpr = $4;
+					n->columns = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS xmltable_column_list ')'
+				{
+					TableExprFunc *n = makeNode(TableExprFunc);
+					n->rowexpr = $3;
+					n->docexpr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' xml_namespace_list ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExprFunc *n = makeNode(TableExprFunc);
+					n->rowexpr = $8;
+					n->docexpr = $9;
+					n->columns = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' xml_namespace_list ')' ','
+				c_expr xmlexists_argument COLUMNS xmltable_column_list ')'
+				{
+					TableExprFunc *n = makeNode(TableExprFunc);
+					n->rowexpr = $8;
+					n->docexpr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -12997,6 +13045,132 @@ xmlexists_argument:
 				}
 		;
 
+xmltable_column_list: xmltable_column_el					{ $$ = list_make1($1); }
+			| xmltable_column_list ',' xmltable_column_el	{ $$ = lappend($1, $3); }
+		;
+
+xmltable_column_el:
+			ColId Typename
+				{
+					TableExprFuncCol	   *fc = makeNode(TableExprFuncCol);
+
+					fc->colname = $1;
+					fc->for_ordinality = false;
+					fc->typeName = $2;
+					fc->notnull = false;
+					fc->colexpr = NULL;
+					fc->coldefexpr = NULL;
+					fc->location = @1;
+
+					$$ = (Node *) fc;
+				}
+			| ColId Typename xmltable_column_opt_list
+				{
+					TableExprFuncCol	   *fc = makeNode(TableExprFuncCol);
+					ListCell		   *option;
+					bool				nullability_seen = false;
+
+					fc->colname = $1;
+					fc->for_ordinality = false;
+					fc->typeName = $2;
+					fc->notnull = false;
+					fc->colexpr = NULL;
+					fc->coldefexpr = NULL;
+					fc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (fc->coldefexpr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value is allowed"),
+										 parser_errposition(defel->location)));
+							fc->coldefexpr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (fc->colexpr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							fc->colexpr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "notnull") == 0)
+						{
+							if (nullability_seen)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("conflicting or redundant NULL / NOT NULL declarations for column \"%s\"", fc->colname),
+										 parser_errposition(defel->location)));
+							fc->notnull = intVal(defel->arg);
+							nullability_seen = true;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) fc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprFuncCol	   *fc = makeNode(TableExprFuncCol);
+
+					fc->colname = $1;
+					fc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					fc->location = @1;
+
+					$$ = (Node *) fc;
+				}
+		;
+
+xmltable_column_opt_list: xmltable_column_opt_el				{ $$ = list_make1($1); }
+			| xmltable_column_opt_list xmltable_column_opt_el	{ $$ = lappend($1, $2); }
+		;
+
+xmltable_column_opt_el:
+			IDENT b_expr
+				{ $$ = makeDefElem($1, $2, @1); }
+			| DEFAULT b_expr
+				{ $$ = makeDefElem("default", $2, @1); }
+			| NOT NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(true), @1); }
+			| NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(false), @1); }
+		;
+
+xml_namespace_list: xml_namespace_el						{ $$ = list_make1($1); }
+			| xml_namespace_list ',' xml_namespace_el		{ $$ = lappend($1, $3); }
+		;
+
+xml_namespace_el:
+			b_expr AS ColLabel
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = $3;
+					$$->indirection = NIL;
+					$$->val = $1;
+					$$->location = @1;
+				}
+			| DEFAULT b_expr
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = NULL;
+					$$->indirection = NIL;
+					$$->val = $2;
+					$$->location = @1;
+				}
+		;
 
 /*
  * Aggregate decoration clauses
@@ -14031,6 +14205,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14333,10 +14508,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 29278cf..5fe5e36 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 f62e45f..a454bd5 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -15,6 +15,7 @@
 
 #include "postgres.h"
 
+#include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "miscadmin.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 *transformTableExprFunc(ParseState *pstate, TableExprFunc * 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_TableExprFunc:
+			result = transformTableExprFunc(pstate, (TableExprFunc *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2747,6 +2753,217 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a table expression - TableExprFunc is raw form of TableExpr.
+ *
+ * Transform the namespace clauses, the document-generating expression, the
+ * row-generating expression, the column-generating expressions, and the
+ * default value expressions.
+ */
+static Node *
+transformTableExprFunc(ParseState *pstate, TableExprFunc * t)
+{
+	TableExpr  *newt = makeNode(TableExpr);
+	const char *constructName;
+	Oid			docType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	docType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, t->location);
+
+	/* Transform and apply typecast to the row-generating expression ... */
+	Assert(t->rowexpr != NULL);
+	newt->rowexpr = coerce_to_specific_type(pstate,
+									transformExprRecurse(pstate, t->rowexpr),
+											TEXTOID,
+											constructName);
+	/* ... and to the document itself */
+	Assert(t->docexpr != NULL);
+	newt->docexpr = coerce_to_specific_type(pstate,
+									transformExprRecurse(pstate, t->docexpr),
+											docType,
+											constructName);
+
+	/* undef ordinality column number */
+	newt->ordinalitycol = -1;
+
+	/* Columns, if any, also need to be transformed */
+	if (t->columns != NIL)
+	{
+		ListCell   *col;
+		char	  **names;
+		int			i = 0;
+
+		newt->evalcols = true;
+
+		newt->colcount = list_length(t->columns);
+		names = palloc(sizeof(char *) * newt->colcount);
+
+		foreach(col, t->columns)
+		{
+			TableExprFuncCol *rawc = (TableExprFuncCol *) lfirst(col);
+			Oid			typid;
+			int32		typmod;
+			Node	   *colexpr;
+			Node	   *coldefexpr;
+			int			j;
+
+			newt->colnames = lappend(newt->colnames,
+									 makeString(pstrdup(rawc->colname)));
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				if (newt->ordinalitycol != -1)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+
+				typid = INT4OID;
+				typmod = -1;
+				newt->ordinalitycol = i;
+			}
+			else
+			{
+				if (rawc->typeName->setof)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+							 errmsg("column \"%s\" cannot be declared SETOF",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &typid, &typmod);
+			}
+
+			newt->coltypes = lappend_oid(newt->coltypes, typid);
+			newt->coltypmods = lappend_int(newt->coltypmods, typmod);
+			newt->colcollations = lappend_oid(newt->colcollations,
+			 type_is_collatable(typid) ? DEFAULT_COLLATION_OID : InvalidOid);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->colexpr)
+				colexpr = coerce_to_specific_type(pstate,
+								 transformExprRecurse(pstate, rawc->colexpr),
+												  TEXTOID,
+												  constructName);
+			else
+				colexpr = NULL;
+
+			if (rawc->coldefexpr)
+				coldefexpr = coerce_to_specific_type_typmod(pstate,
+							  transformExprRecurse(pstate, rawc->coldefexpr),
+															typid, typmod,
+															constructName);
+			else
+				coldefexpr = NULL;
+
+			newt->colexprs = lappend(newt->colexprs, colexpr);
+			newt->coldefexprs = lappend(newt->coldefexprs, coldefexpr);
+
+			if (rawc->notnull)
+				newt->notnulls = bms_add_member(newt->notnulls, i);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(names[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			names[i] = rawc->colname;
+
+			i++;
+		}
+		newt->evalcols = true;
+		pfree(names);
+	}
+	else
+	{
+		newt->evalcols = false;
+
+		/*
+		 * When there are not explicit column, define the implicit one. The
+		 * rules for implicit column can be different. Currently only XMLTABLE
+		 * is supported.
+		 */
+		newt->colcount = 1;
+		newt->colnames = list_make1(makeString(pstrdup("xmltable")));
+		newt->coltypes = list_make1_oid(XMLOID);
+		newt->coltypmods = list_make1_int(-1);
+		newt->colcollations = list_make1_oid(InvalidOid);
+	}
+
+	/* Namespaces, if any, also need to be transformed */
+	if (t->namespaces != NIL)
+	{
+		ListCell   *ns;
+		ListCell   *lc2;
+		List	   *ns_uris = NIL;
+		List	   *ns_names = NIL;
+		bool		default_ns_seen = false;
+
+		foreach(ns, t->namespaces)
+		{
+			ResTarget  *r = (ResTarget *) lfirst(ns);
+
+			Assert(IsA(r, ResTarget));
+
+			ns_uris = lappend(ns_uris,
+							  coerce_to_specific_type(pstate,
+										transformExprRecurse(pstate, r->val),
+													  TEXTOID,
+													  constructName));
+
+			/* Verify consistency of name list: no dupes, only one DEFAULT */
+			if (r->name != NULL)
+			{
+				foreach(lc2, ns_names)
+				{
+					char	   *name = strVal(lfirst(lc2));
+
+					if (name == NULL)
+						continue;
+					if (strcmp(name, r->name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("namespace name \"%s\" is not unique",
+										name),
+								 parser_errposition(pstate, r->location)));
+				}
+			}
+			else
+			{
+				if (default_ns_seen)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one default namespace is allowed"),
+							 parser_errposition(pstate, r->location)));
+				default_ns_seen = true;
+			}
+
+			/* Note the string may be NULL */
+			ns_names = lappend(ns_names, makeString(r->name));
+		}
+
+		newt->ns_uris = ns_uris;
+		newt->ns_names = ns_names;
+	}
+
+	newt->location = t->location;
+
+	return (Node *) newt;
+}
+
+/*
  * Transform a "row compare-op row" construct
  *
  * The inputs are lists of already-transformed expressions.
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 081a8dd..b3b7d95 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_TableExprFunc:
+			/*
+			 * 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 e1ea067..4ca04a3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8547,6 +8547,125 @@ 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->ns_uris != NIL)
+				{
+					ListCell   *lc1,
+							   *lc2;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					forboth(lc1, te->ns_uris, lc2, te->ns_names)
+					{
+						Node	   *expr = (Node *) lfirst(lc1);
+						char	   *name = strVal(lfirst(lc2));
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (name != NULL)
+						{
+							get_rule_expr(expr, context, true);
+							appendStringInfo(buf, " AS %s", name);
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(expr, context, true);
+						}
+					}
+					appendStringInfoString(buf, "), ");
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->rowexpr, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->docexpr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->evalcols)
+				{
+					ListCell   *l1;
+					ListCell   *l2;
+					ListCell   *l3;
+					ListCell   *l4;
+					ListCell   *l5;
+					int			colnum = 0;
+
+					l2 = list_head(te->coltypes);
+					l3 = list_head(te->coltypmods);
+					l4 = list_head(te->colexprs);
+					l5 = list_head(te->coldefexprs);
+
+					appendStringInfoString(buf, " COLUMNS ");
+					foreach(l1, te->colnames)
+					{
+						char	   *colname = strVal(lfirst(l1));
+						Oid			typid;
+						int32		typmod;
+						Node	   *colexpr;
+						Node	   *coldefexpr;
+
+						typid = lfirst_oid(l2);
+						l2 = lnext(l2);
+						typmod = lfirst_int(l3);
+						l3 = lnext(l3);
+						colexpr = (Node *) lfirst(l4);
+						l4 = lnext(l4);
+						coldefexpr = (Node *) lfirst(l5);
+						l5 = lnext(l5);
+
+						if (colnum > 0)
+							appendStringInfoString(buf, ", ");
+
+						appendStringInfoString(buf, quote_identifier(colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (te->ordinalitycol != colnum)
+						{
+							appendStringInfoString(buf,
+								 format_type_with_typemod(typid, typmod));
+
+							if (coldefexpr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) coldefexpr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (colexpr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) colexpr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (!bms_is_empty(te->notnulls) &&
+											bms_is_member(colnum, te->notnulls))
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+							appendStringInfoString(buf, "FOR ORDINALITY");
+
+						colnum++;
+					}
+				}
+
+				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 dcc5d62..fb8bf9d 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,49 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+/* XMLTABLE support */
+#ifdef USE_LIBXML
+/* random number to identify XmlTableContext */
+#define XMLTABLE_CONTEXT_MAGIC	46922182
+typedef struct XmlTableBuilderData
+{
+	int			magic;
+	char	   *def_namespace_name;
+	long int	row_count;
+	PgXmlErrorContext *xmlerrcxt;
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr	doc;
+	xmlXPathContextPtr xpathcxt;
+	xmlXPathCompExprPtr xpathcomp;
+	xmlXPathObjectPtr xpathobj;
+	xmlXPathCompExprPtr *xpathscomp;
+} XmlTableBuilderData;
+#endif
+
+static void XmlTableInitBuilder(TableExprState *state);
+static void XmlTableSetDoc(TableExprState *state, Datum value);
+static void XmlTableSetNamespace(TableExprState *state, char *name,
+					 char *uri);
+static void XmlTableSetRowFilter(TableExprState *state, char *path);
+static void XmlTableSetColumnFilter(TableExprState *state, char *path,
+						int colnum);
+static bool XmlTableFetchRow(TableExprState *state);
+static Datum XmlTableGetValue(TableExprState *state, int colnum,
+				 bool *isnull);
+static void XmlTableDestroyBuilder(TableExprState *state);
+
+const TableExprRoutine XmlTableExprRoutine =
+{
+	XmlTableInitBuilder,
+	XmlTableSetDoc,
+	XmlTableSetNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyBuilder
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4109,583 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Functions for XmlTableExprRoutine
+ *
+ */
+
+/*
+ * 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
+
+/*
+ * Fill in XmlTableBuilderData for XmlTable builder. Initialize XML parser.
+ */
+static void
+XmlTableInitBuilder(TableExprState *state)
+{
+#ifdef USE_LIBXML
+	volatile xmlParserCtxtPtr ctxt = NULL;
+	XmlTableBuilderData *xtCxt;
+	PgXmlErrorContext *xmlerrcxt;
+
+	xtCxt = palloc0(sizeof(XmlTableBuilderData));
+	xtCxt->magic = XMLTABLE_CONTEXT_MAGIC;
+	xtCxt->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) *
+							  state->resultSlot->tts_tupleDescriptor->natts);
+
+	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+	PG_TRY();
+	{
+		xmlInitParser();
+
+		ctxt = xmlNewParserCtxt();
+		if (ctxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate parser context");
+	}
+	PG_CATCH();
+	{
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xtCxt->xmlerrcxt = xmlerrcxt;
+	xtCxt->ctxt = ctxt;
+
+	state->opaque = xtCxt;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * Returns private data from executor state. Ensure validity
+ * by check with MAGIC number.
+ */
+static inline XmlTableBuilderData *
+GetXmlTableBuilderPrivateData(TableExprState *state, const char *fname)
+{
+	XmlTableBuilderData *result;
+
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "%s called with invalid TableExprState",
+			 fname);
+	result = (XmlTableBuilderData *) state->opaque;
+	if (result->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "%s called with invalid TableExprState",
+			 fname);
+
+	return result;
+}
+
+/*
+ * XmlTableSetDoc
+ */
+static void
+XmlTableSetDoc(TableExprState *state, Datum value)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	xmltype    *xmlval = DatumGetXmlP(value);
+	xmlChar    *xstr;
+	int			length;
+	volatile xmlDocPtr doc = NULL;
+	volatile xmlXPathContextPtr xpathcxt = NULL;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetDoc");
+
+	length = VARSIZE(xmlval) - VARHDRSZ;
+	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;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetNamespace.  Add a namespace declaration.
+ */
+static void
+XmlTableSetNamespace(TableExprState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	if (name == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("DEFAULT namespace is not supported")));
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetNamespace");
+
+	if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+						   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 */
+}
+
+/*
+ * XmlTableSetRowFilter
+ */
+static void
+XmlTableSetRowFilter(TableExprState *state, char *path)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	xmlChar    *xstr;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetRowFilter");
+
+	if (path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("row path filter must not be empty string")));
+
+	xstr = 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");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetColumnFilter
+ *
+ * The path can be NULL, when the only one result column is implicit.
+ */
+static void
+XmlTableSetColumnFilter(TableExprState *state, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	xmlChar    *xstr;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetColumnFilter");
+
+	if (path != NULL)
+	{
+		if (*path == '\0')
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_EXCEPTION),
+					 errmsg("column path filter must not be empty string")));
+
+		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");
+	}
+	else
+	{
+		Assert(!state->evalcols || colnum == state->ordinalitycol);
+
+		xtCxt->xpathscomp[colnum] = NULL;
+	}
+#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(TableExprState *state)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableFetchRow");
+
+	/*
+	 * XmlTable returns table - set of composite values. The error context, is
+	 * used for producement more values, between two calls, there can be
+	 * created and used another libxml2 error context. It is libxml2 global
+	 * value, so it should be refreshed any time before any libxml2 usage,
+	 * that is finished by returning some value.
+	 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		xtCxt->row_count = 0;
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->row_count++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+}
+
+/*
+ * 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(TableExprState *state, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
+
+	if (state->evalcols)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = NULL;
+
+		Assert(xtCxt->xpathscomp[colnum] != NULL);
+
+		/* fast leaving */
+		if (cur->type != XML_ELEMENT_NODE)
+			elog(ERROR, "unexpected xmlNode type");
+
+		PG_TRY();
+		{
+			Form_pg_attribute attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum];
+
+			/* 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 = attr->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(&state->in_functions[colnum],
+											   cstr,
+											   state->typioparams[colnum],
+											   attr->atttypmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&state->in_functions[colnum],
+										 (char *) column_xpathobj->stringval,
+										   state->typioparams[colnum],
+										   attr->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
+	{
+		/* Use whole row, when user doesn't specify a column */
+		cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+		result = InputFunctionCall(&state->in_functions[0],
+								   cstr,
+								   state->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
+XmlTableDestroyBuilder(TableExprState *state)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableDestroyBuilder");
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int			i;
+
+		for (i = 0; i < state->resultSlot->tts_tupleDescriptor->natts; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+	/* not valid anymore */
+	xtCxt->magic = 0;
+	state->opaque = NULL;
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 4d8a121..2b8a283 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,29 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		TupleDesc tupdesc;
+		TableExpr *te = (TableExpr *) expr;
+
+		/*
+		 * the result type of TableExpr is determinable, although it is not
+		 * stored in system catalog. We can solve this issue here.  It is
+		 * similar to functions with polymorphic OUT parameters.
+		 */
+		tupdesc = BuildDescFromLists(te->colnames,
+									 te->coltypes,
+									 te->coltypmods,
+									 te->colcollations);
+		assign_record_type_typmod(tupdesc);
+
+		if (resultTypeId)
+			*resultTypeId = tupdesc->tdtypeid;
+		if (resultTupleDesc)
+			*resultTupleDesc = tupdesc;
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..553ceaa
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,65 @@
+/*-------------------------------------------------------------------------
+ *
+ * tableexpr.h
+ *				interface for TableExpr builder
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/tableexpr.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TABLEEXPR_H
+#define TABLEEXPR_H
+
+#include "nodes/execnodes.h"
+
+/*
+ * TableExprRoutine holds function pointers used for generating content of
+ * table-expression functions, such as XMLTABLE.
+ *
+ * InitBuilder initialize table builder private objects.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed
+ * from executor state.
+ *
+ * SetDoc is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * SetNamespace is called to pass namespace declarations from the table
+ * expression.  This function may be NULL if namespaces are not supported by
+ * the table builder.  Namespaces must be given before setting the row and
+ * column filters.  If the name is given as NULL, the entry shall be for the
+ * default namespace.
+ *
+ * SetRowFilter is called do define the row-generating filter, which shall be
+ * used to extract each row from the input document.
+ *
+ * SetColumnFilter is called once for each column, to define the column-
+ * generating filter for the given column.
+ *
+ * FetchRow shall be called repeatedly until it returns that no more rows are
+ * found in the document.  On each invocation it shall set state in the table
+ * builder context such that each subsequent GetValue call returns the value
+ * for the indicated column for the row being processed.
+ *
+ * DestroyBuilder shall release all resources associated with a table builder
+ * context.  It may be called either because all rows have been consumed, or
+ * because an error ocurred while processing the table expression.
+ */
+typedef struct TableExprRoutine
+{
+	void		(*InitBuilder) (TableExprState *state);
+	void		(*SetDoc) (TableExprState *state, Datum value);
+	void		(*SetNamespace) (TableExprState *state, char *name,
+												 char *uri);
+	void		(*SetRowFilter) (TableExprState *state, char *path);
+	void		(*SetColumnFilter) (TableExprState *state, char *path,
+												int colnum);
+	bool		(*FetchRow) (TableExprState *state);
+	Datum		(*GetValue) (TableExprState *state, int colnum, bool *isnull);
+	void		(*DestroyBuilder) (TableExprState *state);
+} TableExprRoutine;
+
+#endif   /* TABLEEXPR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ce13bf7..e31b0ee 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1022,6 +1022,32 @@ 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;
+	const struct TableExprRoutine *routine;	/* table builder methods */
+	MemoryContext buildercxt;	/* memory context used by builder */
+	void	   *opaque;			/* table builder private space */
+	TupleTableSlot *resultSlot; /* output tuple table slot */
+	List	   *ns_names;		/* list of str nodes with namespace names */
+	List	   *ns_uris;		/* list of states of namespace uri exprs */
+	FmgrInfo   *in_functions;	/* input function for each column */
+	Oid		   *typioparams;	/* typioparam for each column */
+	bool		evalcols;		/* true, when column was not defined by user */
+	int			ordinalitycol;	/* number of ordinality column or -1 */
+	int			rownum;			/* row number to be output next */
+	ExprState  *docexpr;		/* state for document expression */
+	ExprState  *rowexpr;		/* state for row-generating expression */
+	List	   *colexprs;		/* state for column-generating expression */
+	List	   *coldefexprs;	/* state for column default values */
+	Bitmapset  *notnulls;		/* nullability flag for each column */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a1bb0ac..25dae40 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -174,6 +174,7 @@ typedef enum NodeTag
 	T_CoerceToDomainValue,
 	T_SetToDefault,
 	T_CurrentOfExpr,
+	T_TableExpr,
 	T_InferenceElem,
 	T_TargetEntry,
 	T_RangeTblRef,
@@ -216,6 +217,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -459,6 +461,8 @@ typedef enum NodeTag
 	T_PartitionSpec,
 	T_PartitionBoundSpec,
 	T_PartitionRangeDatum,
+	T_TableExprFunc,
+	T_TableExprFuncCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7ceaa22..87b13bf 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -164,7 +164,6 @@ typedef struct Query
 										 * part of Query. */
 } Query;
 
-
 /****************************************************************************
  *	Supporting data structures for Parse Trees
  *
@@ -230,6 +229,37 @@ typedef struct ParamRef
 } ParamRef;
 
 /*
+ * TableExprFunc - a TableExpr in raw form.
+ */
+typedef struct TableExprFunc
+{
+	NodeTag		type;
+	Node	   *docexpr;
+	Node	   *rowexpr;
+	List	   *namespaces;		/* list of namespaces */
+	List	   *columns;		/* list of columns (TableExprFuncCol) */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprFunc;
+
+/*
+ * TableExprFuncCol - one column in a TableExpr column list, in raw form.
+ *
+ * If for_ordinality is true (FOR ORDINALITY), then the column is an int4
+ * column and the rest of the fields are ignored.
+ */
+typedef struct TableExprFuncCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	TypeName   *typeName;		/* type of generated column */
+	bool		notnull;	/* nullability flag */
+	Node	   *colexpr;	/* column filter expression */
+	Node	   *coldefexpr;	/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprFuncCol;
+
+/*
  * A_Expr - infix, prefix, and postfix expressions
  */
 typedef enum A_Expr_Kind
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f72ec24..9951289 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -18,6 +18,7 @@
 #define PRIMNODES_H
 
 #include "access/attnum.h"
+#include "nodes/bitmapset.h"
 #include "nodes/pg_list.h"
 
 
@@ -1269,6 +1270,30 @@ typedef struct InferenceElem
 	Oid			inferopclass;	/* OID of att opclass, or InvalidOid */
 } InferenceElem;
 
+/*----------
+ * TableExpr - for table expressions, such as XMLTABLE.
+ *----------
+ */
+typedef struct TableExpr
+{
+	Expr		xpr;
+	List	   *ns_uris;		/* list of namespace uri */
+	List	   *ns_names;		/* list of namespace names */
+	Node	   *docexpr;		/* input document */
+	Node	   *rowexpr;		/* row filter expression */
+	int			colcount;		/* number of output columns */
+	List	   *colnames;		/* column names (list of String) */
+	List	   *coltypes;		/* OID list of column type OIDs */
+	List	   *coltypmods;		/* integer list of column typmods */
+	List	   *colcollations;	/* OID list of column collation OIDs */
+	List	   *colexprs;		/* list of column filter expressions */
+	List	   *coldefexprs;	/* list of column default expressions */
+	Bitmapset  *notnulls;		/* where not null is required */
+	int			ordinalitycol;	/* number of ordinality column, -1 if not used */
+	bool		evalcols;		/* true, when columns should be evaluated */
+	int			location;		/* token location, or -1 if unknown */
+} TableExpr;
+
 /*--------------------
  * TargetEntry -
  *	   a target entry (used in query target lists)
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 9978573..94af546 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -82,6 +82,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -443,10 +444,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 b50992c..3eed819 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 10f6bf5..4c8d9c2 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -18,6 +18,7 @@
 #include "fmgr.h"
 #include "nodes/execnodes.h"
 #include "nodes/primnodes.h"
+#include "executor/tableexpr.h"
 
 typedef struct varlena xmltype;
 
@@ -109,4 +110,6 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern const TableExprRoutine XmlTableExprRoutine;
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..941badb 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,413 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>
 (1 row)
 
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+  <COUNTRY_ID>AU</COUNTRY_ID>
+  <COUNTRY_NAME>Australia</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+  <COUNTRY_ID>CN</COUNTRY_ID>
+  <COUNTRY_NAME>China</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+  <COUNTRY_ID>HK</COUNTRY_ID>
+  <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+  <COUNTRY_ID>IN</COUNTRY_ID>
+  <COUNTRY_NAME>India</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+  <COUNTRY_ID>JP</COUNTRY_ID>
+  <COUNTRY_NAME>Japan</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+  <COUNTRY_ID>SG</COUNTRY_ID>
+  <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+-- XMLTABLE 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
+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>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+ ent 
+-----
+ '
+ "
+ &
+ <
+ >
+(5 rows)
+
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+       ent        
+------------------
+ <ent>'</ent>
+ <ent>"</ent>
+ <ent>&amp;</ent>
+ <ent>&lt;</ent>
+ <ent>&gt;</ent>
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+                                                                                                                                                                                                                     QUERY PLAN                                                                                                                                                                                                                      
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  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>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+ERROR:  unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</en...
+                                              ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+ERROR:  unsupported XML feature
+LINE 1: SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</en...
+                                              ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+                                                                                                                                                                                                                     QUERY PLAN                                                                                                                                                                                                                      
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  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..9d6eb05 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,413 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&nbsp;</chapter>
 (1 row)
 
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+  <COUNTRY_ID>AU</COUNTRY_ID>
+  <COUNTRY_NAME>Australia</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+  <COUNTRY_ID>CN</COUNTRY_ID>
+  <COUNTRY_NAME>China</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+  <COUNTRY_ID>HK</COUNTRY_ID>
+  <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+  <COUNTRY_ID>IN</COUNTRY_ID>
+  <COUNTRY_NAME>India</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+  <COUNTRY_ID>JP</COUNTRY_ID>
+  <COUNTRY_NAME>Japan</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+  <COUNTRY_ID>SG</COUNTRY_ID>
+  <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+-- XMLTABLE 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
+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>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+ ent 
+-----
+ '
+ "
+ &
+ <
+ >
+(5 rows)
+
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+       ent        
+------------------
+ <ent>'</ent>
+ <ent>"</ent>
+ <ent>&amp;</ent>
+ <ent>&lt;</ent>
+ <ent>&gt;</ent>
+(5 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+                                                                                                                                                                                                                     QUERY PLAN                                                                                                                                                                                                                      
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  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>&nbsp;</chapter>');
+
+-- XMLPATH tests
+CREATE TABLE xmldata(data xml);
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="1">
+  <COUNTRY_ID>AU</COUNTRY_ID>
+  <COUNTRY_NAME>Australia</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="2">
+  <COUNTRY_ID>CN</COUNTRY_ID>
+  <COUNTRY_NAME>China</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="3">
+  <COUNTRY_ID>HK</COUNTRY_ID>
+  <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="4">
+  <COUNTRY_ID>IN</COUNTRY_ID>
+  <COUNTRY_NAME>India</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID>
+</ROW>
+<ROW id="5">
+  <COUNTRY_ID>JP</COUNTRY_ID>
+  <COUNTRY_NAME>Japan</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+</ROW>
+<ROW id="6">
+  <COUNTRY_ID>SG</COUNTRY_ID>
+  <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+  <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+</ROW>
+</ROWS>');
+
+-- XMLTABLE 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>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+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;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 993880d..aec32ca 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1,3 +1,15 @@
+XmlTableBuilderData
+TableExprRoutine
+XmlTableContext
+TableExprBuilder
+TableExprState
+TableExprRawCol
+TableExpr
+TableExprColumn
+SQLValueFunction
+max_parallel_hazard_context
+TriggerTransition
+SQLValueFunctionOp
 ABITVEC
 ACCESS_ALLOWED_ACE
 ACL_SIZE_INFORMATION