patch: function xmltable

Started by Pavel Stehuleover 9 years ago161 messages
#1Pavel Stehule
pavel.stehule@gmail.com
1 attachment(s)

Hi

I am sending implementation of xmltable function. The code should to have
near to final quality and it is available for testing.

I invite any help with documentation and testing.

Regards

Pavel

Attachments:

xmltable-20160819.patchtext/x-patch; charset=US-ASCII; name=xmltable-20160819.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 169a385..a6334b6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10099,6 +10099,47 @@ SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
     </para>
    </sect3>
 
+   <sect3>
+    <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>|DEFAULT <replaceable>namespace uri</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 table based on passed XML value.
+    </para>
+
+    <para>
+<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>
+   </sect3>
+
    <sect3 id="functions-xml-xmlagg">
     <title><literal>xmlagg</literal></title>
 
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 743e7d6..77a06da 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -189,6 +189,9 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isnull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4503,203 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * ----------------------------------------------------------------
+ */
+
+#define XMLTABLE_DEFAULT_NAMESPACE			"pgdefxmlns"
+
+static Datum
+execEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isNull, ExprDoneCond *isDone)
+{
+	TupleDesc tupdesc;
+	HeapTuple		tuple;
+	HeapTupleHeader		result;
+	int					i;
+	Datum		*values;
+	bool		*nulls;
+	Datum	value;
+	bool	isnull;
+	xmltype		   *xmlval;
+	text		   *row_path;
+
+	tupdesc = tstate->tupdesc;
+
+	if (tstate->firstRow)
+	{
+		ListCell	*res;
+
+		/* Evaluate expression first */
+		value = ExecEvalExpr(tstate->expr, econtext, &isnull, NULL);
+		if (isnull)
+		{
+			*isDone = ExprSingleResult;
+			*isNull = true;
+			return (Datum) 0;
+		}
+		xmlval = DatumGetXmlP(value);
+
+		value = ExecEvalExpr(tstate->row_path_expr, econtext, &isnull, NULL);
+		if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					  errmsg("row query must not be null")));
+		row_path = DatumGetTextP(value);
+
+		Assert(tstate->xmltableCxt == NULL);
+		tstate->xmltableCxt = initXmlTableContext(xmlval,
+													tstate->used_dns ?
+										XMLTABLE_DEFAULT_NAMESPACE : NULL,
+													tstate->ncols,
+															  tstate->in_functions,
+															  tstate->typioparams,
+													econtext->ecxt_per_query_memory);
+
+		foreach(res, tstate->namespaces)
+		{
+			ResTarget *rt = (ResTarget *) lfirst(res);
+			char	*ns_uri;
+
+			value = ExecEvalExpr((ExprState *) rt->val, econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					  errmsg("namespace uri must not be null")));
+			ns_uri = text_to_cstring(DatumGetTextP(value));
+
+			XmlTableSetNs(tstate->xmltableCxt, rt->name, ns_uri);
+		}
+
+		XmlTableSetRowPath(tstate->xmltableCxt, row_path);
+
+		/* Path caclulation */
+		for (i = 0; i < tstate->ncols; i++)
+		{
+			char *col_path;
+
+			if (tstate->col_path_expr[i] != NULL)
+			{
+				value = ExecEvalExpr(tstate->col_path_expr[i], econtext, &isnull, NULL);
+				if (isnull)
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						  errmsg("column path for column \"%s\" must not be null",
+											  NameStr(tupdesc->attrs[i]->attname))));
+				col_path = text_to_cstring(DatumGetTextP(value));
+			}
+			else
+				col_path = NameStr(tupdesc->attrs[i]->attname);
+
+			XmlTableSetColumnPath(tstate->xmltableCxt, i,
+											tupdesc->attrs[i]->atttypid, col_path);
+		}
+		tstate->firstRow = false;
+	}
+
+	values = tstate->values;
+	nulls = tstate->nulls;
+
+	if (XmlTableFetchRow(tstate->xmltableCxt))
+	{
+		if (tstate->ncols > 0)
+		{
+			for (i = 0; i < tstate->ncols; i++)
+			{
+				if (i != tstate->for_ordinality_col - 1)
+				{
+					values[i] = XmlTableGetValue(tstate->xmltableCxt, i,
+												  tupdesc->attrs[i]->atttypid,
+												  tupdesc->attrs[i]->atttypmod,
+																			  &isnull);
+					if (isnull && tstate->def_expr[i] != NULL)
+						values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+					if (isnull && tstate->not_null[i])
+						ereport(ERROR,
+								(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+								 errmsg("null is not allowed in column \"%s\"",
+									  NameStr(tupdesc->attrs[i]->attname))));
+					nulls[i] = isnull;
+				}
+				else
+				{
+					values[i] = Int32GetDatum(++tstate->rownum);
+					nulls[i] = false;
+				}
+			}
+		}
+		else
+			values[0] = XmlTableGetRowValue(tstate->xmltableCxt, &nulls[0]);
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		result = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(result, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type info we identified before.
+		 */
+		HeapTupleHeaderSetTypeId(result, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(result, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		/* no more rows */
+		XmlTableFreeContext(tstate->xmltableCxt);
+		tstate->xmltableCxt = NULL;
+
+		/* ensure releasing all memory */
+		MemoryContextReset(tstate->per_rowgroup_memory);
+
+		/* next row will be first again */
+		tstate->firstRow = true;
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		return (Datum) 0;
+	}
+
+	*isNull = false;
+	*isDone = ExprMultipleResult;
+
+	return HeapTupleHeaderGetDatum(result);
+}
+
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isNull, ExprDoneCond *isDone)
+{
+	/* Ensure releasing context every exception */
+	Datum		result;
+
+	PG_TRY();
+	{
+		result = execEvalTableExpr(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		if (tstate->xmltableCxt != NULL)
+		{
+			XmlTableFreeContext(tstate->xmltableCxt);
+			tstate->xmltableCxt = NULL;
+		}
+
+		MemoryContextDelete(tstate->per_rowgroup_memory);
+		tstate->per_rowgroup_memory = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5462,132 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr	   *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int				ncols;
+				ListCell	   *col;
+				TupleDesc		tupdesc;
+				int				i;
+
+				tstate->firstRow = true;
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+
+				/* when typmod is not valid, refresh it */
+				if (te->typmod == -1)
+				{
+					TupleDesc tupdesc = TableExprGetTupleDesc(te);
+					tstate->typid = tupdesc->tdtypeid;
+					tstate->typmod = tupdesc->tdtypmod;
+				}
+				else
+				{
+					tstate->typid = te->typid;
+					tstate->typmod = te->typmod;
+				}
+
+				tupdesc = lookup_rowtype_tupdesc_copy(tstate->typid, tstate->typmod);
+				ncols = tupdesc->natts;
+				tstate->tupdesc = tupdesc;
+
+				/* result is one more columns every time */
+				Assert(ncols > 0);
+
+				tstate->values = palloc(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid		in_funcid;
+
+					getTypeInputInfo(tupdesc->attrs[i]->atttypid, &in_funcid,
+														&tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_path_expr = ExecInitExpr((Expr *) te->row_path, parent);
+				tstate->expr = ExecInitExpr((Expr *) te->expr, parent);
+
+				if (te->cols)
+				{
+					Assert(ncols == list_length(te->cols));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_path_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+
+					i = 0;
+					foreach(col, te->cols)
+					{
+						ColumnDef *column = (ColumnDef *) lfirst(col);
+
+						if (column->typeName != NULL)
+						{
+							if (column->cooked_default != NULL)
+								tstate->def_expr[i] = ExecInitExpr((Expr *) column->cooked_default,
+																	parent);
+
+							if (column->cooked_path != NULL)
+								tstate->col_path_expr[i] = ExecInitExpr((Expr *) column->cooked_path,
+																	parent);
+
+							tstate->not_null[i] = column->is_not_null;
+						}
+						else
+						{
+							/* For ordinality column found */
+							tstate->for_ordinality_col = i + 1;
+						}
+						i++;
+					}
+					tstate->rownum = 0;
+				}
+				else
+				{
+					/* There are not any related data */
+					tstate->def_expr = NULL;
+					tstate->col_path_expr = NULL;
+					tstate->not_null = NULL;
+					tstate->ncols = 0;
+				}
+
+				if (te->namespaces)
+				{
+					List		*preparedlist = NIL;
+					ListCell	*res;
+
+					foreach(res, te->namespaces)
+					{
+						ResTarget *rt = (ResTarget *) lfirst(res);
+						ResTarget *prt = makeNode(ResTarget);
+
+						if (rt->name == NULL)
+							tstate->used_dns = true;
+
+						prt->name = rt->name;
+						prt->val = (Node *) ExecInitExpr((Expr *) rt->val, parent);
+						prt->location = rt->location;
+						preparedlist = lappend(preparedlist, prt);
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->xmltableCxt = NULL;
+				tstate->per_rowgroup_memory = AllocSetContextCreate(CurrentMemoryContext,
+																		"XmlTable per rowgroup context",
+																		ALLOCSET_DEFAULT_MINSIZE,
+																		ALLOCSET_DEFAULT_INITSIZE,
+																		ALLOCSET_DEFAULT_MAXSIZE);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c7a0644..f1bbc4c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1986,6 +1986,25 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr *newnode = makeNode(TableExpr);
+
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_NODE_FIELD(row_path);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(cols);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -2623,6 +2642,8 @@ _copyColumnDef(const ColumnDef *from)
 	COPY_SCALAR_FIELD(storage);
 	COPY_NODE_FIELD(raw_default);
 	COPY_NODE_FIELD(cooked_default);
+	COPY_NODE_FIELD(raw_path);
+	COPY_NODE_FIELD(cooked_path);
 	COPY_NODE_FIELD(collClause);
 	COPY_SCALAR_FIELD(collOid);
 	COPY_NODE_FIELD(constraints);
@@ -4583,6 +4604,9 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 448e1a9..6a1b9a0 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2376,6 +2376,8 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
 	COMPARE_SCALAR_FIELD(storage);
 	COMPARE_NODE_FIELD(raw_default);
 	COMPARE_NODE_FIELD(cooked_default);
+	COMPARE_NODE_FIELD(raw_path);
+	COMPARE_NODE_FIELD(cooked_path);
 	COMPARE_NODE_FIELD(collClause);
 	COMPARE_SCALAR_FIELD(collOid);
 	COMPARE_NODE_FIELD(constraints);
@@ -2630,6 +2632,20 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_NODE_FIELD(row_path);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(cols);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2895,6 +2911,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
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index d72a85e..47cc936 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -497,6 +497,8 @@ makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid)
 	n->storage = 0;
 	n->raw_default = NULL;
 	n->cooked_default = NULL;
+	n->raw_path = NULL;
+	n->cooked_path = NULL;
 	n->collClause = NULL;
 	n->collOid = collOid;
 	n->constraints = NIL;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..522aa93 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -257,6 +257,9 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = ((const TableExpr *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +495,8 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			return ((const TableExpr *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +732,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 +936,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 +1137,9 @@ 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;
@@ -1552,6 +1565,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2227,36 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+				ListCell	*col;
+				ListCell	*res;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+
+				foreach(col, te->cols)
+				{
+					ColumnDef	*column = (ColumnDef *) lfirst(col);
+
+					if (walker(column->cooked_default, context))
+						return true;
+					if (walker(column->cooked_path, context))
+						return true;
+				}
+
+				foreach(res, te->namespaces)
+				{
+					ResTarget *rt = (ResTarget *) lfirst(res);
+
+					if (walker(rt->val, context))
+						return true;
+				}
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3053,44 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+				TableExpr *newnode;
+				ListCell *col;
+				ListCell *res;
+				List	*resultlist = NIL;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_path, te->row_path, Node *);
+				MUTATE(newnode->expr, te->expr, Node *);
+
+				foreach(col, te->cols)
+				{
+					ColumnDef *column = (ColumnDef *) lfirst(col);
+					ColumnDef *newcol;
+
+					FLATCOPY(newcol, column, ColumnDef);
+					MUTATE(newcol->cooked_default, column->cooked_default, Node *);
+					MUTATE(newcol->cooked_path, column->cooked_path, Node *);
+					resultlist = lappend(resultlist, newcol);
+				}
+				newnode->cols = resultlist;
+
+				resultlist = NIL;
+				foreach(res, te->namespaces)
+				{
+					ResTarget *rt = (ResTarget *) lfirst(res);
+					ResTarget *newrt;
+
+					FLATCOPY(newrt, rt, ResTarget);
+					MUTATE(newrt->val, rt->val, Node *);
+					resultlist = lappend(resultlist, newrt);
+				}
+				newnode->namespaces = resultlist;
+
+				return (Node *) newnode;
+			}
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3568,6 +3652,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(coldef->raw_default, context))
 					return true;
+				if (walker(coldef->raw_path, context))
+					return true;
 				if (walker(coldef->collClause, context))
 					return true;
 				/* for now, constraints are ignored */
@@ -3622,6 +3708,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1fab807..851ae17 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1587,6 +1587,20 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_OID_FIELD(typid);
+	/* skip typmod, should not be persistent for dynamic RECORD */
+	WRITE_NODE_FIELD(row_path);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(cols);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -2586,6 +2600,8 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
 	WRITE_CHAR_FIELD(storage);
 	WRITE_NODE_FIELD(raw_default);
 	WRITE_NODE_FIELD(cooked_default);
+	WRITE_NODE_FIELD(raw_path);
+	WRITE_NODE_FIELD(cooked_path);
 	WRITE_NODE_FIELD(collClause);
 	WRITE_OID_FIELD(collOid);
 	WRITE_NODE_FIELD(constraints);
@@ -3862,6 +3878,9 @@ outNode(StringInfo str, const void *obj)
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c83063e..0abef07 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2263,6 +2263,109 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_OID_FIELD(typid);
+
+	/* Enforce fresh TupleDesc */
+	local_node->typmod = -1;
+
+	READ_NODE_FIELD(row_path);
+	READ_NODE_FIELD(expr);
+	READ_NODE_FIELD(cols);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readColumnDefNode
+ */
+static ColumnDef *
+_readColumnDefNode(void)
+{
+	READ_LOCALS(ColumnDef);
+
+	READ_STRING_FIELD(colname);
+	READ_NODE_FIELD(typeName);
+	READ_INT_FIELD(inhcount);
+	READ_BOOL_FIELD(is_local);
+	READ_BOOL_FIELD(is_not_null);
+	READ_BOOL_FIELD(is_from_type);
+	READ_CHAR_FIELD(storage);
+	READ_NODE_FIELD(raw_default);
+	READ_NODE_FIELD(cooked_default);
+	READ_NODE_FIELD(raw_path);
+	READ_NODE_FIELD(cooked_path);
+	READ_NODE_FIELD(collClause);
+	READ_OID_FIELD(collOid);
+	READ_NODE_FIELD(constraints);
+	READ_NODE_FIELD(fdwoptions);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+static TypeName *
+_readTypeNameNode(void)
+{
+	READ_LOCALS(TypeName);
+
+	READ_NODE_FIELD(names);
+	READ_OID_FIELD(typeOid);
+	READ_BOOL_FIELD(setof);
+	READ_BOOL_FIELD(pct_type);
+	READ_NODE_FIELD(typmods);
+	READ_INT_FIELD(typemod);
+	READ_NODE_FIELD(arrayBounds);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+static A_Const *
+_readA_ConstNode(void)
+{
+	Value *val;
+
+	READ_LOCALS(A_Const);
+
+	token = pg_strtok(&length);		/* skip: fldname */
+	val = nodeRead(NULL, 0);
+	memcpy(&(local_node->val), val, sizeof(Value));
+
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+static FuncCall *
+_readFuncCallNode(void)
+{
+	READ_LOCALS(FuncCall);
+
+	READ_NODE_FIELD(funcname);
+	READ_NODE_FIELD(args);
+	READ_NODE_FIELD(agg_order);
+	READ_NODE_FIELD(agg_filter);
+	READ_BOOL_FIELD(agg_within_group);
+	READ_BOOL_FIELD(agg_star);
+	READ_BOOL_FIELD(agg_distinct);
+	READ_BOOL_FIELD(func_variadic);
+	READ_NODE_FIELD(over);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2494,6 +2597,16 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("COLUMNDEF", 9))
+		return_value = _readColumnDefNode();
+	else if (MATCH("TYPENAME", 8))
+		return_value = _readTypeNameNode();
+	else if (MATCH("A_CONST", 7))
+		return_value = _readA_ConstNode();
+	else if (MATCH("FUNCCALL", 8))
+		return_value = _readFuncCallNode();
 	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 a40ad40..808dbd8 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -818,6 +818,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 cb5cfc4..56dba08 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	TableExprColOption	*te_colopt;
 }
 
 %type <node>	stmt schema_stmt
@@ -542,6 +543,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	TableExprColList TableExprColOptions TableExprColOptionsOpt
+%type <node>	TableExprCol
+%type <te_colopt>	TableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <target>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -573,10 +581,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
 
@@ -617,7 +625,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
@@ -646,8 +654,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
 
@@ -12539,6 +12547,150 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = $10;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+TableExprColList: TableExprCol							{ $$ = list_make1($1); }
+			| TableExprColList ',' TableExprCol			{ $$ = lappend($1, $3); }
+		;
+
+TableExprCol:
+			ColId Typename TableExprColOptionsOpt IsNotNull
+				{
+					ColumnDef	*column = makeNode(ColumnDef);
+					ListCell	*l;
+
+					column->colname = $1;
+					column->typeName = $2;
+					column->is_not_null = $4;
+					column->location = @1;
+
+					foreach(l, $3)
+					{
+						TableExprColOption *co = (TableExprColOption *) lfirst(l);
+
+						if (co->typ == TABLE_EXPR_COLOPT_DEFAULT)
+						{
+							if (column->raw_default != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed"),
+													parser_errposition(co->location)));
+							column->raw_default = co->val;
+						}
+						else if (co->typ == TABLE_EXPR_COLOPT_PATH)
+						{
+							if (column->raw_path != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+													parser_errposition(co->location)));
+							column->raw_path = co->val;
+						}
+					}
+					$$ = (Node *) column;
+				}
+			| ColId FOR ORDINALITY
+				{
+					ColumnDef	*column = makeNode(ColumnDef);
+
+					column->colname = $1;
+					column->location = @1;
+
+					$$ = (Node *) column;
+				}
+		;
+
+TableExprColOptionsOpt: TableExprColOptions				{ $$ = $1; }
+			| /* EMPTY */								{ $$ = NIL; }
+		;
+
+TableExprColOptions: TableExprColOption					{ $$ = list_make1($1); }
+			| TableExprColOptions TableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+TableExprColOption:
+			DEFAULT c_expr
+				{
+					$$ = palloc(sizeof(TableExprColOption));
+					$$->typ = TABLE_EXPR_COLOPT_DEFAULT;
+					$$->val = $2;
+					$$->location = @1;
+				}
+			| PATH c_expr
+				{
+					$$ = palloc(sizeof(TableExprColOption));
+					$$->typ = TABLE_EXPR_COLOPT_PATH;
+					$$->val = $2;
+					$$->location = @1;
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			a_expr AS ColLabel
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = $3;
+					$$->indirection = NIL;
+					$$->val = (Node *) $1;
+					$$->location = @1;
+				}
+			| DEFAULT a_expr
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = NULL;
+					$$->indirection = NIL;
+					$$->val = (Node *) $2;
+					$$->location = @1;
+				}
 		;
 
 /*
@@ -13677,6 +13829,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13810,6 +13963,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PATH
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -13975,10 +14129,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..0686e07 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1105,8 +1105,8 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
+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,15 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+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 63f7965..ec817cb 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -365,6 +367,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2678,6 +2684,168 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a TableExpr
+ */
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr *newte = makeNode(TableExpr);
+	TupleDesc tupdesc;
+
+	Assert(te->row_path != NULL);
+	Assert(te->expr != NULL);
+
+	newte->row_path = coerce_to_specific_type(pstate,
+								transformExprRecurse(pstate, te->row_path),
+														 TEXTOID,
+														 "XMLTABLE");
+	newte->expr = coerce_to_specific_type(pstate,
+								transformExprRecurse(pstate, te->expr),
+														 XMLOID,
+														 "XMLTABLE");
+
+	if (te->cols != NIL)
+	{
+		ListCell	   *col;
+		int				i = 1;
+		bool			for_ordinality = false;
+
+		tupdesc = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			ColumnDef  *column = (ColumnDef *) lfirst(col);
+			Oid		typid;
+			int32	typmod;
+			int			j;
+
+			if (column->typeName)
+			{
+				typenameTypeIdAndMod(NULL, column->typeName, &typid, &typmod);
+				TupleDescInitEntry(tupdesc, (AttrNumber) i, pstrdup(column->colname), typid, typmod, 0);
+
+				if (column->raw_default)
+					column->cooked_default = coerce_to_specific_type_typmod(pstate,
+								transformExprRecurse(pstate, column->raw_default),
+														 typid, typmod,
+														 "XMLTABLE");
+
+				if (column->raw_path)
+					column->cooked_path = coerce_to_specific_type(pstate,
+								transformExprRecurse(pstate, column->raw_path),
+														 TEXTOID,
+														 "XMLTABLE");
+			}
+			else
+			{
+				/* FOR ORDINALITY column has not defined typename */
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one column FOR ORDINALITY is allowed"),
+											parser_errposition(pstate, column->location)));
+
+				TupleDescInitEntry(tupdesc, (AttrNumber) i, pstrdup(column->colname), INT4OID, -1, 0);
+			}
+
+			/*
+			 * Storage is not used here, but zero value breaks out/read node functions.
+			 * So set default.
+			 */
+			column->storage = 'p';
+
+			/* the name should be unique */
+			for (j = 0; j < i - 1; j++)
+				if (strcmp(NameStr(tupdesc->attrs[j]->attname), column->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+													column->colname),
+								parser_errposition(pstate, column->location)));
+			i++;
+		}
+	}
+	else
+	{
+		/*
+		 * When columsn are not defined, then output is XML column.
+		 * ANSI/SQL standard doesn't specify the name of this column,
+		 * and there are not conformity between databases. Postgres
+		 * uses function name like default. This implementation
+		 * respects it.
+		 */
+		tupdesc = CreateTemplateTupleDesc(1, false);
+
+		/* Generate tupdesc with one auto XML attribute */
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xmltable", XMLOID, -1, 0);
+	}
+
+	newte->cols = te->cols;
+
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *res;
+		char	  **names;
+		bool		found_dns = false;
+		int			nnames = 0;
+
+		names = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(res, te->namespaces)
+		{
+			ResTarget *rt = (ResTarget *) lfirst(res);
+			ResTarget *newrt = makeNode(ResTarget);
+
+			/* Check unique name, and uniq default namespace */
+			if (rt->name != NULL)
+			{
+				int		i;
+
+				for (i = 0; i < nnames; i++)
+					if (strcmp(names[i], rt->name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the namespace name \"%s\" is not unique",
+														rt->name),
+									parser_errposition(pstate, rt->location)));
+				names[nnames++] = rt->name;
+			}
+			else
+			{
+				if (found_dns)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one default namespace is allowed"),
+									parser_errposition(pstate, rt->location)));
+				found_dns = true;
+			}
+
+			newrt->name = rt->name;
+			newrt->val = coerce_to_specific_type(pstate,
+									transformExprRecurse(pstate, rt->val),
+												 TEXTOID,
+												 "XMLTABLE");
+			newrt->location = rt->location;
+			transformlist = lappend(transformlist, newrt);
+		}
+		newte->namespaces = transformlist;
+		pfree(names);
+	}
+	else
+		newte->namespaces = NIL;
+
+	assign_record_type_typmod(tupdesc);
+
+	newte->typid = tupdesc->tdtypeid;
+	newte->typmod = tupdesc->tdtypmod;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
+/*
  * 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 b7b82bf..7ce209d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1837,6 +1837,9 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..6dd69cc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8280,6 +8281,100 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell *res;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(res, te->namespaces)
+					{
+						ResTarget *rt = (ResTarget *) lfirst(res);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (rt->name != NULL)
+						{
+							get_rule_expr((Node *) rt->val, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(rt->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr((Node *) rt->val, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_path, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->cols != NIL)
+				{
+					ListCell	*col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->cols)
+					{
+						ColumnDef *column = (ColumnDef *) lfirst(col);
+						Oid		typid;
+						int32	typmod;
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(column->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (column->typeName != NULL)
+						{
+							typenameTypeIdAndMod(NULL, column->typeName, &typid, &typmod);
+							appendStringInfoString(buf, format_type_with_typemod(typid, typmod));
+
+							if (column->cooked_default != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) column->cooked_default, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (column->cooked_path != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) column->cooked_path, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (column->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 7ed5bcb..287dcd9 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -4073,3 +4073,753 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * We need to work with XPath expression tokens. When expression
+ * starting or finishing with nodenames, then we can use prefix
+ * and suffix. When default namespace is defined, then we should to
+ * enhance any nodename and attribute without namespace by default
+ * namespace. The procession of XPath expression is linear.
+ */
+
+typedef enum
+{
+	XPATH_TOKEN_NONE,
+	XPATH_TOKEN_NAME,
+	XPATH_TOKEN_STRING,
+	XPATH_TOKEN_NUMBER,
+	XPATH_TOKEN_OTHER
+} XPathTokenType;
+
+typedef struct TokenInfo
+{
+	XPathTokenType		ttype;
+	char		   *start;
+	int				length;
+} XPathTokenInfo;
+
+#define TOKEN_STACK_SIZE		10
+
+typedef struct ParserData
+{
+	char		   *str;
+	char		   *cur;
+	XPathTokenInfo	stack[TOKEN_STACK_SIZE];
+	int				stack_length;
+} XPathParserData;
+
+#define NODENAME_FIRSTCHAR(c)	 ((c) == '_' || (c) == '-' || \
+								 ((c) >= 'A' && (c) <= 'Z') || \
+								 ((c) >= 'a' && (c) <= 'z') || \
+								 ((c) >= '0' && (c) <= '9'))
+
+#define IS_NODENAME_CHAR(c)		(NODENAME_FIRSTCHAR(c) || (c) == '.')
+
+/*
+ * Returns next char after last char of token
+ */
+static char *
+getXPathToken(char *str, XPathTokenInfo *ti)
+{
+
+	/* skip initial spaces */
+	while (*str == ' ')
+		str++;
+
+	if (*str != '\0')
+	{
+		char		c = *str;
+
+		ti->start = str++;
+
+		if (c >= '0' && c <= '9')
+		{
+			while (*str >= '0' && *str <= '9')
+				str++;
+			if (*str == '.')
+			{
+				str++;
+				while (*str >= '0' && *str <= '9')
+					str++;
+			}
+			ti->ttype = XPATH_TOKEN_NUMBER;
+		}
+		else if (NODENAME_FIRSTCHAR(c))
+		{
+			while (IS_NODENAME_CHAR(*str))
+				str++;
+
+			ti->ttype = XPATH_TOKEN_NAME;
+		}
+		else if (c == '"')
+		{
+			while (*str != '\0')
+				if (*str++ == '"')
+					break;
+
+			ti->ttype = XPATH_TOKEN_STRING;
+		}
+		else
+			ti->ttype = XPATH_TOKEN_OTHER;
+
+		ti->length = str - ti->start;
+	}
+	else
+	{
+		ti->start = NULL;
+		ti->length = 0;
+
+		ti->ttype = XPATH_TOKEN_NONE;
+	}
+
+	return str;
+}
+
+/*
+ * reset XPath parser stack
+ */
+static void
+initXPathParser(XPathParserData *parser, char *str)
+{
+	parser->str = str;
+	parser->cur = str;
+	parser->stack_length = 0;
+}
+
+static void
+nextXPathToken(XPathParserData *parser, XPathTokenInfo *ti)
+{
+	if (parser->stack_length > 0)
+		memcpy(ti, &parser->stack[--parser->stack_length],
+										  sizeof(XPathTokenInfo));
+	else
+		parser->cur = getXPathToken(parser->cur, ti);
+}
+
+static void
+pushXPathToken(XPathParserData *parser, XPathTokenInfo *ti)
+{
+	if (parser->stack_length == TOKEN_STACK_SIZE)
+		elog(ERROR, "internal error");
+	memcpy(&parser->stack[parser->stack_length++], ti,
+										  sizeof(XPathTokenInfo));
+}
+
+static void
+writeXPathToken(StringInfo str, XPathTokenInfo *ti)
+{
+	Assert(ti->ttype != XPATH_TOKEN_NONE);
+
+	if (ti->ttype != XPATH_TOKEN_OTHER)
+		appendBinaryStringInfo(str, ti->start, ti->length);
+	else
+		appendStringInfoChar(str, *ti->start);
+}
+
+/*
+ * Working horse for XPath transformation. When XPath starting by node name,
+ * then prefix have to be applied. When XPath ending by node name, then
+ * suffix will be used. Any unqualified node name should be qualified by
+ * default namespace.
+ */
+static void
+_transformXPath(StringInfo str, XPathParserData *parser,
+				  bool inside_predicate,
+				  char *prefix, char *suffix, char *default_ns_name)
+{
+	XPathTokenInfo	t1, t2;
+	bool			is_first_token = true;
+	bool			last_token_is_name = false;
+
+	nextXPathToken(parser, &t1);
+
+	while (t1.ttype != XPATH_TOKEN_NONE)
+	{
+		switch (t1.ttype)
+		{
+			case XPATH_TOKEN_NUMBER:
+			case XPATH_TOKEN_STRING:
+				last_token_is_name = false;
+				is_first_token = false;
+				writeXPathToken(str, &t1);
+				nextXPathToken(parser, &t1);
+				break;
+
+			case XPATH_TOKEN_NAME:
+				{
+					bool	is_qual_name = false;
+
+					/* inside predicate ignore keywords "and" "or" */
+					if (inside_predicate)
+					{
+						if ((strncmp(t1.start, "and", 3) == 0 && t1.length == 3) ||
+							(strncmp(t1.start, "or", 2) == 0 && t1.length == 2))
+						{
+							writeXPathToken(str, &t1);
+							nextXPathToken(parser, &t1);
+							break;
+						}
+					}
+
+					last_token_is_name = true;
+					nextXPathToken(parser, &t2);
+					if (t2.ttype == XPATH_TOKEN_OTHER)
+					{
+						if (*t2.start == '(')
+							last_token_is_name = false;
+						else if (*t2.start == ':')
+							is_qual_name = true;
+					}
+
+					if (is_first_token && last_token_is_name && prefix != NULL)
+						appendStringInfoString(str, prefix);
+
+					if (last_token_is_name && !is_qual_name && default_ns_name != NULL)
+						appendStringInfo(str, "%s:", default_ns_name);
+
+					writeXPathToken(str, &t1);
+					is_first_token = false;
+
+					if (is_qual_name)
+					{
+						writeXPathToken(str, &t2);
+						nextXPathToken(parser, &t1);
+						if (t1.ttype == XPATH_TOKEN_NAME)
+							writeXPathToken(str, &t1);
+						else
+							pushXPathToken(parser, &t1);
+					}
+					else
+						pushXPathToken(parser, &t2);
+
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_OTHER:
+				{
+					char c = *t1.start;
+
+					is_first_token = false;
+
+					writeXPathToken(str, &t1);
+
+					if (c == '[')
+						_transformXPath(str, parser, true, NULL, NULL, default_ns_name);
+					else
+					{
+						last_token_is_name = false;
+
+						if (c == ']' && inside_predicate)
+							return;
+
+						else if (c == '@')
+						{
+							nextXPathToken(parser, &t1);
+							if (t1.ttype == XPATH_TOKEN_NAME)
+							{
+								bool		is_qual_name = false;
+
+								nextXPathToken(parser, &t2);
+								if (t2.ttype == XPATH_TOKEN_OTHER && *t2.start == ':')
+									is_qual_name = true;
+
+								if (!is_qual_name && default_ns_name != NULL)
+									appendStringInfo(str, "%s:", default_ns_name);
+
+								writeXPathToken(str, &t1);
+								if (is_qual_name)
+								{
+									writeXPathToken(str, &t2);
+									nextXPathToken(parser, &t1);
+									if (t1.ttype == XPATH_TOKEN_NAME)
+										writeXPathToken(str, &t1);
+									else
+										pushXPathToken(parser, &t1);
+								}
+								else
+									pushXPathToken(parser, &t2);
+							}
+							else
+								pushXPathToken(parser, &t1);
+						}
+					}
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_NONE:
+				elog(ERROR, "should not be here");
+		}
+	}
+
+	if (last_token_is_name && suffix != NULL)
+		appendStringInfoString(str, suffix);
+}
+
+static void
+transformXPath(StringInfo str, char *xpath,
+					  char *prefix, char *suffix, char *default_ns_name)
+{
+	XPathParserData		parser;
+
+	initStringInfo(str);
+	initXPathParser(&parser, xpath);
+	_transformXPath(str, &parser, false, prefix, suffix, default_ns_name);
+}
+
+
+struct XmlTableContext
+{
+	MemoryContext			per_rowgroup_memory;
+	int						ncols;
+	PgXmlErrorContext	   *xmlerrcxt;
+	xmlParserCtxtPtr		ctxt;
+	xmlDocPtr				doc;
+	xmlXPathContextPtr		xpathcxt;
+	xmlXPathCompExprPtr		xpathcomp;
+	xmlXPathObjectPtr		xpathobj;
+	xmlXPathCompExprPtr	   *xpathscomp;
+	FmgrInfo			   *in_functions;
+	Oid					   *typioparams;
+	char				   *default_ns_name;
+	long int				rc;
+};
+
+/*
+ * 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;
+}
+
+#endif
+
+struct XmlTableContext *
+initXmlTableContext(xmltype *xmlval, char *default_ns_name,
+				int ncols, FmgrInfo *in_functions, Oid *typioparams,
+				MemoryContext per_rowgroup_memory)
+{
+#ifdef USE_LIBXML
+	MemoryContext				oldcxt;
+	struct XmlTableContext	   *result = NULL;
+	PgXmlErrorContext		   *xmlerrcxt = NULL;
+	int32						len;
+	xmlChar			*xmlval_str;
+
+	volatile xmlParserCtxtPtr	ctxt = NULL;
+	volatile xmlDocPtr			doc = NULL;
+	volatile xmlXPathContextPtr	xpathcxt = NULL;
+
+	oldcxt = MemoryContextSwitchTo(per_rowgroup_memory);
+
+	len = VARSIZE(xmlval) - VARHDRSZ;
+	xmlval_str = palloc((len + 1) * sizeof(xmlChar));
+	memcpy(xmlval_str, VARDATA(xmlval), len);
+	xmlval_str[len] = '\0';
+
+	result = palloc0(sizeof(struct XmlTableContext));
+	result->per_rowgroup_memory = per_rowgroup_memory;
+	result->in_functions = in_functions;
+	result->typioparams = typioparams;
+	result->default_ns_name = default_ns_name;
+	result->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * ncols);
+
+	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");
+		doc = xmlCtxtReadMemory(ctxt, (char *) xmlval_str, len, NULL, NULL, 0);
+		if (doc == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+				"could not parse XML document");
+		xpathcxt = xmlXPathNewContext(doc);
+		if (xpathcxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+				"could not allocate XPath context");
+		xpathcxt->node = xmlDocGetRootElement(doc);
+		if (xpathcxt->node == NULL  || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+				"could not find root XML element");
+	}
+	PG_CATCH();
+	{
+		if (xpathcxt != NULL)
+			xmlXPathFreeContext(xpathcxt);
+		if (doc != NULL)
+			xmlFreeDoc(doc);
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	result->ncols = ncols;
+	result->xmlerrcxt = xmlerrcxt;
+	result->ctxt = ctxt;
+	result->doc = doc;
+	result->xpathcxt = xpathcxt;
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return (struct XmlTableContext *) result;
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return NULL;
+};
+
+void
+XmlTableSetRowPath(struct XmlTableContext *xtCxt, text *row_path)
+{
+#ifdef USE_LIBXML
+	xmlChar			*row_path_str;
+	MemoryContext	oldcxt;
+	StringInfoData			str;
+	char		*path_str;
+
+	path_str = text_to_cstring(row_path);
+	if (*path_str == '\0')
+		ereport(ERROR,
+					(errcode(ERRCODE_DATA_EXCEPTION),
+					 errmsg("row path filter must not be empty string")));
+
+	transformXPath(&str, path_str, NULL, NULL, xtCxt->default_ns_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+	row_path_str = (xmlChar *) palloc((str.len + 1) * sizeof(xmlChar));
+	memcpy(row_path_str, str.data, str.len);
+	row_path_str[str.len] = '\0';
+
+	xtCxt->xpathcomp = xmlXPathCompile(row_path_str);
+	if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableFreeContext(struct XmlTableContext *xtCxt)
+{
+#ifdef USE_LIBXML
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int		i;
+
+		for (i = 0; i < xtCxt->ncols; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableSetNs(struct XmlTableContext *xtCxt, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+
+	if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+								(xmlChar *)(name ? name : xtCxt->default_ns_name),
+								(xmlChar *) uri))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableSetColumnPath(struct XmlTableContext *xtCxt, int i, Oid typid, char *path)
+{
+#ifdef USE_LIBXML
+	MemoryContext oldcxt;
+	StringInfoData		str;
+	xmlChar			*xmlstr;
+
+	transformXPath(&str, path,
+					  "./", typid != XMLOID ? "/text()" : NULL,
+					  xtCxt->default_ns_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+	xmlstr = palloc((str.len + 1) * sizeof(xmlChar));
+	memcpy(xmlstr, str.data, str.len);
+	xmlstr[str.len] = '\0';
+
+	xtCxt->xpathscomp[i] = xmlXPathCompile(xmlstr);
+	if (xtCxt->xpathscomp[i] == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+bool
+XmlTableFetchRow(struct XmlTableContext *xtCxt)
+{
+#ifdef USE_LIBXML
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+				"could not create XPath object");
+
+		xtCxt->rc = 0;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+};
+
+Datum
+XmlTableGetValue(struct XmlTableContext *xtCxt, int ncol, Oid typid, int32 typmod, bool *isnull)
+{
+#ifdef USE_LIBXML
+	Datum			result = (Datum) 0;
+	xmlNodePtr		cur;
+	char		   *cstr;
+	volatile xmlXPathObjectPtr		column_xpathobj = NULL;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+			  xtCxt->xpathobj->nodesetval != NULL);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+	if (cur->type == XML_ELEMENT_NODE)
+	{
+		PG_TRY();
+		{
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[ncol], 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)
+				{
+					if (count == 1)
+					{
+						cstr = xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[0],
+															xtCxt->xmlerrcxt);
+					}
+					else
+					{
+						StringInfoData		str;
+						int					i;
+
+						/*
+						 * more values, target must be XML.
+						 * Note: possibly any array can be there.
+						 */
+						if (typid != XMLOID)
+							ereport(ERROR,
+										(errcode(ERRCODE_CARDINALITY_VIOLATION),
+										 errmsg("more than one value returned by column XPath expression")));
+
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+										  xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														   xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[ncol],
+													  cstr,
+													  xtCxt->typioparams[ncol],
+													  typmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[ncol],
+													  (char *) column_xpathobj->stringval,
+													  xtCxt->typioparams[ncol],
+													  typmod);
+				*isnull = false;
+			}
+			else
+				elog(ERROR, "unexpected XPath object type");
+		}
+		PG_CATCH();
+		{
+			if (column_xpathobj != NULL)
+				xmlXPathFreeObject(column_xpathobj);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+	else
+		elog(ERROR, "unexpected xmlNode type");
+
+	return (Datum) result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	*isnull = true;
+	return (Datum) 0;
+};
+
+Datum
+XmlTableGetRowValue(struct XmlTableContext *xtCxt, bool *isnull)
+{
+#ifdef USE_LIBXML
+
+	Datum			result = (Datum) 0;
+	xmlNodePtr		cur;
+	char		   *cstr;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+			  xtCxt->xpathobj->nodesetval != NULL);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+	cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+
+	result = InputFunctionCall(&xtCxt->in_functions[0],
+									cstr,
+									xtCxt->typioparams[0],
+									-1);						/* target type is XML always */
+	*isnull = false;
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	*isnull = true;
+	return (Datum) 0;
+};
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d179ae..c8ccf1f 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -20,6 +20,7 @@
 #include "funcapi.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_type.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -221,6 +222,53 @@ get_call_result_type(FunctionCallInfo fcinfo,
 }
 
 /*
+ * When we skip transform stage (in view), then TableExpr's
+ * TupleDesc should not be valid. Refresh is necessary.
+ */
+TupleDesc
+TableExprGetTupleDesc(TableExpr *te)
+{
+	TupleDesc tupdesc;
+
+	if (te->cols != NIL)
+	{
+		ListCell	   *col;
+		int				i = 1;
+
+		tupdesc = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			ColumnDef  *column = (ColumnDef *) lfirst(col);
+			Oid		typid;
+			int32	typmod;
+
+			if (column->typeName)
+			{
+				typenameTypeIdAndMod(NULL, column->typeName, &typid, &typmod);
+				TupleDescInitEntry(tupdesc, (AttrNumber) i, pstrdup(column->colname), typid, typmod, 0);
+			}
+			else
+				TupleDescInitEntry(tupdesc, (AttrNumber) i, pstrdup(column->colname), INT4OID, -1, 0);
+
+			i++;
+		}
+	}
+	else
+	{
+		tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xmltable", XMLOID, -1, 0);
+	}
+
+	assign_record_type_typmod(tupdesc);
+	te->typmod = tupdesc->tdtypmod;
+
+	Assert(te->typid = tupdesc->tdtypeid);
+
+	return tupdesc;
+}
+
+/*
  * get_expr_result_type
  *		As above, but work from a calling expression node tree
  */
@@ -243,6 +291,22 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		TableExpr *te = (TableExpr *) expr;
+
+		if (resultTypeId)
+			*resultTypeId = te->typid;
+
+		/* Enforce fresh RECORD tupledesc */
+		if (te->typmod == -1)
+			TableExprGetTupleDesc(te);
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(te->typid, te->typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..0417a60 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,6 +184,7 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
+extern TupleDesc TableExprGetTupleDesc(TableExpr *te);
 
 /*----------
  *	Support to ease writing functions returning composite types
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e7fd7bd..46a8ab4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1004,6 +1004,31 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	bool		firstRow;			/* true, when first tuple from call should be returned */
+	List	   *namespaces;			/* list of prepared ResTarget fields */
+	bool		used_dns;			/* true, when default namespace is used */
+	Oid			typid;
+	int32		typmod;
+	TupleDesc	tupdesc;			/* cache */
+	int		ncols;					/* number of declared columns */
+	int		for_ordinality_col;		/* number of oridinality column, started by 1 */
+	int		rownum;					/* row counter - for ordinality column */
+	ExprState      *row_path_expr;	/* row xpath expression */
+	ExprState      *expr;			/* processed data */
+	ExprState     **def_expr;		/* array of expressions for default value */
+	ExprState     **col_path_expr;	/* array of expressions for path value */
+	bool	       *not_null;		/* for any column info if NULL is allowed or not */
+	Datum	       *values;								/* prealloc buffer */
+	bool	       *nulls;								/* prealloc buffer */
+	FmgrInfo	  *in_functions;	/* array of infunction for any column */
+	Oid			  *typioparams;		/* array of typIOParam for any column */
+	struct XmlTableContext		*xmltableCxt;
+	MemoryContext				per_rowgroup_memory;
+} TableExprState;
+
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..4877901 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,7 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +217,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1481fff..ad0f700 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -595,6 +595,8 @@ typedef struct ColumnDef
 	char		storage;		/* attstorage setting, or 0 for default */
 	Node	   *raw_default;	/* default value (untransformed parse tree) */
 	Node	   *cooked_default; /* default value (transformed expr tree) */
+	Node	   *raw_path;		/* path value for TableExpr (untransformed parse tree) */
+	Node	   *cooked_path;	/* path value for TableExpr (transformed expr tree) */
 	CollateClause *collClause;	/* untransformed COLLATE spec, if any */
 	Oid			collOid;		/* collation OID (InvalidOid if not set) */
 	List	   *constraints;	/* other constraints on column */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..69e8932 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,35 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - used for XMLTABLE function
+ *
+ * This can be used for json_table, jsonb_table functions in future
+ *----------
+ */
+typedef enum
+{
+	TABLE_EXPR_COLOPT_DEFAULT,
+	TABLE_EXPR_COLOPT_PATH
+} TableExprColOptionType;
+
+typedef struct
+{
+	TableExprColOptionType	typ;
+	Node			*val;
+	int			location;
+} TableExprColOption;
+
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Oid				typid;
+	int32			typmod;
+	Node	   *row_path;		/* row xpath query */
+	Node	   *expr;			/* processed data */
+	List	   *cols;			/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+} TableExpr;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..de83895 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -289,6 +290,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -438,10 +440,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..1218bf3 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+						Oid targetTypeId, int32 targetTypmod,
+						const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..5cf94f9 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,17 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern struct XmlTableContext *initXmlTableContext(xmltype *xmlval,
+				char *default_ns_name,
+				int ncols,
+				FmgrInfo *in_functions, Oid *typioparams,
+				MemoryContext	per_rowgroup_memory);
+extern void XmlTableFreeContext(struct XmlTableContext *xtCxt);
+extern void XmlTableSetNs(struct XmlTableContext *xtCxt, char *name, char *uri);
+extern void XmlTableSetRowPath(struct XmlTableContext *xtCxt, text *row_path);
+extern void XmlTableSetColumnPath(struct XmlTableContext *xtCxt, int i, Oid typid, char *path);
+extern bool XmlTableFetchRow(struct XmlTableContext *xtCxt);
+extern Datum XmlTableGetValue(struct XmlTableContext *xtCxt, int ncol, Oid typid, int32 typmod, bool *isnull);
+extern Datum XmlTableGetRowValue(struct XmlTableContext *xtCxt, bool *isnull);
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..d94f20b 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,105 @@ 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)
+
+-- 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)
+
+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');
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d702703..86a1e6f 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,97 @@ 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)
+
+-- 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.
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y')
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+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.
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5..fdddd28 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,105 @@ 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)
+
+-- 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)
+
+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');
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30..8599084 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,69 @@ 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');
+
+-- 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');
+
+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>');
#2Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#1)
1 attachment(s)
Re: patch: function xmltable

Hi

2016-08-19 10:58 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

I am sending implementation of xmltable function. The code should to have
near to final quality and it is available for testing.

I invite any help with documentation and testing.

new update - the work with nodes is much more correct now.

Regards

Pavel

Show quoted text

Regards

Pavel

Attachments:

xmltable-20160823.patchtext/x-patch; charset=US-ASCII; name=xmltable-20160823.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 169a385..a6334b6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10099,6 +10099,47 @@ SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
     </para>
    </sect3>
 
+   <sect3>
+    <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>|DEFAULT <replaceable>namespace uri</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 table based on passed XML value.
+    </para>
+
+    <para>
+<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>
+   </sect3>
+
    <sect3 id="functions-xml-xmlagg">
     <title><literal>xmlagg</literal></title>
 
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 743e7d6..7abc367 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -189,6 +189,9 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isnull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4503,218 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * ----------------------------------------------------------------
+ */
+
+#define XMLTABLE_DEFAULT_NAMESPACE			"pgdefxmlns"
+
+static Datum
+execEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isNull, ExprDoneCond *isDone)
+{
+	TupleDesc tupdesc;
+	HeapTuple		tuple;
+	HeapTupleHeader		result;
+	int					i;
+	Datum		*values;
+	bool		*nulls;
+	Datum	value;
+	bool	isnull;
+	xmltype		   *xmlval;
+	text		   *row_path;
+
+	tupdesc = tstate->tupdesc;
+
+	if (tstate->firstRow)
+	{
+		ListCell	*ns;
+
+		/* Evaluate expression first */
+		value = ExecEvalExpr(tstate->expr, econtext, &isnull, NULL);
+		if (isnull)
+		{
+			*isDone = ExprSingleResult;
+			*isNull = true;
+			return (Datum) 0;
+		}
+		xmlval = DatumGetXmlP(value);
+
+		value = ExecEvalExpr(tstate->row_path_expr, econtext, &isnull, NULL);
+		if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					  errmsg("row query must not be null")));
+		row_path = DatumGetTextP(value);
+
+		Assert(tstate->xmltableCxt == NULL);
+		tstate->xmltableCxt = initXmlTableContext(xmlval,
+													tstate->used_dns ?
+										XMLTABLE_DEFAULT_NAMESPACE : NULL,
+													tstate->ncols,
+															  tstate->in_functions,
+															  tstate->typioparams,
+													econtext->ecxt_per_query_memory);
+
+		foreach(ns, tstate->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			ExprState  *expr;
+			char	   *ns_name;
+			char	   *ns_uri;
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr *na = (NamedArgExpr *) n;
+
+				expr = (ExprState *) na->arg;
+				ns_name = na->name;
+			}
+			else
+			{
+				expr = (ExprState *) n;
+				ns_name = NULL;
+			}
+
+			value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					  errmsg("namespace uri must not be null")));
+			ns_uri = text_to_cstring(DatumGetTextP(value));
+
+			XmlTableSetNs(tstate->xmltableCxt, ns_name, ns_uri);
+		}
+
+		XmlTableSetRowPath(tstate->xmltableCxt, row_path);
+
+		/* Path caclulation */
+		for (i = 0; i < tstate->ncols; i++)
+		{
+			char *col_path;
+
+			if (tstate->col_path_expr[i] != NULL)
+			{
+				value = ExecEvalExpr(tstate->col_path_expr[i], econtext, &isnull, NULL);
+				if (isnull)
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						  errmsg("column path for column \"%s\" must not be null",
+											  NameStr(tupdesc->attrs[i]->attname))));
+				col_path = text_to_cstring(DatumGetTextP(value));
+			}
+			else
+				col_path = NameStr(tupdesc->attrs[i]->attname);
+
+			XmlTableSetColumnPath(tstate->xmltableCxt, i,
+											tupdesc->attrs[i]->atttypid, col_path);
+		}
+		tstate->firstRow = false;
+	}
+
+	values = tstate->values;
+	nulls = tstate->nulls;
+
+	if (XmlTableFetchRow(tstate->xmltableCxt))
+	{
+		if (tstate->ncols > 0)
+		{
+			for (i = 0; i < tstate->ncols; i++)
+			{
+				if (i != tstate->for_ordinality_col - 1)
+				{
+					values[i] = XmlTableGetValue(tstate->xmltableCxt, i,
+												  tupdesc->attrs[i]->atttypid,
+												  tupdesc->attrs[i]->atttypmod,
+																			  &isnull);
+					if (isnull && tstate->def_expr[i] != NULL)
+						values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+					if (isnull && tstate->not_null[i])
+						ereport(ERROR,
+								(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+								 errmsg("null is not allowed in column \"%s\"",
+									  NameStr(tupdesc->attrs[i]->attname))));
+					nulls[i] = isnull;
+				}
+				else
+				{
+					values[i] = Int32GetDatum(++tstate->rownum);
+					nulls[i] = false;
+				}
+			}
+		}
+		else
+			values[0] = XmlTableGetRowValue(tstate->xmltableCxt, &nulls[0]);
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		result = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(result, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type info we identified before.
+		 */
+		HeapTupleHeaderSetTypeId(result, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(result, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		/* no more rows */
+		XmlTableFreeContext(tstate->xmltableCxt);
+		tstate->xmltableCxt = NULL;
+
+		/* ensure releasing all memory */
+		MemoryContextReset(tstate->per_rowgroup_memory);
+
+		/* next row will be first again */
+		tstate->firstRow = true;
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		return (Datum) 0;
+	}
+
+	*isNull = false;
+	*isDone = ExprMultipleResult;
+
+	return HeapTupleHeaderGetDatum(result);
+}
+
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isNull, ExprDoneCond *isDone)
+{
+	/* Ensure releasing context every exception */
+	Datum		result;
+
+	PG_TRY();
+	{
+		result = execEvalTableExpr(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		if (tstate->xmltableCxt != NULL)
+		{
+			XmlTableFreeContext(tstate->xmltableCxt);
+			tstate->xmltableCxt = NULL;
+		}
+
+		MemoryContextDelete(tstate->per_rowgroup_memory);
+		tstate->per_rowgroup_memory = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5477,133 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr	   *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int				ncols;
+				ListCell	   *col;
+				TupleDesc		tupdesc;
+				int				i;
+
+				tstate->firstRow = true;
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+
+				/* when typmod is not valid, refresh it */
+				if (te->typmod == -1)
+				{
+					TupleDesc tupdesc = TableExprGetTupleDesc(te);
+					tstate->typid = tupdesc->tdtypeid;
+					tstate->typmod = tupdesc->tdtypmod;
+				}
+				else
+				{
+					tstate->typid = te->typid;
+					tstate->typmod = te->typmod;
+				}
+
+				tupdesc = lookup_rowtype_tupdesc_copy(tstate->typid, tstate->typmod);
+				ncols = tupdesc->natts;
+				tstate->tupdesc = tupdesc;
+
+				/* result is one more columns every time */
+				Assert(ncols > 0);
+
+				tstate->values = palloc(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid		in_funcid;
+
+					getTypeInputInfo(tupdesc->attrs[i]->atttypid, &in_funcid,
+														&tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_path_expr = ExecInitExpr((Expr *) te->row_path, parent);
+				tstate->expr = ExecInitExpr((Expr *) te->expr, parent);
+
+				if (te->cols)
+				{
+					Assert(ncols == list_length(te->cols));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_path_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+
+					i = 0;
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+																	  parent);
+							tstate->col_path_expr[i] = ExecInitExpr((Expr *) tec->path_expr,
+																		parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+					tstate->rownum = 0;
+				}
+				else
+				{
+					/* There are not any related data */
+					tstate->def_expr = NULL;
+					tstate->col_path_expr = NULL;
+					tstate->not_null = NULL;
+					tstate->ncols = 0;
+				}
+
+				if (te->namespaces)
+				{
+					List		*preparedlist = NIL;
+					ListCell	*ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node *n = (Node *) lfirst(ns);
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+						{
+							preparedlist = lappend(preparedlist,
+													  ExecInitExpr((Expr *) n, parent));
+							tstate->used_dns = true;
+						}
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->xmltableCxt = NULL;
+				tstate->per_rowgroup_memory = AllocSetContextCreate(CurrentMemoryContext,
+																		"XmlTable per rowgroup context",
+																		ALLOCSET_DEFAULT_MINSIZE,
+																		ALLOCSET_DEFAULT_INITSIZE,
+																		ALLOCSET_DEFAULT_MAXSIZE);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c7a0644..33e0c39 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1986,6 +1986,65 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr *newnode = makeNode(TableExpr);
+
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_NODE_FIELD(row_path);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(cols);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4583,6 +4642,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5084,6 +5149,9 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 448e1a9..e4823af 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2610,6 +2610,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2630,6 +2660,20 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_NODE_FIELD(row_path);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(cols);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2895,6 +2939,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3383,6 +3433,9 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..d5e14f8 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -257,6 +257,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = ((const TableExpr *) expr)->typid;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +498,10 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			return ((const TableExpr *) expr)->typmod;
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +737,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 +941,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1145,12 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1576,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2238,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->path_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3058,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+				TableExpr *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_path, te->row_path, Node *);
+				MUTATE(newnode->expr, te->expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->cols, te->cols, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->path_expr, tec->path_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3697,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 50019f4..e628c41 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1587,6 +1587,50 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_OID_FIELD(typid);
+	/* skip typmod, should not be persistent for dynamic RECORD */
+	WRITE_NODE_FIELD(row_path);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(cols);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3540,6 +3584,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3863,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c83063e..0094e04 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2263,6 +2263,49 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_OID_FIELD(typid);
+
+	/* Enforce fresh TupleDesc */
+	local_node->typmod = -1;
+
+	READ_NODE_FIELD(row_path);
+	READ_NODE_FIELD(expr);
+	READ_NODE_FIELD(cols);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(path_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2494,6 +2537,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 4496fde..4d69944 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -819,6 +819,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 cb5cfc4..372f895 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -120,6 +120,20 @@ typedef struct ImportQual
 	List	   *table_names;
 } ImportQual;
 
+/* Private data for TableExprColOption */
+typedef enum TableExprColOptionType
+{
+	TABLE_EXPR_COLOPT_DEFAULT,
+	TABLE_EXPR_COLOPT_PATH
+} TableExprColOptionType;
+
+typedef struct TableExprColOption
+{
+	TableExprColOptionType	typ;
+	Node			*val;
+	int			location;
+} TableExprColOption;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -229,6 +243,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	struct TableExprColOption *te_colopt;
 }
 
 %type <node>	stmt schema_stmt
@@ -542,6 +557,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	TableExprColList TableExprColOptions TableExprColOptionsOpt
+%type <node>	TableExprCol
+%type <te_colopt>	TableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -573,10 +595,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
 
@@ -617,7 +639,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
@@ -646,8 +668,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
 
@@ -12539,6 +12561,148 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = $10;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+TableExprColList: TableExprCol							{ $$ = list_make1($1); }
+			| TableExprColList ',' TableExprCol			{ $$ = lappend($1, $3); }
+		;
+
+TableExprCol:
+			ColId Typename TableExprColOptionsOpt IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *opt;
+
+					rawc->colname = $1;
+					rawc->typeName = $2;
+					rawc->is_not_null = $4;
+					rawc->location = @1;
+					rawc->for_ordinality = false;
+
+					foreach(opt, $3)
+					{
+						TableExprColOption *co = (TableExprColOption *) lfirst(opt);
+
+						if (co->typ == TABLE_EXPR_COLOPT_DEFAULT)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed"),
+													parser_errposition(co->location)));
+							rawc->default_expr = co->val;
+						}
+						else if (co->typ == TABLE_EXPR_COLOPT_PATH)
+						{
+							if (rawc->path_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+													parser_errposition(co->location)));
+							rawc->path_expr = co->val;
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+TableExprColOptionsOpt: TableExprColOptions				{ $$ = $1; }
+			| /* EMPTY */								{ $$ = NIL; }
+		;
+
+TableExprColOptions: TableExprColOption					{ $$ = list_make1($1); }
+			| TableExprColOptions TableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+TableExprColOption:
+			DEFAULT c_expr
+				{
+					$$ = palloc(sizeof(TableExprColOption));
+					$$->typ = TABLE_EXPR_COLOPT_DEFAULT;
+					$$->val = $2;
+					$$->location = @1;
+				}
+			| PATH c_expr
+				{
+					$$ = palloc(sizeof(TableExprColOption));
+					$$->typ = TABLE_EXPR_COLOPT_PATH;
+					$$->val = $2;
+					$$->location = @1;
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			a_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT a_expr
+				{
+					$$ = $2;
+				}
 		;
 
 /*
@@ -13677,6 +13841,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13810,6 +13975,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PATH
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -13975,10 +14141,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..0686e07 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1105,8 +1105,8 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
+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,15 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+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 63f7965..8293658 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -365,6 +367,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2678,6 +2684,176 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a TableExpr
+ */
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr *newte = makeNode(TableExpr);
+	TupleDesc tupdesc;
+
+	Assert(te->row_path != NULL);
+	Assert(te->expr != NULL);
+
+	newte->row_path = coerce_to_specific_type(pstate,
+								transformExprRecurse(pstate, te->row_path),
+														 TEXTOID,
+														 "XMLTABLE");
+	newte->expr = coerce_to_specific_type(pstate,
+								transformExprRecurse(pstate, te->expr),
+														 XMLOID,
+														 "XMLTABLE");
+
+	if (te->cols != NIL)
+	{
+		ListCell	   *col;
+		int				i = 1;
+		bool			for_ordinality = false;
+
+		tupdesc = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			TableExprRawCol	   *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn	   *newc = makeNode(TableExprColumn);
+			Oid					typid;
+			int32				typmod;
+			int					j;
+
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			if (!rawc->for_ordinality)
+			{
+				typenameTypeIdAndMod(NULL, rawc->typeName, &typid, &typmod);
+
+				if (rawc->path_expr)
+					newc->path_expr = coerce_to_specific_type(pstate,
+									transformExprRecurse(pstate, rawc->path_expr),
+															 TEXTOID,
+															 "XMLTABLE");
+			  if (rawc->default_expr)
+					newc->default_expr = coerce_to_specific_type_typmod(pstate,
+									transformExprRecurse(pstate, rawc->default_expr),
+															 typid, typmod,
+															 "XMLTABLE");
+			}
+			else
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one column FOR ORDINALITY is allowed"),
+											parser_errposition(pstate, rawc->location)));
+
+				typid = INT4OID;
+				typmod = -1;
+			}
+			TupleDescInitEntry(tupdesc, (AttrNumber) i,
+											 pstrdup(rawc->colname),
+											 typid, typmod, 0);
+
+			newc->typid = typid;
+			newc->typmod = typmod;
+
+			newte->cols = lappend(newte->cols, newc);
+
+			/* the name should be unique */
+			for (j = 0; j < i - 1; j++)
+				if (strcmp(NameStr(tupdesc->attrs[j]->attname), rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+													rawc->colname),
+								parser_errposition(pstate, rawc->location)));
+			i++;
+		}
+	}
+	else
+	{
+		/*
+		 * When columsn are not defined, then output is XML column.
+		 * ANSI/SQL standard doesn't specify the name of this column,
+		 * and there are not conformity between databases. Postgres
+		 * uses function name like default. This implementation
+		 * respects it.
+		 */
+		tupdesc = CreateTemplateTupleDesc(1, false);
+
+		/* Generate tupdesc with one auto XML attribute */
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xmltable", XMLOID, -1, 0);
+	}
+
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		char	  **names;
+		bool		found_dns = false;
+		int			nnames = 0;
+
+		names = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node		*n = (Node *) lfirst(ns);
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr   *na = (NamedArgExpr *) n;
+				int				i;
+
+				for (i = 0; i < nnames; i++)
+					if (strcmp(names[i], na->name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the namespace name \"%s\" is not unique",
+														na->name),
+									parser_errposition(pstate, na->location)));
+				names[nnames++] = na->name;
+
+				/* coerce should immediately after expression, else reset the name */
+				na->arg = (Expr *) coerce_to_specific_type(pstate,
+									 transformExprRecurse(pstate, (Node *) na->arg),
+									 TEXTOID,
+									 "XMLTABLE");
+			}
+			else
+			{
+				/* default ns specification (without name) must by only one */
+				if (found_dns)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one default namespace is allowed"),
+									parser_errposition(pstate, exprLocation(n))));
+				found_dns = true;
+				n = coerce_to_specific_type(pstate,
+									 transformExprRecurse(pstate, n),
+									 TEXTOID,
+									 "XMLTABLE");
+			}
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(names);
+	}
+	else
+		newte->namespaces = NIL;
+
+	assign_record_type_typmod(tupdesc);
+
+	newte->typid = tupdesc->tdtypeid;
+	newte->typmod = tupdesc->tdtypmod;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
+/*
  * 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 b7b82bf..7ce209d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1837,6 +1837,9 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..bae7fa9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8280,6 +8281,100 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_path, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->cols != NIL)
+				{
+					ListCell	*col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+										  format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->path_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->path_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 7ed5bcb..287dcd9 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -4073,3 +4073,753 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * We need to work with XPath expression tokens. When expression
+ * starting or finishing with nodenames, then we can use prefix
+ * and suffix. When default namespace is defined, then we should to
+ * enhance any nodename and attribute without namespace by default
+ * namespace. The procession of XPath expression is linear.
+ */
+
+typedef enum
+{
+	XPATH_TOKEN_NONE,
+	XPATH_TOKEN_NAME,
+	XPATH_TOKEN_STRING,
+	XPATH_TOKEN_NUMBER,
+	XPATH_TOKEN_OTHER
+} XPathTokenType;
+
+typedef struct TokenInfo
+{
+	XPathTokenType		ttype;
+	char		   *start;
+	int				length;
+} XPathTokenInfo;
+
+#define TOKEN_STACK_SIZE		10
+
+typedef struct ParserData
+{
+	char		   *str;
+	char		   *cur;
+	XPathTokenInfo	stack[TOKEN_STACK_SIZE];
+	int				stack_length;
+} XPathParserData;
+
+#define NODENAME_FIRSTCHAR(c)	 ((c) == '_' || (c) == '-' || \
+								 ((c) >= 'A' && (c) <= 'Z') || \
+								 ((c) >= 'a' && (c) <= 'z') || \
+								 ((c) >= '0' && (c) <= '9'))
+
+#define IS_NODENAME_CHAR(c)		(NODENAME_FIRSTCHAR(c) || (c) == '.')
+
+/*
+ * Returns next char after last char of token
+ */
+static char *
+getXPathToken(char *str, XPathTokenInfo *ti)
+{
+
+	/* skip initial spaces */
+	while (*str == ' ')
+		str++;
+
+	if (*str != '\0')
+	{
+		char		c = *str;
+
+		ti->start = str++;
+
+		if (c >= '0' && c <= '9')
+		{
+			while (*str >= '0' && *str <= '9')
+				str++;
+			if (*str == '.')
+			{
+				str++;
+				while (*str >= '0' && *str <= '9')
+					str++;
+			}
+			ti->ttype = XPATH_TOKEN_NUMBER;
+		}
+		else if (NODENAME_FIRSTCHAR(c))
+		{
+			while (IS_NODENAME_CHAR(*str))
+				str++;
+
+			ti->ttype = XPATH_TOKEN_NAME;
+		}
+		else if (c == '"')
+		{
+			while (*str != '\0')
+				if (*str++ == '"')
+					break;
+
+			ti->ttype = XPATH_TOKEN_STRING;
+		}
+		else
+			ti->ttype = XPATH_TOKEN_OTHER;
+
+		ti->length = str - ti->start;
+	}
+	else
+	{
+		ti->start = NULL;
+		ti->length = 0;
+
+		ti->ttype = XPATH_TOKEN_NONE;
+	}
+
+	return str;
+}
+
+/*
+ * reset XPath parser stack
+ */
+static void
+initXPathParser(XPathParserData *parser, char *str)
+{
+	parser->str = str;
+	parser->cur = str;
+	parser->stack_length = 0;
+}
+
+static void
+nextXPathToken(XPathParserData *parser, XPathTokenInfo *ti)
+{
+	if (parser->stack_length > 0)
+		memcpy(ti, &parser->stack[--parser->stack_length],
+										  sizeof(XPathTokenInfo));
+	else
+		parser->cur = getXPathToken(parser->cur, ti);
+}
+
+static void
+pushXPathToken(XPathParserData *parser, XPathTokenInfo *ti)
+{
+	if (parser->stack_length == TOKEN_STACK_SIZE)
+		elog(ERROR, "internal error");
+	memcpy(&parser->stack[parser->stack_length++], ti,
+										  sizeof(XPathTokenInfo));
+}
+
+static void
+writeXPathToken(StringInfo str, XPathTokenInfo *ti)
+{
+	Assert(ti->ttype != XPATH_TOKEN_NONE);
+
+	if (ti->ttype != XPATH_TOKEN_OTHER)
+		appendBinaryStringInfo(str, ti->start, ti->length);
+	else
+		appendStringInfoChar(str, *ti->start);
+}
+
+/*
+ * Working horse for XPath transformation. When XPath starting by node name,
+ * then prefix have to be applied. When XPath ending by node name, then
+ * suffix will be used. Any unqualified node name should be qualified by
+ * default namespace.
+ */
+static void
+_transformXPath(StringInfo str, XPathParserData *parser,
+				  bool inside_predicate,
+				  char *prefix, char *suffix, char *default_ns_name)
+{
+	XPathTokenInfo	t1, t2;
+	bool			is_first_token = true;
+	bool			last_token_is_name = false;
+
+	nextXPathToken(parser, &t1);
+
+	while (t1.ttype != XPATH_TOKEN_NONE)
+	{
+		switch (t1.ttype)
+		{
+			case XPATH_TOKEN_NUMBER:
+			case XPATH_TOKEN_STRING:
+				last_token_is_name = false;
+				is_first_token = false;
+				writeXPathToken(str, &t1);
+				nextXPathToken(parser, &t1);
+				break;
+
+			case XPATH_TOKEN_NAME:
+				{
+					bool	is_qual_name = false;
+
+					/* inside predicate ignore keywords "and" "or" */
+					if (inside_predicate)
+					{
+						if ((strncmp(t1.start, "and", 3) == 0 && t1.length == 3) ||
+							(strncmp(t1.start, "or", 2) == 0 && t1.length == 2))
+						{
+							writeXPathToken(str, &t1);
+							nextXPathToken(parser, &t1);
+							break;
+						}
+					}
+
+					last_token_is_name = true;
+					nextXPathToken(parser, &t2);
+					if (t2.ttype == XPATH_TOKEN_OTHER)
+					{
+						if (*t2.start == '(')
+							last_token_is_name = false;
+						else if (*t2.start == ':')
+							is_qual_name = true;
+					}
+
+					if (is_first_token && last_token_is_name && prefix != NULL)
+						appendStringInfoString(str, prefix);
+
+					if (last_token_is_name && !is_qual_name && default_ns_name != NULL)
+						appendStringInfo(str, "%s:", default_ns_name);
+
+					writeXPathToken(str, &t1);
+					is_first_token = false;
+
+					if (is_qual_name)
+					{
+						writeXPathToken(str, &t2);
+						nextXPathToken(parser, &t1);
+						if (t1.ttype == XPATH_TOKEN_NAME)
+							writeXPathToken(str, &t1);
+						else
+							pushXPathToken(parser, &t1);
+					}
+					else
+						pushXPathToken(parser, &t2);
+
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_OTHER:
+				{
+					char c = *t1.start;
+
+					is_first_token = false;
+
+					writeXPathToken(str, &t1);
+
+					if (c == '[')
+						_transformXPath(str, parser, true, NULL, NULL, default_ns_name);
+					else
+					{
+						last_token_is_name = false;
+
+						if (c == ']' && inside_predicate)
+							return;
+
+						else if (c == '@')
+						{
+							nextXPathToken(parser, &t1);
+							if (t1.ttype == XPATH_TOKEN_NAME)
+							{
+								bool		is_qual_name = false;
+
+								nextXPathToken(parser, &t2);
+								if (t2.ttype == XPATH_TOKEN_OTHER && *t2.start == ':')
+									is_qual_name = true;
+
+								if (!is_qual_name && default_ns_name != NULL)
+									appendStringInfo(str, "%s:", default_ns_name);
+
+								writeXPathToken(str, &t1);
+								if (is_qual_name)
+								{
+									writeXPathToken(str, &t2);
+									nextXPathToken(parser, &t1);
+									if (t1.ttype == XPATH_TOKEN_NAME)
+										writeXPathToken(str, &t1);
+									else
+										pushXPathToken(parser, &t1);
+								}
+								else
+									pushXPathToken(parser, &t2);
+							}
+							else
+								pushXPathToken(parser, &t1);
+						}
+					}
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_NONE:
+				elog(ERROR, "should not be here");
+		}
+	}
+
+	if (last_token_is_name && suffix != NULL)
+		appendStringInfoString(str, suffix);
+}
+
+static void
+transformXPath(StringInfo str, char *xpath,
+					  char *prefix, char *suffix, char *default_ns_name)
+{
+	XPathParserData		parser;
+
+	initStringInfo(str);
+	initXPathParser(&parser, xpath);
+	_transformXPath(str, &parser, false, prefix, suffix, default_ns_name);
+}
+
+
+struct XmlTableContext
+{
+	MemoryContext			per_rowgroup_memory;
+	int						ncols;
+	PgXmlErrorContext	   *xmlerrcxt;
+	xmlParserCtxtPtr		ctxt;
+	xmlDocPtr				doc;
+	xmlXPathContextPtr		xpathcxt;
+	xmlXPathCompExprPtr		xpathcomp;
+	xmlXPathObjectPtr		xpathobj;
+	xmlXPathCompExprPtr	   *xpathscomp;
+	FmgrInfo			   *in_functions;
+	Oid					   *typioparams;
+	char				   *default_ns_name;
+	long int				rc;
+};
+
+/*
+ * 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;
+}
+
+#endif
+
+struct XmlTableContext *
+initXmlTableContext(xmltype *xmlval, char *default_ns_name,
+				int ncols, FmgrInfo *in_functions, Oid *typioparams,
+				MemoryContext per_rowgroup_memory)
+{
+#ifdef USE_LIBXML
+	MemoryContext				oldcxt;
+	struct XmlTableContext	   *result = NULL;
+	PgXmlErrorContext		   *xmlerrcxt = NULL;
+	int32						len;
+	xmlChar			*xmlval_str;
+
+	volatile xmlParserCtxtPtr	ctxt = NULL;
+	volatile xmlDocPtr			doc = NULL;
+	volatile xmlXPathContextPtr	xpathcxt = NULL;
+
+	oldcxt = MemoryContextSwitchTo(per_rowgroup_memory);
+
+	len = VARSIZE(xmlval) - VARHDRSZ;
+	xmlval_str = palloc((len + 1) * sizeof(xmlChar));
+	memcpy(xmlval_str, VARDATA(xmlval), len);
+	xmlval_str[len] = '\0';
+
+	result = palloc0(sizeof(struct XmlTableContext));
+	result->per_rowgroup_memory = per_rowgroup_memory;
+	result->in_functions = in_functions;
+	result->typioparams = typioparams;
+	result->default_ns_name = default_ns_name;
+	result->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * ncols);
+
+	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");
+		doc = xmlCtxtReadMemory(ctxt, (char *) xmlval_str, len, NULL, NULL, 0);
+		if (doc == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+				"could not parse XML document");
+		xpathcxt = xmlXPathNewContext(doc);
+		if (xpathcxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+				"could not allocate XPath context");
+		xpathcxt->node = xmlDocGetRootElement(doc);
+		if (xpathcxt->node == NULL  || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+				"could not find root XML element");
+	}
+	PG_CATCH();
+	{
+		if (xpathcxt != NULL)
+			xmlXPathFreeContext(xpathcxt);
+		if (doc != NULL)
+			xmlFreeDoc(doc);
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	result->ncols = ncols;
+	result->xmlerrcxt = xmlerrcxt;
+	result->ctxt = ctxt;
+	result->doc = doc;
+	result->xpathcxt = xpathcxt;
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return (struct XmlTableContext *) result;
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return NULL;
+};
+
+void
+XmlTableSetRowPath(struct XmlTableContext *xtCxt, text *row_path)
+{
+#ifdef USE_LIBXML
+	xmlChar			*row_path_str;
+	MemoryContext	oldcxt;
+	StringInfoData			str;
+	char		*path_str;
+
+	path_str = text_to_cstring(row_path);
+	if (*path_str == '\0')
+		ereport(ERROR,
+					(errcode(ERRCODE_DATA_EXCEPTION),
+					 errmsg("row path filter must not be empty string")));
+
+	transformXPath(&str, path_str, NULL, NULL, xtCxt->default_ns_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+	row_path_str = (xmlChar *) palloc((str.len + 1) * sizeof(xmlChar));
+	memcpy(row_path_str, str.data, str.len);
+	row_path_str[str.len] = '\0';
+
+	xtCxt->xpathcomp = xmlXPathCompile(row_path_str);
+	if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableFreeContext(struct XmlTableContext *xtCxt)
+{
+#ifdef USE_LIBXML
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int		i;
+
+		for (i = 0; i < xtCxt->ncols; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableSetNs(struct XmlTableContext *xtCxt, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+
+	if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+								(xmlChar *)(name ? name : xtCxt->default_ns_name),
+								(xmlChar *) uri))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableSetColumnPath(struct XmlTableContext *xtCxt, int i, Oid typid, char *path)
+{
+#ifdef USE_LIBXML
+	MemoryContext oldcxt;
+	StringInfoData		str;
+	xmlChar			*xmlstr;
+
+	transformXPath(&str, path,
+					  "./", typid != XMLOID ? "/text()" : NULL,
+					  xtCxt->default_ns_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+	xmlstr = palloc((str.len + 1) * sizeof(xmlChar));
+	memcpy(xmlstr, str.data, str.len);
+	xmlstr[str.len] = '\0';
+
+	xtCxt->xpathscomp[i] = xmlXPathCompile(xmlstr);
+	if (xtCxt->xpathscomp[i] == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+bool
+XmlTableFetchRow(struct XmlTableContext *xtCxt)
+{
+#ifdef USE_LIBXML
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+				"could not create XPath object");
+
+		xtCxt->rc = 0;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+};
+
+Datum
+XmlTableGetValue(struct XmlTableContext *xtCxt, int ncol, Oid typid, int32 typmod, bool *isnull)
+{
+#ifdef USE_LIBXML
+	Datum			result = (Datum) 0;
+	xmlNodePtr		cur;
+	char		   *cstr;
+	volatile xmlXPathObjectPtr		column_xpathobj = NULL;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+			  xtCxt->xpathobj->nodesetval != NULL);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+	if (cur->type == XML_ELEMENT_NODE)
+	{
+		PG_TRY();
+		{
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[ncol], 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)
+				{
+					if (count == 1)
+					{
+						cstr = xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[0],
+															xtCxt->xmlerrcxt);
+					}
+					else
+					{
+						StringInfoData		str;
+						int					i;
+
+						/*
+						 * more values, target must be XML.
+						 * Note: possibly any array can be there.
+						 */
+						if (typid != XMLOID)
+							ereport(ERROR,
+										(errcode(ERRCODE_CARDINALITY_VIOLATION),
+										 errmsg("more than one value returned by column XPath expression")));
+
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+										  xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														   xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[ncol],
+													  cstr,
+													  xtCxt->typioparams[ncol],
+													  typmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[ncol],
+													  (char *) column_xpathobj->stringval,
+													  xtCxt->typioparams[ncol],
+													  typmod);
+				*isnull = false;
+			}
+			else
+				elog(ERROR, "unexpected XPath object type");
+		}
+		PG_CATCH();
+		{
+			if (column_xpathobj != NULL)
+				xmlXPathFreeObject(column_xpathobj);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+	else
+		elog(ERROR, "unexpected xmlNode type");
+
+	return (Datum) result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	*isnull = true;
+	return (Datum) 0;
+};
+
+Datum
+XmlTableGetRowValue(struct XmlTableContext *xtCxt, bool *isnull)
+{
+#ifdef USE_LIBXML
+
+	Datum			result = (Datum) 0;
+	xmlNodePtr		cur;
+	char		   *cstr;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+			  xtCxt->xpathobj->nodesetval != NULL);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+	cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+
+	result = InputFunctionCall(&xtCxt->in_functions[0],
+									cstr,
+									xtCxt->typioparams[0],
+									-1);						/* target type is XML always */
+	*isnull = false;
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	*isnull = true;
+	return (Datum) 0;
+};
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d179ae..99c39b8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -221,6 +221,49 @@ get_call_result_type(FunctionCallInfo fcinfo,
 }
 
 /*
+ * When we skip transform stage (in view), then TableExpr's
+ * TupleDesc should not be valid. Refresh is necessary.
+ */
+TupleDesc
+TableExprGetTupleDesc(TableExpr *te)
+{
+	TupleDesc tupdesc;
+
+	if (te->cols != NIL)
+	{
+		ListCell	   *col;
+		int				i = 1;
+
+		tupdesc = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(tupdesc,
+									  (AttrNumber) i,
+												  pstrdup(tec->colname),
+												  tec->typid,
+												  tec->typmod,
+												  0);
+			i++;
+		}
+	}
+	else
+	{
+		tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xmltable", XMLOID, -1, 0);
+	}
+
+	assign_record_type_typmod(tupdesc);
+	te->typmod = tupdesc->tdtypmod;
+
+	Assert(te->typid = tupdesc->tdtypeid);
+
+	return tupdesc;
+}
+
+/*
  * get_expr_result_type
  *		As above, but work from a calling expression node tree
  */
@@ -243,6 +286,22 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		TableExpr *te = (TableExpr *) expr;
+
+		if (resultTypeId)
+			*resultTypeId = te->typid;
+
+		/* Enforce fresh RECORD tupledesc */
+		if (te->typmod == -1)
+			TableExprGetTupleDesc(te);
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(te->typid, te->typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..0417a60 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,6 +184,7 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
+extern TupleDesc TableExprGetTupleDesc(TableExpr *te);
 
 /*----------
  *	Support to ease writing functions returning composite types
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e7fd7bd..46a8ab4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1004,6 +1004,31 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	bool		firstRow;			/* true, when first tuple from call should be returned */
+	List	   *namespaces;			/* list of prepared ResTarget fields */
+	bool		used_dns;			/* true, when default namespace is used */
+	Oid			typid;
+	int32		typmod;
+	TupleDesc	tupdesc;			/* cache */
+	int		ncols;					/* number of declared columns */
+	int		for_ordinality_col;		/* number of oridinality column, started by 1 */
+	int		rownum;					/* row counter - for ordinality column */
+	ExprState      *row_path_expr;	/* row xpath expression */
+	ExprState      *expr;			/* processed data */
+	ExprState     **def_expr;		/* array of expressions for default value */
+	ExprState     **col_path_expr;	/* array of expressions for path value */
+	bool	       *not_null;		/* for any column info if NULL is allowed or not */
+	Datum	       *values;								/* prealloc buffer */
+	bool	       *nulls;								/* prealloc buffer */
+	FmgrInfo	  *in_functions;	/* array of infunction for any column */
+	Oid			  *typioparams;		/* array of typIOParam for any column */
+	struct XmlTableContext		*xmltableCxt;
+	MemoryContext				per_rowgroup_memory;
+} TableExprState;
+
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..504fb9d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -453,6 +456,7 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1481fff..8bf9736 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -697,6 +697,22 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * There are different requests from XMLTABLE, JSON_TABLE functions
+ * on passed data than has CREATE TABLE command. It is reason for
+ * introduction special structure instead using ColumnDef.
+ */
+typedef struct TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;
+	TypeName   *typeName;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..195e637 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,36 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - used for XMLTABLE function
+ *
+ * This can be used for json_table, jsonb_table functions in future
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Oid				typid;
+	int32			typmod;
+	Node	   *row_path;		/* row xpath query */
+	Node	   *expr;			/* processed data */
+	List	   *cols;			/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+} TableExpr;
+
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;
+	Oid			typid;
+	int32		typmod;
+	Oid			collation;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..de83895 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -289,6 +290,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -438,10 +440,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..1218bf3 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+						Oid targetTypeId, int32 targetTypmod,
+						const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..5cf94f9 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,17 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern struct XmlTableContext *initXmlTableContext(xmltype *xmlval,
+				char *default_ns_name,
+				int ncols,
+				FmgrInfo *in_functions, Oid *typioparams,
+				MemoryContext	per_rowgroup_memory);
+extern void XmlTableFreeContext(struct XmlTableContext *xtCxt);
+extern void XmlTableSetNs(struct XmlTableContext *xtCxt, char *name, char *uri);
+extern void XmlTableSetRowPath(struct XmlTableContext *xtCxt, text *row_path);
+extern void XmlTableSetColumnPath(struct XmlTableContext *xtCxt, int i, Oid typid, char *path);
+extern bool XmlTableFetchRow(struct XmlTableContext *xtCxt);
+extern Datum XmlTableGetValue(struct XmlTableContext *xtCxt, int ncol, Oid typid, int32 typmod, bool *isnull);
+extern Datum XmlTableGetRowValue(struct XmlTableContext *xtCxt, bool *isnull);
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..ed6cb79 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,139 @@ 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)
+
+
+-- 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');
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d702703..58e6723 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,127 @@ 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)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz)
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz)
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltableview2;
+ERROR:  relation "xmltableview2" does not exist
+LINE 1: SELECT * FROM xmltableview2;
+                      ^
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y')
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+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.
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5..48fae36 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,138 @@ 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)
+
+-- 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');
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30..5909a51 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,91 @@ 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;
+
+-- 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>');
#3Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#2)
1 attachment(s)
Re: patch: function xmltable

2016-08-23 21:00 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

2016-08-19 10:58 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

I am sending implementation of xmltable function. The code should to have
near to final quality and it is available for testing.

I invite any help with documentation and testing.

new update - the work with nodes is much more correct now.

next update

fix memory leak

Pavel

Show quoted text

Regards

Pavel

Regards

Pavel

Attachments:

xmltable-20160824.patchtext/x-patch; charset=US-ASCII; name=xmltable-20160824.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 169a385..a6334b6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10099,6 +10099,47 @@ SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
     </para>
    </sect3>
 
+   <sect3>
+    <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>|DEFAULT <replaceable>namespace uri</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 table based on passed XML value.
+    </para>
+
+    <para>
+<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>
+   </sect3>
+
    <sect3 id="functions-xml-xmlagg">
     <title><literal>xmlagg</literal></title>
 
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 743e7d6..7abc367 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -189,6 +189,9 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isnull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4503,218 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * ----------------------------------------------------------------
+ */
+
+#define XMLTABLE_DEFAULT_NAMESPACE			"pgdefxmlns"
+
+static Datum
+execEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isNull, ExprDoneCond *isDone)
+{
+	TupleDesc tupdesc;
+	HeapTuple		tuple;
+	HeapTupleHeader		result;
+	int					i;
+	Datum		*values;
+	bool		*nulls;
+	Datum	value;
+	bool	isnull;
+	xmltype		   *xmlval;
+	text		   *row_path;
+
+	tupdesc = tstate->tupdesc;
+
+	if (tstate->firstRow)
+	{
+		ListCell	*ns;
+
+		/* Evaluate expression first */
+		value = ExecEvalExpr(tstate->expr, econtext, &isnull, NULL);
+		if (isnull)
+		{
+			*isDone = ExprSingleResult;
+			*isNull = true;
+			return (Datum) 0;
+		}
+		xmlval = DatumGetXmlP(value);
+
+		value = ExecEvalExpr(tstate->row_path_expr, econtext, &isnull, NULL);
+		if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					  errmsg("row query must not be null")));
+		row_path = DatumGetTextP(value);
+
+		Assert(tstate->xmltableCxt == NULL);
+		tstate->xmltableCxt = initXmlTableContext(xmlval,
+													tstate->used_dns ?
+										XMLTABLE_DEFAULT_NAMESPACE : NULL,
+													tstate->ncols,
+															  tstate->in_functions,
+															  tstate->typioparams,
+													econtext->ecxt_per_query_memory);
+
+		foreach(ns, tstate->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			ExprState  *expr;
+			char	   *ns_name;
+			char	   *ns_uri;
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr *na = (NamedArgExpr *) n;
+
+				expr = (ExprState *) na->arg;
+				ns_name = na->name;
+			}
+			else
+			{
+				expr = (ExprState *) n;
+				ns_name = NULL;
+			}
+
+			value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					  errmsg("namespace uri must not be null")));
+			ns_uri = text_to_cstring(DatumGetTextP(value));
+
+			XmlTableSetNs(tstate->xmltableCxt, ns_name, ns_uri);
+		}
+
+		XmlTableSetRowPath(tstate->xmltableCxt, row_path);
+
+		/* Path caclulation */
+		for (i = 0; i < tstate->ncols; i++)
+		{
+			char *col_path;
+
+			if (tstate->col_path_expr[i] != NULL)
+			{
+				value = ExecEvalExpr(tstate->col_path_expr[i], econtext, &isnull, NULL);
+				if (isnull)
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						  errmsg("column path for column \"%s\" must not be null",
+											  NameStr(tupdesc->attrs[i]->attname))));
+				col_path = text_to_cstring(DatumGetTextP(value));
+			}
+			else
+				col_path = NameStr(tupdesc->attrs[i]->attname);
+
+			XmlTableSetColumnPath(tstate->xmltableCxt, i,
+											tupdesc->attrs[i]->atttypid, col_path);
+		}
+		tstate->firstRow = false;
+	}
+
+	values = tstate->values;
+	nulls = tstate->nulls;
+
+	if (XmlTableFetchRow(tstate->xmltableCxt))
+	{
+		if (tstate->ncols > 0)
+		{
+			for (i = 0; i < tstate->ncols; i++)
+			{
+				if (i != tstate->for_ordinality_col - 1)
+				{
+					values[i] = XmlTableGetValue(tstate->xmltableCxt, i,
+												  tupdesc->attrs[i]->atttypid,
+												  tupdesc->attrs[i]->atttypmod,
+																			  &isnull);
+					if (isnull && tstate->def_expr[i] != NULL)
+						values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+					if (isnull && tstate->not_null[i])
+						ereport(ERROR,
+								(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+								 errmsg("null is not allowed in column \"%s\"",
+									  NameStr(tupdesc->attrs[i]->attname))));
+					nulls[i] = isnull;
+				}
+				else
+				{
+					values[i] = Int32GetDatum(++tstate->rownum);
+					nulls[i] = false;
+				}
+			}
+		}
+		else
+			values[0] = XmlTableGetRowValue(tstate->xmltableCxt, &nulls[0]);
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		result = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(result, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type info we identified before.
+		 */
+		HeapTupleHeaderSetTypeId(result, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(result, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		/* no more rows */
+		XmlTableFreeContext(tstate->xmltableCxt);
+		tstate->xmltableCxt = NULL;
+
+		/* ensure releasing all memory */
+		MemoryContextReset(tstate->per_rowgroup_memory);
+
+		/* next row will be first again */
+		tstate->firstRow = true;
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		return (Datum) 0;
+	}
+
+	*isNull = false;
+	*isDone = ExprMultipleResult;
+
+	return HeapTupleHeaderGetDatum(result);
+}
+
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isNull, ExprDoneCond *isDone)
+{
+	/* Ensure releasing context every exception */
+	Datum		result;
+
+	PG_TRY();
+	{
+		result = execEvalTableExpr(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		if (tstate->xmltableCxt != NULL)
+		{
+			XmlTableFreeContext(tstate->xmltableCxt);
+			tstate->xmltableCxt = NULL;
+		}
+
+		MemoryContextDelete(tstate->per_rowgroup_memory);
+		tstate->per_rowgroup_memory = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5477,133 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr	   *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int				ncols;
+				ListCell	   *col;
+				TupleDesc		tupdesc;
+				int				i;
+
+				tstate->firstRow = true;
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+
+				/* when typmod is not valid, refresh it */
+				if (te->typmod == -1)
+				{
+					TupleDesc tupdesc = TableExprGetTupleDesc(te);
+					tstate->typid = tupdesc->tdtypeid;
+					tstate->typmod = tupdesc->tdtypmod;
+				}
+				else
+				{
+					tstate->typid = te->typid;
+					tstate->typmod = te->typmod;
+				}
+
+				tupdesc = lookup_rowtype_tupdesc_copy(tstate->typid, tstate->typmod);
+				ncols = tupdesc->natts;
+				tstate->tupdesc = tupdesc;
+
+				/* result is one more columns every time */
+				Assert(ncols > 0);
+
+				tstate->values = palloc(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid		in_funcid;
+
+					getTypeInputInfo(tupdesc->attrs[i]->atttypid, &in_funcid,
+														&tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_path_expr = ExecInitExpr((Expr *) te->row_path, parent);
+				tstate->expr = ExecInitExpr((Expr *) te->expr, parent);
+
+				if (te->cols)
+				{
+					Assert(ncols == list_length(te->cols));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_path_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+
+					i = 0;
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+																	  parent);
+							tstate->col_path_expr[i] = ExecInitExpr((Expr *) tec->path_expr,
+																		parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+					tstate->rownum = 0;
+				}
+				else
+				{
+					/* There are not any related data */
+					tstate->def_expr = NULL;
+					tstate->col_path_expr = NULL;
+					tstate->not_null = NULL;
+					tstate->ncols = 0;
+				}
+
+				if (te->namespaces)
+				{
+					List		*preparedlist = NIL;
+					ListCell	*ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node *n = (Node *) lfirst(ns);
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+						{
+							preparedlist = lappend(preparedlist,
+													  ExecInitExpr((Expr *) n, parent));
+							tstate->used_dns = true;
+						}
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->xmltableCxt = NULL;
+				tstate->per_rowgroup_memory = AllocSetContextCreate(CurrentMemoryContext,
+																		"XmlTable per rowgroup context",
+																		ALLOCSET_DEFAULT_MINSIZE,
+																		ALLOCSET_DEFAULT_INITSIZE,
+																		ALLOCSET_DEFAULT_MAXSIZE);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c7a0644..33e0c39 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1986,6 +1986,65 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr *newnode = makeNode(TableExpr);
+
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_NODE_FIELD(row_path);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(cols);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4583,6 +4642,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5084,6 +5149,9 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 448e1a9..e4823af 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2610,6 +2610,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2630,6 +2660,20 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_NODE_FIELD(row_path);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(cols);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2895,6 +2939,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3383,6 +3433,9 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..d5e14f8 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -257,6 +257,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = ((const TableExpr *) expr)->typid;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +498,10 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			return ((const TableExpr *) expr)->typmod;
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +737,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 +941,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1145,12 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1576,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2238,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->path_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3058,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+				TableExpr *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_path, te->row_path, Node *);
+				MUTATE(newnode->expr, te->expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->cols, te->cols, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->path_expr, tec->path_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3697,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 50019f4..e628c41 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1587,6 +1587,50 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_OID_FIELD(typid);
+	/* skip typmod, should not be persistent for dynamic RECORD */
+	WRITE_NODE_FIELD(row_path);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(cols);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3540,6 +3584,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3863,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c83063e..0094e04 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2263,6 +2263,49 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_OID_FIELD(typid);
+
+	/* Enforce fresh TupleDesc */
+	local_node->typmod = -1;
+
+	READ_NODE_FIELD(row_path);
+	READ_NODE_FIELD(expr);
+	READ_NODE_FIELD(cols);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(path_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2494,6 +2537,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 4496fde..4d69944 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -819,6 +819,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 cb5cfc4..372f895 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -120,6 +120,20 @@ typedef struct ImportQual
 	List	   *table_names;
 } ImportQual;
 
+/* Private data for TableExprColOption */
+typedef enum TableExprColOptionType
+{
+	TABLE_EXPR_COLOPT_DEFAULT,
+	TABLE_EXPR_COLOPT_PATH
+} TableExprColOptionType;
+
+typedef struct TableExprColOption
+{
+	TableExprColOptionType	typ;
+	Node			*val;
+	int			location;
+} TableExprColOption;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -229,6 +243,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	struct TableExprColOption *te_colopt;
 }
 
 %type <node>	stmt schema_stmt
@@ -542,6 +557,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	TableExprColList TableExprColOptions TableExprColOptionsOpt
+%type <node>	TableExprCol
+%type <te_colopt>	TableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -573,10 +595,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
 
@@ -617,7 +639,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
@@ -646,8 +668,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
 
@@ -12539,6 +12561,148 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = $10;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+TableExprColList: TableExprCol							{ $$ = list_make1($1); }
+			| TableExprColList ',' TableExprCol			{ $$ = lappend($1, $3); }
+		;
+
+TableExprCol:
+			ColId Typename TableExprColOptionsOpt IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *opt;
+
+					rawc->colname = $1;
+					rawc->typeName = $2;
+					rawc->is_not_null = $4;
+					rawc->location = @1;
+					rawc->for_ordinality = false;
+
+					foreach(opt, $3)
+					{
+						TableExprColOption *co = (TableExprColOption *) lfirst(opt);
+
+						if (co->typ == TABLE_EXPR_COLOPT_DEFAULT)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed"),
+													parser_errposition(co->location)));
+							rawc->default_expr = co->val;
+						}
+						else if (co->typ == TABLE_EXPR_COLOPT_PATH)
+						{
+							if (rawc->path_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+													parser_errposition(co->location)));
+							rawc->path_expr = co->val;
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+TableExprColOptionsOpt: TableExprColOptions				{ $$ = $1; }
+			| /* EMPTY */								{ $$ = NIL; }
+		;
+
+TableExprColOptions: TableExprColOption					{ $$ = list_make1($1); }
+			| TableExprColOptions TableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+TableExprColOption:
+			DEFAULT c_expr
+				{
+					$$ = palloc(sizeof(TableExprColOption));
+					$$->typ = TABLE_EXPR_COLOPT_DEFAULT;
+					$$->val = $2;
+					$$->location = @1;
+				}
+			| PATH c_expr
+				{
+					$$ = palloc(sizeof(TableExprColOption));
+					$$->typ = TABLE_EXPR_COLOPT_PATH;
+					$$->val = $2;
+					$$->location = @1;
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			a_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT a_expr
+				{
+					$$ = $2;
+				}
 		;
 
 /*
@@ -13677,6 +13841,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13810,6 +13975,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PATH
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -13975,10 +14141,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..0686e07 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1105,8 +1105,8 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
+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,15 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+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 63f7965..8293658 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -365,6 +367,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2678,6 +2684,176 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a TableExpr
+ */
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr *newte = makeNode(TableExpr);
+	TupleDesc tupdesc;
+
+	Assert(te->row_path != NULL);
+	Assert(te->expr != NULL);
+
+	newte->row_path = coerce_to_specific_type(pstate,
+								transformExprRecurse(pstate, te->row_path),
+														 TEXTOID,
+														 "XMLTABLE");
+	newte->expr = coerce_to_specific_type(pstate,
+								transformExprRecurse(pstate, te->expr),
+														 XMLOID,
+														 "XMLTABLE");
+
+	if (te->cols != NIL)
+	{
+		ListCell	   *col;
+		int				i = 1;
+		bool			for_ordinality = false;
+
+		tupdesc = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			TableExprRawCol	   *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn	   *newc = makeNode(TableExprColumn);
+			Oid					typid;
+			int32				typmod;
+			int					j;
+
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			if (!rawc->for_ordinality)
+			{
+				typenameTypeIdAndMod(NULL, rawc->typeName, &typid, &typmod);
+
+				if (rawc->path_expr)
+					newc->path_expr = coerce_to_specific_type(pstate,
+									transformExprRecurse(pstate, rawc->path_expr),
+															 TEXTOID,
+															 "XMLTABLE");
+			  if (rawc->default_expr)
+					newc->default_expr = coerce_to_specific_type_typmod(pstate,
+									transformExprRecurse(pstate, rawc->default_expr),
+															 typid, typmod,
+															 "XMLTABLE");
+			}
+			else
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one column FOR ORDINALITY is allowed"),
+											parser_errposition(pstate, rawc->location)));
+
+				typid = INT4OID;
+				typmod = -1;
+			}
+			TupleDescInitEntry(tupdesc, (AttrNumber) i,
+											 pstrdup(rawc->colname),
+											 typid, typmod, 0);
+
+			newc->typid = typid;
+			newc->typmod = typmod;
+
+			newte->cols = lappend(newte->cols, newc);
+
+			/* the name should be unique */
+			for (j = 0; j < i - 1; j++)
+				if (strcmp(NameStr(tupdesc->attrs[j]->attname), rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+													rawc->colname),
+								parser_errposition(pstate, rawc->location)));
+			i++;
+		}
+	}
+	else
+	{
+		/*
+		 * When columsn are not defined, then output is XML column.
+		 * ANSI/SQL standard doesn't specify the name of this column,
+		 * and there are not conformity between databases. Postgres
+		 * uses function name like default. This implementation
+		 * respects it.
+		 */
+		tupdesc = CreateTemplateTupleDesc(1, false);
+
+		/* Generate tupdesc with one auto XML attribute */
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xmltable", XMLOID, -1, 0);
+	}
+
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		char	  **names;
+		bool		found_dns = false;
+		int			nnames = 0;
+
+		names = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node		*n = (Node *) lfirst(ns);
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr   *na = (NamedArgExpr *) n;
+				int				i;
+
+				for (i = 0; i < nnames; i++)
+					if (strcmp(names[i], na->name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the namespace name \"%s\" is not unique",
+														na->name),
+									parser_errposition(pstate, na->location)));
+				names[nnames++] = na->name;
+
+				/* coerce should immediately after expression, else reset the name */
+				na->arg = (Expr *) coerce_to_specific_type(pstate,
+									 transformExprRecurse(pstate, (Node *) na->arg),
+									 TEXTOID,
+									 "XMLTABLE");
+			}
+			else
+			{
+				/* default ns specification (without name) must by only one */
+				if (found_dns)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one default namespace is allowed"),
+									parser_errposition(pstate, exprLocation(n))));
+				found_dns = true;
+				n = coerce_to_specific_type(pstate,
+									 transformExprRecurse(pstate, n),
+									 TEXTOID,
+									 "XMLTABLE");
+			}
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(names);
+	}
+	else
+		newte->namespaces = NIL;
+
+	assign_record_type_typmod(tupdesc);
+
+	newte->typid = tupdesc->tdtypeid;
+	newte->typmod = tupdesc->tdtypmod;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
+/*
  * 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 b7b82bf..7ce209d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1837,6 +1837,9 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..bae7fa9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8280,6 +8281,100 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_path, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->cols != NIL)
+				{
+					ListCell	*col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+										  format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->path_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->path_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 7ed5bcb..e7650d4 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -4073,3 +4073,756 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * We need to work with XPath expression tokens. When expression
+ * starting or finishing with nodenames, then we can use prefix
+ * and suffix. When default namespace is defined, then we should to
+ * enhance any nodename and attribute without namespace by default
+ * namespace. The procession of XPath expression is linear.
+ */
+
+typedef enum
+{
+	XPATH_TOKEN_NONE,
+	XPATH_TOKEN_NAME,
+	XPATH_TOKEN_STRING,
+	XPATH_TOKEN_NUMBER,
+	XPATH_TOKEN_OTHER
+} XPathTokenType;
+
+typedef struct TokenInfo
+{
+	XPathTokenType		ttype;
+	char		   *start;
+	int				length;
+} XPathTokenInfo;
+
+#define TOKEN_STACK_SIZE		10
+
+typedef struct ParserData
+{
+	char		   *str;
+	char		   *cur;
+	XPathTokenInfo	stack[TOKEN_STACK_SIZE];
+	int				stack_length;
+} XPathParserData;
+
+#define NODENAME_FIRSTCHAR(c)	 ((c) == '_' || (c) == '-' || \
+								 ((c) >= 'A' && (c) <= 'Z') || \
+								 ((c) >= 'a' && (c) <= 'z') || \
+								 ((c) >= '0' && (c) <= '9'))
+
+#define IS_NODENAME_CHAR(c)		(NODENAME_FIRSTCHAR(c) || (c) == '.')
+
+/*
+ * Returns next char after last char of token
+ */
+static char *
+getXPathToken(char *str, XPathTokenInfo *ti)
+{
+
+	/* skip initial spaces */
+	while (*str == ' ')
+		str++;
+
+	if (*str != '\0')
+	{
+		char		c = *str;
+
+		ti->start = str++;
+
+		if (c >= '0' && c <= '9')
+		{
+			while (*str >= '0' && *str <= '9')
+				str++;
+			if (*str == '.')
+			{
+				str++;
+				while (*str >= '0' && *str <= '9')
+					str++;
+			}
+			ti->ttype = XPATH_TOKEN_NUMBER;
+		}
+		else if (NODENAME_FIRSTCHAR(c))
+		{
+			while (IS_NODENAME_CHAR(*str))
+				str++;
+
+			ti->ttype = XPATH_TOKEN_NAME;
+		}
+		else if (c == '"')
+		{
+			while (*str != '\0')
+				if (*str++ == '"')
+					break;
+
+			ti->ttype = XPATH_TOKEN_STRING;
+		}
+		else
+			ti->ttype = XPATH_TOKEN_OTHER;
+
+		ti->length = str - ti->start;
+	}
+	else
+	{
+		ti->start = NULL;
+		ti->length = 0;
+
+		ti->ttype = XPATH_TOKEN_NONE;
+	}
+
+	return str;
+}
+
+/*
+ * reset XPath parser stack
+ */
+static void
+initXPathParser(XPathParserData *parser, char *str)
+{
+	parser->str = str;
+	parser->cur = str;
+	parser->stack_length = 0;
+}
+
+static void
+nextXPathToken(XPathParserData *parser, XPathTokenInfo *ti)
+{
+	if (parser->stack_length > 0)
+		memcpy(ti, &parser->stack[--parser->stack_length],
+										  sizeof(XPathTokenInfo));
+	else
+		parser->cur = getXPathToken(parser->cur, ti);
+}
+
+static void
+pushXPathToken(XPathParserData *parser, XPathTokenInfo *ti)
+{
+	if (parser->stack_length == TOKEN_STACK_SIZE)
+		elog(ERROR, "internal error");
+	memcpy(&parser->stack[parser->stack_length++], ti,
+										  sizeof(XPathTokenInfo));
+}
+
+static void
+writeXPathToken(StringInfo str, XPathTokenInfo *ti)
+{
+	Assert(ti->ttype != XPATH_TOKEN_NONE);
+
+	if (ti->ttype != XPATH_TOKEN_OTHER)
+		appendBinaryStringInfo(str, ti->start, ti->length);
+	else
+		appendStringInfoChar(str, *ti->start);
+}
+
+/*
+ * Working horse for XPath transformation. When XPath starting by node name,
+ * then prefix have to be applied. When XPath ending by node name, then
+ * suffix will be used. Any unqualified node name should be qualified by
+ * default namespace.
+ */
+static void
+_transformXPath(StringInfo str, XPathParserData *parser,
+				  bool inside_predicate,
+				  char *prefix, char *suffix, char *default_ns_name)
+{
+	XPathTokenInfo	t1, t2;
+	bool			is_first_token = true;
+	bool			last_token_is_name = false;
+
+	nextXPathToken(parser, &t1);
+
+	while (t1.ttype != XPATH_TOKEN_NONE)
+	{
+		switch (t1.ttype)
+		{
+			case XPATH_TOKEN_NUMBER:
+			case XPATH_TOKEN_STRING:
+				last_token_is_name = false;
+				is_first_token = false;
+				writeXPathToken(str, &t1);
+				nextXPathToken(parser, &t1);
+				break;
+
+			case XPATH_TOKEN_NAME:
+				{
+					bool	is_qual_name = false;
+
+					/* inside predicate ignore keywords "and" "or" */
+					if (inside_predicate)
+					{
+						if ((strncmp(t1.start, "and", 3) == 0 && t1.length == 3) ||
+							(strncmp(t1.start, "or", 2) == 0 && t1.length == 2))
+						{
+							writeXPathToken(str, &t1);
+							nextXPathToken(parser, &t1);
+							break;
+						}
+					}
+
+					last_token_is_name = true;
+					nextXPathToken(parser, &t2);
+					if (t2.ttype == XPATH_TOKEN_OTHER)
+					{
+						if (*t2.start == '(')
+							last_token_is_name = false;
+						else if (*t2.start == ':')
+							is_qual_name = true;
+					}
+
+					if (is_first_token && last_token_is_name && prefix != NULL)
+						appendStringInfoString(str, prefix);
+
+					if (last_token_is_name && !is_qual_name && default_ns_name != NULL)
+						appendStringInfo(str, "%s:", default_ns_name);
+
+					writeXPathToken(str, &t1);
+					is_first_token = false;
+
+					if (is_qual_name)
+					{
+						writeXPathToken(str, &t2);
+						nextXPathToken(parser, &t1);
+						if (t1.ttype == XPATH_TOKEN_NAME)
+							writeXPathToken(str, &t1);
+						else
+							pushXPathToken(parser, &t1);
+					}
+					else
+						pushXPathToken(parser, &t2);
+
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_OTHER:
+				{
+					char c = *t1.start;
+
+					is_first_token = false;
+
+					writeXPathToken(str, &t1);
+
+					if (c == '[')
+						_transformXPath(str, parser, true, NULL, NULL, default_ns_name);
+					else
+					{
+						last_token_is_name = false;
+
+						if (c == ']' && inside_predicate)
+							return;
+
+						else if (c == '@')
+						{
+							nextXPathToken(parser, &t1);
+							if (t1.ttype == XPATH_TOKEN_NAME)
+							{
+								bool		is_qual_name = false;
+
+								nextXPathToken(parser, &t2);
+								if (t2.ttype == XPATH_TOKEN_OTHER && *t2.start == ':')
+									is_qual_name = true;
+
+								if (!is_qual_name && default_ns_name != NULL)
+									appendStringInfo(str, "%s:", default_ns_name);
+
+								writeXPathToken(str, &t1);
+								if (is_qual_name)
+								{
+									writeXPathToken(str, &t2);
+									nextXPathToken(parser, &t1);
+									if (t1.ttype == XPATH_TOKEN_NAME)
+										writeXPathToken(str, &t1);
+									else
+										pushXPathToken(parser, &t1);
+								}
+								else
+									pushXPathToken(parser, &t2);
+							}
+							else
+								pushXPathToken(parser, &t1);
+						}
+					}
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_NONE:
+				elog(ERROR, "should not be here");
+		}
+	}
+
+	if (last_token_is_name && suffix != NULL)
+		appendStringInfoString(str, suffix);
+}
+
+static void
+transformXPath(StringInfo str, char *xpath,
+					  char *prefix, char *suffix, char *default_ns_name)
+{
+	XPathParserData		parser;
+
+	initStringInfo(str);
+	initXPathParser(&parser, xpath);
+	_transformXPath(str, &parser, false, prefix, suffix, default_ns_name);
+}
+
+
+struct XmlTableContext
+{
+	MemoryContext			per_rowgroup_memory;
+	int						ncols;
+	PgXmlErrorContext	   *xmlerrcxt;
+	xmlParserCtxtPtr		ctxt;
+	xmlDocPtr				doc;
+	xmlXPathContextPtr		xpathcxt;
+	xmlXPathCompExprPtr		xpathcomp;
+	xmlXPathObjectPtr		xpathobj;
+	xmlXPathCompExprPtr	   *xpathscomp;
+	FmgrInfo			   *in_functions;
+	Oid					   *typioparams;
+	char				   *default_ns_name;
+	long int				rc;
+};
+
+/*
+ * 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;
+}
+
+#endif
+
+struct XmlTableContext *
+initXmlTableContext(xmltype *xmlval, char *default_ns_name,
+				int ncols, FmgrInfo *in_functions, Oid *typioparams,
+				MemoryContext per_rowgroup_memory)
+{
+#ifdef USE_LIBXML
+	MemoryContext				oldcxt;
+	struct XmlTableContext	   *result = NULL;
+	PgXmlErrorContext		   *xmlerrcxt = NULL;
+	int32						len;
+	xmlChar			*xmlval_str;
+
+	volatile xmlParserCtxtPtr	ctxt = NULL;
+	volatile xmlDocPtr			doc = NULL;
+	volatile xmlXPathContextPtr	xpathcxt = NULL;
+
+	oldcxt = MemoryContextSwitchTo(per_rowgroup_memory);
+
+	len = VARSIZE(xmlval) - VARHDRSZ;
+	xmlval_str = palloc((len + 1) * sizeof(xmlChar));
+	memcpy(xmlval_str, VARDATA(xmlval), len);
+	xmlval_str[len] = '\0';
+
+	result = palloc0(sizeof(struct XmlTableContext));
+	result->per_rowgroup_memory = per_rowgroup_memory;
+	result->in_functions = in_functions;
+	result->typioparams = typioparams;
+	result->default_ns_name = default_ns_name;
+	result->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * ncols);
+
+	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");
+		doc = xmlCtxtReadMemory(ctxt, (char *) xmlval_str, len, NULL, NULL, 0);
+		if (doc == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+				"could not parse XML document");
+		xpathcxt = xmlXPathNewContext(doc);
+		if (xpathcxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+				"could not allocate XPath context");
+		xpathcxt->node = xmlDocGetRootElement(doc);
+		if (xpathcxt->node == NULL  || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+				"could not find root XML element");
+	}
+	PG_CATCH();
+	{
+		if (xpathcxt != NULL)
+			xmlXPathFreeContext(xpathcxt);
+		if (doc != NULL)
+			xmlFreeDoc(doc);
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	result->ncols = ncols;
+	result->xmlerrcxt = xmlerrcxt;
+	result->ctxt = ctxt;
+	result->doc = doc;
+	result->xpathcxt = xpathcxt;
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return (struct XmlTableContext *) result;
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return NULL;
+};
+
+void
+XmlTableSetRowPath(struct XmlTableContext *xtCxt, text *row_path)
+{
+#ifdef USE_LIBXML
+	xmlChar			*row_path_str;
+	MemoryContext	oldcxt;
+	StringInfoData			str;
+	char		*path_str;
+
+	path_str = text_to_cstring(row_path);
+	if (*path_str == '\0')
+		ereport(ERROR,
+					(errcode(ERRCODE_DATA_EXCEPTION),
+					 errmsg("row path filter must not be empty string")));
+
+	transformXPath(&str, path_str, NULL, NULL, xtCxt->default_ns_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+	row_path_str = (xmlChar *) palloc((str.len + 1) * sizeof(xmlChar));
+	memcpy(row_path_str, str.data, str.len);
+	row_path_str[str.len] = '\0';
+
+	xtCxt->xpathcomp = xmlXPathCompile(row_path_str);
+	if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableFreeContext(struct XmlTableContext *xtCxt)
+{
+#ifdef USE_LIBXML
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int		i;
+
+		for (i = 0; i < xtCxt->ncols; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableSetNs(struct XmlTableContext *xtCxt, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+
+	if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+								(xmlChar *)(name ? name : xtCxt->default_ns_name),
+								(xmlChar *) uri))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableSetColumnPath(struct XmlTableContext *xtCxt, int i, Oid typid, char *path)
+{
+#ifdef USE_LIBXML
+	MemoryContext oldcxt;
+	StringInfoData		str;
+	xmlChar			*xmlstr;
+
+	transformXPath(&str, path,
+					  "./", typid != XMLOID ? "/text()" : NULL,
+					  xtCxt->default_ns_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+	xmlstr = palloc((str.len + 1) * sizeof(xmlChar));
+	memcpy(xmlstr, str.data, str.len);
+	xmlstr[str.len] = '\0';
+
+	xtCxt->xpathscomp[i] = xmlXPathCompile(xmlstr);
+	if (xtCxt->xpathscomp[i] == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+bool
+XmlTableFetchRow(struct XmlTableContext *xtCxt)
+{
+#ifdef USE_LIBXML
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+				"could not create XPath object");
+
+		xtCxt->rc = 0;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+};
+
+Datum
+XmlTableGetValue(struct XmlTableContext *xtCxt, int ncol, Oid typid, int32 typmod, bool *isnull)
+{
+#ifdef USE_LIBXML
+	Datum			result = (Datum) 0;
+	xmlNodePtr		cur;
+	char		   *cstr;
+	volatile xmlXPathObjectPtr		column_xpathobj = NULL;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+			  xtCxt->xpathobj->nodesetval != NULL);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+	if (cur->type == XML_ELEMENT_NODE)
+	{
+		PG_TRY();
+		{
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[ncol], 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)
+				{
+					if (count == 1)
+					{
+						cstr = xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[0],
+															xtCxt->xmlerrcxt);
+					}
+					else
+					{
+						StringInfoData		str;
+						int					i;
+
+						/*
+						 * more values, target must be XML.
+						 * Note: possibly any array can be there.
+						 */
+						if (typid != XMLOID)
+							ereport(ERROR,
+										(errcode(ERRCODE_CARDINALITY_VIOLATION),
+										 errmsg("more than one value returned by column XPath expression")));
+
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+										  xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														   xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[ncol],
+													  cstr,
+													  xtCxt->typioparams[ncol],
+													  typmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[ncol],
+													  (char *) column_xpathobj->stringval,
+													  xtCxt->typioparams[ncol],
+													  typmod);
+				*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
+		elog(ERROR, "unexpected xmlNode type");
+
+	return (Datum) result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	*isnull = true;
+	return (Datum) 0;
+};
+
+Datum
+XmlTableGetRowValue(struct XmlTableContext *xtCxt, bool *isnull)
+{
+#ifdef USE_LIBXML
+
+	Datum			result = (Datum) 0;
+	xmlNodePtr		cur;
+	char		   *cstr;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+			  xtCxt->xpathobj->nodesetval != NULL);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+	cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+
+	result = InputFunctionCall(&xtCxt->in_functions[0],
+									cstr,
+									xtCxt->typioparams[0],
+									-1);						/* target type is XML always */
+	*isnull = false;
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	*isnull = true;
+	return (Datum) 0;
+};
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d179ae..99c39b8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -221,6 +221,49 @@ get_call_result_type(FunctionCallInfo fcinfo,
 }
 
 /*
+ * When we skip transform stage (in view), then TableExpr's
+ * TupleDesc should not be valid. Refresh is necessary.
+ */
+TupleDesc
+TableExprGetTupleDesc(TableExpr *te)
+{
+	TupleDesc tupdesc;
+
+	if (te->cols != NIL)
+	{
+		ListCell	   *col;
+		int				i = 1;
+
+		tupdesc = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(tupdesc,
+									  (AttrNumber) i,
+												  pstrdup(tec->colname),
+												  tec->typid,
+												  tec->typmod,
+												  0);
+			i++;
+		}
+	}
+	else
+	{
+		tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xmltable", XMLOID, -1, 0);
+	}
+
+	assign_record_type_typmod(tupdesc);
+	te->typmod = tupdesc->tdtypmod;
+
+	Assert(te->typid = tupdesc->tdtypeid);
+
+	return tupdesc;
+}
+
+/*
  * get_expr_result_type
  *		As above, but work from a calling expression node tree
  */
@@ -243,6 +286,22 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		TableExpr *te = (TableExpr *) expr;
+
+		if (resultTypeId)
+			*resultTypeId = te->typid;
+
+		/* Enforce fresh RECORD tupledesc */
+		if (te->typmod == -1)
+			TableExprGetTupleDesc(te);
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(te->typid, te->typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..0417a60 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,6 +184,7 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
+extern TupleDesc TableExprGetTupleDesc(TableExpr *te);
 
 /*----------
  *	Support to ease writing functions returning composite types
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e7fd7bd..46a8ab4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1004,6 +1004,31 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	bool		firstRow;			/* true, when first tuple from call should be returned */
+	List	   *namespaces;			/* list of prepared ResTarget fields */
+	bool		used_dns;			/* true, when default namespace is used */
+	Oid			typid;
+	int32		typmod;
+	TupleDesc	tupdesc;			/* cache */
+	int		ncols;					/* number of declared columns */
+	int		for_ordinality_col;		/* number of oridinality column, started by 1 */
+	int		rownum;					/* row counter - for ordinality column */
+	ExprState      *row_path_expr;	/* row xpath expression */
+	ExprState      *expr;			/* processed data */
+	ExprState     **def_expr;		/* array of expressions for default value */
+	ExprState     **col_path_expr;	/* array of expressions for path value */
+	bool	       *not_null;		/* for any column info if NULL is allowed or not */
+	Datum	       *values;								/* prealloc buffer */
+	bool	       *nulls;								/* prealloc buffer */
+	FmgrInfo	  *in_functions;	/* array of infunction for any column */
+	Oid			  *typioparams;		/* array of typIOParam for any column */
+	struct XmlTableContext		*xmltableCxt;
+	MemoryContext				per_rowgroup_memory;
+} TableExprState;
+
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..504fb9d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -453,6 +456,7 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1481fff..8bf9736 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -697,6 +697,22 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * There are different requests from XMLTABLE, JSON_TABLE functions
+ * on passed data than has CREATE TABLE command. It is reason for
+ * introduction special structure instead using ColumnDef.
+ */
+typedef struct TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;
+	TypeName   *typeName;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..195e637 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,36 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - used for XMLTABLE function
+ *
+ * This can be used for json_table, jsonb_table functions in future
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Oid				typid;
+	int32			typmod;
+	Node	   *row_path;		/* row xpath query */
+	Node	   *expr;			/* processed data */
+	List	   *cols;			/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+} TableExpr;
+
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;
+	Oid			typid;
+	int32		typmod;
+	Oid			collation;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..de83895 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -289,6 +290,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -438,10 +440,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..1218bf3 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+						Oid targetTypeId, int32 targetTypmod,
+						const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..5cf94f9 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,17 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern struct XmlTableContext *initXmlTableContext(xmltype *xmlval,
+				char *default_ns_name,
+				int ncols,
+				FmgrInfo *in_functions, Oid *typioparams,
+				MemoryContext	per_rowgroup_memory);
+extern void XmlTableFreeContext(struct XmlTableContext *xtCxt);
+extern void XmlTableSetNs(struct XmlTableContext *xtCxt, char *name, char *uri);
+extern void XmlTableSetRowPath(struct XmlTableContext *xtCxt, text *row_path);
+extern void XmlTableSetColumnPath(struct XmlTableContext *xtCxt, int i, Oid typid, char *path);
+extern bool XmlTableFetchRow(struct XmlTableContext *xtCxt);
+extern Datum XmlTableGetValue(struct XmlTableContext *xtCxt, int ncol, Oid typid, int32 typmod, bool *isnull);
+extern Datum XmlTableGetRowValue(struct XmlTableContext *xtCxt, bool *isnull);
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..ed6cb79 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,139 @@ 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)
+
+
+-- 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');
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d702703..58e6723 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,127 @@ 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)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz)
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz)
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltableview2;
+ERROR:  relation "xmltableview2" does not exist
+LINE 1: SELECT * FROM xmltableview2;
+                      ^
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y')
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+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.
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5..48fae36 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,138 @@ 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)
+
+-- 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');
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30..5909a51 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,91 @@ 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;
+
+-- 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>');
#4Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#3)
1 attachment(s)
Re: patch: function xmltable

Hi

minor update - using DefElem instead own private parser type

Regards

Pavel

Attachments:

xmltable-20160904.patchtext/x-patch; charset=US-ASCII; name=xmltable-20160904.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5148095..189d201 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10099,6 +10099,47 @@ SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
     </para>
    </sect3>
 
+   <sect3>
+    <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>|DEFAULT <replaceable>namespace uri</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 table based on passed XML value.
+    </para>
+
+    <para>
+<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>
+   </sect3>
+
    <sect3 id="functions-xml-xmlagg">
     <title><literal>xmlagg</literal></title>
 
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 743e7d6..7abc367 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -189,6 +189,9 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isnull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4503,218 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * ----------------------------------------------------------------
+ */
+
+#define XMLTABLE_DEFAULT_NAMESPACE			"pgdefxmlns"
+
+static Datum
+execEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isNull, ExprDoneCond *isDone)
+{
+	TupleDesc tupdesc;
+	HeapTuple		tuple;
+	HeapTupleHeader		result;
+	int					i;
+	Datum		*values;
+	bool		*nulls;
+	Datum	value;
+	bool	isnull;
+	xmltype		   *xmlval;
+	text		   *row_path;
+
+	tupdesc = tstate->tupdesc;
+
+	if (tstate->firstRow)
+	{
+		ListCell	*ns;
+
+		/* Evaluate expression first */
+		value = ExecEvalExpr(tstate->expr, econtext, &isnull, NULL);
+		if (isnull)
+		{
+			*isDone = ExprSingleResult;
+			*isNull = true;
+			return (Datum) 0;
+		}
+		xmlval = DatumGetXmlP(value);
+
+		value = ExecEvalExpr(tstate->row_path_expr, econtext, &isnull, NULL);
+		if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					  errmsg("row query must not be null")));
+		row_path = DatumGetTextP(value);
+
+		Assert(tstate->xmltableCxt == NULL);
+		tstate->xmltableCxt = initXmlTableContext(xmlval,
+													tstate->used_dns ?
+										XMLTABLE_DEFAULT_NAMESPACE : NULL,
+													tstate->ncols,
+															  tstate->in_functions,
+															  tstate->typioparams,
+													econtext->ecxt_per_query_memory);
+
+		foreach(ns, tstate->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			ExprState  *expr;
+			char	   *ns_name;
+			char	   *ns_uri;
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr *na = (NamedArgExpr *) n;
+
+				expr = (ExprState *) na->arg;
+				ns_name = na->name;
+			}
+			else
+			{
+				expr = (ExprState *) n;
+				ns_name = NULL;
+			}
+
+			value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					  errmsg("namespace uri must not be null")));
+			ns_uri = text_to_cstring(DatumGetTextP(value));
+
+			XmlTableSetNs(tstate->xmltableCxt, ns_name, ns_uri);
+		}
+
+		XmlTableSetRowPath(tstate->xmltableCxt, row_path);
+
+		/* Path caclulation */
+		for (i = 0; i < tstate->ncols; i++)
+		{
+			char *col_path;
+
+			if (tstate->col_path_expr[i] != NULL)
+			{
+				value = ExecEvalExpr(tstate->col_path_expr[i], econtext, &isnull, NULL);
+				if (isnull)
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						  errmsg("column path for column \"%s\" must not be null",
+											  NameStr(tupdesc->attrs[i]->attname))));
+				col_path = text_to_cstring(DatumGetTextP(value));
+			}
+			else
+				col_path = NameStr(tupdesc->attrs[i]->attname);
+
+			XmlTableSetColumnPath(tstate->xmltableCxt, i,
+											tupdesc->attrs[i]->atttypid, col_path);
+		}
+		tstate->firstRow = false;
+	}
+
+	values = tstate->values;
+	nulls = tstate->nulls;
+
+	if (XmlTableFetchRow(tstate->xmltableCxt))
+	{
+		if (tstate->ncols > 0)
+		{
+			for (i = 0; i < tstate->ncols; i++)
+			{
+				if (i != tstate->for_ordinality_col - 1)
+				{
+					values[i] = XmlTableGetValue(tstate->xmltableCxt, i,
+												  tupdesc->attrs[i]->atttypid,
+												  tupdesc->attrs[i]->atttypmod,
+																			  &isnull);
+					if (isnull && tstate->def_expr[i] != NULL)
+						values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+					if (isnull && tstate->not_null[i])
+						ereport(ERROR,
+								(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+								 errmsg("null is not allowed in column \"%s\"",
+									  NameStr(tupdesc->attrs[i]->attname))));
+					nulls[i] = isnull;
+				}
+				else
+				{
+					values[i] = Int32GetDatum(++tstate->rownum);
+					nulls[i] = false;
+				}
+			}
+		}
+		else
+			values[0] = XmlTableGetRowValue(tstate->xmltableCxt, &nulls[0]);
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		result = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(result, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type info we identified before.
+		 */
+		HeapTupleHeaderSetTypeId(result, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(result, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		/* no more rows */
+		XmlTableFreeContext(tstate->xmltableCxt);
+		tstate->xmltableCxt = NULL;
+
+		/* ensure releasing all memory */
+		MemoryContextReset(tstate->per_rowgroup_memory);
+
+		/* next row will be first again */
+		tstate->firstRow = true;
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		return (Datum) 0;
+	}
+
+	*isNull = false;
+	*isDone = ExprMultipleResult;
+
+	return HeapTupleHeaderGetDatum(result);
+}
+
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+						ExprContext *econtext,
+						bool *isNull, ExprDoneCond *isDone)
+{
+	/* Ensure releasing context every exception */
+	Datum		result;
+
+	PG_TRY();
+	{
+		result = execEvalTableExpr(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		if (tstate->xmltableCxt != NULL)
+		{
+			XmlTableFreeContext(tstate->xmltableCxt);
+			tstate->xmltableCxt = NULL;
+		}
+
+		MemoryContextDelete(tstate->per_rowgroup_memory);
+		tstate->per_rowgroup_memory = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5477,133 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr	   *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int				ncols;
+				ListCell	   *col;
+				TupleDesc		tupdesc;
+				int				i;
+
+				tstate->firstRow = true;
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+
+				/* when typmod is not valid, refresh it */
+				if (te->typmod == -1)
+				{
+					TupleDesc tupdesc = TableExprGetTupleDesc(te);
+					tstate->typid = tupdesc->tdtypeid;
+					tstate->typmod = tupdesc->tdtypmod;
+				}
+				else
+				{
+					tstate->typid = te->typid;
+					tstate->typmod = te->typmod;
+				}
+
+				tupdesc = lookup_rowtype_tupdesc_copy(tstate->typid, tstate->typmod);
+				ncols = tupdesc->natts;
+				tstate->tupdesc = tupdesc;
+
+				/* result is one more columns every time */
+				Assert(ncols > 0);
+
+				tstate->values = palloc(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid		in_funcid;
+
+					getTypeInputInfo(tupdesc->attrs[i]->atttypid, &in_funcid,
+														&tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_path_expr = ExecInitExpr((Expr *) te->row_path, parent);
+				tstate->expr = ExecInitExpr((Expr *) te->expr, parent);
+
+				if (te->cols)
+				{
+					Assert(ncols == list_length(te->cols));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_path_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+
+					i = 0;
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+																	  parent);
+							tstate->col_path_expr[i] = ExecInitExpr((Expr *) tec->path_expr,
+																		parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+					tstate->rownum = 0;
+				}
+				else
+				{
+					/* There are not any related data */
+					tstate->def_expr = NULL;
+					tstate->col_path_expr = NULL;
+					tstate->not_null = NULL;
+					tstate->ncols = 0;
+				}
+
+				if (te->namespaces)
+				{
+					List		*preparedlist = NIL;
+					ListCell	*ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node *n = (Node *) lfirst(ns);
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+						{
+							preparedlist = lappend(preparedlist,
+													  ExecInitExpr((Expr *) n, parent));
+							tstate->used_dns = true;
+						}
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->xmltableCxt = NULL;
+				tstate->per_rowgroup_memory = AllocSetContextCreate(CurrentMemoryContext,
+																		"XmlTable per rowgroup context",
+																		ALLOCSET_DEFAULT_MINSIZE,
+																		ALLOCSET_DEFAULT_INITSIZE,
+																		ALLOCSET_DEFAULT_MAXSIZE);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1877fb4..1caf946 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,65 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr *newnode = makeNode(TableExpr);
+
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_NODE_FIELD(row_path);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(cols);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4584,6 +4643,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5085,6 +5150,9 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 448e1a9..e4823af 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2610,6 +2610,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2630,6 +2660,20 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_NODE_FIELD(row_path);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(cols);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2895,6 +2939,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3383,6 +3433,9 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..d5e14f8 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -257,6 +257,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = ((const TableExpr *) expr)->typid;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +498,10 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			return ((const TableExpr *) expr)->typmod;
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +737,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 +941,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1145,12 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1576,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2238,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->path_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3058,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+				TableExpr *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_path, te->row_path, Node *);
+				MUTATE(newnode->expr, te->expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->cols, te->cols, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->path_expr, tec->path_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3697,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 29b7712..e72e38d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1588,6 +1588,50 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_OID_FIELD(typid);
+	/* skip typmod, should not be persistent for dynamic RECORD */
+	WRITE_NODE_FIELD(row_path);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(cols);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3541,6 +3585,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3864,6 +3914,9 @@ outNode(StringInfo str, const void *obj)
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6f9a81e..c7c976a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2264,6 +2264,49 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_OID_FIELD(typid);
+
+	/* Enforce fresh TupleDesc */
+	local_node->typmod = -1;
+
+	READ_NODE_FIELD(row_path);
+	READ_NODE_FIELD(expr);
+	READ_NODE_FIELD(cols);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(path_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2495,6 +2538,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e1baf71..1958dbe 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -819,6 +819,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 cb5cfc4..bc79ab2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -542,6 +542,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	TableExprColList TableExprColOptions TableExprColOptionsOpt
+%type <node>	TableExprCol
+%type <defelt>	TableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -573,10 +580,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
 
@@ -617,7 +624,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
@@ -646,8 +653,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
 
@@ -12539,6 +12546,140 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = $10;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+TableExprColList: TableExprCol							{ $$ = list_make1($1); }
+			| TableExprColList ',' TableExprCol			{ $$ = lappend($1, $3); }
+		;
+
+TableExprCol:
+			ColId Typename TableExprColOptionsOpt IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+
+					rawc->colname = $1;
+					rawc->typeName = $2;
+					rawc->is_not_null = $4;
+					rawc->location = @1;
+					rawc->for_ordinality = false;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed")));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->path_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed")));
+							rawc->path_expr = defel->arg;
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+TableExprColOptionsOpt: TableExprColOptions				{ $$ = $1; }
+			| /* EMPTY */								{ $$ = NIL; }
+		;
+
+TableExprColOptions: TableExprColOption					{ $$ = list_make1($1); }
+			| TableExprColOptions TableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+TableExprColOption:
+			DEFAULT c_expr
+				{
+					$$ = makeDefElem("default", $2);
+				}
+			| PATH c_expr
+				{
+					$$ = makeDefElem("path", $2);
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			a_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT a_expr
+				{
+					$$ = $2;
+				}
 		;
 
 /*
@@ -13677,6 +13818,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13810,6 +13952,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PATH
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -13975,10 +14118,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..0686e07 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1105,8 +1105,8 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
+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,15 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+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 63f7965..8293658 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -365,6 +367,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2678,6 +2684,176 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a TableExpr
+ */
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr *newte = makeNode(TableExpr);
+	TupleDesc tupdesc;
+
+	Assert(te->row_path != NULL);
+	Assert(te->expr != NULL);
+
+	newte->row_path = coerce_to_specific_type(pstate,
+								transformExprRecurse(pstate, te->row_path),
+														 TEXTOID,
+														 "XMLTABLE");
+	newte->expr = coerce_to_specific_type(pstate,
+								transformExprRecurse(pstate, te->expr),
+														 XMLOID,
+														 "XMLTABLE");
+
+	if (te->cols != NIL)
+	{
+		ListCell	   *col;
+		int				i = 1;
+		bool			for_ordinality = false;
+
+		tupdesc = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			TableExprRawCol	   *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn	   *newc = makeNode(TableExprColumn);
+			Oid					typid;
+			int32				typmod;
+			int					j;
+
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			if (!rawc->for_ordinality)
+			{
+				typenameTypeIdAndMod(NULL, rawc->typeName, &typid, &typmod);
+
+				if (rawc->path_expr)
+					newc->path_expr = coerce_to_specific_type(pstate,
+									transformExprRecurse(pstate, rawc->path_expr),
+															 TEXTOID,
+															 "XMLTABLE");
+			  if (rawc->default_expr)
+					newc->default_expr = coerce_to_specific_type_typmod(pstate,
+									transformExprRecurse(pstate, rawc->default_expr),
+															 typid, typmod,
+															 "XMLTABLE");
+			}
+			else
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one column FOR ORDINALITY is allowed"),
+											parser_errposition(pstate, rawc->location)));
+
+				typid = INT4OID;
+				typmod = -1;
+			}
+			TupleDescInitEntry(tupdesc, (AttrNumber) i,
+											 pstrdup(rawc->colname),
+											 typid, typmod, 0);
+
+			newc->typid = typid;
+			newc->typmod = typmod;
+
+			newte->cols = lappend(newte->cols, newc);
+
+			/* the name should be unique */
+			for (j = 0; j < i - 1; j++)
+				if (strcmp(NameStr(tupdesc->attrs[j]->attname), rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+													rawc->colname),
+								parser_errposition(pstate, rawc->location)));
+			i++;
+		}
+	}
+	else
+	{
+		/*
+		 * When columsn are not defined, then output is XML column.
+		 * ANSI/SQL standard doesn't specify the name of this column,
+		 * and there are not conformity between databases. Postgres
+		 * uses function name like default. This implementation
+		 * respects it.
+		 */
+		tupdesc = CreateTemplateTupleDesc(1, false);
+
+		/* Generate tupdesc with one auto XML attribute */
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xmltable", XMLOID, -1, 0);
+	}
+
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		char	  **names;
+		bool		found_dns = false;
+		int			nnames = 0;
+
+		names = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node		*n = (Node *) lfirst(ns);
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr   *na = (NamedArgExpr *) n;
+				int				i;
+
+				for (i = 0; i < nnames; i++)
+					if (strcmp(names[i], na->name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the namespace name \"%s\" is not unique",
+														na->name),
+									parser_errposition(pstate, na->location)));
+				names[nnames++] = na->name;
+
+				/* coerce should immediately after expression, else reset the name */
+				na->arg = (Expr *) coerce_to_specific_type(pstate,
+									 transformExprRecurse(pstate, (Node *) na->arg),
+									 TEXTOID,
+									 "XMLTABLE");
+			}
+			else
+			{
+				/* default ns specification (without name) must by only one */
+				if (found_dns)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one default namespace is allowed"),
+									parser_errposition(pstate, exprLocation(n))));
+				found_dns = true;
+				n = coerce_to_specific_type(pstate,
+									 transformExprRecurse(pstate, n),
+									 TEXTOID,
+									 "XMLTABLE");
+			}
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(names);
+	}
+	else
+		newte->namespaces = NIL;
+
+	assign_record_type_typmod(tupdesc);
+
+	newte->typid = tupdesc->tdtypeid;
+	newte->typmod = tupdesc->tdtypmod;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
+/*
  * 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 b7b82bf..7ce209d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1837,6 +1837,9 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..bae7fa9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8280,6 +8281,100 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr *te = (TableExpr *) node;
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_path, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->cols != NIL)
+				{
+					ListCell	*col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+										  format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->path_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->path_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index b144920..ab6c4d0 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -4071,3 +4071,756 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * We need to work with XPath expression tokens. When expression
+ * starting or finishing with nodenames, then we can use prefix
+ * and suffix. When default namespace is defined, then we should to
+ * enhance any nodename and attribute without namespace by default
+ * namespace. The procession of XPath expression is linear.
+ */
+
+typedef enum
+{
+	XPATH_TOKEN_NONE,
+	XPATH_TOKEN_NAME,
+	XPATH_TOKEN_STRING,
+	XPATH_TOKEN_NUMBER,
+	XPATH_TOKEN_OTHER
+} XPathTokenType;
+
+typedef struct TokenInfo
+{
+	XPathTokenType		ttype;
+	char		   *start;
+	int				length;
+} XPathTokenInfo;
+
+#define TOKEN_STACK_SIZE		10
+
+typedef struct ParserData
+{
+	char		   *str;
+	char		   *cur;
+	XPathTokenInfo	stack[TOKEN_STACK_SIZE];
+	int				stack_length;
+} XPathParserData;
+
+#define NODENAME_FIRSTCHAR(c)	 ((c) == '_' || (c) == '-' || \
+								 ((c) >= 'A' && (c) <= 'Z') || \
+								 ((c) >= 'a' && (c) <= 'z') || \
+								 ((c) >= '0' && (c) <= '9'))
+
+#define IS_NODENAME_CHAR(c)		(NODENAME_FIRSTCHAR(c) || (c) == '.')
+
+/*
+ * Returns next char after last char of token
+ */
+static char *
+getXPathToken(char *str, XPathTokenInfo *ti)
+{
+
+	/* skip initial spaces */
+	while (*str == ' ')
+		str++;
+
+	if (*str != '\0')
+	{
+		char		c = *str;
+
+		ti->start = str++;
+
+		if (c >= '0' && c <= '9')
+		{
+			while (*str >= '0' && *str <= '9')
+				str++;
+			if (*str == '.')
+			{
+				str++;
+				while (*str >= '0' && *str <= '9')
+					str++;
+			}
+			ti->ttype = XPATH_TOKEN_NUMBER;
+		}
+		else if (NODENAME_FIRSTCHAR(c))
+		{
+			while (IS_NODENAME_CHAR(*str))
+				str++;
+
+			ti->ttype = XPATH_TOKEN_NAME;
+		}
+		else if (c == '"')
+		{
+			while (*str != '\0')
+				if (*str++ == '"')
+					break;
+
+			ti->ttype = XPATH_TOKEN_STRING;
+		}
+		else
+			ti->ttype = XPATH_TOKEN_OTHER;
+
+		ti->length = str - ti->start;
+	}
+	else
+	{
+		ti->start = NULL;
+		ti->length = 0;
+
+		ti->ttype = XPATH_TOKEN_NONE;
+	}
+
+	return str;
+}
+
+/*
+ * reset XPath parser stack
+ */
+static void
+initXPathParser(XPathParserData *parser, char *str)
+{
+	parser->str = str;
+	parser->cur = str;
+	parser->stack_length = 0;
+}
+
+static void
+nextXPathToken(XPathParserData *parser, XPathTokenInfo *ti)
+{
+	if (parser->stack_length > 0)
+		memcpy(ti, &parser->stack[--parser->stack_length],
+										  sizeof(XPathTokenInfo));
+	else
+		parser->cur = getXPathToken(parser->cur, ti);
+}
+
+static void
+pushXPathToken(XPathParserData *parser, XPathTokenInfo *ti)
+{
+	if (parser->stack_length == TOKEN_STACK_SIZE)
+		elog(ERROR, "internal error");
+	memcpy(&parser->stack[parser->stack_length++], ti,
+										  sizeof(XPathTokenInfo));
+}
+
+static void
+writeXPathToken(StringInfo str, XPathTokenInfo *ti)
+{
+	Assert(ti->ttype != XPATH_TOKEN_NONE);
+
+	if (ti->ttype != XPATH_TOKEN_OTHER)
+		appendBinaryStringInfo(str, ti->start, ti->length);
+	else
+		appendStringInfoChar(str, *ti->start);
+}
+
+/*
+ * Working horse for XPath transformation. When XPath starting by node name,
+ * then prefix have to be applied. When XPath ending by node name, then
+ * suffix will be used. Any unqualified node name should be qualified by
+ * default namespace.
+ */
+static void
+_transformXPath(StringInfo str, XPathParserData *parser,
+				  bool inside_predicate,
+				  char *prefix, char *suffix, char *default_ns_name)
+{
+	XPathTokenInfo	t1, t2;
+	bool			is_first_token = true;
+	bool			last_token_is_name = false;
+
+	nextXPathToken(parser, &t1);
+
+	while (t1.ttype != XPATH_TOKEN_NONE)
+	{
+		switch (t1.ttype)
+		{
+			case XPATH_TOKEN_NUMBER:
+			case XPATH_TOKEN_STRING:
+				last_token_is_name = false;
+				is_first_token = false;
+				writeXPathToken(str, &t1);
+				nextXPathToken(parser, &t1);
+				break;
+
+			case XPATH_TOKEN_NAME:
+				{
+					bool	is_qual_name = false;
+
+					/* inside predicate ignore keywords "and" "or" */
+					if (inside_predicate)
+					{
+						if ((strncmp(t1.start, "and", 3) == 0 && t1.length == 3) ||
+							(strncmp(t1.start, "or", 2) == 0 && t1.length == 2))
+						{
+							writeXPathToken(str, &t1);
+							nextXPathToken(parser, &t1);
+							break;
+						}
+					}
+
+					last_token_is_name = true;
+					nextXPathToken(parser, &t2);
+					if (t2.ttype == XPATH_TOKEN_OTHER)
+					{
+						if (*t2.start == '(')
+							last_token_is_name = false;
+						else if (*t2.start == ':')
+							is_qual_name = true;
+					}
+
+					if (is_first_token && last_token_is_name && prefix != NULL)
+						appendStringInfoString(str, prefix);
+
+					if (last_token_is_name && !is_qual_name && default_ns_name != NULL)
+						appendStringInfo(str, "%s:", default_ns_name);
+
+					writeXPathToken(str, &t1);
+					is_first_token = false;
+
+					if (is_qual_name)
+					{
+						writeXPathToken(str, &t2);
+						nextXPathToken(parser, &t1);
+						if (t1.ttype == XPATH_TOKEN_NAME)
+							writeXPathToken(str, &t1);
+						else
+							pushXPathToken(parser, &t1);
+					}
+					else
+						pushXPathToken(parser, &t2);
+
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_OTHER:
+				{
+					char c = *t1.start;
+
+					is_first_token = false;
+
+					writeXPathToken(str, &t1);
+
+					if (c == '[')
+						_transformXPath(str, parser, true, NULL, NULL, default_ns_name);
+					else
+					{
+						last_token_is_name = false;
+
+						if (c == ']' && inside_predicate)
+							return;
+
+						else if (c == '@')
+						{
+							nextXPathToken(parser, &t1);
+							if (t1.ttype == XPATH_TOKEN_NAME)
+							{
+								bool		is_qual_name = false;
+
+								nextXPathToken(parser, &t2);
+								if (t2.ttype == XPATH_TOKEN_OTHER && *t2.start == ':')
+									is_qual_name = true;
+
+								if (!is_qual_name && default_ns_name != NULL)
+									appendStringInfo(str, "%s:", default_ns_name);
+
+								writeXPathToken(str, &t1);
+								if (is_qual_name)
+								{
+									writeXPathToken(str, &t2);
+									nextXPathToken(parser, &t1);
+									if (t1.ttype == XPATH_TOKEN_NAME)
+										writeXPathToken(str, &t1);
+									else
+										pushXPathToken(parser, &t1);
+								}
+								else
+									pushXPathToken(parser, &t2);
+							}
+							else
+								pushXPathToken(parser, &t1);
+						}
+					}
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_NONE:
+				elog(ERROR, "should not be here");
+		}
+	}
+
+	if (last_token_is_name && suffix != NULL)
+		appendStringInfoString(str, suffix);
+}
+
+static void
+transformXPath(StringInfo str, char *xpath,
+					  char *prefix, char *suffix, char *default_ns_name)
+{
+	XPathParserData		parser;
+
+	initStringInfo(str);
+	initXPathParser(&parser, xpath);
+	_transformXPath(str, &parser, false, prefix, suffix, default_ns_name);
+}
+
+
+struct XmlTableContext
+{
+	MemoryContext			per_rowgroup_memory;
+	int						ncols;
+	PgXmlErrorContext	   *xmlerrcxt;
+	xmlParserCtxtPtr		ctxt;
+	xmlDocPtr				doc;
+	xmlXPathContextPtr		xpathcxt;
+	xmlXPathCompExprPtr		xpathcomp;
+	xmlXPathObjectPtr		xpathobj;
+	xmlXPathCompExprPtr	   *xpathscomp;
+	FmgrInfo			   *in_functions;
+	Oid					   *typioparams;
+	char				   *default_ns_name;
+	long int				rc;
+};
+
+/*
+ * 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;
+}
+
+#endif
+
+struct XmlTableContext *
+initXmlTableContext(xmltype *xmlval, char *default_ns_name,
+				int ncols, FmgrInfo *in_functions, Oid *typioparams,
+				MemoryContext per_rowgroup_memory)
+{
+#ifdef USE_LIBXML
+	MemoryContext				oldcxt;
+	struct XmlTableContext	   *result = NULL;
+	PgXmlErrorContext		   *xmlerrcxt = NULL;
+	int32						len;
+	xmlChar			*xmlval_str;
+
+	volatile xmlParserCtxtPtr	ctxt = NULL;
+	volatile xmlDocPtr			doc = NULL;
+	volatile xmlXPathContextPtr	xpathcxt = NULL;
+
+	oldcxt = MemoryContextSwitchTo(per_rowgroup_memory);
+
+	len = VARSIZE(xmlval) - VARHDRSZ;
+	xmlval_str = palloc((len + 1) * sizeof(xmlChar));
+	memcpy(xmlval_str, VARDATA(xmlval), len);
+	xmlval_str[len] = '\0';
+
+	result = palloc0(sizeof(struct XmlTableContext));
+	result->per_rowgroup_memory = per_rowgroup_memory;
+	result->in_functions = in_functions;
+	result->typioparams = typioparams;
+	result->default_ns_name = default_ns_name;
+	result->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * ncols);
+
+	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");
+		doc = xmlCtxtReadMemory(ctxt, (char *) xmlval_str, len, NULL, NULL, 0);
+		if (doc == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+				"could not parse XML document");
+		xpathcxt = xmlXPathNewContext(doc);
+		if (xpathcxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+				"could not allocate XPath context");
+		xpathcxt->node = xmlDocGetRootElement(doc);
+		if (xpathcxt->node == NULL  || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+				"could not find root XML element");
+	}
+	PG_CATCH();
+	{
+		if (xpathcxt != NULL)
+			xmlXPathFreeContext(xpathcxt);
+		if (doc != NULL)
+			xmlFreeDoc(doc);
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	result->ncols = ncols;
+	result->xmlerrcxt = xmlerrcxt;
+	result->ctxt = ctxt;
+	result->doc = doc;
+	result->xpathcxt = xpathcxt;
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return (struct XmlTableContext *) result;
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return NULL;
+};
+
+void
+XmlTableSetRowPath(struct XmlTableContext *xtCxt, text *row_path)
+{
+#ifdef USE_LIBXML
+	xmlChar			*row_path_str;
+	MemoryContext	oldcxt;
+	StringInfoData			str;
+	char		*path_str;
+
+	path_str = text_to_cstring(row_path);
+	if (*path_str == '\0')
+		ereport(ERROR,
+					(errcode(ERRCODE_DATA_EXCEPTION),
+					 errmsg("row path filter must not be empty string")));
+
+	transformXPath(&str, path_str, NULL, NULL, xtCxt->default_ns_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+	row_path_str = (xmlChar *) palloc((str.len + 1) * sizeof(xmlChar));
+	memcpy(row_path_str, str.data, str.len);
+	row_path_str[str.len] = '\0';
+
+	xtCxt->xpathcomp = xmlXPathCompile(row_path_str);
+	if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableFreeContext(struct XmlTableContext *xtCxt)
+{
+#ifdef USE_LIBXML
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int		i;
+
+		for (i = 0; i < xtCxt->ncols; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableSetNs(struct XmlTableContext *xtCxt, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+
+	if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+								(xmlChar *)(name ? name : xtCxt->default_ns_name),
+								(xmlChar *) uri))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+void
+XmlTableSetColumnPath(struct XmlTableContext *xtCxt, int i, Oid typid, char *path)
+{
+#ifdef USE_LIBXML
+	MemoryContext oldcxt;
+	StringInfoData		str;
+	xmlChar			*xmlstr;
+
+	transformXPath(&str, path,
+					  "./", typid != XMLOID ? "/text()" : NULL,
+					  xtCxt->default_ns_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+	xmlstr = palloc((str.len + 1) * sizeof(xmlChar));
+	memcpy(xmlstr, str.data, str.len);
+	xmlstr[str.len] = '\0';
+
+	xtCxt->xpathscomp[i] = xmlXPathCompile(xmlstr);
+	if (xtCxt->xpathscomp[i] == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+			"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+};
+
+bool
+XmlTableFetchRow(struct XmlTableContext *xtCxt)
+{
+#ifdef USE_LIBXML
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(xtCxt->per_rowgroup_memory);
+
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+				"could not create XPath object");
+
+		xtCxt->rc = 0;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+};
+
+Datum
+XmlTableGetValue(struct XmlTableContext *xtCxt, int ncol, Oid typid, int32 typmod, bool *isnull)
+{
+#ifdef USE_LIBXML
+	Datum			result = (Datum) 0;
+	xmlNodePtr		cur;
+	char		   *cstr;
+	volatile xmlXPathObjectPtr		column_xpathobj = NULL;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+			  xtCxt->xpathobj->nodesetval != NULL);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+	if (cur->type == XML_ELEMENT_NODE)
+	{
+		PG_TRY();
+		{
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[ncol], 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)
+				{
+					if (count == 1)
+					{
+						cstr = xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[0],
+															xtCxt->xmlerrcxt);
+					}
+					else
+					{
+						StringInfoData		str;
+						int					i;
+
+						/*
+						 * more values, target must be XML.
+						 * Note: possibly any array can be there.
+						 */
+						if (typid != XMLOID)
+							ereport(ERROR,
+										(errcode(ERRCODE_CARDINALITY_VIOLATION),
+										 errmsg("more than one value returned by column XPath expression")));
+
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+										  xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														   xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[ncol],
+													  cstr,
+													  xtCxt->typioparams[ncol],
+													  typmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[ncol],
+													  (char *) column_xpathobj->stringval,
+													  xtCxt->typioparams[ncol],
+													  typmod);
+				*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
+		elog(ERROR, "unexpected xmlNode type");
+
+	return (Datum) result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	*isnull = true;
+	return (Datum) 0;
+};
+
+Datum
+XmlTableGetRowValue(struct XmlTableContext *xtCxt, bool *isnull)
+{
+#ifdef USE_LIBXML
+
+	Datum			result = (Datum) 0;
+	xmlNodePtr		cur;
+	char		   *cstr;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+			  xtCxt->xpathobj->nodesetval != NULL);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+	cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+
+	result = InputFunctionCall(&xtCxt->in_functions[0],
+									cstr,
+									xtCxt->typioparams[0],
+									-1);						/* target type is XML always */
+	*isnull = false;
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	*isnull = true;
+	return (Datum) 0;
+};
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d49fe5..7d14810 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -219,6 +219,49 @@ get_call_result_type(FunctionCallInfo fcinfo,
 }
 
 /*
+ * When we skip transform stage (in view), then TableExpr's
+ * TupleDesc should not be valid. Refresh is necessary.
+ */
+TupleDesc
+TableExprGetTupleDesc(TableExpr *te)
+{
+	TupleDesc tupdesc;
+
+	if (te->cols != NIL)
+	{
+		ListCell	   *col;
+		int				i = 1;
+
+		tupdesc = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(tupdesc,
+									  (AttrNumber) i,
+												  pstrdup(tec->colname),
+												  tec->typid,
+												  tec->typmod,
+												  0);
+			i++;
+		}
+	}
+	else
+	{
+		tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xmltable", XMLOID, -1, 0);
+	}
+
+	assign_record_type_typmod(tupdesc);
+	te->typmod = tupdesc->tdtypmod;
+
+	Assert(te->typid = tupdesc->tdtypeid);
+
+	return tupdesc;
+}
+
+/*
  * get_expr_result_type
  *		As above, but work from a calling expression node tree
  */
@@ -241,6 +284,22 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		TableExpr *te = (TableExpr *) expr;
+
+		if (resultTypeId)
+			*resultTypeId = te->typid;
+
+		/* Enforce fresh RECORD tupledesc */
+		if (te->typmod == -1)
+			TableExprGetTupleDesc(te);
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(te->typid, te->typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..0417a60 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,6 +184,7 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
+extern TupleDesc TableExprGetTupleDesc(TableExpr *te);
 
 /*----------
  *	Support to ease writing functions returning composite types
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a4ea1b9..9759be9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1004,6 +1004,31 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	bool		firstRow;			/* true, when first tuple from call should be returned */
+	List	   *namespaces;			/* list of prepared ResTarget fields */
+	bool		used_dns;			/* true, when default namespace is used */
+	Oid			typid;
+	int32		typmod;
+	TupleDesc	tupdesc;			/* cache */
+	int		ncols;					/* number of declared columns */
+	int		for_ordinality_col;		/* number of oridinality column, started by 1 */
+	int		rownum;					/* row counter - for ordinality column */
+	ExprState      *row_path_expr;	/* row xpath expression */
+	ExprState      *expr;			/* processed data */
+	ExprState     **def_expr;		/* array of expressions for default value */
+	ExprState     **col_path_expr;	/* array of expressions for path value */
+	bool	       *not_null;		/* for any column info if NULL is allowed or not */
+	Datum	       *values;								/* prealloc buffer */
+	bool	       *nulls;								/* prealloc buffer */
+	FmgrInfo	  *in_functions;	/* array of infunction for any column */
+	Oid			  *typioparams;		/* array of typIOParam for any column */
+	struct XmlTableContext		*xmltableCxt;
+	MemoryContext				per_rowgroup_memory;
+} TableExprState;
+
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..504fb9d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -453,6 +456,7 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1481fff..8bf9736 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -697,6 +697,22 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * There are different requests from XMLTABLE, JSON_TABLE functions
+ * on passed data than has CREATE TABLE command. It is reason for
+ * introduction special structure instead using ColumnDef.
+ */
+typedef struct TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;
+	TypeName   *typeName;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..195e637 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,36 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - used for XMLTABLE function
+ *
+ * This can be used for json_table, jsonb_table functions in future
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Oid				typid;
+	int32			typmod;
+	Node	   *row_path;		/* row xpath query */
+	Node	   *expr;			/* processed data */
+	List	   *cols;			/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+} TableExpr;
+
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;
+	Oid			typid;
+	int32		typmod;
+	Oid			collation;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..de83895 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -289,6 +290,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -438,10 +440,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..1218bf3 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+						Oid targetTypeId, int32 targetTypmod,
+						const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..5cf94f9 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,17 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern struct XmlTableContext *initXmlTableContext(xmltype *xmlval,
+				char *default_ns_name,
+				int ncols,
+				FmgrInfo *in_functions, Oid *typioparams,
+				MemoryContext	per_rowgroup_memory);
+extern void XmlTableFreeContext(struct XmlTableContext *xtCxt);
+extern void XmlTableSetNs(struct XmlTableContext *xtCxt, char *name, char *uri);
+extern void XmlTableSetRowPath(struct XmlTableContext *xtCxt, text *row_path);
+extern void XmlTableSetColumnPath(struct XmlTableContext *xtCxt, int i, Oid typid, char *path);
+extern bool XmlTableFetchRow(struct XmlTableContext *xtCxt);
+extern Datum XmlTableGetValue(struct XmlTableContext *xtCxt, int ncol, Oid typid, int32 typmod, bool *isnull);
+extern Datum XmlTableGetRowValue(struct XmlTableContext *xtCxt, bool *isnull);
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..ed6cb79 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,139 @@ 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)
+
+
+-- 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');
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d702703..58e6723 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,127 @@ 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)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz)
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz)
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltableview2;
+ERROR:  relation "xmltableview2" does not exist
+LINE 1: SELECT * FROM xmltableview2;
+                      ^
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y')
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+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.
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5..48fae36 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,138 @@ 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)
+
+-- 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');
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30..5909a51 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,91 @@ 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;
+
+-- 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>');
#5Craig Ringer
craig@2ndquadrant.com
In reply to: Pavel Stehule (#4)
Re: patch: function xmltable

On 4 September 2016 at 16:06, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Hi

minor update - using DefElem instead own private parser type

I'm really glad that you're doing this and I'll take a look at it for this CF.

It's quite a big patch so I expect this will take a few rounds of
review and updating.

Patch applies cleanly and builds cleanly on master both with and
without --with-xml .

Overall, I think this needs to be revised with appropriate comments.
Whitespace/formatting needs fixing since it's all over the place.
Documentation is insufficient (per notes below).

Re identifier naming, some of this code uses XmlTable naming patterns,
some uses TableExpr prefixes. Is that intended to indicate a bounary
between things re-usable for other structured data ingesting
functions? Do you expect a "JSONEXPR" or similar in future? That's
alluded to by

+/*----------
+ * TableExpr - used for XMLTABLE function
+ *
+ * This can be used for json_table, jsonb_table functions in future
+ *----------
+ */
+typedef struct TableExpr
+{
...

If so, should this really be two patches, one to add the table
expression infrastructure and another to add XMLTABLE that uses it?
Also, why in that case does so much of the TableExpr code call
directly into XmlTable code? It doesn't look very generic.

Overall I find identifier naming to be a bit inconsisent and think
it's necessary to make it clear that all the "TableExpr" stuff is for
XMLTABLE specifically, if that's the case, or make the delineation
clearer if not.

I'd also like to see tests that exercise the ruleutils get_rule_expr
parts of the code for the various XMLTABLE variants.

Similarly, since this seems to add a new xpath parser, that needs
comprehensive tests. Maybe re-purpose an existing xpath test data set?

More detailed comments:
====

Docs comments:

The <function>xmltable</function> produces [a] table based on
[the] passed XML value.

The docs are pretty minimal and don't explain the various clauses of
XMLTABLE. What is "BY REF" ? Is PATH an xpath expression? If so, is
there a good cross reference link available? The PASSING clause? etc.

How does XMLTABLE decide what to iterate over, and how to iterate over it?

Presumably the FOR ORDINALITY clause makes a column emit a numeric counter.

What standard, if any, does this conform to? Does it resemble
implementations elsewhere? What limitations or unsupported features
does it have relative to those standards?

execEvalTableExpr seems to be defined twice, with a difference in
case. This is probably not going to fly:

+static Datum
+execEvalTableExpr(TableExprState *tstate,
+                        ExprContext *econtext,
+                        bool *isNull, ExprDoneCond *isDone)
+{
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+                        ExprContext *econtext,
+                        bool *isNull, ExprDoneCond *isDone)
+{

It looks like you've split the function into a "guts" and "wrapper"
part, with the error handling PG_TRY / PG_CATCH block in the wrapper.
That seems reasonable for readability, but the naming isn't.

A comment is needed to explain what ExecEvalTableExpr is / does. If
it's XMLTABLE specific (which it looks like based on the code), its
name should reflect that. This pattern is repeated elsewhere; e.g.
TableExprState is really the state for an XMLTABLE expression. But
PostgreSQL actually has TABLE statements, and in future we might want
to support table-expressions, so I don't think this naming is
appropriate. This is made worse by the lack of comments on things like
the definition of TableExprState. Please use something that makes it
clear it's for XMLTABLE and add appropriate comments.

Formatting of variables, arguments, function signatures etc is
random/haphazard and doesn't follow project convention. It's neither
aligned or unaligned in the normal way, I don't understand the random
spacing at all. Maybe you should try to run pgindent and then extract
just the changes related to your patch? Or run your IDE/editor's
indent function on your changes? Right now it's actually kind of hard
to read. Do you edit with tabstop set to 1 normally or something like
that?

There's a general lack of comments throughout the added code.

In execEvalTableExpr, why are we looping over namespaces? What's that
for? Comment would be nice.

Typo: Path caclulation => Path calculation

What does XmlTableSetRowPath() do? It seems to copy its argument.
Nothing further is done with the row_path argument after it's called
by execEvalTableExpr, so what context is that memory in and do we have
to worry about it if it's large?

execEvalTableExpr says it's doing "path calculation". What it actually
appears to do is evaluate the path expressions, if provided, and
otherwise use the column name as the implied path expression. (The
docs should mention that).

It's wasn't immediately obvious to me what the branch around
tstate->for_ordinality_col is for and what the alternate path's
purpose is in terms of XMLTABLE's behaviour, until I read the parser
definition. That's largely because the behaviour of XMLTABLE is
underspecified in the docs, since once you know ORDINALITY columns
exist it's pretty obvious what it's doing.

Similarly, for the alternate branch tstate->ncols , the
XmlTableGetRowValue call there is meant to do what exactly, and
why/under what conditions? Is it for situations where the field type
is a whole-row value? a composite type? (I'm deliberately not studying
this too deeply, these are points I'd like to see commented so it can
be understood to some reasonable degree at a skim-read).

/* result is one more columns every time */
"one or more"

/* when typmod is not valid, refresh it */
if (te->typmod == -1)

Is this a cache? How is it valid or not valid and when? The comment
(thanks!) on TableExprGetTupleDesc says:

/*
* When we skip transform stage (in view), then TableExpr's
* TupleDesc should not be valid. Refresh is necessary.
*/

but I'm not really grasping what you're trying to explain here. What
transform stage? What view? This could well be my ignorance of this
part of the code; if it should be understandable by a reader who is
appropriately familiar with the executor that's fine, but if it's
specific to how XMLTABLE works some more explanation would be good.

Good that you've got all the required node copy/in/out funcs in place.

Please don't use the name "used_dns". Anyone reading that will read it
as "domain name service" and that's actually confusing with XML
because of XML schema lookups. Maybe used_defnamespace ? used
def_ns?

I haven't looked closely at keyword/parser changes yet, but it doesn't
look like you added any reserved keywords, which is good. It does add
unreserved keywords PATH and COLUMNS ; I'm not sure what policy for
unreserved keywords is or the significance of that.

New ereport() calls specify ERRCODEs, which is good.

PostgreSQL already has XPATH support in the form of xmlexists(...)
etc. Why is getXPathToken() etc needed? What re-use is possible here?
There's no explanation in the patch header or comments. Should the new
xpath parser be re-used by the existing xpath stuff? Why can't we use
libxml's facilities? etc. This at least needs explaining in the
submission, and some kind of hint as to why we have two different ways
to do it is needed in the code. If we do need a new XML parser, should
it be bundled in adt/xml.c along with a lot of user-facing
functionality, or a separate file?

How does XmlTableGetValue(...) and XmlTableGetRowValue(...) relate to
this? It doesn't look like they're intended to be called directly by
the user, and they're not documented (or commented).

I don't understand this at all:

+/*
+ * There are different requests from XMLTABLE, JSON_TABLE functions
+ * on passed data than has CREATE TABLE command. It is reason for
+ * introduction special structure instead using ColumnDef.
+ */
+typedef struct TableExprRawCol
+{
+    NodeTag     type;
+    char       *colname;
+    TypeName   *typeName;
+    bool        for_ordinality;
+    bool        is_not_null;
+    Node       *path_expr;
+    Node       *default_expr;
+    int         location;
+} TableExprRawCol;

That's my first-pass commentary. I'll return to this once you've had a
chance to take a look at these and tell me all the places I got it
wrong ;)

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#5)
Re: patch: function xmltable

Hi

2016-09-06 6:54 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

On 4 September 2016 at 16:06, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Hi

minor update - using DefElem instead own private parser type

I'm really glad that you're doing this and I'll take a look at it for this
CF.

It's quite a big patch so I expect this will take a few rounds of
review and updating.

Thank you for review

Patch applies cleanly and builds cleanly on master both with and
without --with-xml .

Overall, I think this needs to be revised with appropriate comments.
Whitespace/formatting needs fixing since it's all over the place.
Documentation is insufficient (per notes below).

I am not able to write documentation in English language :( - This function
is pretty complex - so I hope so anybody with better language skills can
help with this. It respects standard and it respects little bit different
Oracle's behave too (different order of DEFAULT and PATH parts).

Re identifier naming, some of this code uses XmlTable naming patterns,
some uses TableExpr prefixes. Is that intended to indicate a bounary
between things re-usable for other structured data ingesting
functions? Do you expect a "JSONEXPR" or similar in future? That's
alluded to by

This structure should be reused by JSON_TABLE function. Now, it is little
bit strange, because there is only XMLTABLE implementation - and I have to
choose between a) using two different names now, b) renaming some part in
future.

And although XMLTABLE and JSON_TABLE functions are pretty similar - share
90% of data (input value, path, columns definitions), these functions has
different syntax - so only middle level code should be shared.

+/*----------
+ * TableExpr - used for XMLTABLE function
+ *
+ * This can be used for json_table, jsonb_table functions in future
+ *----------
+ */
+typedef struct TableExpr
+{
...

If so, should this really be two patches, one to add the table
expression infrastructure and another to add XMLTABLE that uses it?
Also, why in that case does so much of the TableExpr code call
directly into XmlTable code? It doesn't look very generic.

Currently the common part is not too big - just the Node related part - I
am not sure about necessity of two patches. I am agree, there is missing
some TableExpBuilder, where can be better isolated the XML part.

Overall I find identifier naming to be a bit inconsisent and think
it's necessary to make it clear that all the "TableExpr" stuff is for
XMLTABLE specifically, if that's the case, or make the delineation
clearer if not.

I'd also like to see tests that exercise the ruleutils get_rule_expr
parts of the code for the various XMLTABLE variants.

Similarly, since this seems to add a new xpath parser, that needs
comprehensive tests. Maybe re-purpose an existing xpath test data set?

sure

More detailed comments:
====

Docs comments:

The <function>xmltable</function> produces [a] table based on
[the] passed XML value.

The docs are pretty minimal and don't explain the various clauses of
XMLTABLE. What is "BY REF" ? Is PATH an xpath expression? If so, is
there a good cross reference link available? The PASSING clause? etc.

How does XMLTABLE decide what to iterate over, and how to iterate over it?

Presumably the FOR ORDINALITY clause makes a column emit a numeric counter.

What standard, if any, does this conform to? Does it resemble
implementations elsewhere? What limitations or unsupported features
does it have relative to those standards?

execEvalTableExpr seems to be defined twice, with a difference in
case. This is probably not going to fly:

+static Datum
+execEvalTableExpr(TableExprState *tstate,
+                        ExprContext *econtext,
+                        bool *isNull, ExprDoneCond *isDone)
+{
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+                        ExprContext *econtext,
+                        bool *isNull, ExprDoneCond *isDone)
+{

It looks like you've split the function into a "guts" and "wrapper"
part, with the error handling PG_TRY / PG_CATCH block in the wrapper.
That seems reasonable for readability, but the naming isn't.

I invite any idea how these functions should be named.

A comment is needed to explain what ExecEvalTableExpr is / does. If
it's XMLTABLE specific (which it looks like based on the code), its
name should reflect that. This pattern is repeated elsewhere; e.g.
TableExprState is really the state for an XMLTABLE expression. But
PostgreSQL actually has TABLE statements, and in future we might want
to support table-expressions, so I don't think this naming is
appropriate. This is made worse by the lack of comments on things like
the definition of TableExprState. Please use something that makes it
clear it's for XMLTABLE and add appropriate comments.

I understand, so using TableExpr can be strange (for XMLTABLE function).
But when we will have JSON_TABLE function, then it will have a sense.

"TableExprState" is consistent with "TableExpr".

Any idea how it should be changed?

Formatting of variables, arguments, function signatures etc is
random/haphazard and doesn't follow project convention. It's neither
aligned or unaligned in the normal way, I don't understand the random
spacing at all. Maybe you should try to run pgindent and then extract
just the changes related to your patch? Or run your IDE/editor's
indent function on your changes? Right now it's actually kind of hard
to read. Do you edit with tabstop set to 1 normally or something like
that?

There's a general lack of comments throughout the added code.

In execEvalTableExpr, why are we looping over namespaces? What's that
for? Comment would be nice.

Typo: Path caclulation => Path calculation

What does XmlTableSetRowPath() do? It seems to copy its argument.
Nothing further is done with the row_path argument after it's called
by execEvalTableExpr, so what context is that memory in and do we have
to worry about it if it's large?

execEvalTableExpr says it's doing "path calculation". What it actually
appears to do is evaluate the path expressions, if provided, and
otherwise use the column name as the implied path expression. (The
docs should mention that).

It's wasn't immediately obvious to me what the branch around
tstate->for_ordinality_col is for and what the alternate path's
purpose is in terms of XMLTABLE's behaviour, until I read the parser
definition. That's largely because the behaviour of XMLTABLE is
underspecified in the docs, since once you know ORDINALITY columns
exist it's pretty obvious what it's doing.

Similarly, for the alternate branch tstate->ncols , the
XmlTableGetRowValue call there is meant to do what exactly, and
why/under what conditions? Is it for situations where the field type
is a whole-row value? a composite type? (I'm deliberately not studying
this too deeply, these are points I'd like to see commented so it can
be understood to some reasonable degree at a skim-read).

/* result is one more columns every time */
"one or more"

/* when typmod is not valid, refresh it */
if (te->typmod == -1)

Is this a cache? How is it valid or not valid and when? The comment
(thanks!) on TableExprGetTupleDesc says:

/*
* When we skip transform stage (in view), then TableExpr's
* TupleDesc should not be valid. Refresh is necessary.
*/

but I'm not really grasping what you're trying to explain here. What
transform stage? What view? This could well be my ignorance of this
part of the code; if it should be understandable by a reader who is
appropriately familiar with the executor that's fine, but if it's
specific to how XMLTABLE works some more explanation would be good.

This is most difficult part of this patch, and I am not sure it it is fully
correctly implemented. I use TupleDesc cache. The TupleDesc is created in
parser/transform stage. When the XMLTABLE is used in some view, then the
transformed parser tree is materialized - and when the view is used in
query, then this tree is loaded and the parser/transform stage is
"skipped". I'll check this code against implementation of ROW constructor
and I'll try to do more comments there.

Good that you've got all the required node copy/in/out funcs in place.

Please don't use the name "used_dns". Anyone reading that will read it
as "domain name service" and that's actually confusing with XML
because of XML schema lookups. Maybe used_defnamespace ? used
def_ns?

good idea

I haven't looked closely at keyword/parser changes yet, but it doesn't
look like you added any reserved keywords, which is good. It does add
unreserved keywords PATH and COLUMNS ; I'm not sure what policy for
unreserved keywords is or the significance of that.

New ereport() calls specify ERRCODEs, which is good.

PostgreSQL already has XPATH support in the form of xmlexists(...)
etc. Why is getXPathToken() etc needed? What re-use is possible here?
There's no explanation in the patch header or comments. Should the new
xpath parser be re-used by the existing xpath stuff? Why can't we use
libxml's facilities? etc. This at least needs explaining in the
submission, and some kind of hint as to why we have two different ways
to do it is needed in the code. If we do need a new XML parser, should
it be bundled in adt/xml.c along with a lot of user-facing
functionality, or a separate file?

libxml2 and our XPATH function doesn't support default namespace (
http://plasmasturm.org/log/259/ ). This is pretty useful feature - so I
implemented. This is the mayor issue of libxml2 library. Another difference
between XPATH function and XMLTABLE function is using two phase searching
and implicit prefix "./" and suffix ("/text()") in XMLTABLE. XMLTABLE using
two XPATH expressions - for row data cutting and next for column data
cutting (from row data). The our XPATH functions is pretty simple mapped to
libxml2 XPATH API. But it is not possible with XMLTABLE function - due
design of this function in standard (it is more user friendly and doesn't
require exactly correct xpath expressions).

I didn't find any API in libxml2 for a work with parsed xpath expressions -
I need some info about the first and last token of xpath expression - it is
base for decision about using prefix or suffix.

This functionality (xpath expression parser) cannot be used for our XPATH
function now - maybe default namespace in future.

How does XmlTableGetValue(...) and XmlTableGetRowValue(...) relate to
this? It doesn't look like they're intended to be called directly by
the user, and they're not documented (or commented).

Probably I used wrong names. XMLTABLE function is running in two different
modes - with explicitly defined columns (XmlTableGetValue is used), and
without explicitly defined columns - so result is one XML column and only
one one step searching is used (there are not column related xpath
expressions) ( XmlTableGetRowValue is used). The function XmlTableGetValue
is used for getting one column value, the function XmlTableGetRowValue is
used for getting one value too, but in special case, when there are not any
other value.

I don't understand this at all:

+/*
+ * There are different requests from XMLTABLE, JSON_TABLE functions
+ * on passed data than has CREATE TABLE command. It is reason for
+ * introduction special structure instead using ColumnDef.
+ */
+typedef struct TableExprRawCol
+{
+    NodeTag     type;
+    char       *colname;
+    TypeName   *typeName;
+    bool        for_ordinality;
+    bool        is_not_null;
+    Node       *path_expr;
+    Node       *default_expr;
+    int         location;
+} TableExprRawCol;

I am sorry. It is my fault. Now we have very similar node ColumnDef. This
node is designed for usage in utility commands - and it is not designed for
usage inside a query. I had to decide between enhancing ColumnDef node or
introduction new special node. Because there are more special attributes
and it is hard to serialize current ColumnDef, I decided to use new node.

That's my first-pass commentary. I'll return to this once you've had a
chance to take a look at these and tell me all the places I got it
wrong ;)

Thank for this

Regard

Pavel

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#7Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#6)
Re: patch: function xmltable

libxml2 and our XPATH function doesn't support default namespace (
http://plasmasturm.org/log/259/ ). This is pretty useful feature - so I
implemented. This is the mayor issue of libxml2 library. Another difference
between XPATH function and XMLTABLE function is using two phase searching
and implicit prefix "./" and suffix ("/text()") in XMLTABLE. XMLTABLE using
two XPATH expressions - for row data cutting and next for column data
cutting (from row data). The our XPATH functions is pretty simple mapped to
libxml2 XPATH API. But it is not possible with XMLTABLE function - due
design of this function in standard (it is more user friendly and doesn't
require exactly correct xpath expressions).

libxm2 doesn't support xpath 2.0 where default namespace was introduced.

#8Craig Ringer
craig@2ndquadrant.com
In reply to: Pavel Stehule (#6)
Re: patch: function xmltable

On 7 September 2016 at 04:13, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Overall, I think this needs to be revised with appropriate comments.
Whitespace/formatting needs fixing since it's all over the place.
Documentation is insufficient (per notes below).

I am not able to write documentation in English language :( - This function
is pretty complex - so I hope so anybody with better language skills can
help with this. It respects standard and it respects little bit different
Oracle's behave too (different order of DEFAULT and PATH parts).

OK, no problem. It can't be committed without more comprehensive docs
though, especially for new and nontrivial functionality.

Is there some reference material you can point to so someone else can
help with docs? And can you describe what differences there are
between your implementation and the reference?

Alternately, if you document it in Czech, do you know of anyone who
could assist in translating to English for the main documentation?

Re identifier naming, some of this code uses XmlTable naming patterns,
some uses TableExpr prefixes. Is that intended to indicate a bounary
between things re-usable for other structured data ingesting
functions? Do you expect a "JSONEXPR" or similar in future? That's
alluded to by

This structure should be reused by JSON_TABLE function. Now, it is little
bit strange, because there is only XMLTABLE implementation - and I have to
choose between a) using two different names now, b) renaming some part in
future.

OK. Are you planning on writing this JSON_TABLE or are you leaving
room for future growth? Either way is fine, just curious.

And although XMLTABLE and JSON_TABLE functions are pretty similar - share
90% of data (input value, path, columns definitions), these functions has
different syntax - so only middle level code should be shared.

That makes sense.

I think it would be best if you separated out the TableExpr
infrastructure from the XMLTABLE implementation though, so we can
review the first level infrastrcture separately and make this a
2-patch series. Most importantly, doing it that way will help you find
places where TableExpr code calls directly into XMLTABLE code. If
TableExpr is supposed to be reusable for json etc, it probably
shouldn't be calling XmlTable stuff directly.

That also means somewhat smaller simpler patches, which probably isn't bad.

I don't necessarily think this needs to be fully pluggable with
callbacks etc. It doesn't sound like you expect this to be used by
extensions or to have a lot of users, right? So it probably just needs
clearer separation of the infrastructure layer from the xmltable
layer. I think splitting the patch will make that easier to see and
make it easier to find problems.

My biggest complaint at the moment is that execEvalTableExpr calls
initXmlTableContext(...) directly, is aware of XML namespaces
directly, calls XmlTableSetRowPath() directly, calls
XmlTableFetchRow() directly, etc. It is in no way generic/reusable for
some later JSONTABLE feature. That needs to be fixed by:

* Renaming it so it's clearly only for XMLTABLE; or
* Abstracting the init context, set row path, fetch row etc operations
so json ones can be plugged in later

Currently the common part is not too big - just the Node related part - I am
not sure about necessity of two patches.

The problem is that the common part is all mixed in with the
XMLTABLE-specific part, so it's not at all clear it can be common with
something else.

I am agree, there is missing some
TableExpBuilder, where can be better isolated the XML part.

Yeah, that's sort of what I'm getting at.

execEvalTableExpr seems to be defined twice, with a difference in
case. This is probably not going to fly:

+static Datum
+execEvalTableExpr(TableExprState *tstate,
+                        ExprContext *econtext,
+                        bool *isNull, ExprDoneCond *isDone)
+{
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+                        ExprContext *econtext,
+                        bool *isNull, ExprDoneCond *isDone)
+{

It looks like you've split the function into a "guts" and "wrapper"
part, with the error handling PG_TRY / PG_CATCH block in the wrapper.
That seems reasonable for readability, but the naming isn't.

I invite any idea how these functions should be named.

Definitely not how they are ;) . They really can't differ in a single
character's case.

I'm not sure if PostgreSQL has any formal convention for this. Some
places use _impl e.g. pg_read_barrier_impl() but that's in the
context of an interface-vs-implementation separation, which isn't the
case here.

Some places use _internal, like AlterObjectRename_internal(...), but
that's where there's an associated public/external part, which isn't
the case here.

Some places use _guts e.g. pg_logical_slot_get_changes_guts(...),
largely where there's common use by several callers.

This is a fairly arbitrary function split for readability/length. Is
it actually useful to split this function up at all?

Anyone else have an opinion?

A comment is needed to explain what ExecEvalTableExpr is / does. If
it's XMLTABLE specific (which it looks like based on the code), its
name should reflect that. This pattern is repeated elsewhere; e.g.
TableExprState is really the state for an XMLTABLE expression. But
PostgreSQL actually has TABLE statements, and in future we might want
to support table-expressions, so I don't think this naming is
appropriate. This is made worse by the lack of comments on things like
the definition of TableExprState. Please use something that makes it
clear it's for XMLTABLE and add appropriate comments.

I understand, so using TableExpr can be strange (for XMLTABLE function). But
when we will have JSON_TABLE function, then it will have a sense.

It's pretty hard to review that as shared infrastructure when it's
still tangled up in xmltable specifics, though.

"TableExprState" is consistent with "TableExpr".

Any idea how it should be changed?

I think if you want it to be shareable infrasructure, you need to
write it so it can be used as shared infrastructure. Not just name it
that way but then make it XMLTABLE specific in actual functionality.

/* when typmod is not valid, refresh it */
if (te->typmod == -1)

Is this a cache? How is it valid or not valid and when? The comment
(thanks!) on TableExprGetTupleDesc says:

/*
* When we skip transform stage (in view), then TableExpr's
* TupleDesc should not be valid. Refresh is necessary.
*/

but I'm not really grasping what you're trying to explain here. What
transform stage? What view? This could well be my ignorance of this
part of the code; if it should be understandable by a reader who is
appropriately familiar with the executor that's fine, but if it's
specific to how XMLTABLE works some more explanation would be good.

This is most difficult part of this patch, and I am not sure it it is fully
correctly implemented. I use TupleDesc cache. The TupleDesc is created in
parser/transform stage. When the XMLTABLE is used in some view, then the
transformed parser tree is materialized - and when the view is used in
query, then this tree is loaded and the parser/transform stage is "skipped".
I'll check this code against implementation of ROW constructor and I'll try
to do more comments there.

Thanks. It would be good to highlight when this does and does not
happen and why. Why is it necessary at all?

What happens if XMLTABLE is used in a query directly, not part of a
view? if XMLTABLE is used in a view? If XMLTABLE is used in a prepared
statement / plpgsql statement / etc? What about a CTE term?

Not necessarily list all these cases one by one, just explain what
happens, when and why. Especially if it's complex, so other readers
can understand it and don't have to study it in detail to understand
what is going on. It does not need to be good public-facing
documentation, and details of wording, grammar etc can be fixed up
later, it's the ideas that matter.

Is this similar to other logic elsewhere? If so, reference that other
logic so readers know where to look. That way if they're
changing/bugfixing/etc one place they know there's another place that
might need changing.

I don't know this area of the code well enough to give a solid review
of the actual functionality, and I don't yet understand what it's
trying to do so it's hard to review it by studying what it actually
does vs what it claims to do. Maybe Peter E can help, he said he was
thinking of looking at this patch too. But more information on what
it's trying to do would be a big help.

PostgreSQL already has XPATH support in the form of xmlexists(...)
etc. Why is getXPathToken() etc needed? What re-use is possible here?
There's no explanation in the patch header or comments. Should the new
xpath parser be re-used by the existing xpath stuff? Why can't we use
libxml's facilities? etc. This at least needs explaining in the
submission, and some kind of hint as to why we have two different ways
to do it is needed in the code. If we do need a new XML parser, should
it be bundled in adt/xml.c along with a lot of user-facing
functionality, or a separate file?

libxml2 and our XPATH function doesn't support default namespace (
http://plasmasturm.org/log/259/ ). This is pretty useful feature - so I
implemented.

OK, that makes sense.

For the purpose of getting this patch in, is it a _necessary_ feature?
Can XMLTABLE be usefully implemented without it, and if so, can it be
added in a subsequent patch? It would be nice to simplify this by
using existing libxml2 functionality in the first version rather than
adding a whole new xpath as well!

This is the mayor issue of libxml2 library. Another difference
between XPATH function and XMLTABLE function is using two phase searching
and implicit prefix "./" and suffix ("/text()") in XMLTABLE. XMLTABLE using
two XPATH expressions - for row data cutting and next for column data
cutting (from row data). The our XPATH functions is pretty simple mapped to
libxml2 XPATH API. But it is not possible with XMLTABLE function - due
design of this function in standard (it is more user friendly and doesn't
require exactly correct xpath expressions).

So you can't use existing libxml2 xpath support to implement XMLTABLE,
even without default namespaces?

I didn't find any API in libxml2 for a work with parsed xpath expressions -
I need some info about the first and last token of xpath expression - it is
base for decision about using prefix or suffix.

This functionality (xpath expression parser) cannot be used for our XPATH
function now - maybe default namespace in future.

So we'll have two different XPATH implementations for different
places, with different limitations, different possible bugs, etc?

What would be needed to make the new XPATH work for our built-in xpath
functions too?

How does XmlTableGetValue(...) and XmlTableGetRowValue(...) relate to
this? It doesn't look like they're intended to be called directly by
the user, and they're not documented (or commented).

Probably I used wrong names. XMLTABLE function is running in two different
modes - with explicitly defined columns (XmlTableGetValue is used), and
without explicitly defined columns - so result is one XML column and only
one one step searching is used (there are not column related xpath
expressions) ( XmlTableGetRowValue is used). The function XmlTableGetValue
is used for getting one column value, the function XmlTableGetRowValue is
used for getting one value too, but in special case, when there are not any
other value.

So both are internal implementation of the parser-level XMLTABLE(...)
construct and are not intended to be called directly by users - right?

Comments please! A short comment on the function saying this would be
a big help.

Regarding naming, do we already have a convention for functions that
are internal implementation of something the user "spells"
differently? Where it's transformed by the parser? I couldn't find
one. I don't much care about the names so long as there are comments
explaining what calls the functions and what the user-facing interface
that matches the function is.

Is it safe for users to call these directly? What happens if they do
so incorrectly?

Why are they not in pg_proc.h? Do they need to be?

+/*
+ * There are different requests from XMLTABLE, JSON_TABLE functions
+ * on passed data than has CREATE TABLE command. It is reason for
+ * introduction special structure instead using ColumnDef.
+ */
+typedef struct TableExprRawCol
+{
+    NodeTag     type;
+    char       *colname;
+    TypeName   *typeName;
+    bool        for_ordinality;
+    bool        is_not_null;
+    Node       *path_expr;
+    Node       *default_expr;
+    int         location;
+} TableExprRawCol;

I am sorry. It is my fault. Now we have very similar node ColumnDef. This
node is designed for usage in utility commands - and it is not designed for
usage inside a query.

Makes sense.

I had to decide between enhancing ColumnDef node or
introduction new special node. Because there are more special attributes and
it is hard to serialize current ColumnDef, I decided to use new node.

Seems reasonable. The summary is "this is the parse node for a column
of an XMLTABLE expression".

Suggested comment:

/*
* This is the parsenode for a column definition in a table-expression
like XMLTABLE.
*
* We can't re-use ColumnDef here; the utility command column
definition has all the
* wrong attributes for use in table-expressions and just doesn't make
sense here.
*/
typedef struct TableExprColumn
{
...
};

?

Why "RawCol" ? What does it become when it's not "raw" anymore? Is
that a reference to ColumnDef's raw_default and cooked_default for
untransformed vs transformed parse-trees?

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#8)
Re: patch: function xmltable

2016-09-07 5:03 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

On 7 September 2016 at 04:13, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Overall, I think this needs to be revised with appropriate comments.
Whitespace/formatting needs fixing since it's all over the place.
Documentation is insufficient (per notes below).

I am not able to write documentation in English language :( - This

function

is pretty complex - so I hope so anybody with better language skills can
help with this. It respects standard and it respects little bit different
Oracle's behave too (different order of DEFAULT and PATH parts).

OK, no problem. It can't be committed without more comprehensive docs
though, especially for new and nontrivial functionality.

Is there some reference material you can point to so someone else can
help with docs? And can you describe what differences there are
between your implementation and the reference?

Alternately, if you document it in Czech, do you know of anyone who
could assist in translating to English for the main documentation?

Re identifier naming, some of this code uses XmlTable naming patterns,
some uses TableExpr prefixes. Is that intended to indicate a bounary
between things re-usable for other structured data ingesting
functions? Do you expect a "JSONEXPR" or similar in future? That's
alluded to by

This structure should be reused by JSON_TABLE function. Now, it is little
bit strange, because there is only XMLTABLE implementation - and I have

to

choose between a) using two different names now, b) renaming some part in
future.

OK. Are you planning on writing this JSON_TABLE or are you leaving
room for future growth? Either way is fine, just curious.

And although XMLTABLE and JSON_TABLE functions are pretty similar - share
90% of data (input value, path, columns definitions), these functions has
different syntax - so only middle level code should be shared.

That makes sense.

I think it would be best if you separated out the TableExpr
infrastructure from the XMLTABLE implementation though, so we can
review the first level infrastrcture separately and make this a
2-patch series. Most importantly, doing it that way will help you find
places where TableExpr code calls directly into XMLTABLE code. If
TableExpr is supposed to be reusable for json etc, it probably
shouldn't be calling XmlTable stuff directly.

That also means somewhat smaller simpler patches, which probably isn't bad.

I don't necessarily think this needs to be fully pluggable with
callbacks etc. It doesn't sound like you expect this to be used by
extensions or to have a lot of users, right? So it probably just needs
clearer separation of the infrastructure layer from the xmltable
layer. I think splitting the patch will make that easier to see and
make it easier to find problems.

My biggest complaint at the moment is that execEvalTableExpr calls
initXmlTableContext(...) directly, is aware of XML namespaces
directly, calls XmlTableSetRowPath() directly, calls
XmlTableFetchRow() directly, etc. It is in no way generic/reusable for
some later JSONTABLE feature. That needs to be fixed by:

* Renaming it so it's clearly only for XMLTABLE; or
* Abstracting the init context, set row path, fetch row etc operations
so json ones can be plugged in later

Currently the common part is not too big - just the Node related part -

I am

not sure about necessity of two patches.

The problem is that the common part is all mixed in with the
XMLTABLE-specific part, so it's not at all clear it can be common with
something else.

I am agree, there is missing some
TableExpBuilder, where can be better isolated the XML part.

Yeah, that's sort of what I'm getting at.

execEvalTableExpr seems to be defined twice, with a difference in
case. This is probably not going to fly:

+static Datum
+execEvalTableExpr(TableExprState *tstate,
+                        ExprContext *econtext,
+                        bool *isNull, ExprDoneCond *isDone)
+{
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+                        ExprContext *econtext,
+                        bool *isNull, ExprDoneCond *isDone)
+{

It looks like you've split the function into a "guts" and "wrapper"
part, with the error handling PG_TRY / PG_CATCH block in the wrapper.
That seems reasonable for readability, but the naming isn't.

I invite any idea how these functions should be named.

Definitely not how they are ;) . They really can't differ in a single
character's case.

I'm not sure if PostgreSQL has any formal convention for this. Some
places use _impl e.g. pg_read_barrier_impl() but that's in the
context of an interface-vs-implementation separation, which isn't the
case here.

Some places use _internal, like AlterObjectRename_internal(...), but
that's where there's an associated public/external part, which isn't
the case here.

Some places use _guts e.g. pg_logical_slot_get_changes_guts(...),
largely where there's common use by several callers.

This is a fairly arbitrary function split for readability/length. Is
it actually useful to split this function up at all?

Anyone else have an opinion?

A comment is needed to explain what ExecEvalTableExpr is / does. If
it's XMLTABLE specific (which it looks like based on the code), its
name should reflect that. This pattern is repeated elsewhere; e.g.
TableExprState is really the state for an XMLTABLE expression. But
PostgreSQL actually has TABLE statements, and in future we might want
to support table-expressions, so I don't think this naming is
appropriate. This is made worse by the lack of comments on things like
the definition of TableExprState. Please use something that makes it
clear it's for XMLTABLE and add appropriate comments.

I understand, so using TableExpr can be strange (for XMLTABLE function).

But

when we will have JSON_TABLE function, then it will have a sense.

It's pretty hard to review that as shared infrastructure when it's
still tangled up in xmltable specifics, though.

"TableExprState" is consistent with "TableExpr".

Any idea how it should be changed?

I think if you want it to be shareable infrasructure, you need to
write it so it can be used as shared infrastructure. Not just name it
that way but then make it XMLTABLE specific in actual functionality.

/* when typmod is not valid, refresh it */
if (te->typmod == -1)

Is this a cache? How is it valid or not valid and when? The comment
(thanks!) on TableExprGetTupleDesc says:

/*
* When we skip transform stage (in view), then TableExpr's
* TupleDesc should not be valid. Refresh is necessary.
*/

but I'm not really grasping what you're trying to explain here. What
transform stage? What view? This could well be my ignorance of this
part of the code; if it should be understandable by a reader who is
appropriately familiar with the executor that's fine, but if it's
specific to how XMLTABLE works some more explanation would be good.

This is most difficult part of this patch, and I am not sure it it is

fully

correctly implemented. I use TupleDesc cache. The TupleDesc is created in
parser/transform stage. When the XMLTABLE is used in some view, then the
transformed parser tree is materialized - and when the view is used in
query, then this tree is loaded and the parser/transform stage is

"skipped".

I'll check this code against implementation of ROW constructor and I'll

try

to do more comments there.

Thanks. It would be good to highlight when this does and does not
happen and why. Why is it necessary at all?

What happens if XMLTABLE is used in a query directly, not part of a
view? if XMLTABLE is used in a view? If XMLTABLE is used in a prepared
statement / plpgsql statement / etc? What about a CTE term?

Not necessarily list all these cases one by one, just explain what
happens, when and why. Especially if it's complex, so other readers
can understand it and don't have to study it in detail to understand
what is going on. It does not need to be good public-facing
documentation, and details of wording, grammar etc can be fixed up
later, it's the ideas that matter.

Is this similar to other logic elsewhere? If so, reference that other
logic so readers know where to look. That way if they're
changing/bugfixing/etc one place they know there's another place that
might need changing.

I don't know this area of the code well enough to give a solid review
of the actual functionality, and I don't yet understand what it's
trying to do so it's hard to review it by studying what it actually
does vs what it claims to do. Maybe Peter E can help, he said he was
thinking of looking at this patch too. But more information on what
it's trying to do would be a big help.

PostgreSQL already has XPATH support in the form of xmlexists(...)
etc. Why is getXPathToken() etc needed? What re-use is possible here?
There's no explanation in the patch header or comments. Should the new
xpath parser be re-used by the existing xpath stuff? Why can't we use
libxml's facilities? etc. This at least needs explaining in the
submission, and some kind of hint as to why we have two different ways
to do it is needed in the code. If we do need a new XML parser, should
it be bundled in adt/xml.c along with a lot of user-facing
functionality, or a separate file?

libxml2 and our XPATH function doesn't support default namespace (
http://plasmasturm.org/log/259/ ). This is pretty useful feature - so I
implemented.

OK, that makes sense.

For the purpose of getting this patch in, is it a _necessary_ feature?
Can XMLTABLE be usefully implemented without it, and if so, can it be
added in a subsequent patch? It would be nice to simplify this by
using existing libxml2 functionality in the first version rather than
adding a whole new xpath as well!

This is not a xpath implementation - it is preprocessing of xpath
expression. without it, a users have to set explicitly PATH clause with
explicit prefix "./" and explicit suffix "/text()". The usability will be
significantly lower, and what is worst - the examples from internet should
not work. Although is is lot of lines, this code is necessary.

This is the mayor issue of libxml2 library. Another difference
between XPATH function and XMLTABLE function is using two phase searching
and implicit prefix "./" and suffix ("/text()") in XMLTABLE. XMLTABLE

using

two XPATH expressions - for row data cutting and next for column data
cutting (from row data). The our XPATH functions is pretty simple mapped

to

libxml2 XPATH API. But it is not possible with XMLTABLE function - due
design of this function in standard (it is more user friendly and doesn't
require exactly correct xpath expressions).

So you can't use existing libxml2 xpath support to implement XMLTABLE,
even without default namespaces?

I didn't find any API in libxml2 for a work with parsed xpath

expressions -

I need some info about the first and last token of xpath expression - it

is

base for decision about using prefix or suffix.

This functionality (xpath expression parser) cannot be used for our XPATH
function now - maybe default namespace in future.

So we'll have two different XPATH implementations for different
places, with different limitations, different possible bugs, etc?

It is just preprocessing. The evaluation of xpath expression is part of
libxml2 and it is shared.

Our XPATH function is not short, but the reason is reading namespaces data
from 2D array. The evaluation of xpath expression is on few lines.

What would be needed to make the new XPATH work for our built-in xpath
functions too?

How does XmlTableGetValue(...) and XmlTableGetRowValue(...) relate to
this? It doesn't look like they're intended to be called directly by
the user, and they're not documented (or commented).

Probably I used wrong names. XMLTABLE function is running in two

different

modes - with explicitly defined columns (XmlTableGetValue is used), and
without explicitly defined columns - so result is one XML column and only
one one step searching is used (there are not column related xpath
expressions) ( XmlTableGetRowValue is used). The function

XmlTableGetValue

is used for getting one column value, the function XmlTableGetRowValue is
used for getting one value too, but in special case, when there are not

any

other value.

So both are internal implementation of the parser-level XMLTABLE(...)
construct and are not intended to be called directly by users - right?

No - it is called from executor - and it should not be called differently.
I have to do better separation from executor, and these functions will be
private.

Comments please! A short comment on the function saying this would be
a big help.

Regarding naming, do we already have a convention for functions that
are internal implementation of something the user "spells"
differently? Where it's transformed by the parser? I couldn't find
one. I don't much care about the names so long as there are comments
explaining what calls the functions and what the user-facing interface
that matches the function is.

Is it safe for users to call these directly? What happens if they do
so incorrectly?

Why are they not in pg_proc.h? Do they need to be?

+/*
+ * There are different requests from XMLTABLE, JSON_TABLE functions
+ * on passed data than has CREATE TABLE command. It is reason for
+ * introduction special structure instead using ColumnDef.
+ */
+typedef struct TableExprRawCol
+{
+    NodeTag     type;
+    char       *colname;
+    TypeName   *typeName;
+    bool        for_ordinality;
+    bool        is_not_null;
+    Node       *path_expr;
+    Node       *default_expr;
+    int         location;
+} TableExprRawCol;

I am sorry. It is my fault. Now we have very similar node ColumnDef. This
node is designed for usage in utility commands - and it is not designed

for

usage inside a query.

Makes sense.

I had to decide between enhancing ColumnDef node or
introduction new special node. Because there are more special attributes

and

it is hard to serialize current ColumnDef, I decided to use new node.

Seems reasonable. The summary is "this is the parse node for a column
of an XMLTABLE expression".

Suggested comment:

/*
* This is the parsenode for a column definition in a table-expression
like XMLTABLE.
*
* We can't re-use ColumnDef here; the utility command column
definition has all the
* wrong attributes for use in table-expressions and just doesn't make
sense here.
*/
typedef struct TableExprColumn
{
...
};

?

Why "RawCol" ? What does it become when it's not "raw" anymore? Is
that a reference to ColumnDef's raw_default and cooked_default for
untransformed vs transformed parse-trees?

TableExprColumn is better

Regards

Pavel

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#10Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#8)
Re: patch: function xmltable

Suggested comment:

/*
* This is the parsenode for a column definition in a table-expression
like XMLTABLE.
*
* We can't re-use ColumnDef here; the utility command column
definition has all the
* wrong attributes for use in table-expressions and just doesn't make
sense here.
*/
typedef struct TableExprColumn
{
...
};

?

Why "RawCol" ? What does it become when it's not "raw" anymore? Is
that a reference to ColumnDef's raw_default and cooked_default for
untransformed vs transformed parse-trees?

My previous reply was wrong - it is used by parser only and holds TypeName
field. The analogy with ColumnDef raw_default is perfect.

Regards

Pavel

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#11Craig Ringer
craig@2ndquadrant.com
In reply to: Pavel Stehule (#10)
Re: patch: function xmltable

On 7 September 2016 at 14:44, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Suggested comment:

/*
* This is the parsenode for a column definition in a table-expression
like XMLTABLE.
*
* We can't re-use ColumnDef here; the utility command column
definition has all the
* wrong attributes for use in table-expressions and just doesn't make
sense here.
*/
typedef struct TableExprColumn
{
...
};

?

Why "RawCol" ? What does it become when it's not "raw" anymore? Is
that a reference to ColumnDef's raw_default and cooked_default for
untransformed vs transformed parse-trees?

My previous reply was wrong - it is used by parser only and holds TypeName
field. The analogy with ColumnDef raw_default is perfect.

Cool, lets just comment that then.

I'll wait on an updated patch per discussion to date. Hopefully
somebody else with more of a clue than me can offer better review of
the executor/view/caching part you specifically called out as complex.
Otherwise maybe it'll be clearer in a revised version.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#9)
1 attachment(s)
Re: patch: function xmltable

Hi,

I am sending new version of this patch

1. now generic TableExpr is better separated from a real content generation
2. I removed cached typmod - using row type cache everywhere - it is
consistent with other few places in Pg where dynamic types are used - the
result tupdesc is generated few times more - but it is not on critical path.
3. More comments, few more lines in doc.
4. Reformated by pgindent

Regards

Pavel

Attachments:

xmltable-20160909.patchtext/x-patch; charset=US-ASCII; name=xmltable-20160909.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5148095..ca861d2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10099,6 +10099,97 @@ SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
     </para>
    </sect3>
 
+   <sect3>
+    <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>|DEFAULT <replaceable>namespace uri</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 table based on passed XML value.
+    </para>
+
+    <para>
+<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</literal> clause allow to specify a list
+     of namespaces specified by <replaceable>namespace URI</replaceable> and
+     <replaceable>namespace name</replaceable> (alias>. The default namespace is
+     specified by <replaceable>namespace URI</replaceable> after keyword
+     <literal>DEFAULT</literal>.
+
+<screen><![CDATA[
+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');
+
+ a  
+----
+ 10
+]]></screen>
+   </para>
+
+    <para>
+     The <literal>BY REF</literal> clauses have no effect in
+     PostgreSQL, but are allowed for SQL conformance and compatibility
+     with other implementations.  Per SQL standard, the
+     first <literal>BY REF</literal> is required, the second is
+     optional.  Also note that the SQL standard specifies
+     the <function>xmlexists</function> construct to take an XQuery
+     expression as first argument, but PostgreSQL currently only
+     supports XPath, which is a subset of XQuery.
+    </para>
+
+   <para>
+     The optional <literal>COLUMNS</literal> clause allow to specify a list
+     of colums of generated table. The column with special mark
+     <literal>FOR ORDINALITY</literal> ensures row numbers in this column.
+     When <literal>PATH</literal> is not defined, then the name of column is
+     used as implicit path. Only one column should be marked <literal>FOR ORDINALITY</literal>.
+     When path expression is empty, then possible <literal>DEFAULT</literal> value
+     is used.
+<screen><![CDATA[
+SELECT *
+  FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y')
+                '/rows/row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>');
+
+           xmltable           
+------------------------------
+ <a xmlns="http://x.y">10</a>
+]]></screen>
+   </para>
+
+   </sect3>
+
    <sect3 id="functions-xml-xmlagg">
     <title><literal>xmlagg</literal></title>
 
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 743e7d6..304afcb 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,9 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState * tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4504,213 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExprProtected(TableExprState * tstate,
+						   ExprContext *econtext,
+						   bool *isNull, ExprDoneCond *isDone)
+{
+	TupleDesc	tupdesc;
+	Datum		result;
+	int			i;
+	Datum		value;
+	bool		isnull;
+	const TableExprBuilder *builder;
+	void	   *builderCxt;
+
+	tupdesc = tstate->tupdesc;
+	builder = tstate->builder;
+	builderCxt = tstate->builderCxt;
+
+	if (builderCxt == NULL)
+	{
+		ListCell   *ns;
+
+		builderCxt = builder->CreateContext(tupdesc,
+											tstate->in_functions,
+											tstate->typioparams,
+											tstate->per_rowset_memory);
+		tstate->builderCxt = builderCxt;
+
+		/* Evaluate document expression first */
+		value = ExecEvalExpr(tstate->expr, econtext, &isnull, NULL);
+		if (isnull)
+		{
+			*isDone = ExprSingleResult;
+			*isNull = true;
+			return (Datum) 0;
+		}
+
+		/*
+		 * The content can be bigger document and transformation to cstring
+		 * can be expensive. The table builder is better place for this task -
+		 * pass value as Datum.
+		 */
+		builder->SetContent(builderCxt, value);
+
+		/* Evaluate namespace specifications */
+		foreach(ns, tstate->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			ExprState  *expr;
+			char	   *ns_name;
+			char	   *ns_uri;
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr *na = (NamedArgExpr *) n;
+
+				expr = (ExprState *) na->arg;
+				ns_name = na->name;
+			}
+			else
+			{
+				expr = (ExprState *) n;
+				ns_name = NULL;
+			}
+
+			value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("namespace uri must not be null")));
+			ns_uri = TextDatumGetCString(value);
+
+			builder->SetNS(builderCxt, ns_name, ns_uri);
+		}
+
+		/* Evaluate row path filter */
+		value = ExecEvalExpr(tstate->row_path_expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("row query must not be null")));
+		builder->SetRowPath(builderCxt, TextDatumGetCString(value));
+
+		/* Evaluate column paths */
+		for (i = 0; i < tstate->ncols; i++)
+		{
+			char	   *col_path;
+
+			if (tstate->col_path_expr[i] != NULL)
+			{
+				value = ExecEvalExpr(tstate->col_path_expr[i], econtext, &isnull, NULL);
+				if (isnull)
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("column path for column \"%s\" must not be null",
+							NameStr(tupdesc->attrs[i]->attname))));
+				col_path = TextDatumGetCString(value);
+			}
+			else
+				col_path = NameStr(tupdesc->attrs[i]->attname);
+
+			builder->SetColumnPath(builderCxt, col_path, i);
+		}
+	}
+
+	/* Now we can prepare result */
+	if (builder->FetchRow(builderCxt))
+	{
+		HeapTuple	tuple;
+		HeapTupleHeader dtuple;
+		Datum	   *values;
+		bool	   *nulls;
+
+		values = tstate->values;
+		nulls = tstate->nulls;
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			if (i != tstate->for_ordinality_col - 1)
+			{
+				values[i] = builder->GetValue(builderCxt, i, &isnull);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tupdesc->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+			else
+			{
+				values[i] = Int32GetDatum(++tstate->rownum);
+				nulls[i] = false;
+			}
+		}
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(dtuple, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type info we identified before.
+		 */
+		HeapTupleHeaderSetTypeId(dtuple, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(dtuple, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+
+		*isNull = false;
+		*isDone = ExprMultipleResult;
+
+		result = HeapTupleHeaderGetDatum(dtuple);
+	}
+	else
+	{
+		/* no more rows */
+		builder->DestroyContext(builderCxt);
+		tstate->builderCxt = NULL;
+
+		/* ensure releasing all memory */
+		MemoryContextReset(tstate->per_rowset_memory);
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	return result;
+}
+
+static Datum
+ExecEvalTableExpr(TableExprState * tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	/* Ensure releasing context every exception */
+	Datum		result;
+
+	PG_TRY();
+	{
+		result = ExecEvalTableExprProtected(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		if (tstate->builderCxt != NULL)
+		{
+			tstate->builder->DestroyContext(tstate->builderCxt);
+			tstate->builderCxt = NULL;
+		}
+
+		MemoryContextDelete(tstate->per_rowset_memory);
+		tstate->per_rowset_memory = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5473,122 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int			ncols;
+				ListCell   *col;
+				TupleDesc	tupdesc;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->builderCxt = NULL;
+
+				/* Only XmlTableBuilder is supported now */
+				tstate->builder = &XmlTableBuilder;
+
+				tupdesc = lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+													  exprTypmod((Node *) te));
+
+				ncols = tupdesc->natts;
+				tstate->tupdesc = tupdesc;
+
+				/* result is one more columns every time */
+				Assert(ncols > 0);
+
+				tstate->values = palloc(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid			in_funcid;
+
+					getTypeInputInfo(tupdesc->attrs[i]->atttypid, &in_funcid,
+									 &tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_path_expr = ExecInitExpr((Expr *) te->row_path, parent);
+				tstate->expr = ExecInitExpr((Expr *) te->expr, parent);
+
+				if (te->cols)
+				{
+					Assert(ncols == list_length(te->cols));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_path_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+
+					i = 0;
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+															   parent);
+							tstate->col_path_expr[i] = ExecInitExpr((Expr *) tec->path_expr,
+																	parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+					tstate->rownum = 0;
+				}
+				else
+				{
+					/* There are not any related data */
+					tstate->def_expr = NULL;
+					tstate->col_path_expr = NULL;
+					tstate->not_null = NULL;
+					tstate->ncols = 0;
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->per_rowset_memory = AllocSetContextCreate(CurrentMemoryContext,
+											 "XmlTable per rowgroup context",
+													ALLOCSET_DEFAULT_MINSIZE,
+												   ALLOCSET_DEFAULT_INITSIZE,
+												   ALLOCSET_DEFAULT_MAXSIZE);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4f39dad..a7bfedc 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr * from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(row_path);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(cols);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol * from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn * from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4586,6 +4643,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5087,6 +5150,9 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4800165..22f15e5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2613,6 +2613,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol * a, const TableExprRawCol * b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn * a, const TableExprColumn * b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2633,6 +2663,18 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr * a, const TableExpr * b)
+{
+	COMPARE_NODE_FIELD(row_path);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(cols);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2898,6 +2940,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3386,6 +3434,9 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..ad9b1dc 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -32,6 +33,7 @@ static bool planstate_walk_subplans(List *plans, bool (*walker) (),
 												void *context);
 static bool planstate_walk_members(List *plans, PlanState **planstates,
 					   bool (*walker) (), void *context);
+static TupleDesc TableExprGetTupleDesc(const TableExpr * te);
 
 
 /*
@@ -257,6 +259,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +500,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate tupdesc from columns'd definitions, and returns
+				 * typmod of this blessed tupdesc.
+				 */
+				TupleDesc	tupdesc = TableExprGetTupleDesc((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +748,8 @@ expression_returns_set_walker(Node *node, void *context)
 		return false;
 	if (IsA(node, XmlExpr))
 		return false;
+	if (IsA(node, TableExpr))
+		return true;
 
 	return expression_tree_walker(node, expression_returns_set_walker,
 								  context);
@@ -929,6 +952,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1156,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1588,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2250,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->path_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3070,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_path, te->row_path, Node *);
+				MUTATE(newnode->expr, te->expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->cols, te->cols, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->path_expr, tec->path_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3709,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3761,3 +3862,42 @@ planstate_walk_members(List *plans, PlanState **planstates,
 
 	return false;
 }
+
+/*
+ * Build tupdesc for TableExpr from column's def.
+ */
+static TupleDesc
+TableExprGetTupleDesc(const TableExpr * te)
+{
+	TupleDesc	tupdesc;
+
+	if (te->cols != NIL)
+	{
+		ListCell   *col;
+		int			i = 1;
+
+		tupdesc = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(tupdesc,
+							   (AttrNumber) i,
+							   pstrdup(tec->colname),
+							   tec->typid,
+							   tec->typmod,
+							   0);
+			i++;
+		}
+	}
+	else
+	{
+		tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xmltable", XMLOID, -1, 0);
+	}
+
+	assign_record_type_typmod(tupdesc);
+
+	return tupdesc;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 90fecb1..82286d8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1588,6 +1588,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_NODE_FIELD(row_path);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(cols);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3542,6 +3584,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 894a48f..a70be66 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2265,6 +2265,44 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(row_path);
+	READ_NODE_FIELD(expr);
+	READ_NODE_FIELD(cols);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(path_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2496,6 +2534,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e1baf71..1958dbe 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -819,6 +819,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 1526c73..f280d20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -542,6 +542,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	TableExprColList TableExprColOptions TableExprColOptionsOpt
+%type <node>	TableExprCol
+%type <defelt>	TableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -573,10 +580,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
 
@@ -617,7 +624,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
@@ -646,8 +653,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
 
@@ -12553,6 +12560,142 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = $10;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+TableExprColList: TableExprCol							{ $$ = list_make1($1); }
+			| TableExprColList ',' TableExprCol			{ $$ = lappend($1, $3); }
+		;
+
+TableExprCol:
+			ColId Typename TableExprColOptionsOpt IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+
+					rawc->colname = $1;
+					rawc->typeName = $2;
+					rawc->is_not_null = $4;
+					rawc->location = @1;
+					rawc->for_ordinality = false;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->path_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->path_expr = defel->arg;
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+TableExprColOptionsOpt: TableExprColOptions				{ $$ = $1; }
+			| /* EMPTY */								{ $$ = NIL; }
+		;
+
+TableExprColOptions: TableExprColOption					{ $$ = list_make1($1); }
+			| TableExprColOptions TableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+TableExprColOption:
+			DEFAULT c_expr
+				{
+					$$ = makeDefElem("default", $2, @1);
+				}
+			| PATH c_expr
+				{
+					$$ = makeDefElem("path", $2, @1);
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			a_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT a_expr
+				{
+					$$ = $2;
+				}
 		;
 
 /*
@@ -13691,6 +13834,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13824,6 +13968,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PATH
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -13989,10 +14134,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..0686e07 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1105,8 +1105,8 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
+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,15 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+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 63f7965..893c8a4 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr * te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -365,6 +367,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2678,6 +2684,157 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a TableExpr
+ */
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr * te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	char	  **names;
+
+	Assert(te->row_path != NULL);
+	Assert(te->expr != NULL);
+
+	newte->row_path = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_path),
+											  TEXTOID,
+											  "XMLTABLE");
+	newte->expr = coerce_to_specific_type(pstate,
+									  transformExprRecurse(pstate, te->expr),
+										  XMLOID,
+										  "XMLTABLE");
+
+	if (te->cols != NIL)
+	{
+		ListCell   *col;
+		int			i = 0;
+		bool		for_ordinality = false;
+
+		names = palloc(sizeof(char *) * list_length(te->cols));
+
+		foreach(col, te->cols)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc = makeNode(TableExprColumn);
+			Oid			typid;
+			int32		typmod;
+			int			j;
+
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			if (!rawc->for_ordinality)
+			{
+				typenameTypeIdAndMod(NULL, rawc->typeName, &typid, &typmod);
+
+				if (rawc->path_expr)
+					newc->path_expr = coerce_to_specific_type(pstate,
+							   transformExprRecurse(pstate, rawc->path_expr),
+															  TEXTOID,
+															  "XMLTABLE");
+				if (rawc->default_expr)
+					newc->default_expr = coerce_to_specific_type_typmod(pstate,
+							transformExprRecurse(pstate, rawc->default_expr),
+															   typid, typmod,
+																 "XMLTABLE");
+			}
+			else
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one column FOR ORDINALITY is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+
+				typid = INT4OID;
+				typmod = -1;
+			}
+
+			newc->typid = typid;
+			newc->typmod = typmod;
+
+			newte->cols = lappend(newte->cols, newc);
+
+			names[i] = rawc->colname;
+
+			/* the name should be unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(names[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			i++;
+		}
+
+		pfree(names);
+	}
+
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		bool		found_default_namespace = false;
+		int			nnames = 0;
+
+		names = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr *na = (NamedArgExpr *) n;
+				int			i;
+
+				for (i = 0; i < nnames; i++)
+					if (strcmp(names[i], na->name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+							errmsg("the namespace name \"%s\" is not unique",
+								   na->name),
+								 parser_errposition(pstate, na->location)));
+				names[nnames++] = na->name;
+
+				na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+														   TEXTOID,
+														   "XMLTABLE");
+			}
+			else
+			{
+				/* default ns specification (without name) must by only one */
+				if (found_default_namespace)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one default namespace is allowed"),
+							 parser_errposition(pstate, exprLocation(n))));
+				found_default_namespace = true;
+				n = coerce_to_specific_type(pstate,
+											transformExprRecurse(pstate, n),
+											TEXTOID,
+											"XMLTABLE");
+			}
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(names);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
+/*
  * 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 b7b82bf..7ce209d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1837,6 +1837,9 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..3930452 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8280,6 +8281,100 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_path, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->cols != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->path_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->path_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index b144920..9773f5c 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,28 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+static void *XmlTableCreateContext(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparms,
+					  MemoryContext mcxt);
+static void XmlTableSetContent(void *tcontext, Datum value);
+static void XmlTableSetNS(void *tcontext, char *name, char *uri);
+static void XmlTableSetRowPath(void *tcontext, char *path);
+static void XmlTableSetColumnPath(void *tcontext, char *path, int colnum);
+static bool XmlTableFetchRow(void *tcontext);
+static Datum XmlTableGetValue(void *tcontext, int colnum, bool *isnull);
+static void XmlTableDestroyContext(void *tcontext);
+
+const TableExprBuilder XmlTableBuilder = {
+	XmlTableCreateContext,
+	XmlTableSetContent,
+	XmlTableSetNS,
+	XmlTableSetRowPath,
+	XmlTableSetColumnPath,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyContext
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4071,3 +4094,845 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * We need to work with XPath expression tokens. When expression
+ * starting or finishing with nodenames, then we can use prefix
+ * and suffix. When default namespace is defined, then we should to
+ * enhance any nodename and attribute without namespace by default
+ * namespace. The procession of XPath expression is linear.
+ */
+
+typedef enum
+{
+	XPATH_TOKEN_NONE,
+	XPATH_TOKEN_NAME,
+	XPATH_TOKEN_STRING,
+	XPATH_TOKEN_NUMBER,
+	XPATH_TOKEN_OTHER
+}	XPathTokenType;
+
+typedef struct TokenInfo
+{
+	XPathTokenType ttype;
+	char	   *start;
+	int			length;
+}	XPathTokenInfo;
+
+#define TOKEN_STACK_SIZE		10
+
+typedef struct ParserData
+{
+	char	   *str;
+	char	   *cur;
+	XPathTokenInfo stack[TOKEN_STACK_SIZE];
+	int			stack_length;
+}	XPathParserData;
+
+#define NODENAME_FIRSTCHAR(c)	 ((c) == '_' || (c) == '-' || \
+								 ((c) >= 'A' && (c) <= 'Z') || \
+								 ((c) >= 'a' && (c) <= 'z') || \
+								 ((c) >= '0' && (c) <= '9'))
+
+#define IS_NODENAME_CHAR(c)		(NODENAME_FIRSTCHAR(c) || (c) == '.')
+
+/*
+ * Returns next char after last char of token
+ */
+static char *
+getXPathToken(char *str, XPathTokenInfo * ti)
+{
+	/* skip initial spaces */
+	while (*str == ' ')
+		str++;
+
+	if (*str != '\0')
+	{
+		char		c = *str;
+
+		ti->start = str++;
+
+		if (c >= '0' && c <= '9')
+		{
+			while (*str >= '0' && *str <= '9')
+				str++;
+			if (*str == '.')
+			{
+				str++;
+				while (*str >= '0' && *str <= '9')
+					str++;
+			}
+			ti->ttype = XPATH_TOKEN_NUMBER;
+		}
+		else if (NODENAME_FIRSTCHAR(c))
+		{
+			while (IS_NODENAME_CHAR(*str))
+				str++;
+
+			ti->ttype = XPATH_TOKEN_NAME;
+		}
+		else if (c == '"')
+		{
+			while (*str != '\0')
+				if (*str++ == '"')
+					break;
+
+			ti->ttype = XPATH_TOKEN_STRING;
+		}
+		else
+			ti->ttype = XPATH_TOKEN_OTHER;
+
+		ti->length = str - ti->start;
+	}
+	else
+	{
+		ti->start = NULL;
+		ti->length = 0;
+
+		ti->ttype = XPATH_TOKEN_NONE;
+	}
+
+	return str;
+}
+
+/*
+ * reset XPath parser stack
+ */
+static void
+initXPathParser(XPathParserData * parser, char *str)
+{
+	parser->str = str;
+	parser->cur = str;
+	parser->stack_length = 0;
+}
+
+/*
+ * Returns token from stack or read token
+ */
+static void
+nextXPathToken(XPathParserData * parser, XPathTokenInfo * ti)
+{
+	if (parser->stack_length > 0)
+		memcpy(ti, &parser->stack[--parser->stack_length],
+			   sizeof(XPathTokenInfo));
+	else
+		parser->cur = getXPathToken(parser->cur, ti);
+}
+
+/*
+ * Push token to stack
+ */
+static void
+pushXPathToken(XPathParserData * parser, XPathTokenInfo * ti)
+{
+	if (parser->stack_length == TOKEN_STACK_SIZE)
+		elog(ERROR, "internal error");
+	memcpy(&parser->stack[parser->stack_length++], ti,
+		   sizeof(XPathTokenInfo));
+}
+
+/*
+ * Write token to output string
+ */
+static void
+writeXPathToken(StringInfo str, XPathTokenInfo * ti)
+{
+	Assert(ti->ttype != XPATH_TOKEN_NONE);
+
+	if (ti->ttype != XPATH_TOKEN_OTHER)
+		appendBinaryStringInfo(str, ti->start, ti->length);
+	else
+		appendStringInfoChar(str, *ti->start);
+}
+
+/*
+ * Working horse for XPath transformation. When XPath starting by node name,
+ * then prefix have to be applied. When XPath ending by node name, then
+ * suffix will be used. Any unqualified node name should be qualified by
+ * default namespace. inside_predicate is true, when _transformXPath
+ * is recursivly called because the predicate expression was found.
+ */
+static void
+_transformXPath(StringInfo str, XPathParserData * parser,
+				bool inside_predicate,
+				char *prefix, char *suffix, char *def_namespace_name)
+{
+	XPathTokenInfo t1,
+				t2;
+	bool		is_first_token = true;
+	bool		last_token_is_name = false;
+
+	nextXPathToken(parser, &t1);
+
+	while (t1.ttype != XPATH_TOKEN_NONE)
+	{
+		switch (t1.ttype)
+		{
+			case XPATH_TOKEN_NUMBER:
+			case XPATH_TOKEN_STRING:
+				last_token_is_name = false;
+				is_first_token = false;
+				writeXPathToken(str, &t1);
+				nextXPathToken(parser, &t1);
+				break;
+
+			case XPATH_TOKEN_NAME:
+				{
+					bool		is_qual_name = false;
+
+					/* inside predicate ignore keywords "and" "or" */
+					if (inside_predicate)
+					{
+						if ((strncmp(t1.start, "and", 3) == 0 && t1.length == 3) ||
+						 (strncmp(t1.start, "or", 2) == 0 && t1.length == 2))
+						{
+							writeXPathToken(str, &t1);
+							nextXPathToken(parser, &t1);
+							break;
+						}
+					}
+
+					last_token_is_name = true;
+					nextXPathToken(parser, &t2);
+					if (t2.ttype == XPATH_TOKEN_OTHER)
+					{
+						if (*t2.start == '(')
+							last_token_is_name = false;
+						else if (*t2.start == ':')
+							is_qual_name = true;
+					}
+
+					if (is_first_token && last_token_is_name && prefix != NULL)
+						appendStringInfoString(str, prefix);
+
+					if (last_token_is_name && !is_qual_name && def_namespace_name != NULL)
+						appendStringInfo(str, "%s:", def_namespace_name);
+
+					writeXPathToken(str, &t1);
+					is_first_token = false;
+
+					if (is_qual_name)
+					{
+						writeXPathToken(str, &t2);
+						nextXPathToken(parser, &t1);
+						if (t1.ttype == XPATH_TOKEN_NAME)
+							writeXPathToken(str, &t1);
+						else
+							pushXPathToken(parser, &t1);
+					}
+					else
+						pushXPathToken(parser, &t2);
+
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_OTHER:
+				{
+					char		c = *t1.start;
+
+					is_first_token = false;
+
+					writeXPathToken(str, &t1);
+
+					if (c == '[')
+						_transformXPath(str, parser, true, NULL, NULL, def_namespace_name);
+					else
+					{
+						last_token_is_name = false;
+
+						if (c == ']' && inside_predicate)
+							return;
+
+						else if (c == '@')
+						{
+							nextXPathToken(parser, &t1);
+							if (t1.ttype == XPATH_TOKEN_NAME)
+							{
+								bool		is_qual_name = false;
+
+								nextXPathToken(parser, &t2);
+								if (t2.ttype == XPATH_TOKEN_OTHER && *t2.start == ':')
+									is_qual_name = true;
+
+								if (!is_qual_name && def_namespace_name != NULL)
+									appendStringInfo(str, "%s:", def_namespace_name);
+
+								writeXPathToken(str, &t1);
+								if (is_qual_name)
+								{
+									writeXPathToken(str, &t2);
+									nextXPathToken(parser, &t1);
+									if (t1.ttype == XPATH_TOKEN_NAME)
+										writeXPathToken(str, &t1);
+									else
+										pushXPathToken(parser, &t1);
+								}
+								else
+									pushXPathToken(parser, &t2);
+							}
+							else
+								pushXPathToken(parser, &t1);
+						}
+					}
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_NONE:
+				elog(ERROR, "should not be here");
+		}
+	}
+
+	if (last_token_is_name && suffix != NULL)
+		appendStringInfoString(str, suffix);
+}
+
+static void
+transformXPath(StringInfo str, char *xpath,
+			   char *prefix, char *suffix, char *def_namespace_name)
+{
+	XPathParserData parser;
+
+	initStringInfo(str);
+	initXPathParser(&parser, xpath);
+	_transformXPath(str, &parser, false, prefix, suffix, def_namespace_name);
+}
+
+/*
+ * Functions for XmlTableBuilder
+ *
+ */
+typedef struct XmlTableContext
+{
+	TupleDesc	tupdesc;
+	MemoryContext per_rowset_memory;
+	char	   *def_namespace_name;
+	FmgrInfo   *in_functions;
+	Oid		   *typioparams;
+	long int	rc;
+	PgXmlErrorContext *xmlerrcxt;
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr	doc;
+	xmlXPathContextPtr xpathcxt;
+	xmlXPathCompExprPtr xpathcomp;
+	xmlXPathObjectPtr xpathobj;
+	xmlXPathCompExprPtr *xpathscomp;
+}	XmlTableContext;
+
+/*
+ * Convert XML node to cstring (dump subtree in case of element,
+ * return value otherwise)
+ */
+static char *
+xml_xmlnodetostr(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt)
+{
+	char	   *result;
+
+	if (cur->type == XML_ELEMENT_NODE)
+	{
+		xmlBufferPtr buf;
+		xmlNodePtr	cur_copy;
+
+		buf = xmlBufferCreate();
+
+		/*
+		 * The result of xmlNodeDump() won't contain namespace definitions
+		 * from parent nodes, but xmlCopyNode() duplicates a node along with
+		 * its required namespace definitions.
+		 */
+		cur_copy = xmlCopyNode(cur, 1);
+
+		if (cur_copy == NULL)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not copy node");
+
+		PG_TRY();
+		{
+			xmlNodeDump(buf, NULL, cur_copy, 0, 1);
+			result = pstrdup((const char *) xmlBufferContent(buf));
+		}
+		PG_CATCH();
+		{
+			xmlFreeNode(cur_copy);
+			xmlBufferFree(buf);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+		xmlFreeNode(cur_copy);
+		xmlBufferFree(buf);
+	}
+	else
+	{
+		xmlChar    *str;
+
+		str = xmlXPathCastNodeToString(cur);
+		PG_TRY();
+		{
+			/* Here we rely on XML having the same representation as TEXT */
+			char	   *escaped = escape_xml((char *) str);
+
+			result = escaped;
+		}
+		PG_CATCH();
+		{
+			xmlFree(str);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+		xmlFree(str);
+	}
+
+	return result;
+}
+
+/*
+ * Secure cstring for usage in libxml2
+ */
+static xmlChar *
+to_xmlstr(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+#endif
+
+/*
+ * Create XmlTableContext for XmlTable builder. Initialize XML parser.
+ */
+static void *
+XmlTableCreateContext(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparams,
+					  MemoryContext per_rowset_memory)
+{
+#ifdef USE_LIBXML
+	MemoryContext oldcxt;
+	XmlTableContext *result = NULL;
+	PgXmlErrorContext *xmlerrcxt = NULL;
+	volatile xmlParserCtxtPtr ctxt = NULL;
+
+	oldcxt = MemoryContextSwitchTo(per_rowset_memory);
+
+	result = palloc0(sizeof(struct XmlTableContext));
+	result->tupdesc = tupdesc;
+	result->per_rowset_memory = per_rowset_memory;
+	result->in_functions = in_functions;
+	result->typioparams = typioparams;
+	result->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * tupdesc->natts);
+
+	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+	PG_TRY();
+	{
+		xmlInitParser();
+
+		ctxt = xmlNewParserCtxt();
+		if (ctxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate parser context");
+	}
+	PG_CATCH();
+	{
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	result->xmlerrcxt = xmlerrcxt;
+	result->ctxt = ctxt;
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(void *tcontext, Datum value)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	xmltype    *xmlval = DatumGetXmlP(value);
+	xmlChar    *xstr;
+	int			length;
+	volatile xmlDocPtr doc = NULL;
+	volatile xmlXPathContextPtr xpathcxt = NULL;
+	MemoryContext oldcxt;
+
+	length = VARSIZE(xmlval) - VARHDRSZ;
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+
+	xstr = to_xmlstr(VARDATA(xmlval), length);
+
+	PG_TRY();
+	{
+		doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL, 0);
+		if (doc == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+						"could not parse XML document");
+		xpathcxt = xmlXPathNewContext(doc);
+		if (xpathcxt == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate XPath context");
+		xpathcxt->node = xmlDocGetRootElement(doc);
+		if (xpathcxt->node == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not find root XML element");
+	}
+	PG_CATCH();
+	{
+		if (xpathcxt != NULL)
+			xmlXPathFreeContext(xpathcxt);
+		if (doc != NULL)
+			xmlFreeDoc(doc);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xtCxt->doc = doc;
+	xtCxt->xpathcxt = xpathcxt;
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+#define DEFAULT_NAMESPACE_NAME		"pgdefnamespace"
+
+/*
+ * XmlTableSetNS - set namespace. use fake name when the name
+ * is null, and store this name as default namespace.
+ */
+static void
+XmlTableSetNS(void *tcontext, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	if (name == NULL)
+	{
+		Assert(xtCxt->def_namespace_name == NULL);
+
+		name = DEFAULT_NAMESPACE_NAME;
+		xtCxt->def_namespace_name = name;
+	}
+
+	if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+						   to_xmlstr(name, strlen(name)),
+						   to_xmlstr(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowPath
+ */
+static void
+XmlTableSetRowPath(void *tcontext, char *path)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	xmlChar    *xstr;
+	MemoryContext oldcxt;
+	StringInfoData str;
+
+	if (path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("row path filter must not be empty string")));
+
+	transformXPath(&str, path, NULL, NULL, xtCxt->def_namespace_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+	xstr = to_xmlstr(str.data, str.len);
+
+	xtCxt->xpathcomp = xmlXPathCompile(xstr);
+	if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetColumnPath
+ */
+static void
+XmlTableSetColumnPath(void *tcontext, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	MemoryContext oldcxt;
+	StringInfoData str;
+	xmlChar    *xstr;
+	Oid			typid;
+
+	if (path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("column path filter must not be empty string")));
+
+	typid = xtCxt->tupdesc->attrs[colnum]->atttypid;
+
+	transformXPath(&str, path,
+				   "./", typid != XMLOID ? "/text()" : NULL,
+				   xtCxt->def_namespace_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+	xstr = to_xmlstr(str.data, str.len);
+
+	xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+	if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableFetchRow - returns true when is possible to read
+ * values from XML document.
+ *
+ * First call evaluates row path over content.
+ * Next step and at other calls increment row counter and
+ * compare it with number values in result set.
+ */
+static bool
+XmlTableFetchRow(void *tcontext)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		xtCxt->rc = 0;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+}
+
+/*
+ * Apply column path on current node and returns result.
+ * Allows multivalue when expected type is XML.
+ * When COLUMNS part was not entered, returns current node.
+ */
+static Datum
+XmlTableGetValue(void *tcontext, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+	if (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = NULL;
+
+		/* fast leaving */
+		if (cur->type != XML_ELEMENT_NODE)
+			elog(ERROR, "unexpected xmlNode type");
+
+		PG_TRY();
+		{
+			/* Set current node as entry point for XPath evaluation */
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+			/* Evaluate column path */
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+			if (column_xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+				xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+							"could not create XPath object");
+
+			if (column_xpathobj->type == XPATH_NODESET)
+			{
+				int			count;
+
+				if (column_xpathobj->nodesetval != NULL)
+					count = column_xpathobj->nodesetval->nodeNr;
+				else
+					count = 0;
+
+				if (count > 0)
+				{
+					if (count == 1)
+					{
+						/* 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 (xtCxt->tupdesc->attrs[colnum]->atttypid != XMLOID)
+							ereport(ERROR,
+									(errcode(ERRCODE_CARDINALITY_VIOLATION),
+									 errmsg("more than one value returned by column XPath expression")));
+
+						/* Concate serialized values */
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+												   xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														  xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[colnum],
+											   cstr,
+											   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[colnum],
+										 (char *) column_xpathobj->stringval,
+										   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+				*isnull = false;
+			}
+			else
+				elog(ERROR, "unexpected XPath object type");
+
+			xmlXPathFreeObject(column_xpathobj);
+			column_xpathobj = NULL;
+		}
+		PG_CATCH();
+		{
+			if (column_xpathobj != NULL)
+				xmlXPathFreeObject(column_xpathobj);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+	else
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+		result = InputFunctionCall(&xtCxt->in_functions[0],
+								   cstr,
+								   xtCxt->typioparams[0],
+								   -1); /* target type is XML always */
+		*isnull = false;
+	}
+
+	if (cstr != NULL)
+		pfree(cstr);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * Release all libxml2 memory and related resources
+ */
+static void
+XmlTableDestroyContext(void *tcontext)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int			i;
+
+		for (i = 0; i < xtCxt->tupdesc->natts; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e28477d..8b25152 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "executor/instrument.h"
+#include "executor/tableexpr.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
@@ -1004,6 +1005,38 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+/* ----------------
+ *		TableExprState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	Oid			typid;
+	int32		typmod;
+	TupleDesc	tupdesc;		/* cache */
+	int			ncols;			/* number of declared columns */
+	int			for_ordinality_col;		/* number of oridinality column,
+										 * started by 1 */
+	int			rownum;			/* row counter - for ordinality column */
+	ExprState  *row_path_expr;	/* row xpath expression */
+	ExprState  *expr;			/* processed data */
+	ExprState **def_expr;		/* array of expressions for default value */
+	ExprState **col_path_expr;	/* array of expressions for path value */
+	bool	   *not_null;		/* for any column info if NULL is allowed or
+								 * not */
+	Datum	   *values;			/* prealloc buffer */
+	bool	   *nulls;			/* prealloc buffer */
+	FmgrInfo   *in_functions;	/* array of infunction for any column */
+	Oid		   *typioparams;	/* array of typIOParam for any column */
+	const TableExprBuilder *builder;	/* pointers to builder functions */
+	void	   *builderCxt;		/* data for content builder */
+
+	MemoryContext per_rowset_memory;
+}	TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..504fb9d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -453,6 +456,7 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8d3dcf4..94dbf73 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -698,6 +698,25 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - raw column definition in table-expression functions
+ *		like XMLTABLE.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions
+ * and just doesn't make sense here.
+ */
+typedef struct TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;
+	TypeName   *typeName;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+}	TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..3fcf546 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,44 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - holds data for table-expression functions like XMLTABLE
+ *
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Node	   *row_path;		/* row xpath query */
+	Node	   *expr;			/* processed data */
+	List	   *cols;			/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+}	TableExpr;
+
+/*----------
+ * TableExprColumn - a column definition in table-expression functions.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions
+ * and just doesn't make sense here.
+ *
+ * Raw form of this node is TableExprRawCol node - different in
+ * typeName field. After transformation we use typid, typmod.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;
+	Oid			typid;
+	int32		typmod;
+	Oid			collation;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+}	TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..de83895 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -289,6 +290,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -438,10 +440,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..1229b76 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,6 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern const TableExprBuilder XmlTableBuilder;
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..ed6cb79 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,139 @@ 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)
+
+
+-- 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');
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d702703..58e6723 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,127 @@ 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)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz)
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz)
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltableview2;
+ERROR:  relation "xmltableview2" does not exist
+LINE 1: SELECT * FROM xmltableview2;
+                      ^
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y')
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+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.
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5..48fae36 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,138 @@ 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)
+
+-- 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');
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30..5909a51 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,91 @@ 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;
+
+-- 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>');
#13Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#12)
1 attachment(s)
Re: patch: function xmltable

2016-09-09 10:35 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi,

I am sending new version of this patch

1. now generic TableExpr is better separated from a real content generation
2. I removed cached typmod - using row type cache everywhere - it is
consistent with other few places in Pg where dynamic types are used - the
result tupdesc is generated few times more - but it is not on critical path.
3. More comments, few more lines in doc.
4. Reformated by pgindent

new update

more regress tests

Regards

Pavel

Show quoted text

Regards

Pavel

Attachments:

xmltable-20160909-2.patch.gzapplication/x-gzip; name=xmltable-20160909-2.patch.gzDownload
#14Craig Ringer
craig@2ndquadrant.com
In reply to: Pavel Stehule (#13)
Re: patch: function xmltable

On 9 September 2016 at 21:44, Pavel Stehule <pavel.stehule@gmail.com> wrote:

2016-09-09 10:35 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi,

I am sending new version of this patch

1. now generic TableExpr is better separated from a real content
generation
2. I removed cached typmod - using row type cache everywhere - it is
consistent with other few places in Pg where dynamic types are used - the
result tupdesc is generated few times more - but it is not on critical path.
3. More comments, few more lines in doc.
4. Reformated by pgindent

Thanks.

I applied this on top of the same base as your prior patch so I could
compare changes.

The new docs look good. Thanks for that, I know it's a pain. It'll
need to cover ORDINAL too, but that's not hard. I'll try to find some
time to help with the docs per the references you sent offlist.

Out of interest, should the syntax allow room for future expansion to
permit reading from file rather than just string literal / column
reference? It'd be ideal to avoid reading big documents wholly into
memory when using INSERT INTO ... SELECT XMLTABLE (...) . I don't
suggest adding that to this patch, just making sure adding it later
would not cause problems.

I see you added a builder context abstraction as discussed, so there's
no longer any direct reference to XMLTABLE specifics from TableExpr
code. Good, thanks for that. It'll make things much less messy when
adding other table expression types as you expressed the desire to do,
and means the TableExpr code now makes more sense as generic
infrastructure.

ExecEvalTableExprProtected and ExecEvalTableExpr are OK with me, or
better than execEvalTableExpr and ExecEvalTableExpr were anyway.
Eventual committer will probably have opinions here.

Mild nitpick: since you can have multiple namespaces, shouldn't
builder->SetNS be builder->AddNS ?

Added comments are helpful, thanks.

On first read-through this is a big improvement and addresses all the
concerns I raised. Documentation is much much better, thanks, I know
that's a pain.

I'll take a closer read-through shortly.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Craig Ringer
craig@2ndquadrant.com
In reply to: Craig Ringer (#14)
2 attachment(s)
Re: patch: function xmltable

I'll take a closer read-through shortly.

Missing file. You omitted executor/tableexpr.h from the patch, so I
can't compile.

I've expanded and copy-edited the docs. Some of it is guesswork based
on the references you sent and a glance at the code. Please check my
changes carefully. I found a few surprises, like the fact that DEFAULT
isn't a normal literal, it's an xpath expression evaluated at the same
time as the rowexpression.

Updated patch attached as XMLTABLE-v3 includes the docs changes. Note
that it's missing tableexpr.h. For convenient review or to apply to
your working tree I also attach a diff of just my docs changes as
proposed-docs-changes.diff.

Docs:

- Can you send the sample data used to generate the example output?
I'd like to include at least a cut down part of it in the docs to make
it clear how the input correlates with output, and preferably put the
whole thing in an appendix.

- How does it decide what subset of the document to iterate over?
That's presumably rowexpr, which is xpath in postgresql? (I added this
to docs).

- xmlnamespaces clause in docs needs an example for a non-default namespace.

- What effect does xmlnamespaces clause have? Does supplying it allow
you to reference qualified names in xpath? What happens if you don't
specify it for a document that has namespaces or don't define all the
namespaces? What if you reference an undefined namespace in xpath?
What about if an undefined namespace isn't referenced by xpath, but is
inside a node selected by an xpath expression?

- What are the rules for converting the matched XML node into a value?
If the matched node is not a simple text node or lacks a text node as
its single child, what happens?

- What happens if the matched has multiple text node children? This
can happen if, for example, you have something like

<matchedNode>
some text <!-- comment splits up text node --> other text
</matchedNode>

- Is there a way to get an attribute as a value? If so, an example
should show this because it's going to be a common need. Presumably
you want node/@attrname ?

- What happens if COLUMNS is not specified at all? It looks like it
returns a single column result set with the matched entries as 'xml'
type, so added to docs, please verify.

- PASSING clause isn't really defined. You can specify one PASSING
entry as a literal/colref/expression, and it's the argument xml
document, right? The external docs you referred to say that PASSING
may have a BY VALUE keyword, alias its argument with AS, and may have
expressions, e.g.

PASSING BY VALUE '<x/>' AS a, '<y/>' AS b

Neither aliases nor multiple entries are supported by the code or
grammar. Should this be documented as a restriction? Do you know if
that's an extension by the other implementation or if it's SQL/XML
standard? (I've drafted a docs update to cover this in the updated
patch).

- What does BY REF mean? Should this just be mentioned with a "see
xmlexists(...)" since it seems to be compatibility noise? Is there a
corresponding BY VALUE or similar?

- The parser definitions re-use xmlexists_argument . I don't mind
that, but it's worth noting here in case others do.

- Why do the expression arguments take c_expr (anything allowed in
a_expr or b_expr), not b_expr (restricted expression) ?

- Column definitions are underdocumented. The grammar says they can be
NOT NULL, for example, but I don't see that in any of the references
you mailed me nor in the docs. What behaviour is expected for a NOT
NULL column? I've documented my best guess (not checked closely
against the code, please verify).

-

Test suggestions:

- Coverage of multiple text() node children of an element, where split
up by comment or similar

- Coverage of xpath that matches a node with child element nodes

More to come. Please review my docs changes in the mean time. I'm
spending a lot more time on this than I expected so I might have to
get onto other things for a while too.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

proposed-docs-changes.patchtext/x-patch; charset=US-ASCII; name=proposed-docs-changes.patchDownload
From 464e8f16386c0fbfb8b5c6fdee66402c95c6ae9f Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 12 Sep 2016 12:26:14 +0800
Subject: [PATCH] Proposed docs changes

---
 doc/src/sgml/func.sgml | 103 +++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 82 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ca861d2..3ba492e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10099,7 +10099,7 @@ SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
     </para>
    </sect3>
 
-   <sect3>
+   <sect3 id="functions-xmltable" xreflabel="XMLTABLE">
     <title><literal>xmltable</literal></title>
 
    <indexterm>
@@ -10111,7 +10111,9 @@ SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
 </synopsis>
 
     <para>
-      The <function>xmltable</function> produces table based on passed XML value.
+      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>
@@ -10140,11 +10142,11 @@ SELECT  xmltable.*
     </para>
 
    <para>
-     The optional <literal>xmlnamespaces</literal> clause allow to specify a list
-     of namespaces specified by <replaceable>namespace URI</replaceable> and
-     <replaceable>namespace name</replaceable> (alias>. The default namespace is
-     specified by <replaceable>namespace URI</replaceable> after keyword
-     <literal>DEFAULT</literal>.
+     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 specified with
+     <literal>DEFAULT</> <replaceable>namespace-URI</>.
 
 <screen><![CDATA[
 SELECT *
@@ -10159,25 +10161,84 @@ SELECT *
    </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 SQL standard, the
-     first <literal>BY REF</literal> is required, the second is
-     optional.  Also note that the SQL standard specifies
-     the <function>xmlexists</function> construct to take an XQuery
-     expression as first argument, but PostgreSQL currently only
-     supports XPath, which is a subset of XQuery.
+     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.
     </para>
 
    <para>
-     The optional <literal>COLUMNS</literal> clause allow to specify a list
-     of colums of generated table. The column with special mark
-     <literal>FOR ORDINALITY</literal> ensures row numbers in this column.
-     When <literal>PATH</literal> is not defined, then the name of column is
-     used as implicit path. Only one column should be marked <literal>FOR ORDINALITY</literal>.
-     When path expression is empty, then possible <literal>DEFAULT</literal> value
-     is used.
-<screen><![CDATA[
+     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. 
+    </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>
+     If the path expression does not match for a given row but a
+     <literal>DEFAULT</> xpath expression is specified, the resulting
+     default value is used. If no <literal>DEFAULT</> is given the
+     field will be <literal>NULL</>.  Note that unlike
+     <literal>PATH</>, the <literal>DEFAULT</> for a column is
+     evaluated only once, relative to the root of the
+     <replaceable>xml</> argument, when processing begins.
+    </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>
+     
+    <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>. <screen><![CDATA[
 SELECT *
   FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y')
                 '/rows/row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>');
-- 
2.5.5

0001-XMLTABLE-v3.patch.gzapplication/x-gzip; name=0001-XMLTABLE-v3.patch.gzDownload
#16Craig Ringer
craig@2ndquadrant.com
In reply to: Craig Ringer (#15)
Re: patch: function xmltable

On 12 September 2016 at 12:28, Craig Ringer <craig@2ndquadrant.com> wrote:

I'll take a closer read-through shortly.

DEFAULT
isn't a normal literal, it's an xpath expression evaluated at the same
time as the rowexpression.

Sorry for the spam, but turns out that's not the case as implemented
here. The docs you referenced say it should be an xpath expression,
but the implementation here is of a literal value, and examples
elsewhere on the Internet show a literal value. Unclear if the
referenced docs are wrong or what and I don't have anything to test
with.

Feel free to fix/trim the DEFAULT related changes in above docs patch as needed.

Also, tests/docs should probably cover what happens when PATH matches
more than one element, i.e. produces a list of more than one match.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#16)
Re: patch: function xmltable

2016-09-12 6:36 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

On 12 September 2016 at 12:28, Craig Ringer <craig@2ndquadrant.com> wrote:

I'll take a closer read-through shortly.

DEFAULT
isn't a normal literal, it's an xpath expression evaluated at the same
time as the rowexpression.

Sorry for the spam, but turns out that's not the case as implemented
here. The docs you referenced say it should be an xpath expression,
but the implementation here is of a literal value, and examples
elsewhere on the Internet show a literal value. Unclear if the
referenced docs are wrong or what and I don't have anything to test
with.

It is not spam. The previous comment was not correct. DEFAULT is a
expression - result of this expression is used, when data is missing.

In standard, and some others implementation, this is literal only. It is
similar to DEFAULT clause in CREATE STATEMENT. Postgres allows expression
there. Usually Postgres allows expressions everywhere when it has sense,
and when it is allowed by bizon parser.

Show quoted text

Feel free to fix/trim the DEFAULT related changes in above docs patch as
needed.

Also, tests/docs should probably cover what happens when PATH matches
more than one element, i.e. produces a list of more than one match.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#18Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#14)
1 attachment(s)
Re: patch: function xmltable

2016-09-12 3:58 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

On 9 September 2016 at 21:44, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

2016-09-09 10:35 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi,

I am sending new version of this patch

1. now generic TableExpr is better separated from a real content
generation
2. I removed cached typmod - using row type cache everywhere - it is
consistent with other few places in Pg where dynamic types are used -

the

result tupdesc is generated few times more - but it is not on critical

path.

3. More comments, few more lines in doc.
4. Reformated by pgindent

Thanks.

I applied this on top of the same base as your prior patch so I could
compare changes.

The new docs look good. Thanks for that, I know it's a pain. It'll
need to cover ORDINAL too, but that's not hard. I'll try to find some
time to help with the docs per the references you sent offlist.

Out of interest, should the syntax allow room for future expansion to
permit reading from file rather than just string literal / column
reference? It'd be ideal to avoid reading big documents wholly into
memory when using INSERT INTO ... SELECT XMLTABLE (...) . I don't
suggest adding that to this patch, just making sure adding it later
would not cause problems.

this is little bit different question - it is server side function, so
first question is - how to push usually client side content to server? Next
question is how to get this content to a executor. Now only COPY statement
is able to do.

I am thinking so this should not be problem, but it requires maybe some
special keywords - fileref, local fileref, and some changes in protocol.
Because this function has own implementation in parser/transform stage,
then nothing will be lost in process, and we can implement lazy parameter
evaluation. Another question if libxml2 has enough possibility to work with
stream.

One idea - we can introduce "external (server side|client side) blobs" with
special types and special streaming IO. With these types, there no changes
are necessary on syntax level. With this, the syntax sugar flag "BY REF"
can be useful.

I see you added a builder context abstraction as discussed, so there's
no longer any direct reference to XMLTABLE specifics from TableExpr
code. Good, thanks for that. It'll make things much less messy when
adding other table expression types as you expressed the desire to do,
and means the TableExpr code now makes more sense as generic
infrastructure.

ExecEvalTableExprProtected and ExecEvalTableExpr are OK with me, or
better than execEvalTableExpr and ExecEvalTableExpr were anyway.
Eventual committer will probably have opinions here.

Mild nitpick: since you can have multiple namespaces, shouldn't
builder->SetNS be builder->AddNS ?

good idea.

Added comments are helpful, thanks.

On first read-through this is a big improvement and addresses all the
concerns I raised. Documentation is much much better, thanks, I know
that's a pain.

I'll take a closer read-through shortly.

updated patch attached - with your documentation.

Regards

Pavel

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

xmltable-5.patchtext/x-patch; charset=US-ASCII; name=xmltable-5.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5148095..56f7aaf 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10099,6 +10099,155 @@ SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
     </para>
    </sect3>
 
+   <sect3 id="functions-xmltable" xreflabel="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>|DEFAULT <replaceable>namespace uri</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>
+<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 specified with
+     <literal>DEFAULT</> <replaceable>namespace-URI</>.
+
+<screen><![CDATA[
+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');
+
+ 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.
+    </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')
+                '/rows/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>
+     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</>.
+    </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>
+
+    <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>
+   </sect3>
+
    <sect3 id="functions-xml-xmlagg">
     <title><literal>xmlagg</literal></title>
 
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 743e7d6..c386be1 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,9 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState * tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4504,213 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExprProtected(TableExprState * tstate,
+						   ExprContext *econtext,
+						   bool *isNull, ExprDoneCond *isDone)
+{
+	TupleDesc	tupdesc;
+	Datum		result;
+	int			i;
+	Datum		value;
+	bool		isnull;
+	const TableExprBuilder *builder;
+	void	   *builderCxt;
+
+	tupdesc = tstate->tupdesc;
+	builder = tstate->builder;
+	builderCxt = tstate->builderCxt;
+
+	if (builderCxt == NULL)
+	{
+		ListCell   *ns;
+
+		builderCxt = builder->CreateContext(tupdesc,
+											tstate->in_functions,
+											tstate->typioparams,
+											tstate->per_rowset_memory);
+		tstate->builderCxt = builderCxt;
+
+		/* Evaluate document expression first */
+		value = ExecEvalExpr(tstate->expr, econtext, &isnull, NULL);
+		if (isnull)
+		{
+			*isDone = ExprSingleResult;
+			*isNull = true;
+			return (Datum) 0;
+		}
+
+		/*
+		 * The content can be bigger document and transformation to cstring
+		 * can be expensive. The table builder is better place for this task -
+		 * pass value as Datum.
+		 */
+		builder->SetContent(builderCxt, value);
+
+		/* Evaluate namespace specifications */
+		foreach(ns, tstate->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			ExprState  *expr;
+			char	   *ns_name;
+			char	   *ns_uri;
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr *na = (NamedArgExpr *) n;
+
+				expr = (ExprState *) na->arg;
+				ns_name = na->name;
+			}
+			else
+			{
+				expr = (ExprState *) n;
+				ns_name = NULL;
+			}
+
+			value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("namespace uri must not be null")));
+			ns_uri = TextDatumGetCString(value);
+
+			builder->AddNS(builderCxt, ns_name, ns_uri);
+		}
+
+		/* Evaluate row path filter */
+		value = ExecEvalExpr(tstate->row_path_expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("row query must not be null")));
+		builder->SetRowPath(builderCxt, TextDatumGetCString(value));
+
+		/* Evaluate column paths */
+		for (i = 0; i < tstate->ncols; i++)
+		{
+			char	   *col_path;
+
+			if (tstate->col_path_expr[i] != NULL)
+			{
+				value = ExecEvalExpr(tstate->col_path_expr[i], econtext, &isnull, NULL);
+				if (isnull)
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("column path for column \"%s\" must not be null",
+							NameStr(tupdesc->attrs[i]->attname))));
+				col_path = TextDatumGetCString(value);
+			}
+			else
+				col_path = NameStr(tupdesc->attrs[i]->attname);
+
+			builder->SetColumnPath(builderCxt, col_path, i);
+		}
+	}
+
+	/* Now we can prepare result */
+	if (builder->FetchRow(builderCxt))
+	{
+		HeapTuple	tuple;
+		HeapTupleHeader dtuple;
+		Datum	   *values;
+		bool	   *nulls;
+
+		values = tstate->values;
+		nulls = tstate->nulls;
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			if (i != tstate->for_ordinality_col - 1)
+			{
+				values[i] = builder->GetValue(builderCxt, i, &isnull);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tupdesc->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+			else
+			{
+				values[i] = Int32GetDatum(++tstate->rownum);
+				nulls[i] = false;
+			}
+		}
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(dtuple, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type info we identified before.
+		 */
+		HeapTupleHeaderSetTypeId(dtuple, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(dtuple, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+
+		*isNull = false;
+		*isDone = ExprMultipleResult;
+
+		result = HeapTupleHeaderGetDatum(dtuple);
+	}
+	else
+	{
+		/* no more rows */
+		builder->DestroyContext(builderCxt);
+		tstate->builderCxt = NULL;
+
+		/* ensure releasing all memory */
+		MemoryContextReset(tstate->per_rowset_memory);
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	return result;
+}
+
+static Datum
+ExecEvalTableExpr(TableExprState * tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	/* Ensure releasing context every exception */
+	Datum		result;
+
+	PG_TRY();
+	{
+		result = ExecEvalTableExprProtected(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		if (tstate->builderCxt != NULL)
+		{
+			tstate->builder->DestroyContext(tstate->builderCxt);
+			tstate->builderCxt = NULL;
+		}
+
+		MemoryContextDelete(tstate->per_rowset_memory);
+		tstate->per_rowset_memory = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5473,111 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int			ncols;
+				ListCell   *col;
+				TupleDesc	tupdesc;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->builderCxt = NULL;
+
+				/* Only XmlTableBuilder is supported now */
+				tstate->builder = &XmlTableBuilder;
+
+				tupdesc = lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+													  exprTypmod((Node *) te));
+
+				ncols = tupdesc->natts;
+				tstate->tupdesc = tupdesc;
+
+				/* result is one more columns every time */
+				Assert(ncols > 0);
+
+				tstate->values = palloc0(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid			in_funcid;
+
+					getTypeInputInfo(tupdesc->attrs[i]->atttypid, &in_funcid,
+									 &tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_path_expr = ExecInitExpr((Expr *) te->row_path, parent);
+				tstate->expr = ExecInitExpr((Expr *) te->expr, parent);
+
+				if (te->cols)
+				{
+					Assert(ncols == list_length(te->cols));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_path_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+
+					i = 0;
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+															   parent);
+							tstate->col_path_expr[i] = ExecInitExpr((Expr *) tec->path_expr,
+																	parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->per_rowset_memory = AllocSetContextCreate(CurrentMemoryContext,
+											"XmlTable per rowgroup context",
+											ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4f39dad..a7bfedc 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr * from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(row_path);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(cols);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol * from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn * from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4586,6 +4643,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5087,6 +5150,9 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4800165..22f15e5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2613,6 +2613,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol * a, const TableExprRawCol * b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn * a, const TableExprColumn * b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2633,6 +2663,18 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr * a, const TableExpr * b)
+{
+	COMPARE_NODE_FIELD(row_path);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(cols);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2898,6 +2940,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3386,6 +3434,9 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..ad9b1dc 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -32,6 +33,7 @@ static bool planstate_walk_subplans(List *plans, bool (*walker) (),
 												void *context);
 static bool planstate_walk_members(List *plans, PlanState **planstates,
 					   bool (*walker) (), void *context);
+static TupleDesc TableExprGetTupleDesc(const TableExpr * te);
 
 
 /*
@@ -257,6 +259,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +500,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate tupdesc from columns'd definitions, and returns
+				 * typmod of this blessed tupdesc.
+				 */
+				TupleDesc	tupdesc = TableExprGetTupleDesc((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +748,8 @@ expression_returns_set_walker(Node *node, void *context)
 		return false;
 	if (IsA(node, XmlExpr))
 		return false;
+	if (IsA(node, TableExpr))
+		return true;
 
 	return expression_tree_walker(node, expression_returns_set_walker,
 								  context);
@@ -929,6 +952,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1156,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1588,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2250,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->path_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3070,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_path, te->row_path, Node *);
+				MUTATE(newnode->expr, te->expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->cols, te->cols, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->path_expr, tec->path_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3709,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3761,3 +3862,42 @@ planstate_walk_members(List *plans, PlanState **planstates,
 
 	return false;
 }
+
+/*
+ * Build tupdesc for TableExpr from column's def.
+ */
+static TupleDesc
+TableExprGetTupleDesc(const TableExpr * te)
+{
+	TupleDesc	tupdesc;
+
+	if (te->cols != NIL)
+	{
+		ListCell   *col;
+		int			i = 1;
+
+		tupdesc = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(tupdesc,
+							   (AttrNumber) i,
+							   pstrdup(tec->colname),
+							   tec->typid,
+							   tec->typmod,
+							   0);
+			i++;
+		}
+	}
+	else
+	{
+		tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "xmltable", XMLOID, -1, 0);
+	}
+
+	assign_record_type_typmod(tupdesc);
+
+	return tupdesc;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 90fecb1..82286d8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1588,6 +1588,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_NODE_FIELD(row_path);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(cols);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3542,6 +3584,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 894a48f..a70be66 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2265,6 +2265,44 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(row_path);
+	READ_NODE_FIELD(expr);
+	READ_NODE_FIELD(cols);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(path_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2496,6 +2534,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e1baf71..1958dbe 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -819,6 +819,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 1526c73..f280d20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -542,6 +542,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	TableExprColList TableExprColOptions TableExprColOptionsOpt
+%type <node>	TableExprCol
+%type <defelt>	TableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -573,10 +580,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
 
@@ -617,7 +624,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
@@ -646,8 +653,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
 
@@ -12553,6 +12560,142 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $7;
+					n->expr = $8;
+					n->cols = $10;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+TableExprColList: TableExprCol							{ $$ = list_make1($1); }
+			| TableExprColList ',' TableExprCol			{ $$ = lappend($1, $3); }
+		;
+
+TableExprCol:
+			ColId Typename TableExprColOptionsOpt IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+
+					rawc->colname = $1;
+					rawc->typeName = $2;
+					rawc->is_not_null = $4;
+					rawc->location = @1;
+					rawc->for_ordinality = false;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->path_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->path_expr = defel->arg;
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+TableExprColOptionsOpt: TableExprColOptions				{ $$ = $1; }
+			| /* EMPTY */								{ $$ = NIL; }
+		;
+
+TableExprColOptions: TableExprColOption					{ $$ = list_make1($1); }
+			| TableExprColOptions TableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+TableExprColOption:
+			DEFAULT c_expr
+				{
+					$$ = makeDefElem("default", $2, @1);
+				}
+			| PATH c_expr
+				{
+					$$ = makeDefElem("path", $2, @1);
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			a_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT a_expr
+				{
+					$$ = $2;
+				}
 		;
 
 /*
@@ -13691,6 +13834,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13824,6 +13968,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PATH
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -13989,10 +14134,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..0686e07 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1105,8 +1105,8 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
+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,15 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+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 63f7965..893c8a4 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr * te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -365,6 +367,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2678,6 +2684,157 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a TableExpr
+ */
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr * te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	char	  **names;
+
+	Assert(te->row_path != NULL);
+	Assert(te->expr != NULL);
+
+	newte->row_path = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_path),
+											  TEXTOID,
+											  "XMLTABLE");
+	newte->expr = coerce_to_specific_type(pstate,
+									  transformExprRecurse(pstate, te->expr),
+										  XMLOID,
+										  "XMLTABLE");
+
+	if (te->cols != NIL)
+	{
+		ListCell   *col;
+		int			i = 0;
+		bool		for_ordinality = false;
+
+		names = palloc(sizeof(char *) * list_length(te->cols));
+
+		foreach(col, te->cols)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc = makeNode(TableExprColumn);
+			Oid			typid;
+			int32		typmod;
+			int			j;
+
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			if (!rawc->for_ordinality)
+			{
+				typenameTypeIdAndMod(NULL, rawc->typeName, &typid, &typmod);
+
+				if (rawc->path_expr)
+					newc->path_expr = coerce_to_specific_type(pstate,
+							   transformExprRecurse(pstate, rawc->path_expr),
+															  TEXTOID,
+															  "XMLTABLE");
+				if (rawc->default_expr)
+					newc->default_expr = coerce_to_specific_type_typmod(pstate,
+							transformExprRecurse(pstate, rawc->default_expr),
+															   typid, typmod,
+																 "XMLTABLE");
+			}
+			else
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one column FOR ORDINALITY is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+
+				typid = INT4OID;
+				typmod = -1;
+			}
+
+			newc->typid = typid;
+			newc->typmod = typmod;
+
+			newte->cols = lappend(newte->cols, newc);
+
+			names[i] = rawc->colname;
+
+			/* the name should be unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(names[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			i++;
+		}
+
+		pfree(names);
+	}
+
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		bool		found_default_namespace = false;
+		int			nnames = 0;
+
+		names = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr *na = (NamedArgExpr *) n;
+				int			i;
+
+				for (i = 0; i < nnames; i++)
+					if (strcmp(names[i], na->name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+							errmsg("the namespace name \"%s\" is not unique",
+								   na->name),
+								 parser_errposition(pstate, na->location)));
+				names[nnames++] = na->name;
+
+				na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+														   TEXTOID,
+														   "XMLTABLE");
+			}
+			else
+			{
+				/* default ns specification (without name) must by only one */
+				if (found_default_namespace)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one default namespace is allowed"),
+							 parser_errposition(pstate, exprLocation(n))));
+				found_default_namespace = true;
+				n = coerce_to_specific_type(pstate,
+											transformExprRecurse(pstate, n),
+											TEXTOID,
+											"XMLTABLE");
+			}
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(names);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
+/*
  * 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 b7b82bf..7ce209d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1837,6 +1837,9 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..3930452 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8280,6 +8281,100 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_path, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->cols != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->path_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->path_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index b144920..d80f029 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,28 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+static void *XmlTableCreateContext(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparms,
+					  MemoryContext mcxt);
+static void XmlTableSetContent(void *tcontext, Datum value);
+static void XmlTableAddNS(void *tcontext, char *name, char *uri);
+static void XmlTableSetRowPath(void *tcontext, char *path);
+static void XmlTableSetColumnPath(void *tcontext, char *path, int colnum);
+static bool XmlTableFetchRow(void *tcontext);
+static Datum XmlTableGetValue(void *tcontext, int colnum, bool *isnull);
+static void XmlTableDestroyContext(void *tcontext);
+
+const TableExprBuilder XmlTableBuilder = {
+	XmlTableCreateContext,
+	XmlTableSetContent,
+	XmlTableAddNS,
+	XmlTableSetRowPath,
+	XmlTableSetColumnPath,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyContext
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4071,3 +4094,845 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * We need to work with XPath expression tokens. When expression
+ * starting or finishing with nodenames, then we can use prefix
+ * and suffix. When default namespace is defined, then we should to
+ * enhance any nodename and attribute without namespace by default
+ * namespace. The procession of XPath expression is linear.
+ */
+
+typedef enum
+{
+	XPATH_TOKEN_NONE,
+	XPATH_TOKEN_NAME,
+	XPATH_TOKEN_STRING,
+	XPATH_TOKEN_NUMBER,
+	XPATH_TOKEN_OTHER
+}	XPathTokenType;
+
+typedef struct TokenInfo
+{
+	XPathTokenType ttype;
+	char	   *start;
+	int			length;
+}	XPathTokenInfo;
+
+#define TOKEN_STACK_SIZE		10
+
+typedef struct ParserData
+{
+	char	   *str;
+	char	   *cur;
+	XPathTokenInfo stack[TOKEN_STACK_SIZE];
+	int			stack_length;
+}	XPathParserData;
+
+#define NODENAME_FIRSTCHAR(c)	 ((c) == '_' || (c) == '-' || \
+								 ((c) >= 'A' && (c) <= 'Z') || \
+								 ((c) >= 'a' && (c) <= 'z') || \
+								 ((c) >= '0' && (c) <= '9'))
+
+#define IS_NODENAME_CHAR(c)		(NODENAME_FIRSTCHAR(c) || (c) == '.')
+
+/*
+ * Returns next char after last char of token
+ */
+static char *
+getXPathToken(char *str, XPathTokenInfo * ti)
+{
+	/* skip initial spaces */
+	while (*str == ' ')
+		str++;
+
+	if (*str != '\0')
+	{
+		char		c = *str;
+
+		ti->start = str++;
+
+		if (c >= '0' && c <= '9')
+		{
+			while (*str >= '0' && *str <= '9')
+				str++;
+			if (*str == '.')
+			{
+				str++;
+				while (*str >= '0' && *str <= '9')
+					str++;
+			}
+			ti->ttype = XPATH_TOKEN_NUMBER;
+		}
+		else if (NODENAME_FIRSTCHAR(c))
+		{
+			while (IS_NODENAME_CHAR(*str))
+				str++;
+
+			ti->ttype = XPATH_TOKEN_NAME;
+		}
+		else if (c == '"')
+		{
+			while (*str != '\0')
+				if (*str++ == '"')
+					break;
+
+			ti->ttype = XPATH_TOKEN_STRING;
+		}
+		else
+			ti->ttype = XPATH_TOKEN_OTHER;
+
+		ti->length = str - ti->start;
+	}
+	else
+	{
+		ti->start = NULL;
+		ti->length = 0;
+
+		ti->ttype = XPATH_TOKEN_NONE;
+	}
+
+	return str;
+}
+
+/*
+ * reset XPath parser stack
+ */
+static void
+initXPathParser(XPathParserData * parser, char *str)
+{
+	parser->str = str;
+	parser->cur = str;
+	parser->stack_length = 0;
+}
+
+/*
+ * Returns token from stack or read token
+ */
+static void
+nextXPathToken(XPathParserData * parser, XPathTokenInfo * ti)
+{
+	if (parser->stack_length > 0)
+		memcpy(ti, &parser->stack[--parser->stack_length],
+			   sizeof(XPathTokenInfo));
+	else
+		parser->cur = getXPathToken(parser->cur, ti);
+}
+
+/*
+ * Push token to stack
+ */
+static void
+pushXPathToken(XPathParserData * parser, XPathTokenInfo * ti)
+{
+	if (parser->stack_length == TOKEN_STACK_SIZE)
+		elog(ERROR, "internal error");
+	memcpy(&parser->stack[parser->stack_length++], ti,
+		   sizeof(XPathTokenInfo));
+}
+
+/*
+ * Write token to output string
+ */
+static void
+writeXPathToken(StringInfo str, XPathTokenInfo * ti)
+{
+	Assert(ti->ttype != XPATH_TOKEN_NONE);
+
+	if (ti->ttype != XPATH_TOKEN_OTHER)
+		appendBinaryStringInfo(str, ti->start, ti->length);
+	else
+		appendStringInfoChar(str, *ti->start);
+}
+
+/*
+ * Working horse for XPath transformation. When XPath starting by node name,
+ * then prefix have to be applied. When XPath ending by node name, then
+ * suffix will be used. Any unqualified node name should be qualified by
+ * default namespace. inside_predicate is true, when _transformXPath
+ * is recursivly called because the predicate expression was found.
+ */
+static void
+_transformXPath(StringInfo str, XPathParserData * parser,
+				bool inside_predicate,
+				char *prefix, char *suffix, char *def_namespace_name)
+{
+	XPathTokenInfo t1,
+				t2;
+	bool		is_first_token = true;
+	bool		last_token_is_name = false;
+
+	nextXPathToken(parser, &t1);
+
+	while (t1.ttype != XPATH_TOKEN_NONE)
+	{
+		switch (t1.ttype)
+		{
+			case XPATH_TOKEN_NUMBER:
+			case XPATH_TOKEN_STRING:
+				last_token_is_name = false;
+				is_first_token = false;
+				writeXPathToken(str, &t1);
+				nextXPathToken(parser, &t1);
+				break;
+
+			case XPATH_TOKEN_NAME:
+				{
+					bool		is_qual_name = false;
+
+					/* inside predicate ignore keywords "and" "or" */
+					if (inside_predicate)
+					{
+						if ((strncmp(t1.start, "and", 3) == 0 && t1.length == 3) ||
+						 (strncmp(t1.start, "or", 2) == 0 && t1.length == 2))
+						{
+							writeXPathToken(str, &t1);
+							nextXPathToken(parser, &t1);
+							break;
+						}
+					}
+
+					last_token_is_name = true;
+					nextXPathToken(parser, &t2);
+					if (t2.ttype == XPATH_TOKEN_OTHER)
+					{
+						if (*t2.start == '(')
+							last_token_is_name = false;
+						else if (*t2.start == ':')
+							is_qual_name = true;
+					}
+
+					if (is_first_token && last_token_is_name && prefix != NULL)
+						appendStringInfoString(str, prefix);
+
+					if (last_token_is_name && !is_qual_name && def_namespace_name != NULL)
+						appendStringInfo(str, "%s:", def_namespace_name);
+
+					writeXPathToken(str, &t1);
+					is_first_token = false;
+
+					if (is_qual_name)
+					{
+						writeXPathToken(str, &t2);
+						nextXPathToken(parser, &t1);
+						if (t1.ttype == XPATH_TOKEN_NAME)
+							writeXPathToken(str, &t1);
+						else
+							pushXPathToken(parser, &t1);
+					}
+					else
+						pushXPathToken(parser, &t2);
+
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_OTHER:
+				{
+					char		c = *t1.start;
+
+					is_first_token = false;
+
+					writeXPathToken(str, &t1);
+
+					if (c == '[')
+						_transformXPath(str, parser, true, NULL, NULL, def_namespace_name);
+					else
+					{
+						last_token_is_name = false;
+
+						if (c == ']' && inside_predicate)
+							return;
+
+						else if (c == '@')
+						{
+							nextXPathToken(parser, &t1);
+							if (t1.ttype == XPATH_TOKEN_NAME)
+							{
+								bool		is_qual_name = false;
+
+								nextXPathToken(parser, &t2);
+								if (t2.ttype == XPATH_TOKEN_OTHER && *t2.start == ':')
+									is_qual_name = true;
+
+								if (!is_qual_name && def_namespace_name != NULL)
+									appendStringInfo(str, "%s:", def_namespace_name);
+
+								writeXPathToken(str, &t1);
+								if (is_qual_name)
+								{
+									writeXPathToken(str, &t2);
+									nextXPathToken(parser, &t1);
+									if (t1.ttype == XPATH_TOKEN_NAME)
+										writeXPathToken(str, &t1);
+									else
+										pushXPathToken(parser, &t1);
+								}
+								else
+									pushXPathToken(parser, &t2);
+							}
+							else
+								pushXPathToken(parser, &t1);
+						}
+					}
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_NONE:
+				elog(ERROR, "should not be here");
+		}
+	}
+
+	if (last_token_is_name && suffix != NULL)
+		appendStringInfoString(str, suffix);
+}
+
+static void
+transformXPath(StringInfo str, char *xpath,
+			   char *prefix, char *suffix, char *def_namespace_name)
+{
+	XPathParserData parser;
+
+	initStringInfo(str);
+	initXPathParser(&parser, xpath);
+	_transformXPath(str, &parser, false, prefix, suffix, def_namespace_name);
+}
+
+/*
+ * Functions for XmlTableBuilder
+ *
+ */
+typedef struct XmlTableContext
+{
+	TupleDesc	tupdesc;
+	MemoryContext per_rowset_memory;
+	char	   *def_namespace_name;
+	FmgrInfo   *in_functions;
+	Oid		   *typioparams;
+	long int	rc;
+	PgXmlErrorContext *xmlerrcxt;
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr	doc;
+	xmlXPathContextPtr xpathcxt;
+	xmlXPathCompExprPtr xpathcomp;
+	xmlXPathObjectPtr xpathobj;
+	xmlXPathCompExprPtr *xpathscomp;
+}	XmlTableContext;
+
+/*
+ * Convert XML node to cstring (dump subtree in case of element,
+ * return value otherwise)
+ */
+static char *
+xml_xmlnodetostr(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt)
+{
+	char	   *result;
+
+	if (cur->type == XML_ELEMENT_NODE)
+	{
+		xmlBufferPtr buf;
+		xmlNodePtr	cur_copy;
+
+		buf = xmlBufferCreate();
+
+		/*
+		 * The result of xmlNodeDump() won't contain namespace definitions
+		 * from parent nodes, but xmlCopyNode() duplicates a node along with
+		 * its required namespace definitions.
+		 */
+		cur_copy = xmlCopyNode(cur, 1);
+
+		if (cur_copy == NULL)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not copy node");
+
+		PG_TRY();
+		{
+			xmlNodeDump(buf, NULL, cur_copy, 0, 1);
+			result = pstrdup((const char *) xmlBufferContent(buf));
+		}
+		PG_CATCH();
+		{
+			xmlFreeNode(cur_copy);
+			xmlBufferFree(buf);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+		xmlFreeNode(cur_copy);
+		xmlBufferFree(buf);
+	}
+	else
+	{
+		xmlChar    *str;
+
+		str = xmlXPathCastNodeToString(cur);
+		PG_TRY();
+		{
+			/* Here we rely on XML having the same representation as TEXT */
+			char	   *escaped = escape_xml((char *) str);
+
+			result = escaped;
+		}
+		PG_CATCH();
+		{
+			xmlFree(str);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+		xmlFree(str);
+	}
+
+	return result;
+}
+
+/*
+ * Secure cstring for usage in libxml2
+ */
+static xmlChar *
+to_xmlstr(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+#endif
+
+/*
+ * Create XmlTableContext for XmlTable builder. Initialize XML parser.
+ */
+static void *
+XmlTableCreateContext(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparams,
+					  MemoryContext per_rowset_memory)
+{
+#ifdef USE_LIBXML
+	MemoryContext oldcxt;
+	XmlTableContext *result = NULL;
+	PgXmlErrorContext *xmlerrcxt = NULL;
+	volatile xmlParserCtxtPtr ctxt = NULL;
+
+	oldcxt = MemoryContextSwitchTo(per_rowset_memory);
+
+	result = palloc0(sizeof(struct XmlTableContext));
+	result->tupdesc = tupdesc;
+	result->per_rowset_memory = per_rowset_memory;
+	result->in_functions = in_functions;
+	result->typioparams = typioparams;
+	result->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * tupdesc->natts);
+
+	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+	PG_TRY();
+	{
+		xmlInitParser();
+
+		ctxt = xmlNewParserCtxt();
+		if (ctxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate parser context");
+	}
+	PG_CATCH();
+	{
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	result->xmlerrcxt = xmlerrcxt;
+	result->ctxt = ctxt;
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(void *tcontext, Datum value)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	xmltype    *xmlval = DatumGetXmlP(value);
+	xmlChar    *xstr;
+	int			length;
+	volatile xmlDocPtr doc = NULL;
+	volatile xmlXPathContextPtr xpathcxt = NULL;
+	MemoryContext oldcxt;
+
+	length = VARSIZE(xmlval) - VARHDRSZ;
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+
+	xstr = to_xmlstr(VARDATA(xmlval), length);
+
+	PG_TRY();
+	{
+		doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL, 0);
+		if (doc == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+						"could not parse XML document");
+		xpathcxt = xmlXPathNewContext(doc);
+		if (xpathcxt == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate XPath context");
+		xpathcxt->node = xmlDocGetRootElement(doc);
+		if (xpathcxt->node == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not find root XML element");
+	}
+	PG_CATCH();
+	{
+		if (xpathcxt != NULL)
+			xmlXPathFreeContext(xpathcxt);
+		if (doc != NULL)
+			xmlFreeDoc(doc);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xtCxt->doc = doc;
+	xtCxt->xpathcxt = xpathcxt;
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+#define DEFAULT_NAMESPACE_NAME		"pgdefnamespace"
+
+/*
+ * XmlTableAddNS - add namespace. use fake name when the name
+ * is null, and store this name as default namespace.
+ */
+static void
+XmlTableAddNS(void *tcontext, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	if (name == NULL)
+	{
+		Assert(xtCxt->def_namespace_name == NULL);
+
+		name = DEFAULT_NAMESPACE_NAME;
+		xtCxt->def_namespace_name = name;
+	}
+
+	if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+						   to_xmlstr(name, strlen(name)),
+						   to_xmlstr(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowPath
+ */
+static void
+XmlTableSetRowPath(void *tcontext, char *path)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	xmlChar    *xstr;
+	MemoryContext oldcxt;
+	StringInfoData str;
+
+	if (path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("row path filter must not be empty string")));
+
+	transformXPath(&str, path, NULL, NULL, xtCxt->def_namespace_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+	xstr = to_xmlstr(str.data, str.len);
+
+	xtCxt->xpathcomp = xmlXPathCompile(xstr);
+	if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetColumnPath
+ */
+static void
+XmlTableSetColumnPath(void *tcontext, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	MemoryContext oldcxt;
+	StringInfoData str;
+	xmlChar    *xstr;
+	Oid			typid;
+
+	if (path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("column path filter must not be empty string")));
+
+	typid = xtCxt->tupdesc->attrs[colnum]->atttypid;
+
+	transformXPath(&str, path,
+				   "./", typid != XMLOID ? "/text()" : NULL,
+				   xtCxt->def_namespace_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+	xstr = to_xmlstr(str.data, str.len);
+
+	xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+	if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableFetchRow - returns true when is possible to read
+ * values from XML document.
+ *
+ * First call evaluates row path over content.
+ * Next step and at other calls increment row counter and
+ * compare it with number values in result set.
+ */
+static bool
+XmlTableFetchRow(void *tcontext)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		xtCxt->rc = 0;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+}
+
+/*
+ * Apply column path on current node and returns result.
+ * Allows multivalue when expected type is XML.
+ * When COLUMNS part was not entered, returns current node.
+ */
+static Datum
+XmlTableGetValue(void *tcontext, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+	if (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = NULL;
+
+		/* fast leaving */
+		if (cur->type != XML_ELEMENT_NODE)
+			elog(ERROR, "unexpected xmlNode type");
+
+		PG_TRY();
+		{
+			/* Set current node as entry point for XPath evaluation */
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+			/* Evaluate column path */
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+			if (column_xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+				xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+							"could not create XPath object");
+
+			if (column_xpathobj->type == XPATH_NODESET)
+			{
+				int			count;
+
+				if (column_xpathobj->nodesetval != NULL)
+					count = column_xpathobj->nodesetval->nodeNr;
+				else
+					count = 0;
+
+				if (count > 0)
+				{
+					if (count == 1)
+					{
+						/* 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 (xtCxt->tupdesc->attrs[colnum]->atttypid != XMLOID)
+							ereport(ERROR,
+									(errcode(ERRCODE_CARDINALITY_VIOLATION),
+									 errmsg("more than one value returned by column XPath expression")));
+
+						/* Concate serialized values */
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+												   xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														  xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[colnum],
+											   cstr,
+											   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[colnum],
+										 (char *) column_xpathobj->stringval,
+										   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+				*isnull = false;
+			}
+			else
+				elog(ERROR, "unexpected XPath object type");
+
+			xmlXPathFreeObject(column_xpathobj);
+			column_xpathobj = NULL;
+		}
+		PG_CATCH();
+		{
+			if (column_xpathobj != NULL)
+				xmlXPathFreeObject(column_xpathobj);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+	else
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+		result = InputFunctionCall(&xtCxt->in_functions[0],
+								   cstr,
+								   xtCxt->typioparams[0],
+								   -1); /* target type is XML always */
+		*isnull = false;
+	}
+
+	if (cstr != NULL)
+		pfree(cstr);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * Release all libxml2 memory and related resources
+ */
+static void
+XmlTableDestroyContext(void *tcontext)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int			i;
+
+		for (i = 0; i < xtCxt->tupdesc->natts; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..ad5d8e2
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/primnodes.h"
+
+/*
+ * This structure holds a collection of function pointers used
+ * for generating content of table-expression functions like
+ * XMLTABLE.
+ *
+ * CreateContext is called before execution and it does query level
+ * initialization. Returns pointer to private TableExprBuilder data
+ * (context). A query context should be active in call time. A param
+ * missing_columns is true, when a user doesn't specify returned
+ * columns.
+ *
+ * AddNs add namespace info when namespaces are is supported.
+ * then passed namespace is default namespace. Namespaces should be
+ * passed before Row/Column Paths setting. When name is NULL, then
+ * related uri is default namespace.
+ *
+ * SetRowPath sets a row generating filter.
+ *
+ * SetColumnPath sets a column generating filter.
+ *
+ * FetchRow ensure loading row raleted data. Returns false, when
+ * document doesn't containt any next data.
+ *
+ * GetValue returns a value related to colnum column.
+ *
+ * DestroyContext - release all memory
+ */
+typedef struct TableExprBuilder
+{
+	void	   *(*CreateContext) (TupleDesc tupdesc,
+									FmgrInfo *in_functions, Oid *typioparams,
+											MemoryContext per_rowset_memory);
+	void		(*SetContent) (void *tcontext, Datum value);
+	void		(*AddNS) (void *tcontext, char *name, char *uri);
+	void		(*SetRowPath) (void *tcontext, char *path);
+	void		(*SetColumnPath) (void *tcontext, char *path, int colnum);
+	bool		(*FetchRow) (void *tcontext);
+	Datum		(*GetValue) (void *tcontext, int colnum, bool *isnull);
+	void		(*DestroyContext) (void *tcontext);
+}	TableExprBuilder;
+
+#endif   /* TABLEEXPR_H */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e28477d..b886e57 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "executor/instrument.h"
+#include "executor/tableexpr.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
@@ -1004,6 +1005,36 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+/* ----------------
+ *		TableExprState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	TupleDesc	tupdesc;		/* cache */
+	int			ncols;			/* number of declared columns */
+	int			for_ordinality_col;		/* number of oridinality column,
+										 * started by 1 */
+	int			rownum;			/* row counter - for ordinality column */
+	ExprState  *row_path_expr;	/* row xpath expression */
+	ExprState  *expr;			/* processed data */
+	ExprState **def_expr;		/* array of expressions for default value */
+	ExprState **col_path_expr;	/* array of expressions for path value */
+	bool	   *not_null;		/* for any column info if NULL is allowed or
+								 * not */
+	Datum	   *values;			/* prealloc buffer */
+	bool	   *nulls;			/* prealloc buffer */
+	FmgrInfo   *in_functions;	/* array of infunction for any column */
+	Oid		   *typioparams;	/* array of typIOParam for any column */
+	const TableExprBuilder *builder;	/* pointers to builder functions */
+	void	   *builderCxt;		/* data for content builder */
+
+	MemoryContext per_rowset_memory;
+}	TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..504fb9d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -453,6 +456,7 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8d3dcf4..94dbf73 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -698,6 +698,25 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - raw column definition in table-expression functions
+ *		like XMLTABLE.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions
+ * and just doesn't make sense here.
+ */
+typedef struct TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;
+	TypeName   *typeName;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+}	TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..3fcf546 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,44 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - holds data for table-expression functions like XMLTABLE
+ *
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Node	   *row_path;		/* row xpath query */
+	Node	   *expr;			/* processed data */
+	List	   *cols;			/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+}	TableExpr;
+
+/*----------
+ * TableExprColumn - a column definition in table-expression functions.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions
+ * and just doesn't make sense here.
+ *
+ * Raw form of this node is TableExprRawCol node - different in
+ * typeName field. After transformation we use typid, typmod.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;
+	Oid			typid;
+	int32		typmod;
+	Oid			collation;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+}	TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..de83895 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -289,6 +290,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -438,10 +440,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..1229b76 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,6 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern const TableExprBuilder XmlTableBuilder;
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..7ccf710 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,321 @@ 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');
+ a  
+----
+ 10
+(1 row)
+
+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)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d702703..05c3101 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,234 @@ 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:  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 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)
+
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5..02918a4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,320 @@ 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');
+ a  
+----
+ 10
+(1 row)
+
+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)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30..4c7f1cb 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,125 @@ 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 './*');
#19Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#15)
Re: patch: function xmltable

2016-09-12 6:28 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

I'll take a closer read-through shortly.

Missing file. You omitted executor/tableexpr.h from the patch, so I
can't compile.

I've expanded and copy-edited the docs. Some of it is guesswork based
on the references you sent and a glance at the code. Please check my
changes carefully. I found a few surprises, like the fact that DEFAULT
isn't a normal literal, it's an xpath expression evaluated at the same
time as the rowexpression.

Updated patch attached as XMLTABLE-v3 includes the docs changes. Note
that it's missing tableexpr.h. For convenient review or to apply to
your working tree I also attach a diff of just my docs changes as
proposed-docs-changes.diff.

Docs:

- Can you send the sample data used to generate the example output?
I'd like to include at least a cut down part of it in the docs to make
it clear how the input correlates with output, and preferably put the
whole thing in an appendix.

it is in regress tests.

- How does it decide what subset of the document to iterate over?
That's presumably rowexpr, which is xpath in postgresql? (I added this
to docs).

- xmlnamespaces clause in docs needs an example for a non-default
namespace.

- What effect does xmlnamespaces clause have? Does supplying it allow
you to reference qualified names in xpath? What happens if you don't
specify it for a document that has namespaces or don't define all the
namespaces? What if you reference an undefined namespace in xpath?
What about if an undefined namespace isn't referenced by xpath, but is
inside a node selected by an xpath expression?

All this is under libxml2 control - when you use undefined namespace, then
libxml2 raises a error. The namespaces in document and in XPath queries are
absolutely independent - the relation is a URI. When you use bad URI
(referenced by name), then the result will be empty set. When you use
undefined name, then you will get a error.

- What are the rules for converting the matched XML node into a value?
If the matched node is not a simple text node or lacks a text node as
its single child, what happens?

This process is described and controlled by "XML SQL mapping". The Postgres
has minimalistic implementation without possibility of external control and
without schema support. The my implementation is simple. When user doesn't
specify result target like explicit using of text() function, then the
text() function is used implicitly when target type is not XML. Then I dump
result to string and I enforce related input functions for target types.

- What happens if the matched has multiple text node children? This
can happen if, for example, you have something like

<matchedNode>
some text <!-- comment splits up text node --> other text
</matchedNode>

depends on target type - it is allowed in XML, and it is disallowed for
other types. I though about support of a arrays - but the patch will be
much more complex - there can be recursion - so I disallowed it. When the
user have to solve this issue, then he can use nested XMLTABLE functions
and nested function is working with XML type.

Just for record - This issue is solved in JSON_TABLE functions - it allows
nested PATHS. But XMLTABLE doesn't allow it.

- Is there a way to get an attribute as a value? If so, an example
should show this because it's going to be a common need. Presumably
you want node/@attrname ?

you can use reference to current node "." - so "./@attname" should to work
- a example is in regress tests

- What happens if COLUMNS is not specified at all? It looks like it
returns a single column result set with the matched entries as 'xml'
type, so added to docs, please verify.

sure, that is it

- PASSING clause isn't really defined. You can specify one PASSING
entry as a literal/colref/expression, and it's the argument xml
document, right? The external docs you referred to say that PASSING
may have a BY VALUE keyword, alias its argument with AS, and may have
expressions, e.g.

PASSING BY VALUE '<x/>' AS a, '<y/>' AS b

Neither aliases nor multiple entries are supported by the code or
grammar. Should this be documented as a restriction? Do you know if
that's an extension by the other implementation or if it's SQL/XML
standard? (I've drafted a docs update to cover this in the updated
patch).

The ANSI allows to pass more documents - and then do complex queries with
XQuery. Passing more than one document has not sense in libxml2 based
implementation, so I didn't supported it. The referenced names can be
implemented later - but it needs to changes in XPATH function too.

- What does BY REF mean? Should this just be mentioned with a "see
xmlexists(...)" since it seems to be compatibility noise? Is there a
corresponding BY VALUE or similar?

When the XML document is stored as serialized DOM, then by ref means link
on this DOM. It has not sense in Postgres - because we store XML documents
by value only.

- The parser definitions re-use xmlexists_argument . I don't mind
that, but it's worth noting here in case others do.

It is one clause - see SQL/XML doc PASSING <XML table argument passing
mechanism>

- Why do the expression arguments take c_expr (anything allowed in
a_expr or b_expr), not b_expr (restricted expression) ?

I don't know - I expect the problems with parser - because PASSING is
restricted keyword in ANSI/SQL and unreserved keyword in Postgres.

- Column definitions are underdocumented. The grammar says they can be
NOT NULL, for example, but I don't see that in any of the references
you mailed me nor in the docs. What behaviour is expected for a NOT
NULL column? I've documented my best guess (not checked closely
against the code, please verify).

yes - some other databases allows it - I am thinking so it is useful.

-

Test suggestions:

- Coverage of multiple text() node children of an element, where split
up by comment or similar

- Coverage of xpath that matches a node with child element nodes

I'll do it.

Show quoted text

More to come. Please review my docs changes in the mean time. I'm
spending a lot more time on this than I expected so I might have to
get onto other things for a while too.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#20Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#16)
Re: patch: function xmltable

2016-09-12 6:36 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

On 12 September 2016 at 12:28, Craig Ringer <craig@2ndquadrant.com> wrote:

I'll take a closer read-through shortly.

DEFAULT
isn't a normal literal, it's an xpath expression evaluated at the same
time as the rowexpression.

Sorry for the spam, but turns out that's not the case as implemented
here. The docs you referenced say it should be an xpath expression,
but the implementation here is of a literal value, and examples
elsewhere on the Internet show a literal value. Unclear if the
referenced docs are wrong or what and I don't have anything to test
with.

Feel free to fix/trim the DEFAULT related changes in above docs patch as
needed.

Also, tests/docs should probably cover what happens when PATH matches
more than one element, i.e. produces a list of more than one match.

It is there for case, when this is allowed. When you change the target
type to any non XML type, then a error is raised.

I didn't write a negative test cases until the text of messages will be
final (or checked by native speaker).

Regards

Pavel

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#21Craig Ringer
craig@2ndquadrant.com
In reply to: Pavel Stehule (#18)
Re: patch: function xmltable

On 12 September 2016 at 13:07, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Out of interest, should the syntax allow room for future expansion to
permit reading from file rather than just string literal / column
reference? It'd be ideal to avoid reading big documents wholly into
memory when using INSERT INTO ... SELECT XMLTABLE (...) . I don't
suggest adding that to this patch, just making sure adding it later
would not cause problems.

this is little bit different question - it is server side function, so first
question is - how to push usually client side content to server? Next
question is how to get this content to a executor. Now only COPY statement
is able to do.

Probably start with support for server-side files. When people are
dealing with really big files they'll be more willing to copy files to
the server or bind them into the server file system over the network.

The v3 protocol doesn't really allow any way for client-to-server
streaming during a query, I think that's hopeless until we have a
protocol bump.

updated patch attached - with your documentation.

Will take a look and a better read of the code. Likely tomorrow, I've
got work to do as well.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#20)
Re: patch: function xmltable

Hi

There is some opened questions - the standard (and some other databases)
requires entering XPath expression as string literal.

I am thinking so it is too strong not necessary limit - (it enforces
dynamic query in more cases), so I allowed the expressions there.

Another questions is when these expressions should be evaluated. There are
two possibilities - once per query, once per input row. I selected "once
per input row mode" - it is simpler to implement it, and it is consistent
with other "similar" generators - see the behave and related discussion to
"array_to_string" and evaluation of separator argument. The switch to "once
per query" should not be hard - but it can be strange for users, because
some his volatile expression should be stable.

Regards

Pavel

#23Craig Ringer
craig@2ndquadrant.com
In reply to: Pavel Stehule (#22)
Re: patch: function xmltable

On 12 September 2016 at 14:02, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Hi

There is some opened questions - the standard (and some other databases)
requires entering XPath expression as string literal.

I am thinking so it is too strong not necessary limit - (it enforces dynamic
query in more cases), so I allowed the expressions there.

I agree. There's no reason not to permit expressions there, and there
are many other places where we have similar extensions.

Another questions is when these expressions should be evaluated. There are
two possibilities - once per query, once per input row. I selected "once per
input row mode" - it is simpler to implement it, and it is consistent with
other "similar" generators - see the behave and related discussion to
"array_to_string" and evaluation of separator argument. The switch to "once
per query" should not be hard - but it can be strange for users, because
some his volatile expression should be stable.

I would've expected once per query. There's no way the expressions can
reference the row data, so there's no reason to evaluate them each
time.

The only use case I see for evaluating them each time is - maybe -
DEFAULT. Where maybe there's a use for nextval() or other volatile
functions. But honestly, I think that's better done explicitly in a
post-pass, i.e.

select uuid_generate_v4(), x.*
from (
xmltable(.....) x
);

in cases where that's what the user actually wants.

There's no other case I can think of where expressions as arguments to
set-returning functions are evaluated once per output row.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Craig Ringer
craig@2ndquadrant.com
In reply to: Pavel Stehule (#19)
Re: patch: function xmltable

On 12 September 2016 at 13:07, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Out of interest, should the syntax allow room for future expansion to
permit reading from file rather than just string literal / column
reference? It'd be ideal to avoid reading big documents wholly into
memory when using INSERT INTO ... SELECT XMLTABLE (...) . I don't
suggest adding that to this patch, just making sure adding it later
would not cause problems.

this is little bit different question - it is server side function, so first
question is - how to push usually client side content to server? Next
question is how to get this content to a executor. Now only COPY statement
is able to do.

Probably start with support for server-side files. When people are
dealing with really big files they'll be more willing to copy files to
the server or bind them into the server file system over the network.

The v3 protocol doesn't really allow any way for client-to-server
streaming during a query, I think that's hopeless until we have a
protocol bump.

updated patch attached - with your documentation.

- Can you send the sample data used to generate the example output?
I'd like to include at least a cut down part of it in the docs to make
it clear how the input correlates with output, and preferably put the
whole thing in an appendix.

it is in regress tests.

Makes sense.

It's not that verbose (for XML) and I wonder if it's just worth
including it in-line in the docs along with the XMLTABLE example. It'd
be much easier to understand how XMLTABLE works and what it does then.

- What effect does xmlnamespaces clause have? Does supplying it allow
you to reference qualified names in xpath? What happens if you don't
specify it for a document that has namespaces or don't define all the
namespaces? What if you reference an undefined namespace in xpath?
What about if an undefined namespace isn't referenced by xpath, but is
inside a node selected by an xpath expression?

All this is under libxml2 control - when you use undefined namespace, then
libxml2 raises a error. The namespaces in document and in XPath queries are
absolutely independent - the relation is a URI. When you use bad URI
(referenced by name), then the result will be empty set. When you use
undefined name, then you will get a error.

OK, makes sense.

- What are the rules for converting the matched XML node into a value?
If the matched node is not a simple text node or lacks a text node as
its single child, what happens?

This process is described and controlled by "XML SQL mapping". The Postgres
has minimalistic implementation without possibility of external control and
without schema support. The my implementation is simple. When user doesn't
specify result target like explicit using of text() function, then the
text() function is used implicitly when target type is not XML. Then I dump
result to string and I enforce related input functions for target types.

OK, so a subset of the full spec functionality is provided because of
limitations in Pg and libxml2. Makes sense.

My only big concern here is that use of text() is a common mistake in
XSLT, and I think the same thing will happen here. Users expect
comments to be ignored, but in fact a comment inserts a comment node
into the XML DOM, so a comment between two pieces of text produces a
tree of

element
text()
comment()
text()

If you match element/text(), you get a 2-node result and will presumably ERROR.

There is no good way to tell this from

element
text()
element
text()

when you use an xpath expression like element/text() . So you can't
safely solve it just by concatenating all resulting text() nodes
without surprising behaviour.

- What happens if the matched has multiple text node children? This
can happen if, for example, you have something like

<matchedNode>
some text <!-- comment splits up text node --> other text
</matchedNode>

depends on target type - it is allowed in XML, and it is disallowed for
other types. I though about support of a arrays - but the patch will be much
more complex - there can be recursion - so I disallowed it. When the user
have to solve this issue, then he can use nested XMLTABLE functions and
nested function is working with XML type.

I don't really understand how that'd work.

Do you know how other implementations handle this?

I think users are going to be VERY surprised when comments in text
break their XML.

- Is there a way to get an attribute as a value? If so, an example
should show this because it's going to be a common need. Presumably
you want node/@attrname ?

you can use reference to current node "." - so "./@attname" should to work -
a example is in regress tests

cool, just needs mention in docs then.

- PASSING clause isn't really defined. You can specify one PASSING
entry as a literal/colref/expression, and it's the argument xml
document, right? The external docs you referred to say that PASSING
may have a BY VALUE keyword, alias its argument with AS, and may have
expressions, e.g.

PASSING BY VALUE '<x/>' AS a, '<y/>' AS b

Neither aliases nor multiple entries are supported by the code or
grammar. Should this be documented as a restriction?

The ANSI allows to pass more documents - and then do complex queries with
XQuery. Passing more than one document has not sense in libxml2 based
implementation, so I didn't supported it. The referenced names can be
implemented later - but it needs to changes in XPATH function too.

OK, so my docs addition that just says they're not supported should be fine.

- What does BY REF mean? Should this just be mentioned with a "see
xmlexists(...)" since it seems to be compatibility noise? Is there a
corresponding BY VALUE or similar?

When the XML document is stored as serialized DOM, then by ref means link on
this DOM. It has not sense in Postgres - because we store XML documents by
value only.

Right. And since there's already precent for xmlexists there's no
point worrying about whether we lose opportunities to implement it
later.

- Why do the expression arguments take c_expr (anything allowed in
a_expr or b_expr), not b_expr (restricted expression) ?

I don't know - I expect the problems with parser - because PASSING is
restricted keyword in ANSI/SQL and unreserved keyword in Postgres.

I mean for the rowpath argument, not the parts within
xmlexists_argument. If the rowpath doesn't need to be c_expr
presumably it should be a b_expr or even, if it doesn't cause parsing
ambiguities, an a_expr ? There doesn't seem to be the same issue here
as we have with BETWEEN etc.

- Column definitions are underdocumented. The grammar says they can be
NOT NULL, for example, but I don't see that in any of the references
you mailed me nor in the docs. What behaviour is expected for a NOT
NULL column? I've documented my best guess (not checked closely
against the code, please verify).

yes - some other databases allows it - I am thinking so it is useful.

Sure. Sounds like my docs additions are probably right then, except
for incorrect description of DEFAULT.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#24)
1 attachment(s)
Re: patch: function xmltable

2016-09-12 8:46 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

On 12 September 2016 at 13:07, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Out of interest, should the syntax allow room for future expansion to
permit reading from file rather than just string literal / column
reference? It'd be ideal to avoid reading big documents wholly into
memory when using INSERT INTO ... SELECT XMLTABLE (...) . I don't
suggest adding that to this patch, just making sure adding it later
would not cause problems.

this is little bit different question - it is server side function, so

first

question is - how to push usually client side content to server? Next
question is how to get this content to a executor. Now only COPY

statement

is able to do.

Probably start with support for server-side files. When people are
dealing with really big files they'll be more willing to copy files to
the server or bind them into the server file system over the network.

The v3 protocol doesn't really allow any way for client-to-server
streaming during a query, I think that's hopeless until we have a
protocol bump.

updated patch attached - with your documentation.

- Can you send the sample data used to generate the example output?
I'd like to include at least a cut down part of it in the docs to make
it clear how the input correlates with output, and preferably put the
whole thing in an appendix.

it is in regress tests.

Makes sense.

It's not that verbose (for XML) and I wonder if it's just worth
including it in-line in the docs along with the XMLTABLE example. It'd
be much easier to understand how XMLTABLE works and what it does then.

- What effect does xmlnamespaces clause have? Does supplying it allow
you to reference qualified names in xpath? What happens if you don't
specify it for a document that has namespaces or don't define all the
namespaces? What if you reference an undefined namespace in xpath?
What about if an undefined namespace isn't referenced by xpath, but is
inside a node selected by an xpath expression?

All this is under libxml2 control - when you use undefined namespace,

then

libxml2 raises a error. The namespaces in document and in XPath queries

are

absolutely independent - the relation is a URI. When you use bad URI
(referenced by name), then the result will be empty set. When you use
undefined name, then you will get a error.

OK, makes sense.

- What are the rules for converting the matched XML node into a value?
If the matched node is not a simple text node or lacks a text node as
its single child, what happens?

This process is described and controlled by "XML SQL mapping". The

Postgres

has minimalistic implementation without possibility of external control

and

without schema support. The my implementation is simple. When user

doesn't

specify result target like explicit using of text() function, then the
text() function is used implicitly when target type is not XML. Then I

dump

result to string and I enforce related input functions for target types.

OK, so a subset of the full spec functionality is provided because of
limitations in Pg and libxml2. Makes sense.

My only big concern here is that use of text() is a common mistake in
XSLT, and I think the same thing will happen here. Users expect
comments to be ignored, but in fact a comment inserts a comment node
into the XML DOM, so a comment between two pieces of text produces a
tree of

element
text()
comment()
text()

If you match element/text(), you get a 2-node result and will presumably
ERROR.

There is no good way to tell this from

element
text()
element
text()

when you use an xpath expression like element/text() . So you can't
safely solve it just by concatenating all resulting text() nodes
without surprising behaviour.

- What happens if the matched has multiple text node children? This
can happen if, for example, you have something like

<matchedNode>
some text <!-- comment splits up text node --> other text
</matchedNode>

I fixed this case - new regress tests added

depends on target type - it is allowed in XML, and it is disallowed for
other types. I though about support of a arrays - but the patch will be

much

more complex - there can be recursion - so I disallowed it. When the user
have to solve this issue, then he can use nested XMLTABLE functions and
nested function is working with XML type.

I don't really understand how that'd work.

Do you know how other implementations handle this?

I think users are going to be VERY surprised when comments in text
break their XML.

- Is there a way to get an attribute as a value? If so, an example
should show this because it's going to be a common need. Presumably
you want node/@attrname ?

you can use reference to current node "." - so "./@attname" should to

work -

a example is in regress tests

cool, just needs mention in docs then.

- PASSING clause isn't really defined. You can specify one PASSING
entry as a literal/colref/expression, and it's the argument xml
document, right? The external docs you referred to say that PASSING
may have a BY VALUE keyword, alias its argument with AS, and may have
expressions, e.g.

PASSING BY VALUE '<x/>' AS a, '<y/>' AS b

Neither aliases nor multiple entries are supported by the code or
grammar. Should this be documented as a restriction?

The ANSI allows to pass more documents - and then do complex queries with
XQuery. Passing more than one document has not sense in libxml2 based
implementation, so I didn't supported it. The referenced names can be
implemented later - but it needs to changes in XPATH function too.

OK, so my docs addition that just says they're not supported should be
fine.

- What does BY REF mean? Should this just be mentioned with a "see
xmlexists(...)" since it seems to be compatibility noise? Is there a
corresponding BY VALUE or similar?

When the XML document is stored as serialized DOM, then by ref means

link on

this DOM. It has not sense in Postgres - because we store XML documents

by

value only.

Right. And since there's already precent for xmlexists there's no
point worrying about whether we lose opportunities to implement it
later.

- Why do the expression arguments take c_expr (anything allowed in
a_expr or b_expr), not b_expr (restricted expression) ?

I don't know - I expect the problems with parser - because PASSING is
restricted keyword in ANSI/SQL and unreserved keyword in Postgres.

I mean for the rowpath argument, not the parts within
xmlexists_argument. If the rowpath doesn't need to be c_expr
presumably it should be a b_expr or even, if it doesn't cause parsing
ambiguities, an a_expr ? There doesn't seem to be the same issue here
as we have with BETWEEN etc.

b_expr enforces shift/reduce conflict :(

- Column definitions are underdocumented. The grammar says they can be
NOT NULL, for example, but I don't see that in any of the references
you mailed me nor in the docs. What behaviour is expected for a NOT
NULL column? I've documented my best guess (not checked closely
against the code, please verify).

yes - some other databases allows it - I am thinking so it is useful.

Sure. Sounds like my docs additions are probably right then, except
for incorrect description of DEFAULT.

I found other opened question - how we can translate empty tag to SQL
value? The Oracle should not to solve this question, but PostgreSQL does.
Some databases returns empty string.

I prefer return a empty string - not null in this case. The reason is
simple - Empty string is some information - and NULL is less information.
When it is necessary I can transform empty string to NULL - different
direction is not unique.

Regards

Pavel

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

xmltable-6.patch.gzapplication/x-gzip; name=xmltable-6.patch.gzDownload
#26Craig Ringer
craig@2ndquadrant.com
In reply to: Pavel Stehule (#25)
Re: patch: function xmltable

On 15 September 2016 at 19:31, Pavel Stehule <pavel.stehule@gmail.com> wrote:

b_expr enforces shift/reduce conflict :(

No problem then. I just thought it'd be worth allowing more if it
worked to do so.

I found other opened question - how we can translate empty tag to SQL value?
The Oracle should not to solve this question, but PostgreSQL does. Some
databases returns empty string.

Oracle doesn't solve the problem? it ERRORs?

I prefer return a empty string - not null in this case.

I agree, and that's consistent with how most XML is interpreted. XSLT
for example considers <x></x> and <x/> to be pretty much the same
thing.

The reason is simple
- Empty string is some information - and NULL is less information. When it
is necessary I can transform empty string to NULL - different direction is
not unique.

Yep, I definitely agree. The only issue is if people want a DEFAULT to
be applied for empty tags. But that's something they can do in a
post-process pass easily enough, since XMLTABLE is callable as a
subquery / WITH expression / etc.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Craig Ringer (#23)
Re: patch: function xmltable

On 9/12/16 1:14 AM, Craig Ringer wrote:

I would've expected once per query. There's no way the expressions can
reference the row data, so there's no reason to evaluate them each
time.

The only use case I see for evaluating them each time is - maybe -
DEFAULT. Where maybe there's a use for nextval() or other volatile
functions. But honestly, I think that's better done explicitly in a
post-pass, i.e.

select uuid_generate_v4(), x.*
from (
xmltable(.....) x
);

in cases where that's what the user actually wants.

There's no other case I can think of where expressions as arguments to
set-returning functions are evaluated once per output row.

The SQL standard appears to show what the behavior ought to be:

<XML table> is equivalent to

LATERAL ( XNDC
SELECT SLI1 AS CN1, SLI2 AS CN2, ..., SLINC AS CNNC FROM XMLITERATE (
XMLQUERY ( XTRP XQAL
RETURNING SEQUENCE BY REF EMPTY ON EMPTY ) )
AS I ( V, N )
) AS CORR DCLP

and SLIj is

CASE WHEN XEj
THEN XMLCAST( XQCj AS DTj CPMj )
ELSE DEFj END

where DEFj is the default expression.

So simplified it is

LATERAL ( SELECT CASE WHEN ... ELSE DEFj END, ... FROM something )

which indicates that the default expression is evaluated for every row.

If we're not sure about all this, it might be worth restricting the
default expressions to stable or immutable expressions for the time being.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#26)
Re: patch: function xmltable

2016-09-16 1:44 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

On 15 September 2016 at 19:31, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

b_expr enforces shift/reduce conflict :(

No problem then. I just thought it'd be worth allowing more if it
worked to do so.

I found other opened question - how we can translate empty tag to SQL

value?

The Oracle should not to solve this question, but PostgreSQL does. Some
databases returns empty string.

Oracle doesn't solve the problem? it ERRORs?

Oracle returns NULL. But there are not any difference between NULL and
empty string

Regards

Pavel

Show quoted text

I prefer return a empty string - not null in this case.

I agree, and that's consistent with how most XML is interpreted. XSLT
for example considers <x></x> and <x/> to be pretty much the same
thing.

The reason is simple
- Empty string is some information - and NULL is less information. When

it

is necessary I can transform empty string to NULL - different direction

is

not unique.

Yep, I definitely agree. The only issue is if people want a DEFAULT to
be applied for empty tags. But that's something they can do in a
post-process pass easily enough, since XMLTABLE is callable as a
subquery / WITH expression / etc.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#29Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#25)
1 attachment(s)
Re: patch: function xmltable

Hi

new update:

* doc is moved to better place - xml processing functions
* few more regress tests
* call forgotten check_srf_call_placement

Regards

Pavel

Attachments:

xmltable-8.patch.gzapplication/x-gzip; name=xmltable-8.patch.gzDownload
�"c�Wxmltable-8.patch�<ks�����_��;7�"Y�+N�(:Um%q�������'��HH��"U>b�M���]<R������*c���b������mmMd�x���v��d��Y�����<X��/n����7��n��#�����}��������z��X����y��y���%��A��x�n�AEi
~g<ND
�dS�l��Ohd_D��(|������g��Ea
��|�u��f��\g���
����Dx���7T�3���@�R�l���'������4�N S����v����V�Z���^��6�nog��������L�7"��(�
hVX���&��Q�����l�v�*Mg���Y���X$�(���4���V,`G�H�E�5��v����-�C,�5M�t�:�XNy|�����W@J}�z����%�L�(�6���D�v����p��P�L�*ILs+�����e_x���Ec�����n&6|#D�WMv�h9��z�A���J0�7�������n�}M�b����I�8]���JzWL b<���vZ�
�����
���i���R�����@�%j���,��Y"����:����<u������6�N4CH��R�0�q����;m����p���rP�]Q'�T$3�Q��,�e	���-��W��_���z�N.����.��j�+s�����_�t��O�;��]��-��C��������T)Cs�Pu�v���P��EA6
�WY�Q%��;�����~:9��_�]��)�z�$���
���Q����yY��*5c5��U
:�iw�A�+@|�e��
���L��r�b�8e�H��$ZIF�@/��Y�� ���
#_�D�he��>���t��L;W>�`����Y�DC1;YS����
���
u�hh�oi���M��R�b����36�	,�d4�O������#���R�9���1����M�|��������J'�/�2����
7DiT,�]&Dx���������~x����F����
�+��;���}:���mx|��}����R����~��%��@��'������g�8x���oh�6�]Db�
����H^��I��B��O+������)p����x5�A��/���|%?��	���E��q�B������F�{��hT�9x�M�f�gQ,�
�����Y��v�z��}�j����z�����3�
�xFU�8
�������r�F/P[����T���z���]�}d5��Xj�NxSg�M=+|Nz������P�l����w���(!.�fb�3	V������l.d?^��i|7����+�\�|$��������b�vXX�����H��7�QP~����e(?���a�~?x4�Y�k����:�"�#��f��V�a��d&<9�����!z|-���K����*|Ux-N��|�+�4�4�~��3X/����Ov�_�����?�u"�]h$_��><}(�b����������F2����b��L��<�(k���jPfT?�XJ���VaT4�8dYx�������?��0��%�>:���D ���rn�4�|�\��G�tA>�������{�5�v��(AL}�/;�1�3}���4� v��,�A�<I��!���[��	)��� ���l����(w?�2S��!���aP��D��n[w��y;���8�6����`��h�r�����n�ww�;mRC��[���K�����qc��zg�$��R�W���E����)�6�tLU�
a8�?�!N��A*����^�/��.��*��I�I�JOY'b�G&c���p��q��MK�x'����z���/$�I6���2HjL�2J�/B�b_ 2	`%B�K�:��HvQN�3���i��V�a]�=L��	23bQ��2�D-������
�P��5����%)����f�9�����N�\reh)�H�_���q(��c'�eJ��`5N��� ����0�`��Q�&*U6�B2rI6�Y���e�^�������X	��sV�*��B��E��dJI0�T4�s��yL���k��R����Xq�U�I�H���p��A�@<�.
QR8�N�� �r$aCJ4�,�:�����q�b�s*.��H3g\������
J#�0m1��x/C�s���.��N>�������b��[�#|	��e���\���XT�/$VZy����Y�?7��Y�h�sz%c�F�jJc�XY�HA�����P�B��JF�6���|	��`�?y@[�D8A��]��[�>�`�AX�-�j�}v�u���LI�	�T��h���-��{�����'��S%$`
U�)��j��
hV#�I�4��b_p�th��l����a
<4�,�GB
�$��)R�X��1V�e��`�������CB���r��n_\1U�����-�NW\;b:��X�
"������g>r�A��	*9�����9�e
�|���z��Ud�t~���C� Le�j;� �Z����tU��*���TN�Q����'��5!zi*�b�U�)O���L� �0�<C���D��$���H���@h9���o�b�)�B1>��P�mCo���V��$��_��kF���@*�b/���0���/&�p�����VG���!N�z9#�O�;�jb5�}2����a����I�"��0%@��,�5:`q`�[m6n���$����H8�����)��Ol�CK,�������H���$SVLL��������H����P�P�P������+bn�����n��P@W�&����Ue���6q�Xj�6Q�4:
&+����,����an�\��:s�I���1���{�Z��i�-f�2~$��"���PA�G�������M�~`�4#�<zQ�0��]���.��	�g���g���J���S:H�\�r��YY�l�iU5�n+���;p��t��*���F��@��(��F��4[1���Q+(n�Rdj���8���zf=�X4�Q	����vvqt|�;9����������Y4�=-�OP��l:B[F������\c�����P��,�����XN$#2���j;)���f�P
�,���*U3V��t�B�'|b����-���(&�Y��������������10-��)+<����L^�+���g��hTU��@�{.*~��xk*U��T�f��9W_��s@L�f��&���i&\���L*��;�d�����+��;��o=�4T^���q��b�7��*��*
A�a�����L�a�Ea�C�����|�DA�>PR{<�D�IPw��Q<�L�^���X�s�Hik��c�����'("kr�*rc�/}���qOOV�����8�����8��%�?I
����8����U�>���L|��U���05�S�lO�^s���9��C��;�&��E�
�D;v7�Yq��]\�~�]}oN��<����!����;t����r�{?�a�����C��:+������=���i_�nQtmR���neI���l�@@�	:����<���=���t ��%h�(R eg��9�b�d�	�=���Us�%���v*��h\��F�������C��;���a�����`i�lg@������x&~u���M>�h��J��"�M�6�����.��W�y;F~3
N�i/�����@�z�<T.��L�N��T����Fs
�QH�cq��:����
xAk����c�QJi�sG������$Gjv��,�JX1n�@��Ms���T��RUQY��V���������^U/z�7C�7W�����T9���
��[
:�Y�W=kW��b���hG���=~���
>p�;{���>�)�L���?����]�����9�TpU���$��8���}@QxY�t�3�D�c�U=���/���K������;������`�+W�B���������V�����d�=n��� ��g�!�h��
�i�/F�dQ�E�\W�]]hk��$8��R�TB�OeXj�SF�)�8.��$w��H_m7���O|x�����Ix}�������X��k�b��9b�#D�a��J[��$0����Q���P!��
����5�,�D?�Ed�er����(
�!f�����uX��%�P�W�j$4�"�S�B�������-��%�/��IH_l�7ww���O��5�o��".�{-���|�
#���o��?�r����G���~������t�q��Q�
I@n/�p�\o�.30�G"���l�@R���5�-�
2L�t�Q��8<"��{:���~�d�{2�G��|���H�������
��^�V�AJwv: sH��-x9f5��[��B�!%�N��b�05�X�luc3h�4����1H�ph]Tu0�2�}�f"���H�S1��;4'��*��Q���`o�CNVU�P@5S�NR_3
�i2+l���6DD�	�Z����kZ�,�4D/�d�s�R�[�Dj���	[l��m����`
�����t$�HN& "�����*W�������������E��,�b��9�j��� %{���yr��4$|�P��!�%�[�Q����@��
oG���_��<f����:��2X���|Y��k5'N����@����u��h�����({�Z�+��	�72���R�Nrp��j!��{���	����p���Vg�����`�.bEj����eMc��=G�o�A"�����B��`��RI�`��*e]�b�yJ�6��!��8��S�|xv�"�!�I��]{''g��������dR�(���6%�)�t�^��(���.W�� �R�ZA&s	����� ��bt��E��e7���p�ax`�P�"�j�����4�$E�U~�����_D7�P����U�����m�%���0�:��C��
G��~�C����f�yF��]~f���U��*CX%����@��*�rd����7�;���<���_�S��q��+���U�5��U�3o�����rN�����$�n�\�&�h���rz�p�G�^#�rB���;�zW �����>>����@�Z��6���
QU�I)���>�&����<�sP?��;nN15C ��f���P0|����	C�"�b;�:�|qCA`�����i��r��y������j�+NW�v����l~P�!��'VE���z���!"��Nn���7)!	�"S��,r�.Q��to�G�Yk4�B�9~��]�7�rx|�O�+��Zl^���M25:����ZIi�`��D^�:@v0D��@����jjh��x@������	n��C�������:J$�:a$�q������z0�@��1��K{���)��>B�~��<�q�E���o��(a	X���9{�9�yM�f�E[��������B�����v�a����-�),���N�qtg�C�z.��t���C&��e�cq L�~z��t�������4],R�fZEB�C��F�DLI�N�l��ZW�8�������!T��_0~S��9�h:?����F,��@�%���l�: I@?�]~p��!�#�SP6��iN���hS�L��1+��/�%kI��(����������/5���#K�J����:�	78�]iD�$�b�`K�/�_�5wvvLI�8���DB�N���j)�((L������L������lre��6��(�`��2�+�R��S�(d�Q���Eo���'�]���v��m�����LOa�7��z*���vL�K�f#o���dA��e/h6����v���-�Ma��g��U���gXc��+��"�EF�C�����?���R$P�F�9��Yil�
[x��:��������X=]��N�����)�
�W�c���
#�"�N-�2�B�"|a
�79"O����[*����$�95m�m���gcn{l��?����������&���Vz��:7��tC�p�S)�|Q�;N�,'�,�*�'�&��o�Mtt�g��1)��c�m �r��U�#n���1�|��W-�r�������B�D;*k�j���=�������#�Egv�m�����2�$�8�Hf����`h;L00\bgw����HjI�n_;C?���T�JRU�T��x��Y��	�U1A�`�!�c����G�p�d��Hi������Ag�	���L���"��;�zPN,sIlE2�F9�BwY2��:�����~��s���6�h����
��\YSPU����5��MY;gC:�2�X����'P�*YZ��C��")E@�qS�U�?�/��F��
�^�[��:���%��������hGL�����}d`���C-K�tj������6��_�0�|c�$��Gd��e�.�9K���������R�X�1��A�M�dQ4l���?�\����Y���_���u�:cB%���n:�u������5���x3���k���>gI��&{p	�E���d�f�'�ia	`l*����2�Pq;}�6r��23���T/7��vY=����"�`��s�u��9��	��z=#��K���R���q�}����(>���I	�4���T�h]���#���maP�d[��(�.���{���v6������D[�!�m��y� �`�-r���9H���a�.z�6O@>�7_zk���j`����y�"M4������Q���Y^73�-��������a�:�@b���g�����|Q)���D��N�E�P���v�F{U2L�b�rI[�����Q������G������6en'&eb:9���z�V9} H�Z�p����H���'��H��w&��;�P��3UX`�X�')������q����$����$'����N�YG"��)�=�1F{��;>��b���#�,^l~
��baV�tx|&_@:Ld��w�=����+5J&Qf�]��=L
-'���0R=e�	h���'a, 1���<jj����V>��p���������/��gc���
z~}��]��i����0�������J�T>9)���o���3$
�j'R��LQ8h�r���~���s�d
D�Pad��)���ql�sk��
�T"=������Z+�E����t!
}q+D��'�~
z8�\P�R��Y�V�#��!��	3	�#_��*��`(��r�D�P��;NL��_aL���1�����r�����+��`�U�G|�H��J[l�k\i������U�����$��-��w�}�^T�B��j
^�k{	�o$�&�c��t��������%��v�SyV�L� #���M��g�c�&�>��*l���@�28���<���?��K� (21
����0n#<8�O^�ak��������CW�aN�D�rKN���[*3f���E�t���b��`o{;���:��N{�p�C��-�<4���T!|E����qi:����x"8����������j����Q�
hS��%hf�Mqk��:��V��?
W>}��$�\�vn���������H�E��c��5N�N�[����^��V�������]e\
W>��2+�N��T���E^:1Z�������8��'����O�L<vh�v$����\��MD[[�gh��j���o[������Ay�OPf!i���q4|/�BB(���K��q�r<�e�Q�j	D&e�-]���T���hN�D���B�1�h[{���\&�
�;�K�S�d4�Fw�������\'���x�����)�SHRo����c�l"qF��*�R8��'�����JGB�Q��QX���xu��S0,NU$�B�h��\��e)���Rl�
�4���z�4��nLn5�?���+��������%�[�Mz���Y����[Y�4����n �jm��|_6�a�vD#��g���I�;�h��.hn���'A9 D>E]�	,aKVu
>�QaO��d�V�2{����[w�c�m>��>������\��W� c������!7��r���wP�2~�
�6����uR�w��4&�]FS�Y#}�:���H
y:`���B�e_a�>���	4���q����~Fq�����������:���c4������N���o����������r���p3���j���}�[��rJ�HJ��z������Y�A'7�BuQ��I�,��2[9�]�$X����4�8]���!����2%�0
�$����Q�����t/`sF5I�b��X�6g�HE���2��w�)���|[�W�r����EM��)l���-2��������v��0��^�x��	h��KT��7�b�,3���\4�[�x���[_�B�}tTI^��0sG�[)�RIF����(h��^��X7��c���#���n�5={��L����u�L�l��I!�<36�F::?���$3��JsF�)|S+!����R3go7������[�l���N��`�)w��Y]�t `��i$@���f-��3X^�C���Fl��x�����u���"n����V��z������rZ�g;R�m����n���-d��^���e����d2:� �^�H�'��t��<@5rP
_Xp����s�xJgq���9�y
���4��R2���`#W�g@h��v��5�m�D]j��|u�}�u��&��Y���$��"Hg-�`g;������/b\��D}hYx����[?���3V�bY�����Q"�	�m�I���/�J�:�l�j~��+b�:�E���:������7WE+\������NQ|���g���P{��YGk��JQ���X��ce�su�!�<(��D��e&N"�#8VG�4�S*���U�]�4�������hc�P�"�x�"�2�����3�#�Ljd�,�������B��"�6R3i�m�,����zkgO�3�����{@Yq?8��U^B��y��pz�7i*��yO���"d�%��i��g��7cK.�K�^&o�����*�'��}�n����� �H>�e�?��i�p���\��a��~��C��{?�R�8����~���w����~k{��_���uQ,]�����]V>���6�t�S���W:����4|����M�
s�]�e �����c"'���	J���v��*v���v���UK�
�(�q���V,�������B��c��S�H>@�.M	N�l1�3%�3;��7�q0�����'��%��4��In���H~-�����$N�9��!A�4�E��G���Aym���yimC�����.����7��3�E�S��x���9���D�p�����59�H�m�O�c�W�o�F��5!���@?+6A��9�[�m;	j�@�>��Tj��:�O"`�D�5���;���Va��T6�Ma����(�J�9�U�?�h
�6E����&h��������]Xn��we.P�tGf�i��'9?��u�=��ttS�Yk��	��~�.� �*M�u�h0dG�[?��C����7��������1�����\��%�W�,�N2tR(c����h���Q��n[;w�z�$F��|:bW��E���\����[�kE�hQX1:�3�Q����		ww�H����0��$�hj�q���zt��$G�|�d������?��l�;��������
��UZ"yA^�<�r��y4;3#������<������i�3��|k���[�u�[�!^�����t�uJ���t�VcTo[q����������`N8NH ���9��^��������G����U��Ef�4A��;�������~�S���d�q3�.�m�Qa,#�D�.�������]�{t��=�2�v�����]�M��Nt����o�X�������y
�Q��l�\���5�4}9*�D&T�����������|(HH�LH*	���Y�0�E6v�2>�f�j"l�O�t�M�����[�����L23�2 Rd:���5�������������?���0��If�������\w�!��;��c�3d�4x�_�)�W,Se?'$���*x��XzU�`�qR>��z�x\����~5�O�xR}�<�J���S�[k�!�R,5�5O}���R~�K?���*�8��I��O�����R��M��U�'�z	������N�	�_��<��K��0�K[Z=-��� ���xQy��&���8�����i�$���	:�P�}
�u�	�*�o���m�V�}�8�'��hPWjU�X��Wz�t��Y����Q�vy]t��mo	���w��"���[����-*������I����uQ+���R�G�Q|}����#2���U�xW:)�,�������B��m�J�=.���O�|N�/������� �z
cQ=+�8`H�3
������`�^�
�{?�b*�9)������)8��a���3Xd�XO<�����F�x��T�����K��j��N�%zqR���Uaz����
$����-�U��T�\*S�V��wP��2~�i�@�*��5P������I�e@��&�����/k��r��w3�0�;"�����_^�O=������(���%\(�TN���� �Y�O����K�x��#}7
���O��-6��O0�u������o��2-~��������9���Z�J	�\��?�0�w������Xg`T�]%��]?!�]���\G�������A_v���5�1Q�l��K�j�z���!A�=/��K��y����,&������W<���X�i���+�+[vX�v�"�%�����-�=�����;��Ms��w:=����������d/!)������/�
c&�K:���I��@��\����G��/0�b��|�!0F;T�w������=�B�<��|�����'��Pf�#A�l��3��$�PKX_*/�.�����]�sP������Z�n=N�}��J�'2�Y����~�����9E�EcR�L2�)��T��qRHs#u�"�4���nE�����A�
�G�	�i!��S���������a�����e���R�V�i���sb4o�D�HXrDI��;mmRN�Y�
�0@����Wx�1�����5����`�:�	���^��V�0��@9>���HJ��B/�H�`4��|xt�d������� R~�~�[`2���oJ��V$�a�k`"��u-D5���E,��L����z�k��g�O[���&r!�� ���B��Ss2�B$��B�:��g�n�6��F	��Vd�g`V�D�>(Z�N8�^���y�����/gK�i~(\��0�O0�zFj�-=3#�p���E���
f6�����x���h)���o�g�HO�T@z�5�V��m�fuw�����oYsD��V�4Q���j~�[�tQ.���%4;h�����i
�-��d����ZF��tp����*�X�f��+
���[�n���j�R����H�~�:E[C2U*��T���S�9�? �p~;�����n�nL���^��?����`���TV>?+�w�	�te����F�dW^>4����@pl��tR�FA0w��3�}������:�w��@��������������@��� +_����"K|�hbrs2h�S���aDD����\F�6��/Q��n�'~'��D�����IF�3�o0�� 2�5�Y�,�����e�����L�mY�G�G����\�����0�
h��2�w���]�I����C��;=WW�u�55���b��hmV8��D�F��'�D'��)U��*���<E�N/7+����TP�_73l��8n<}�w��8�
u8r�5jj�zN����C�w�I3f�1r�x��Y��rHl
�R���v���O�������9An2�JHqA��D�!
��.�vw����|!��� 
3����W���b�����`�����B����j8����xs��>�X���
[��&���tG>����z�����8���5�YV
8�J���	^&B�v4v������	��kEs�e7G��;���:�A�3<�	�����C�!)�`��KO]k�`#�f)Qq��$��d���Z�
��'�
����8JE�����0Cr��P�s`]7/����iE����`�����������+��O��u�����}Xww
|�b���
	�k3�4���v�� "�[�C^[#1��D�LdyEJ(���i���J�uh��..
�*|���
���S#��Z��N�d����fA_����>�[����^�kE"_kE=��F��^��^�8�l�P�%n[�������H,Y�C�Z���b>��`�K��o�v���g���
�������&t��.0|�x�mF:��tIFM�lAiw@o��'��w�'D���Q^W6��<�
���������=��;1u���9�U�z"N}X�,�;��}�3OY0���gF�u�������k��.o���+t�r�����u�b��:�H&��$��Q�3,���{���9wPi��;�Z�X��c����8�0�"�4\�&���Q�5���r��VL����F^!I[b�l<�*AX�Kj!����$���!����"����a(�!Q������c�BdR�����{�{��.|�������o�*���nc:������s�tG�;`��2�t����z��!����kN�a�\|��/j�Z��BQ�>:�w�r�<=���e+�j��\V�{h����z����N/>�� !�D��c�����x�E9
���oH�:��:�M:���\�)�T��_�L`�$�c�IF��:�-j��:WI�Uc9�v�BU����U=7Wu3�m�vV���,���� w���-���Y�t�����������U������!gW�)q�E:����\��"G�a��F�*}��Qr=�=�#��b��}���0�p�I����=�D���-����|��k/2�����~�x�q�R�6Jk�G�6vs�~��z����a��������!{T�V��`�%S��R���C7i��t�6F���^����P)|���XI�����z\�Ghr�������^�/�:	5�
�]��G��}���'��x�������>]y�T����D���a���������LJ�~��p��N����'^+A��\8c�q�3�M{>�
��3����~����f��r�;1N �P�������W~H�nh���R��x$�>�#K�n���1�G1ix'�J��������Z�N�a����>u>�@,NH<�C����)���Z�����g�7xc���������0��}m#������y�#W "��2	��_���L��K�L@�8��%3/)��.�Y���M	,�=K�F*��w����6�6wF0e�W���!bW����=�)2C��
�]�C������L��\��q���o2,J{
�V_����M��J�����������i�;�N��f�b{��nK�:�*�K���P��%l��#��$�e�*�������s7G�_f-�����ePQ��y+��8�8GL��
�3�Ea�&������p�V�6n"�M�����/��uR�*����(\2����8��Y�&�T�����H������6u?$��;�~NN�Uwg�~����tr��n	$y4���)U������\6{������N"�s�8��sP�!�����k� ��7;���%�7~{:�6������k��*��Id�c�����_����.q8q�����XR:��IE~w7S�/��w��Y�9��G��Ao��xO?�����wz��4G�l0�l�<���^���$��G���4dkc��vz��[o26{�G�G��u���lY�����/�t��r��y/����n��bvs��5$���\������>	��U��}G��m��<�
���(rt��L���JT�b�sZd�0&��t��i@mp}�0����zf�g{d��+
��������CS,0ZvF������E���
�sa�;��\��1��r������+�<��#A���C���^-!s�-���M�L��D�� ����8����]^�L��(��L��w��o����Fz��7:~3������}Q.6�P���,_>^��0�G���s���}"$@-��)�An���7���^6_�9%W�b�e]	���?@���zh��G	o���I�'���Q�;HaU�l����L�����W'�O����������0�����>x(�y��X���c�8������q{�R�C���������� �mv�,���

���
@\toF�O/�����M�������d@@���Vr��7Y-AnM�P�O'���0��O�.����N�v����E'����5����Q@L��V���W������S�G������:���m�r�y�S�f��6^����K�������aM�G�H�e���d���M��F��o�x����%���A��s�^�gyi)�s����F@�Z��������~�5��k��]�oAc)C�nrP��fto���|Q���a(�^]�(^t���\Av����w s�#H*�x?�@�����F�l9��3g�<XA�O��J��Z/��vvH�CveU#5��t�F��R�kIao"�McGf��� ��#�����5jk��+l��Cw�Q��V��AR5^���|/� ���
�8�'��/�9=%��j���u<O�f	7�b"��Iw���2�����������:��d��Z��mb����]1���l�A��x�������a�������eh.acV����:�o.RQ��Q�lS�R�-�L:�6� ��M&<S�
O��������}�aHN��Dp���D�<su��7'���S@s9r�\Q�K[�O]��,�1�I~�]q���@��/u*+z��AW��y��((�����p'�i$%������G�2���j?�'������_����n��0�1�^U��1��6�@jil��������8��!d{(���y�*�&M�g�|E��1�>]u�����'���TG�/x��d�!�s��w����4$���Q#�����.�pu3��|D^w�����o�>i�&�EH�+c��ONU�]S�C�q%��@?���(K�K�"7')Q���h��� 4��Yhgg�0� a�����zE����?����;A��F��9�[���q��7������z��w�����T=�VC���c�����o�-�		���u�n��i��d]����=}\k�9m����4�I tJJ��{�����5M'�������v�t�����q2�r���Sa���MXW��&y1�������A��1l����������������\����I6
�D����az<B�G� ���Ju�����r0�m�v/����OO�.6������x":��
��a&�h�$a0�!xo��$Q�����o�NA��\��%�Q��:����D�d�[Y2����s���^"k)�Z���4�1���x^�"B���U�g�[MR6�<��X�H�S�}��*��q4�
Rn�OE����q�0�sC��h/���Us������2��Z;fN�H�`"N�84�5�&�����wO-$ �s,���]������,V��;�c��HZ:������hc2c��P�
+o>�,%�����bI�/:����D5Ef�X�E��~?��K�@���������&"��g�vV����5@[)	H,�B"ia54+�
	Z��1'���h���'�x�3�	�`R��DhI�+��S�Pq�t��m��{�j(�
�fl�����I�k������+����on�����U����6+�c���w'��_=�uU�K7*P����|D���z�-��H�<�����u@)������K�9��Q"�q�!�8z7����g�c���a��uE�{t���,��g���&�!khf0�oH�?�����l2���p<h�����-^��Kc�WC<�
�����z������*�Sp,|^�P�
&�����3L��xmD+��^
���M����V������$�({��N~t���v�B��+���&�&
?�B����8��Z�Pl�(x�y&����)����\m|�O���0u>�8/E3�J�=~�r�R��x=-9
bf��.9A�1�/��]0��1��}�`����z�X@!�+�!�Oh4��d:Ap%h�D�:�fH�pt-��xr*�t'�m�cJQJ�f9'mUe����&%�	��:K�1�g�j�2�����#T_4_�_Wk���b��H0��(�����F��lWG,��(��r��K��y�?�[��jc*N���v�	�/Q$;�/`5HQ��>30~pj�f�U��K�_>=�z
�
�>�����'���
��+h�=#T�u8�����B�/�8�������HPg7F�LH�;[�1�2�j�Z����!��5��t �.�
vf�(�lHG����q�����������D�7�`a����`>9(�n����i���SoN����ME��)���Uy�8
��u��h��Jz�iU��8�Uz>�������x_������/:� bhsvC,�ErA���)24�f���V�"��V�m,2x469�t2�u����FL;��6-��F��9E� #d������:�v�t�W�\����������6O��2�qCHl21T!����d}� �-��J�>4�Mf
��p�R.���2��C��/�d.s���H�Q����z�YT.�t�)�1Nx!�K�&���� G�b)G�Q5f�>��v=9u����p!�cka��R\�$��5���%
 A�z�yd�2;���7W�mRnQ�N��!�����e�p!���W-^G�n��?���F���"F�#l��:���@7�H�����D�H�RV=	[l'H�F���(DU��1z�YZ���FXXfh1�m���j�M��GP_��I����������yu\��3�$L9H��,P��EYqVq2�"�{D�����p+d�8S3��n$��Dsfv�o.�R#)9����H��m��rL���Zz��b�t��1wh��m�(��l2}	�>`��SD>�6��
p
���S�%N&eq��+Y�A�Q��O����s��x#h*
����$�j`l�O(B�/�S�4'��O�$�� �����E�E�h��%��#������[S]m�o�����]B���/�!>��V��[Z�u�E����@�
�W���S�i��a��	�IO��K��;�7���<7;���-��f�0��C>$���x�$�����8�'����)9�k�%�r�������ys`��7 �i�!��U���|B?��G6�,�Z*S�IY����@}j"����7����&���oB"K��.P��.^�T�"�
)�M��YQ���9@r&(GE�C�_t{h�M�����5�|Vt�UyY�R�� �IY!r���Y
KB�?�T�D�
�M�y{���@�K���\l��s���X3�>�rx[��O"u��O\{y�j��a��q����ZQ�5���L87���&�{^*��
~=��G�����6�
�
������HQ���h*$3��d�<GC�y*L[�1������<-�^�'
���|�M�;�>J��(����Ad(n�����n�H���+���1#^���D\��^�C�hcW*>PX�dE4/	hM�������M���k�$���
=���;|E��c(M	�j�>�PW=
:S�	�]���3@&�>J��F-�k�"Cj���CA [}q`V��*�������y����ZX���i����!yO�?y����t�-�1��K�J��'[q3���b<�]�R�M���;������%-5�������{���
�c-��^' ���C���C�V#���&n�2t7$V(�(�a�h�W����1�Y������E�YQ�������~����6g:��'�*�7�|8�������^Ly�~*�vE�2���l�I�����0,���C���	����P
��U)�1�d�\D��c,z�.[F\�K�0xx��+���~���4D�:���-��z��jY���B��6 ��;+�Y��������*�/0�.�Mp�������T���������'Q ���`�w���`����'=���|��#�����)*_P��p`���
/��� ��wi)��#^	M���������=���������m�r����� �^��B6�h~b��1���'��p�w�����
�HAvU��L��U���f�i���L�d�Kwb����cO����2(W�c�L���Hx����"��,L�#�H����MVe��U;�=R~�Ec���������]\����O�@��b��,z
��<se"���4U�?C��+�K_�*�����=��4'�����n������=�����}�X���%�$b�d��L��(5lP�
ZfE.�������������
�����(�''��eA��y[*eV���pf8yC���a�'����B;��:�4Z[����4�L8��b�hq�%���;�����GJaAW������q��4�m�������r���=�J��iE��q�[!v���cR����1_�m�zR��c\�T�]�At4n�?y7)^X'c���$�b:HU.�%v�N���?��3�A�21�5!���)�b��&k74Y���u��m+�
��;&���D�
z��V��V���AT��G)A�h�8�6�y5�)Gy-_,�����9X�%?��i�/����H	|���e��E�~��0�V ����V�HYY�L��8�gq�F�hA��g��IM����������2���}��7$�$!_��l�T��{%�o	@������|��w
&�w����d-m�L�a�fT�����k��[�$)vL�@Q��%5������l���~r�Wzj-LKY��{q�u0��q��X�����[?
��S������wmRF�v+���Pi��,��4�:���=�h9
��mX�
���r�E9���FJ3�
r��B�a\-��
�nh
��wY�3�(���M$s�a7����ExT��l\�;���������,���(��V6
R������?��AP�m2���MA�f���.Y_����>�J��h��TD������:</�����"�l:�l�H
��Cx���aM���j��W^T�*��oR�6~�z����q8�&����6c����xwVF���R��Y�^il�����{"{��j���������!�U4�@�t<;�>�^�\���q��f��Q���y-fR\����6�w�ELV��9~��Vg���hov�������[N�\&���3;8M�on���N���7NZr,v��
�Q�k��k��lM�r^+u/�O��e��`w����f �xr9���w����P�=h�t�r4�g@���������<�M����1�(�)�3����m�f�D��x��d'�}0�p���Z����U��\�}��p���o��]IH
4�K9�{?�up�����I�DH��u��:���8~�VZ�TL�&�3���N����O�G�����������Eg��m�(�����{L}���u:����b����R(���������9� i)�e���AJ[��zz�Q�n��1;������_6y�26]���xe;��|9��'�+T2@���MK_�3H-�ih!������xl\����})�f�x������"�
�-c4��� ��]��C�L\�fIv:���W=�=2�O4M��[bK6YFh�b�)��?����:�k�z>��G0�����N���!��'��'PU�CVu>�R�2�����hP����IQ���]����=���(���4�f,�U/��$>�^��g��C����Zp�	�2#��*D���������l�,��cM�.��V@��I���*�������qTD@���5��>/�3DH����1�X�G������y���j���Yk'���
����a!'H�����=��
�8����(��L����J4re�m
��Tkt��h���A��RF+���I���Rn�EO������ct�.�M���~|��)��i�l�n
"9L," s"���P�������n��V�bk���0������0r���P��V]�o^b+(�Hz�
�)��K�U��r�5�f���z��R��ED���k��(F]��8\
`/�E��"���+�"%
	�����ga*���h�B�j���lO�Y�U��s�
���v��n�@��P-��]QL��-9C�3%?g=1|�{O\K�\�/cn�z�=��'��Sh��vPw�`��E�G���ga�BI�8�"�;��4L�����{\
}Z	3J���V���+���@D������������4�j�(De�)$�Y$�����9f91�����!$%;��\
�hk4j�.K�WL����vV�C�X
��D������!���e4������?�
H�����"���,P*l�t]]�HX-�hQq9#=Q=���
���tYe��J��""	���:�R;C�-2K��(S��n&�z�(e�����>[4	����_s��=�Wxe�<����,u;�c�&��/���g���v��k�b��X��>��5;������N9�0�F��h@��3���,���mO�w��dP���m�HZtb�gIP-<[���U�e���~(4���J���7��2�f���R��U���wI���'���SXE
�jih����?j��37-�����������OO�5�h����A�y{���m���hs�^�Pn������_��q��~�je�u
x�hf�|vR)�����V|��M/����������yL��&��A����_h�pFo�������"jZ����n�(�8��_�8��!�]���>�h{���2�b#�����~*�$��X}fn�[��x��%"���b���n�&a�o�un^\��I�KrH
K<�`�N�E�����/tw�� �w^T��1�����]��57����K�0c�|��!�1vXb��N�hT".���T>	�E����)D�����&H
�[=F�����0�
w@��{|��`�XViy�G�Ft�8sY
]�j	��������s���E�bg;f���&�<�n�w�2��o�s��(M��R	��M�W�{
�I��Hq'y��������7������W�P�X���B;h��n%z$���U�3�~��W�P���K���g�T�b�B��+�[�y��9j��)��=�{y��B��nx#P�&%�O���	�������A;��9�����K�����W�xmf`4'�(��gv��Y(JL���bA4��� ���<�jD���s���yV��&1h�����m~��"�V� ��=���l���o��D�X�|���9���0I�<{������j�/��z����2^�Z<)�Ke�vB>Kd��e��99a<�c�\{[>�-����^��#v�}:�6.�������p��*P�xoNg����a���i*q�qP(iM]YQ%������Z8��
���P	P@:oT�'	*�2b�A�rtI�`�u�2a�P#
�Kw�u���=�h�JRG���:)��]�K����X��@,����+z=T�[��|�o��L�`��U�m����)��f��]����k������b�V�8/g����\
������u�T�����y��N"U\�����tE�J���q�����D�S6�\��W������ ���H��Y�'��������Y�)����xP�����5�������x}4����df�e������lnk'Wp���C��jV>\};�H'wh	�ul$���A�m$�m6�#SE���?#J���8���h6`�,�t�����Sv�;�$mh��3�)RmBH�9�Q�;�g�k��:t��_F/#����������4�����J��]�:o
{���6���B�LK������[ -������
�^6<��T>O;�=h��8�EY2�S�F�?����q$�_���{9m��7����P���*��DN�����N�
�
i�2+�[&�#�w+��Na�����#F�������lv�om��]�G$J06��p���3[�:}���l�c���HZ��V��Q��E0��������{����j�^�on7���A����]Ox���y�+��g������z?��mn^__g�qw�1���`t���a����%���lg�I=5��?�o���^:�;�U��X�o���(�}f�VF�81���
~�v��|�����6�2�{[<yS��W�����������v~L�S��P��9m��5+�G�7�7��F2�������n+�G�)g��N,��|3�UnB�z��p���U���j�[�[�j_�<��W������n5o�k���p��;���g3��Gk���Q���Z�u�\���n�?�x�?�4�;�n\���������y�^��i�;�1��*u�w�����Fm��X�E�����E:)v�����y/j��*_ze����Di�wG���y�(�{���/��I���K7G���G��4{ 8=���S0��������%9YW-��MN�T��69�j4���@�d�$
KZ$��7��z7�F�5j�%8��J�+�Hd	{��G���':���ng%SH=M(��uUu�:Q1��}b��t(����e�;�6�M�9@�dX9N���(t�PK4��]�-Y�p������c�����d��#�����V���H���
�����������Os��������?��r��4� �/8s��?�����x�zy���*>����o*�T���w�a�(
��`�No���_��E�W?��6��_b���-[�Ci��i�g�*��v��.�f�'r���
>>\�Z��E�:&�*d����/��~���y�X����,������������ ����H������y���0�*�iN�{�v����B1��t���M{�t���At���Z$�$\3�����o��q�'�P�!&�f7!V���g��6�d,��,�6Y��
 �;i�#�X��+�A���f���>��{U�A���t]�%j�L���L9��������N��Ze
dI|��
�K��-�V�qO�-N�:^��s���f�`��5t"]��u�����7�
h���)���!�������t�����d0�
����?�z��Z�	���?�!����0�{����k?U��y���<���<�&��yl��T���t����6#������yCL7�ocY�M�	�b��O!n<���@<��������%�(4-��/%W��X'+��yd��7��W�Q�M��
�?����(������<#�9F�2L�:.����TPQ�����(�rr���>4��J��d)�=�3�Q������K )Z�*�@a",)5���<�#�����sh���fk�\����DI(���h�W��U?����]��W�jsy��Y�V��C�2/���g��t>_]�Y��\z����Pb.���N�������� w�5�����	����6�u�4EI�S���:��a�6��F����/����0h�a��p/������i��C�f�^Pc���#�Nm��B�����r/��eH��C�f�^Pc���#��/�;���f�^PcY�5Q���D�f��V���	#@M2v�/�u?�hDS�=E&�X���6�I]U���>�I�`$F�l�B�2-'�jv�T���RR����9������X
U��~i�KO//U"�����b�:��I��QKr�*����H��WE�cF���r�%F��f`.}����!)g�"-��E�CGd ���y*��(R�6���5�\H�f�iF?�f�f�f����(JgWL�`��	4����GL�]R�]�}�`��2���?���!f&��b��:�,���C^���u���|����<���NH���%�g�!�[%��kw'��O"��|	�z�'	9�&���[�����$z��^�w���X2��	�O&+�p�D&���s����o=��,��ml�
����?Z�{t���������7Gmx�o�r�	���Z2"���A�]0��Q���B�����|����,^I3��C�@��hu{�������2�f�-,����A_����������6&��x������y��������������5'�i>�b�L�1��b�\��O��&%�����XC3D)a$����9t!�����AN/���i�-�y�������Y0��� {��s$���[O����}��������D�Ht���	V
+���T�^�]��P:<j�f[�4�=�7�I|�s.�����+�����V~�����K����O��IQ�
{���>r���]��M�c�r�w(��oM�#��Q��@�����8����c�Q�.�5������O�d'�T��=Gk>�����6������P���Q�Y�l0�J��w�M�xscal0���m��m��m��p�>�L��TN�^���d�%���������/��>1����l6{�}����p�?�����G���~L`�n���U7��3�{������<>��z�������xY.\�/\��U�o���|���/|�_O�Y/���z_[�X� ��k'�Dn=��F�Z<�&��� ~�3e���G�}#��"n�2r��hBc���7��$�����������kN>L���Y������'��xx��g�{=OY����X�~{W���%��9��*o��6yS�}G�1���\v���o���#C��s���a{�|d��wy����]��s�V�vQ��	�}!�Q�
����|
��n���/�����m1���w��n�.����.�]�|�(��>c��1���q�,<�-���-�-<�=�g!~=�gy���is��g;���������g���,��z��������Q�4��lWH����y����]�.v��]�Ph��������]Ay�;(��g;����v��
f����f�4�I�������5��7C���.7;��9�^mr	��&��tRG�E�G?����������'C|ygvgvgv_���������GH%8��9n���~�O����%��N�F'mrj�h��m�b//�'����K�k�C�S�V���<�~�Q�0�������~#���_������r�+���������4G���-[�Ci����8KP1P�K�w�53>��e0�n���������5>�z�2q�����wAz#�%%���%�������-�o�t��	��|��������+��l�n�V]-��9C���|'�4�[
�N�n��{�?�v���r������,t���)�4���B��PaFK����[��G3&�~�c���	�%�O��b�t<��!�xj~�V3`D��$�[����������2��F�X���{A�e�6?�8+�Y0
5��������]���{A�e�7?�HS�0�d���{A�eCxK�D�5�	"� �F�&�5������=_�	/�1����=��{�b�H�$�z)���N�lA������r����?8��m'�����!)g�"��e���@�C��Ti�5��R�]���1�d������3�=h�3�s�[?��|����7����b#�O��}������:��;�����W��i�>p;_j�n+�7p��o;p���Yu���V�bR�������K��;<I�q4��M���<��&��|�����g��y���f�.�b��B�P!���c�A�n�vH�g���d��{[������t��;��'q�����5y��7C�V��~�6&���mLfW������������������Guk`��}!�}m1l���i=���?�?�O�C/�����v>��/��?�g���������P�,��Z�I��}�G��g���.]K��s����V.�=��k]l�gxf�D�d���/��^}��G~w7�/���������-��������^j���7�h���:���9�;Z�;�� ;���g/���$����7�v�I z��^��������C��1�3+i���|:�`����If�{D���l�6?��t��p)�p)�p)�5\�=����}��}�u�'�u?\��^Lf�y��9�I����m����Q��U[<���9d����/�x���W[�L3��=O��N�������m|!O����|��:��
�#h�m/]?��?�F�/1?�F���o�C�r���.�	��_�Z��Z�������b">"���rau����<�9K���'H�M��
#30Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#29)
1 attachment(s)
Re: patch: function xmltable

2016-09-18 11:53 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

new update:

* doc is moved to better place - xml processing functions
* few more regress tests
* call forgotten check_srf_call_placement

another small update - fix XMLPath parser - support multibytes characters

Regards

Pavel

Show quoted text

Regards

Pavel

Attachments:

xmltable-9.patch.gzapplication/gzip; name=xmltable-9.patch.gzDownload
���Wxmltable-9.patch�<ks�����_��;7�"Y�+N�(:Um%q�������'��HH��"U>b�M���]<R������*c���b������mmMd�x���v��d��Y�����<X��/n����7��n��#�����}��������z��X����y��y���%��A��x�n�AEi
~g<ND
�dS�l��Ohd_D��(|������g��Ea
��|�u��f��\g���
����Dx���7T�3���@�R�l���'������4�N S����v����V�Z���^��6�nog��������L�7"��(�
hVX���&��Q�����l�v�*Mg���Y���X$�(���4���V,`G�H�E�5��v����-�C,�5M�t�:�XNy|�����W@J}�z����%�L�(�6���D�v����p��P�L�*ILs+�����e_x���Ec�����n&6|#D�WMv�h9��z�A���J0�7�������n�}M�b����I�8]���JzWL b<���vZ�
�����
���i���R�����@�%j���,��Y"����:����<u������6�N4CH��R�0�q����;m����p���rP�]Q'�T$3�Q��,�e	���-��W��_���z�N.����.��j�+s�����_�t��O�;��]��-��C��������T)Cs�Pu�v���P��EA6
�WY�Q%��;�����~:9��_�]��)�z�$���
���Q����yY��*5c5��U
:�iw�A�+@|�e��
���L��r�b�8e�H��$ZIF�@/��Y�� ���
#_�D�he��>���t��L;W>�`����Y�DC1;YS����
���
u�hh�oi���M��R�b����36�	,�d4�O������#���R�9���1����M�|��������J'�/�2����
7DiT,�]&Dx���������~x����F����
�+��;���}:���mx|��}����R����~��%��@��'������g�8x���oh�6�]Db�
����H^��I��B��O+������)p����x5�A��/���|%?��	���E��q�B������F�{��hT�9x�M�f�gQ,�
�����Y��v�z��}�j����z�����3�
�xFU�8
�������r�F/P[����T���z���]�}d5��Xj�NxSg�M=+|Nz������P�l����w���(!.�fb�3	V������l.d?^��i|7����+�\�|$��������b�vXX�����H��7�QP~����e(?���a�~?x4�Y�k����:�"�#��f��V�a��d&<9�����!z|-���K����*|Ux-N��|�+�4�4�~��3X/����Ov�_�����?�u"�]h$_��><}(�b����������F2����b��L��<�(k���jPfT?�XJ���VaT4�8dYx�������?��0��%�>:���D ���rn�4�|�\��G�tA>�������{�5�v��(AL}�/;�1�3}���4� v��,�A�<I��!���[��	)��� ���l����(w?�2S��!���aP��D��n[w��y;���8�6����`��h�r�����n�ww�;mRC��[���K�����qc��zg�$��R�W���E����)�6�tLU�
a8�?�!N��A*����^�/��.��*��I�I�JOY'b�G&c���p��q��MK�x'����z���/$�I6���2HjL�2J�/B�b_ 2	`%B�K�:��HvQN�3���i��V�a]�=L��	23bQ��2�D-������
�P��5����%)����f�9�����N�\reh)�H�_���q(��c'�eJ��`5N��� ����0�`��Q�&*U6�B2rI6�Y���e�^�������X	��sV�*��B��E��dJI0�T4�s��yL���k��R����Xq�U�I�H���p��A�@<�.
QR8�N�� �r$aCJ4�,�:�����q�b�s*.��H3g\������
J#�0m1��x/C�s���.��N>�������b��[�#|	��e���\���XT�/$VZy����Y�?7��Y�h�sz%c�F�jJc�XY�HA�����P�B��JF�6���|	��`�?y@[�D8A��]��[�>�`�AX�-�j�}v�u���LI�	�T��h���-��{�����'��S%$`
U�)��j��
hV#�I�4��b_p�th��l����a
<4�,�GB
�$��)R�X��1V�e��`�������CB���r��n_\1U�����-�NW\;b:��X�
"������g>r�A��	*9�����9�e
�|���z��Ud�t~���C� Le�j;� �Z����tU��*���TN�Q����'��5!zi*�b�U�)O���L� �0�<C���D��$���H���@h9���o�b�)�B1>��P�mCo���V��$��_��kF���@*�b/���0���/&�p�����VG���!N�z9#�O�;�jb5�}2����a����I�"��0%@��,�5:`q`�[m6n���$����H8�����)��Ol�CK,�������H���$SVLL��������H����P�P�P������+bn�����n��P@W�&����Ue���6q�Xj�6Q�4:
&+����,����an�\��:s�I���1���{�Z��i�-f�2~$��"���PA�G�������M�~`�4#�<zQ�0��]���.��	�g���g���J���S:H�\�r��YY�l�iU5�n+���;p��t��*���F��@��(��F��4[1���Q+(n�Rdj���8���zf=�X4�Q	����vvqt|�;9����������Y4�=-�OP��l:B[F������\c�����P��,�����XN$#2���j;)���f�P
�,���*U3V��t�B�'|b����-���(&�Y��������������10-��)+<����L^�+���g��hTU��@�{.*~��xk*U��T�f��9W_��s@L�f��&���i&\���L*��;�d�����+��;��o=�4T^���q��b�7��*��*
A�a�����L�a�Ea�C�����|�DA�>PR{<�D�IPw��Q<�L�^���X�s�Hik��c�����'("kr�*rc�/}���qOOV�����8�����8��%�?I
����8����U�>���L|��U���05�S�lO�^s���9��C��;�&��E�
�D;v7�Yq��]\�~�]}oN��<����!����;t����r�{?�a�����C��:+������=���i_�nQtmR���neI���l�@@�	:����<���=���t ��%h�(R eg��9�b�d�	�=���Us�%���v*��h\��F�������C��;���a�����`i�lg@������x&~u���M>�h��J��"�M�6�����.��W�y;F~3
N�i/�����@�z�<T.��L�N��T����Fs
�QH�cq��:����
xAk����c�QJi�sG������$Gjv��,�JX1n�@��Ms���T��RUQY��V���������^U/z�7C�7W�����T9���
��[
:�Y�W=kW��b���hG���=~���
>p�;{���>�)�L���?����]�����9�TpU���$��8���}@QxY�t�3�D�c�U=���/���K������;������`�+W�B���������V�����d�=n��� ��g�!�h��
�i�/F�dQ�E�\W�]]hk��$8��R�TB�OeXj�SF�)�8.��$w��H_m7���O|x�����Ix}�������X��k�b��9b�#D�a��J[��$0����Q���P!��
����5�,�D?�Ed�er����(
�!f�����uX��%�P�W�j$4�"�S�B�������-��%�/��IH_l�7ww���O��5�o��".�{-���|�
#���o��?�r����G���~������t�q��Q�
I@n/�p�\o�.30�G"���l�@R���5�-�
2L�t�Q��8<"��{:���~�d�{2�G��|���H�������
��^�V�AJwv: sH��-x9f5��[��B�!%�N��b�05�X�luc3h�4����1H�ph]Tu0�2�}�f"���H�S1��;4'��*��Q���`o�CNVU�P@5S�NR_3
�i2+l���6DD�	�Z����kZ�,�4D/�d�s�R�[�Dj���	[l��m����`
�����t$�HN& "�����*W�������������E��,�b��9�j��� %{���yr��4$|�P��!�%�[�Q����@��
oG���_��<f����:��2X���|Y��k5'N����@����u��h�����({�Z�+��	�72���R�Nrp��j!��{���	����p���Vg�����`�.bEj����eMc��=G�o�A"�����B��`��RI�`��*e]�b�yJ�6��!��8��S�|xv�"�!�I��]{''g��������dR�(���6%�)�t�^��(���.W�� �R�ZA&s	����� ��bt��E��e7���p�ax`�P�"�j�����4�$E�U~�����_D7�P����U�����m�%���0�:��C��
G��~�C����f�yF��]~f���U��*CX%����@��*�rd����7�;���<���_�S��q��+���U�5��U�3o�����rN�����$�n�\�&�h���rz�p�G�^#�rB���;�zW �����>>����@�Z��6���
QU�I)���>�&����<�sP?��;nN15C ��f���P0|����	C�"�b;�:�|qCA`�����i��r��y������j�+NW�v����l~P�!��'VE���z���!"��Nn���7)!	�"S��,r�.Q��to�G�Yk4�B�9~��]�7�rx|�O�+��Zl^���M25:����ZIi�`��D^�:@v0D��@����jjh��x@������	n��C�������:J$�:a$�q������z0�@��1��K{���)��>B�~��<�q�E���o��(a	X���9{�9�yM�f�E[��������B�����v�a����-�),���N�qtg�C�z.��t���C&��e�cq L�~z��t�������4],R�fZEB�C��F�DLI�N�l��ZW�8�������!T��_0~S��9�h:?����F,��@�%���l�: I@?�]~p��!�#�SP6��iN���hS�L��1+��/�%kI��(����������/5���#K�J����:�	78�]iD�$�b�`K�/�_�5wvvLI�8���DB�N���j)�((L������L������lre��6��(�`��2�+�R��S�(d�Q���Eo���'�]���v��m�����LOa�7��z*���vL�K�f#o���dA��e/h6����v���-�Ma��g��U���gXc��+��"�EF�C�����?���R$P�F�9��Yil�
[x��:��������X=]��N�����)�
�W�c���
#�"�N-�2�B�"|a
�79"O����[*����$�95m�m���gcn{l��?����������&���Vz��:7��tC�p�S)�|Q�;N�,'�,�*�'�&��o�Mtt�g��1)��c�m �r��U�#n���1�|��W-�r�������B�D;*k�j���=������u+�������1�{�f�I��c|���n��`X�i0P.��w����HZ����/�������4�F��h4��I�D������������_k~�=]�l�
�_�����q�s�����Y
�L���"��Nu��\����d��j����d��:�;���~��sP��6�����
��\U�_U���3l&�)#�
�v�!�teO����Y��T��Y���ER�����z��V_|�$D7{M�.�&������U�����hGL�����}d`���C#K�tj�������m��9a�5���I����~c�,V]Fs� w-��������9�Jk&������h�L��~���a��%�~'m���}�fu��J���t��"��+����g����{a����}�%y
��!L�/"eT'[7>!MKKkSa�����@����d��Z���:�R��h��eM��V�g��m���6�Bg��$�K���t��/��K���l�.�d�Gw7P"|&��II�4���T�h]���#���maP�d[��(�����{���v6����;�D[!m��y� �`�-r���9H���a��{�6O@>��_�����j�7��{��SQ&dFA�,�
�(���,����A@���|��0�jC�@r������'��|Y)��D��M�d�@���n�A{u2L�b�rE[�����QB1Vk]���'���]����T�q��<&R��Z���� uj�.�+�^*k2��O�3<�-*������b��5w���;N*w�8���q�<����2mi���z6$��4�������T]1�b��~/�
?�Wt1�4�c:�
>�/ &2|��������S>�U%�(3C�.���F-'����0R=e��i���'a, 9���5�Kt�Q+�
�:k�r���������4!�j��Wz�0T��dHbr�CI�g��M�^*O���u��]��)�R�(�r��l�`?����Y2�"J��2��v��86���KRe*��e��[�������U�.��/n������OC&�Ij[��9-��qd�3Ds8	`&�����\2�B	9O"b(����PL��_AL���1����}6��r���X�%���7��Fr��W�b�_�J�/P��^�\�����OR���~�����5A�*�O�V������N�F"n�l��.d�Eo�q#gl	i�]���T���'S��H������%&l��sh���v^��(�j���3�+�k���	E%������3b�m��P���@z��bykk{k�����%D��>����P���[*3f���e�ta�K�b��`o{;���:g�N{�paB��-�<4���T!xE�����ui:����x"8��������q������Q�
h����?����5�D�u���I�+�>�L�J.I;���z�{d��kZ��"P�1_��Q',��{�s����U�^���q�����#�Zv�����?V��K'V��bu���W�D�
���O�Z���E���,ft�6��)��������$rd���_^����]HYx�n�^J����jj�R�vT��C�d�t���[D�MnK�&U �*�S���v�Pc�E-���eF.����������)d2�
��W
����A�p�K`a'���x�����)�SHRo�����co�l"qF��.�J:�U'�����J�B�Q�$��X���xu��Q0,	NW$��h��*��RbI+����������i~%����z��i�W ����W�oK��l�42���Y���;Y}�4�����/�m��<O5�a�v� �B�<��@���N� �%���r��IRN��OY�Cs|K��UF]���A���<������z����6��1�6��i����f�f.��+r��1�)���1��=^������T���A�&z���Nj��[��$��i*7k�oU�~���!O#9�TH��W����f�Mc��C5������Xn"#x*��w����=��
�0qe,�	�"�-��X���t�\�yWn�X�\M4_q��#p��C��F��N�w���������i�wr#/U�|���N.��s��I�
>!)�M���%���Y}.kS���K�ye�a+B�������l���&IP������l�h|ZVF���5����c�@��\�U����}5�m� df+�L�}�h�a&���?��������e�<����m��(�L�2���VD)F���6��PxU�W��>���C��VJh�$#���T
�e�.�x��K.d���#���n�5={��L����u�L�l��)!�>�6�F:8?���$3��Js��)��VB�s'>��f��n>�3g�������w�fS�jM��$�� @��G��H�|��:/Jg��>��/�%��D���[Q�p�-�����SY���
�NlxWCt���*�*=��QJ�h�m^w3��m!�O�Zt/�L'��	AX]+i8��:�!K$���A)|a!����~���S8���������h����7(�Rqv`o�j�(B{F����1n��BP����;���`�c5=�>�:�L'q�~�<�hy��;�������w�". �����[��m����6��B���]����N�\�M�.@~�U�ag�W�z
\��,�����N�0/�:s}sU�����O���w*�|ZK�q
��:�
i���)�`6���rz���y��y9��!68@���H�N��0����I#h��
"8��j�8�"��5� �6�$� �:2��t��h����(�'�d�yWS��]$�F&
��m�E��Xo��){[�r
PV��~g���c�z��%��M��bv��{�x�&|F������R���e�/�7�����z���>k�r���m��$��?z�Jx���v����\��a)l�F!� ��,~�:q�H�� ����������^k�0����X��������|���m����|3A�t�	-e����1�����Z�,A����GD��������DMU�������DHQ���I9�Y@(F����J����rD�J
c�����"6{��x��fq��;6�pS\���$�]�hL��i���E�k	t^�j��8�l�����G�{y^�%��	���>��"[;2���]-+��o*gg4)����O���3�K�����G���
9�H�n�O�c�W�o�Fu�5!���q`�����I�-����U�{hj+5�Z��'0v��r�~M�#�U���f�)�~���ERi4'����m����B �Q�$����:?�r��mwv���E
����8���$����rD��&�:�)Z���S��������DvA���n��Q���d�oj@;��5��$c�s<@��2����5�9Q�{����$C'�*��F}��k���q'��Hb���#v5;Q����B����B_+D������������;�N�H���E�->��	�$��P����5�c�G$9����'[��|��?�j��p��P�<7`�m0|8������������c��Y9���{S���1O��nN�����[����b�����g�������%�)����F������X��u7�����9!������{=�+��m^�ZW�O���$=��v�{[@���N!���]:H��t�@�-�F������jL�>z���u�����K4e��*��e�����N��`)�?8|'��KK�F8����5�Di��Is����=���q8P	'3�b�����	���������
	��
I'��z=������.U�'��:M��������N�f�q��d��P��If�`�Cd@�L#����|����u��nR�x�,AjPs���av���x�<�uGR����k�18C����A����LA�b9�*�9)I��T��6�����m�q�>���QY}����?��q�U�T�^+'���<E��Xj�kB���R~�K?���*�8D��X����zyyc�t��N����r��T���
��W'X���7o�X�?����@���-�����j�|����z[SPN^WJX���\R�`�a(��>�����o���e���\����b
��o4�+�*v��N�����z���%~��]^����-AP]��-������d���l�iGTN��U��u�o�F�F����?����S��m���V�����qf1.�����-l[W�q�,U��pR}	�����c�@|���zZ�q����4��5P;*���E�-�{?�a*�9.������	8��a���SXd�X�|A\�G�R<�O��VY��}�����b�^�)�U��a8o	�u��K���,U+��G��U��T���_�F������b��*�8���r\~U��*��}��b���Z������>���'� 4�������^W��i������Q�'��� ���)[?��j�S����4x?�9�r���h�*?����.�b������u2���
��w4R���/j�*%ra�*�.��������b��Q�"�J�A�~B��22��\G�������A_v���5�1Q�l��K�j��z���%A�o^�u�����(��oYL�S7E��W���a,�4uP������
;�
w���������-�=���m����f8���:=���zh��o�e�^R�����_�L�7�t�]�"�d/1�sM�owb��TJ��o�����P��=��+w���ly�w�����C���Pf��@H�l��3���@KX_*�I����.)tP������Z��z���U�Od�������<@15<�j\s�:�����d+S|������I���
�\����J��)
KG�9]+��&��z�[Ni���B�f���i*G��)RRK�ZE��W�X�{�y�'"D�RH�����F��&�����tk��|�W;C
����_s��	)�3��n��E
�o}��T�ck��,�GRr��8�"uH�������I��C���.�H�i8��m;l������)�Z������l��O�������0bI=f2M��]��9�}���7�9u�� ��1!$��:�P�Q�d\�Y�Z�����&��(I����!����3�h�'E�7�W3�g�6��t��;����<L�������A�,��.5~AQ*�������<l-^��:.Z��b�;�3n��[*�<���{+���_����hAJ�[�Q3�5M�,@'b�������]��o�oI�Z����u�Ri�;�?Y�����Q}'�"z,��4������JC�d����[���Z�U(�7)R�_�N1��JA����+�C�Tdc�H;���o���z��w�����������-:�������u�/Sa�>��%�U���C�0����mQ�N�����N�~v��}4�o��G��y�9P���w��s���^no�9�%�<�����s;����4�������#}��T��:mF;�����y��=�[������&�wy���B|��L�L������rMDe?�z`<i�*�����<Sg�A������=!;W� �b,���t��n���|~�b�������#L����"�������SYAc���������[����1|J�r���}�G�N�&�����S�=����y�7�O����!NyC����d��Z��S*!f����l���m��?1Ce�uh����my�����q*� @G���	r��WB��"�$re��u�w����n������Q��Ae.�z�W���-VC����H�050\W�������������lv�P�
9k�!�+�Nw�����g��g����_�p������OB��D����N:������r�Z1k���v���	��
��������a����C�!)|�`��KO_k�`#�����CV�I�j�r-�����z�
�
��S�"�V�X]��!��rp(��`]�/�z��iE���r0�u���e��y�y�]������}��������[LC�2�� ��?�lCl�Qn7_CCDA#t�p�kk$���(���,�(	��7�y�Y+��tK���aP�ORQa43u
`�1#SK�=�����"_�,H�K�\a���Gq��z��k�ZQ�7�:Q��h�n��L�u����I�Y��%i�l'|����:�����/��
f����i'��e<�0�TlP�����M6�tu����K�f���H�T�4��v�F�~2�~WzBl��ueK/)�s�J��f��"p�af5t'�nS5�"g�UhE���S/��F��u��)Fu�������r�"������G�X7n�a�B�v��^�:E����@$�ut��:4,�����%�s�A���������?�i�3�x �H#�<�FR�X#J���9Y�WN���������k$KL���TB'HkqE-T�&��B�x�A���^8DT9��ECT:�\4%r#%�3OL��?�:D&����H��]<]��!"��/����2vU������t�M�M��8�*�:w���e��N����C����7�.����MR����D������i�U�kf�o�Vq��-��q����A�x���%N/>��$!�D�sc����xkD9
���oH�9��:�M:����\�)���2_�L`���c�IF��:�#j��8WI�Uc9�q�BU�����"���3�m�vV���,���� w�C��r���D��e��������u������!g��)q�}E:����\��"G�����U�t���z�{�Ge�&-�*+�a���(���(��B�t�GFz�
X����^�k��]|#�����m�����m���������a�G:�o����Q+����#J���/��n����*	"l��[=��iC�R�H����mq����*�������������W�tjr5��\��������O���{1y%�*}����.�2%���#����5��C�Mc��:4����r�:�,�n�x��Eos������ds4�y�+0:�y��������V6�u�����q%8:��8������m]\8�q+�d��Co�h����0��(&
��;�#�z��x���u���1�;]��.�g>��	�'q����7�=uXKw���L�o��Vr�y\������mD�7��3q6�q"��*�p����5p6=����t�VD��~��d�%-6��6g�6�)�e�g���C���|�����?���
�����>D�j]������=Efh[A���sh;��1����`�5}����} �!�����������~d�]F��0�feu�0"_F�����P��v�b{��oK��:�*�K���@��%���#��$�e�j�������s7G�_f-������_Q����@B����v�:g���
M���7rM���4=&��m��D����M�1_%H�� zUDy5P�dd� #q0n�/�zM�����{���g)i��m�~H��#�99�������������!�%���$Z��T���oor�l�`+���$�;����9�"���`0:�F	��x�s��:��w�����hS}�J�����h�D�:v����U���!���l�`����<�T�ww3�B/y���V��?zd�14�fZ���S��m���Wa8d�	m�&���z��J(�.�������r��=�ou ��`��y��6���5���cW�%^�i?$��pY���Gj�Z���1�9p���H��~�7@�G�$dq��b�Q#T((d�q�&��&�+��U�����!�I�>ucP\�9�����E������Y����E#�`��&�Kh����Q�_y�w��@�~Ur.�u���]X'`�'���������K������~/�6�N�����������&G&PX��LP�3�)����.�&B�?T�&���������Z#�*~E�o���^�h_����������V1;E��������vf� ���E8�5��M����������'�*�Y���!�s���(10[�f�x���^nW~��_he���V5�F}L~����z�y�q��t�L�O9	c�
��`�A�@'~��0����qV�t�����=@���!��w���%� 8�i���#�>:jCC{��������s�)+���xv|H��s2  ^�������MUK�[ �g��'��{�I�E0tB������?8�����F*T��:��i�*s��b\�Q��|����q������@��o~(�����k�t�y����a��]�5�)��T_��, &.�l���+���9>`<��BpT�Zn�����z�����|.�-d7�������F������\��/n]����M��~���Df�e��r��;�@_}��N���"}��i	8"�l�+�/��O0�0�*/1Mn��8����Z�������%�F��+�t��~m��_}��3�������;��~�{e52g�������z�u���*@��
���#������Z������lt�sV�V�>Msj�>���vZ���^KI?�G���d���r�?!�A8ySv����t�Q%Z�~��m���G�n�����DAP�`{����d4�������_s+����j�������Z<��BP�~�
;��� �)~F�i���&�h��������Y�wG������H�	��Z:u���;l�����@������Tb�A4��"���)�k�j�
��`�i��!��q7��N,�X�������H��g�)b�����x|�$�^��W�A]���7N���c�k��c�F��C�%��K����~�+�_���S	�L�4��p'�$%���Qh;_HS�+��=���t3�;+�/a��pAi�c���=#c6m6�4�2�^k����rI8��!d{(��s��*�&C�����|'��?�>��X_�
;O8�E�����l��
�C�Y��#:4��#�F�2�������5����y!�����n�5�d��P;4!���!k>������.��P���ySBD�p ��9I��Xj|��������b4���:Y���?��*�;~q�(���9|i�����q��5��Z�^�q}��B4u���C:����$���{m�62�������[�[��;�����>}��s,�
���iRuA�T���[5���Dg�N���	�9��ls�������E�d�8	7���8q���M�r�K�=�g�����c���/���������,<�������\��.)�����x��`?@��
U�s=08�Cp���<C�9���F�!>���H��g�Dt��8��L��>�e�`HC�2b��2Q��4��o��B�a��%Q�(^;���D�d�wY����9b:�����k!z^����(r��y
�HY�{:y�m�9o
I������L#�O~�iV���'��V�r�@,�������X_8�o���� q�Y5W����Y*R�h���Il���8��$��4���S	���:�v�����b1�E+�����I$-�B�uE^�1�1fq(2��7���K���<�e�X���U�S�E��"�X����h�E��9�Q��N��J�MDD��B�p<	3�k��R�XR�D��(jhWp�t*4cN�m�56�P���=��'��M}��%���O����I*w�������+���96���&	���|���a!%g�5x�p3��r|�c�M��Y��-l�;�g���|m���SX����B���G�A�
Q����������O��9��'���;�y�>8z#�x�M0�5$Xg��b�>��i=��ue��&��[W��7@�L����qz��!Qk���v
�Q������1^��LN'#�����A�,um���.�`^�L�����/������_|p�{Tq��c	�����Cc0��G�7��y�@�e��tgz5���r�y3��������$�(���N~t���;���^��5MPM~�I!Umqh_u�����W��dM���qSzW#��������a�lz~(_�f,�f{0�$�	����y~Zq�"�&�J"}��$�#�_zU\0�j��}�$����>�XB!�+^Z�Oh4��e:Ap%haX3$��U���]<��p����1��(�f9'mUU����&-�I��&K�1U�z�2BRYe�!�/�o�o����>2��$�cw����f�����I�k"���X
T
���l����EyH��B���Js�j\���K:�i�KX
AT��O�����Z��x]������O��F����t��a��#a|��J���4��*�:B���&^���k��ti���e�#R���Q>���XkL��D�V �-`����!H�����'�$����.i��h�b��:�0=M!���6X���,��O
��!�8$z��+����������h8�����n7���X����%����VM��4�cY���EB~����x�w���1�C��[b�/�������<�E�QP���z��V�u^@���1r��A�#����5=b��1���>0�.T��3BV�^Y���j�1H{��%�?9u=>\R[5�	�WF=n�|�M%*�:��7U�-���j�R�	CpS�|�'��)	�����L�%sL�M�O����hd��UJ��r��,j�N&�T�'��i��\����V�#b������>��v]Gu����p!�cki���\�&������#
 A5z�yT�%2;��g6WY�KmSnY�I��!7����U�p)���W/^G����$�u�"E��@�8G��3�4kN��H" F7�a"HY
��N����C5P��|�M��iZ�"[��FZ8vo1�m��mQ���&�#�/������b
����U��o^���ND4	S!�LQ�|f�������8�<#�{D�����p+T�8S3B�E7�W_��4��7��i����r8��r��x\9��T-�e��A:B���;��"�H}�1�j���)�����Y�C�)"�][Q��;���
-������+U�A�Q�������s���4��J��g{=0.�'!�W��r�4�O��� �����E���h��%��#�WV��^]S���o�����B�T_ ��U�N���@����i�����G/:�^/��<��\�K2��o���y���;��Z>.�g�@i���>xH���bsI�9����hPB�}Wj���;:��)��ks`��7 �i���0��!vW��~���l�Y��T����c���"*��D����o����M�G"�D��
��l']�V��ERh��0�H��1����=#��|���i7yk����p�IZ��V��`G���<&�e��Y�zgY((	��lPE9+.q�����/}��3p����/�c�t� ���0���M>0?	�3��^3�
�^���U6��
[����7��Zh���f����)F~�!�m����/Eu���>H���TJf w�@x����T���c>�1�GYuZ*^���������VF�'��@��z|�F����#��A��8f���-��N�}���$�6v���GVD���D]�}�����Q�qO�6H��S��E�'��w9�vE�`����	u����3e't�
%h.�r0��P��0j�"�����R�t����`������L�$'^�g�C�ja
pl|/�#���� -��5x���1��Eg�J�f)[q3���b<��]�R�M���;������%-5������{F�������ako����y>�C�V#���%n�2t7 Vh�(�a�h�W����1�Y������E�Y����5���~������"tP�MPUhn��p�(1j����"�<?Qn��"V��~^v���i��I|�I���!�U0�A_�9v�<yGU��EL7�>����o��e�����7���B�\��w(7Ntm�=`�=��x�[�c6������I����_�&g�iz���l�|�_bf!�?��Uzi~�]pF�Nb������bg��;�t4�.p�)W�#�G�����)�w���f.�_|z�!���d[���!���ow�+�iZxW�x���}���v9����|�u"'��8��M<��8ks�kg�	d2����*)a���4RP]U�.7�&�I�*�RT�H��{�j&S��%�;1B]����{�OITX�c��N��HxU��AE�U�\<GQ�qk����9�v|�B;J��e�B�>�BH��#f��1V�K?U0	�����^`1k�F��	11�&"�=HS��3t����Q�������8���~��S���:��N��{�c��kw������H]�L"��H��l�4���P�E��cV�61r2X6�F���"�������}����2oK��*�����r���{5�yd��Q�Bh�ng��hK[�����	��;]\�m"v�#\�D�5AYp���0��H�twmw<���QD�6Ij���t�[9i�I�q%�����h���;{�GFj*�JE�	���R=.��1�Y)��� 	:�7�����/����Vn�}1�j�
;f'm����y���`�`������u�C1�V����K"�u��������Y~�"r]�++�R��K'+0����`5oz�����THy#_,��!����98�%?��������	��hl		��2��"P?Sa~'r��a3�N��HY��l��:�gq�E�lA��g�&��L=q�	-w]�gm���P�oH�IB�6����f��*����z���}��(�g�l6�|�9��Y��3�-���Q�!�q���]�>)R2�|EQ���8�H���~�����1^q����4-e������t���q�c�>Fo�(��
������\���2��5�p����[%P���������d�>Z���6,���
5�
��p\)�D���
X�
p��r6Xf��)�[��e�p��>�7�������v��y��}p��d�{�[���\8;@��8;�(*�v��"�G�@�"AA����D����Cb&�d}a��r*�}X���@���"r���w�Y��r?��^�FV����i?�������U&��a;����]3_	?��SB��*�/m|/z����q8�'�����"q��H��xZFG��R��i�^iHl��8���$��{�Xxx����\E#tyJ���A��'j�R�v�70k�S��0'�r&�eY�{�hc��+�(r��T�����:;�}�@sx��}�D&lL��
p��29�����4E��q_�>�6k��8i���9^*DF���9���S�����V8"����2p��Q��w3�c<�y��;��0T������c_���H�`�9x�G��w?z�1F/�7%tf5��-��������U���.�|Zk�6���/M�����W��<�k	����c%'��A������Gb$�@�����Q5������"�b���0���0���Wuz�l�\(����-4��/:�fl�EA��`�u�X�1�@�#_�l��.�I�g�K�l>._@�G��;�!���h�EY�����������2&�cv��	�U:��h��el�:DO��v/�r�OZW"-.���"4����=�3H#��i1������xl]����R�#	�v�&��Q�
�7e��[�hP��A:�#�X��b=������t�
�^����D�8?�4��vl�e�-�T�u�)��s��WCo��`>�m��y�Z�������:e���T�����l�<@W�Y��lK��;x��v���~Q{CB&E�n�w9���9�WGq_@g��5k�������{y��Y(U���k�u&h��������l`V'g[dq��j��p���8��NzMM�@�U� <��&LH%qQtK�YZ��%w�H���
f+�(���w�{)�x{[���v6�m�����A�<X2(��i�r;��*�A��;�A=�I��SV��R�����w��j�.p�
��O]����V2I��ZC�M����Y�X�0�G��2�Z�����������o�F�������"2W)"8�
��a�>om���Amu��vC��DC_0
#���i�1V��p����K�m Ml]
�J��s��8�V��X��*�-"z�I�V_�b��\����2[4Y�}4�.R���N���"�Q��6/�{�� ��^�K?f�� =�Zf$	�r���09����5cW4Sz+cY���l�/�����'��.��1�}��KC�
��)4�X;��m�f�����K���c!��s8��A&JKX���=��>����C��`�U��}~ ��IZ���q�dsL��
�~�J���R��,�`H������tX@������u�\.�s�5�h�eH��*i�8+6�@��FB��`����������2�D
	���H��G$�qV_T���(56<�
����a$�`x�������C[���	uYe��J�BD�.Z��u��w��3Zf��<���L/�L�4Q��H]�6��=z}vh�/B���w�W��d	�'`c�K��N�X��F��{gg�l{����Z���,j���<���g�M��s�	�F�h�
(2}�~U�%���������J=t������,I�c�gk�<\_��D��\�W�'��(6�����J��WY���%j~�8t�O`5@���������5����Pn�4_ek��q���\S��M�d��w(������6��U�f_]
�Z�e
w}���V�Z����f�����Yh�W���7Ehz�V/�
�4�g�c��7��rF�v�B��3z�����-Q������=8@t�Ga'Dj��,��%�n>�s����Y�.�q+6�H���}��RJ2!I���g�p�&�u����0-^"b�*(�ha�jF�^���u���$����sf�R��
	�
�x����i����cu��`�m�X����)��^*�����������\t�`���qw���I�-��o�m!�o�L=�5`_R0��Ai�����U�����C[��JK�)�e��e�+t�W�����N>w��f����;�1�� ��5i�!v�����~C��!���,�,�����{5��@!�����q��.�=*����vB�^�Bm�r��;
��aCj����t�T�����^�B%r.��6k�Ip�yg�����DmA��N��-��H6�����O��-u��6)i|�t�-O�
��_��d
�!%��1�����.7XQ=��fE�����]����������gI�(1e6��%��O��t<?R���m��M+��i������K�g���WD��:�����`�w�������Mb��A��@���<�%���U�������#����y�����R�x\������|��l��N%.r|�x$�z���|[muU�lMG�luFm0\�����������@=#���l��#�2o��T�2c�P�����JD=���'!�p�^����t���TLe�P)���$�J����U�2�F������R�K*�x���^�-�u\,Q�f��i���$C5�X�	TaW�>z����3����s�|������$J��a�\c7:�9�������?��~��q^�.�!���������R>�����Q�:�TqA�f&*��*Ir��1�_�VaO��r1oX_�����q.GR���>y-����\�5OL�O�������������7F3�=���G�MZ�(Nf�Q[����������vr�0��x(�\����og��-A�����8cs2h��D��&td�(��CR�gd�>�#��ux���������tt��V`��
M�}f6E��A	4G���xV��f��CG<�e�2b��������P�f��1uP+Ct"w��V�Mi�9��&�P`�)rv���~��|�pp��!5������i��X'�(+B�3
�(�G?_�8`#���W�}/�mV��s�2J�R�����	�QW����%��!m��*
���^{x���[�C��D6>�:��=k�II��:���1�cOlFZ�tDO����Zn�!�8K"%���T��fc<���W �������M����5�0�5�+^1���4A����4���yj�)�h�g76��a�/�
3��j����t2`)�6��sk��VB�����@[��"#���x%w}�*!H	h�����<��~�[��B����k��������+�h/++'+V4Ec���(�J�4-���V��`�N�@�X��ps�d5b�,������[R +[F4!����@����PWSNIi���!eZG��8���x��o}/�����DC�^�K��_���F��z���^�C����o����||���]��p
1�������,<�H������1�����o�7�|��]�b����%����_�����7�#+�� �	�+0�o�3\LN��%e�?�o�_�	���� ��c��m�?�i�~a����-���M���+���W��f����*��A��A����K�����:�$X��2"�K,%�m������LE@i�h�����c:�?>����#��w�*��N���@*�A1UWD>�%���&._�%�on����oI��kb<�4c����������N����.������Y6������sXyS@��m�D�K��(j"��a3���3������0�Y������:�����9�(�7Y/U����Jz����������x�a�7�
�r8yj&1��j[]��%^:��A:�sl��!]y�����?��[8��tq�6�F�-p��.{;"��O�C�����j� �.j��d��)�>�W��"���G6���U�Cqo��aND�O��������	�����QR��%7��^��a�I�,6���y/�?�f������yP��������l�g��:}��]�z��\jh]BZ9�+�����k�"�?}��q��js�Z�W���G�#q4h�0|�K������]�l>[��,��D�r2>������Z��xc0�����b�3h�A��&��on��I'������w���������,�y<�Y�} ���xM��xy�T+�u��f&����~����������4�*]�+�-��+�k���/���St;���)�x(U��4j�������7��V2j:^�h$����|��r�������o�?��M������t2���%���V�V���?���5�����j��\���
lb����`��:�Q��Z�V�j����o*���w������|�z���`���f4�_����7O1vP�������������M|g6j�V�
."^w����Iy���.	��xY�����+�h~�V�*H�{�_<o����o��K��l�{*����v?*h������o��Yh��gA�d���������&�S��I��Z���E` t�k��%-���IW�M�����[������g��x�����G����������v;+��B�iB1���j;���u��� �H��\,+���������� ��Q���G��B�^��@`�-q��Ty\�����7[�%7��&+?yW]o@�I�VTl�B��Z�
t�O�?�1�����������m��n�\k���eg�\~_���U�����<�~����S�R����oX�4�K���Q:�@��_�9_�x;�������0*�l���_b�>��&�(���������*
p7��p���.�1QV)K����4�c����b],�k�� ���W'��LfM��]��GGz��������P�X��y8=�u�Y�BC���M���\o��f�KsD�����$��k&��P�������$J6���M������3���K�&��m�]��N���,�	�Q� msU�f��>m�{]�E����t]�%j�L���0L9:��������N��Zg
dE|����K�,�-�V�qO�E*N�&^��s���v�`��5t"]��uQ}�rU�`��o���d*f�u7��@:��x��x0�
����?D�M�M��i�+���c��O����w���zy���e��q��<�&��ylO	��Y�3���8�G��p�`�~�j]j�K�U}
p��4-�	���-���,�Da h!W|)�X���:YQ�[(�#�}��7�"��n��VP�����Fq������)��3R�a��q������R���R�GAU��e���!-V2�K�����H�R�u��I�bTa
aI�)l��I�|}��C3��6[���v�'J�@��d[DT�V�T:��6��l�����:���b�,�C�2/��a�B��|u�f��r�m'�B��Pb>:%���#ZjB���Z�t���0'�~�c���	�%�O�^���H�h���a��X��a���/l�j
����k~�6`	`<j��5�	��0���f�x(�l�j*�2k���x(���j;��aD�%�q'S����j+�[�&�����1��
�4C�a��I�n1[�}�F4��Sd���a�~`��t��a���(���F�`$��K!@-�j��f�l5�=/%��I��S�}��t�R��Xu�Kc\zf9�x��%�n/g���-Hr��FR�|�����Rx}U�=f���P�9Qb��h���G���r�� ���~QD���,D��|�
"������=�f�)��<��G���L�z�Uwe��J���O��G�}f���c�|7o��78�a�E�����3�q1tfwSg��.���:s�v����V�o�n'$�v�����P�W%��kw'��O"��|	�z�'	9�&���[�����$z��^�w���d���LV�t�I&�����E��V��������x�*���������7/nnn�o��h��|S�{Np_��%#����R��)��*�b==����G.e�J���B*L�y��[^�0������Pr�<�{:����D:����F��H[Zf�He���1	6&��W�1�]�|��#|�������8Y��9��f�\�a���:�Z�_R[�_���54CTQF2N���_�C�q�h;���/��������/��%9�|�i=���gS���������i_%������V"D$��3�	N
+���Tp^�]��P:<j�f[�4�=�7�I|�s.�����+�����V~�;���K����O��I�'{���>r���]��M���p8�
����d:��ah��1�h` '�B��$�c�QW�:����8�������S�^��$���x]9i�����{P�Cc��8�'�����)onl �
��]�m_�m_�m�n�g�����IY���$�-�������������|y����6d��[���(=��yG�V��=���`u�z���������/������Y,�����u4���r��|�����[� _� ���W>��gq��$��1���xq������r��v2M���n���sh"�r��}1S�:(�pT��7�z-�)+G
�&4I�w�O�a\����[8�_������DH|���Kkn{����g�x�����u���U��w���8�P���c������h�7����3�5��st���|��!��9�|��p�H>�����|d����.U�9|��uQ��	�}!�Q�
����|
��n���/�����m1���w��n�.0���.�]�|�(��>c��1���q�,<�-���-�-<�=�g!~=�gy���is��g��g��C�G�������`�O�Y^x�Q�,��Z���g�B"�v����V��u����z;�Bkw�v����
���Aa=������x��+��g�����P������Ak�o�^?;]lv�3(������M�3��^<�5���6>�T���
�%��������3�����?����Jp�;s�P
S�,��=,b��lr:�����U���a���z��^��/M���	�N�[�����{��V����j��SX�F�J����}����,�W�e������Mi!�s+�l�������i"���"�]z������\���w��Wa�K�.\��� ���)�/�nAz�7XR��p��p��wQR.��-�����+���8!|����zaq�8���r������
����"�����wI3<����o����W�cQng�-����8.��g��<OY���.�*��
3XR�����=�1A�s�f�MP�(�|*�"���GZ0DO�#��j����}aK4��PS��8�]����K��P�u/�q���g�6�C�f�^PS��YfF��C�f�^P�X��#��/�;��f�^P���5Q���D�a��6���
�GM2v{;G�q��b���a�~`�}�~����X0�#I�^
C����S=[�����TE9�|���Am'�����!)g�"���P�����SA���:K=zt��w���f��k�v��(�����o��ZR���{����e!:��C�L\����u�YL�U��jO�����Rw[!x�����|��K.D��C
�_�,&u�~�'��}�M����G���-���lj��g/���yY2���������S�U�*�zz�=��������,trI������M=1�&�Q����O������5����Ut��������<e��U<�gqk`�<�gyqk`qk`qk�Q�X�i_�a_[[�iZ�B�z����O����?�����>��/��?�g���������,Y(�H��O���:
��g��t#Yzf���rg[�l�lw�u�������������{9�������������o�-�����F��Hmz��&
^wR/~{���X�;�� ;��/��]XO
�w�����;�$�=_u/.'hn�_p8A��P�g��J���QF�M'��j:��W`�\� 
o4�����x��y�����������p)����?����[��������p�{1���iO�x'����o�L��J�������!�,�l�k_����e��,�_�y���r�v'U��n�9pxr������Gp���W�A�o{���5��4*x��4�~�(��:$/>��R�����������j?O]`]X�/&�#�`�9+V��9��#��������D�Z�
#31Craig Ringer
craig@2ndquadrant.com
In reply to: Pavel Stehule (#30)
Re: patch: function xmltable

On 22 September 2016 at 02:31, Pavel Stehule <pavel.stehule@gmail.com> wrote:

another small update - fix XMLPath parser - support multibytes characters

I'm returning for another round of review.

The code doesn't handle the 5 XML built-in entities correctly in
text-typed output. It processes &apos; and &quot; but not &amp, &lt or
&gt; . See added test. I have not fixed this, but I think it's clearly
broken:

+ -- 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
+ -------
+  '
+  "
+  &amp;
+  &lt;
+  &gt;
+ (5 rows)

so I've adjusted the docs to claim that they're expanded. The code
needs fixing to avoid entity-escaping when the output column type is
not 'xml'.

&apos; and &quot; entities in xml-typed output are expanded, not
preserved. I don't know if this is intended but I suspect it is:

+ 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)

For the docs changes relevant to the above search for "The five
predefined XML entities". Adjust that bit of docs if I guessed wrong
about the intended behaviour.

The tests don't cover CDATA or PCDATA . I didn't try to add that, but
they should.

Did some docs copy-editing and integrated some examples. Explained how
nested elements work, that multiple top level elements is an error,
etc. Explained the time-of-evaluation stuff. Pointed out that you can
refer to prior output columns in PATH and DEFAULT, since that's weird
and unusual compared to normal SQL. Documented handling of multiple
node matches, including the surprising results of somepath/text() on
<somepath>x<!--blah-->y</somepath>. Documented handling of nested
elements. Documented that xmltable works only on XML documents, not
fragments/forests.

Regarding evaluation time, it struck me that evaluating path
expressions once per row means the xpath must be parsed and processed
once per row. Isn't it desirable to store and re-use the preparsed
xpath? I don't think this is a major problem, since we can later
detect stable/immutable expressions including constants, evaluate only
once in that case, and cache. It's just worth thinking about.

The docs and tests don't seem to cover XML entities. What's the
behaviour there? Core XML only defines one entity, but if a schema
defines more how are they processed? The tests need to cover the
predefined entities &quot; &amp; &apos; &lt; and &gt; at least.

I have no idea whether the current code can fetch a DTD and use any
<!ENTITY > declarations to expand entities, but I'm guessing not? If
not, external DTDs, and internal DTDs with external entities should be
documented as unsupported.

It doesn't seem to cope with internal DTDs at all (libxml2 limitation?):

SELECT * FROM xmltable('/' PASSING $XML$<?xml version="1.0" standalone="yes" ?>
<!DOCTYPE foo [
<!ELEMENT foo (#PCDATA)>
<!ENTITY pg "PostgreSQL">
]>
<foo>Hello &pg;.</foo>
$XML$ COLUMNS foo text);

+ ERROR:  invalid XML content
+ LINE 1: SELECT * FROM xmltable('/' PASSING $XML$<?xml version="1.0" ...
+                                            ^
+ DETAIL:  line 2: StartTag: invalid element name
+ <!DOCTYPE foo [
+  ^
+ line 3: StartTag: invalid element name
+   <!ELEMENT foo (#PCDATA)>
+    ^
+ line 4: StartTag: invalid element name
+   <!ENTITY pg "PostgreSQL">
+    ^
+ line 6: Entity 'pg' not defined
+ <foo>Hello &pg;.</foo>
+                ^

libxml seems to support documents with internal DTDs:

$ xmllint --valid /tmp/x
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE foo [
<!ELEMENT foo (#PCDATA)>
<!ENTITY pg "PostgreSQL">
]>
<foo>Hello &pg;.</foo>

so presumably the issue lies in the xpath stuff? Note that it's not
even ignoring the DTD and choking on the undefined entity, it's
choking on the DTD its self.

OK, code comments:

In +ExecEvalTableExpr, shouldn't you be using PG_ENSURE_ERROR_CLEANUP
instead of a PG_TRY() / PG_CATCH() block?

I think the new way you handle the type stuff is much, much better,
and with comments to explain too. Thanks very much.

There's an oversight in tableexpr vs xmltable separation here:

+        case T_TableExpr:
+            *name = "xmltable";
+            return 2;

presumably you need to look at the node and decide what kind of table
expression it is or just use a generic "tableexpr".

Same problem here:

+        case T_TableExpr:
+            {
+                TableExpr  *te = (TableExpr *) node;
+
+                /* c_expr shoud be closed in brackets */
+                appendStringInfoString(buf, "XMLTABLE(");

I don't have the libxml knowledge or remaining brain to usefully
evaluate the xpath and xml specifics in xpath.c today. It does strike
me that the new xpath parser should probably live in its own file,
though.

I think this is all a big improvement. Barring the notes above and my
lack of review of the guts of the xml.c parts of it, I'm pretty happy
with what I see now.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#32Craig Ringer
craig@2ndquadrant.com
In reply to: Craig Ringer (#31)
1 attachment(s)
Re: patch: function xmltable

Did some docs copy-editing and integrated some examples.

Whoops, forgot to attach.

Rather than sending a whole new copy of the patch, here's a diff
against your patched tree of my changes so you can see what I've done
and apply the parts you want.

Note that I didn't updated the expected files.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

docs-and-test-updates-to-xmltable-v9.patchtext/x-patch; charset=US-ASCII; name=docs-and-test-updates-to-xmltable-v9.patchDownload
From 1b0187063855519394139d9b88b2d9d696adb201 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Fri, 23 Sep 2016 16:06:51 +0800
Subject: [PATCH] Docs and test updates to xmltable

---
 doc/src/sgml/func.sgml       | 89 ++++++++++++++++++++++++++++++++++++++++----
 src/test/regress/sql/xml.sql | 22 ++++++++++-
 2 files changed, 101 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f6bead6..3292fcd 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10368,7 +10368,7 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
 </ROWS>
 ]]></screen>
 
-     following query produces result:
+     the following query produces the result:
 
 <screen><![CDATA[
 SELECT  xmltable.*
@@ -10438,7 +10438,8 @@ SELECT *
      <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.
+     <literal>AS</> aliases are not supported. The argument must be
+     a well-formed XML document; fragments/forests are not accepted.
     </para>
 
    <para>
@@ -10473,16 +10474,73 @@ SELECT *
     <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.
+     <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. It is possible for a <literal>PATH</> expression to
+     reference output columns that appear before it in the column-list, so
+     paths may be dynamically constructed based on other parts of the XML
+     document:
+     <programlisting>
+
+     </programlisting>
+    <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</>.
+     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>
@@ -10492,6 +10550,22 @@ SELECT *
      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 result 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
@@ -10502,8 +10576,7 @@ SELECT *
 
     <note>
      <para>
-      Empty tag is translated as empty string (possible attribute xsi:nil
-      has not any effect. The XPath expression in PATH clause is evaluated
+      The XPath expression in PATH clause is evaluated
       for any input row. The expression in DEFAULT expression is evaluated for
       any missing value (for any output row).
      </para>
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index b67af31..60529fc 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -393,8 +393,26 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"
 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?>bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
-SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?>bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+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
+
+-- 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);
+
+-- Undefined entities (no DTD)
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&nbsp;</ent></a></x>' COLUMNS ent text);
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&nbsp;</ent></a></x>' COLUMNS ent xml);
+
+-- Defined entities (inline DTD)
+
+SELECT * FROM xmltable('/' PASSING $XML$<?xml version="1.0" standalone="yes" ?>
+<!DOCTYPE foo [
+  <!ELEMENT foo (#PCDATA)>
+  <!ENTITY pg "PostgreSQL">
+]>
+<foo>Hello &pg;.</foo>
+$XML$ COLUMNS foo text);
 
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT  xmltable.*
-- 
2.5.5

#33Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#31)
Re: patch: function xmltable

2016-09-23 10:05 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

On 22 September 2016 at 02:31, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

another small update - fix XMLPath parser - support multibytes characters

I'm returning for another round of review.

The code doesn't handle the 5 XML built-in entities correctly in
text-typed output. It processes &apos; and &quot; but not &amp, &lt or
&gt; . See added test. I have not fixed this, but I think it's clearly
broken:

+ -- 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
+ -------
+  '
+  "
+  &amp;
+  &lt;
+  &gt;
+ (5 rows)

so I've adjusted the docs to claim that they're expanded. The code
needs fixing to avoid entity-escaping when the output column type is
not 'xml'.

&apos; and &quot; entities in xml-typed output are expanded, not
preserved. I don't know if this is intended but I suspect it is:

+ 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)

For the docs changes relevant to the above search for "The five
predefined XML entities". Adjust that bit of docs if I guessed wrong
about the intended behaviour.

The tests don't cover CDATA or PCDATA . I didn't try to add that, but
they should.

Did some docs copy-editing and integrated some examples. Explained how
nested elements work, that multiple top level elements is an error,
etc. Explained the time-of-evaluation stuff. Pointed out that you can
refer to prior output columns in PATH and DEFAULT, since that's weird
and unusual compared to normal SQL. Documented handling of multiple
node matches, including the surprising results of somepath/text() on
<somepath>x<!--blah-->y</somepath>. Documented handling of nested
elements. Documented that xmltable works only on XML documents, not
fragments/forests.

Regarding evaluation time, it struck me that evaluating path
expressions once per row means the xpath must be parsed and processed
once per row. Isn't it desirable to store and re-use the preparsed
xpath? I don't think this is a major problem, since we can later
detect stable/immutable expressions including constants, evaluate only
once in that case, and cache. It's just worth thinking about.

The docs and tests don't seem to cover XML entities. What's the
behaviour there? Core XML only defines one entity, but if a schema
defines more how are they processed? The tests need to cover the
predefined entities &quot; &amp; &apos; &lt; and &gt; at least.

I have no idea whether the current code can fetch a DTD and use any
<!ENTITY > declarations to expand entities, but I'm guessing not? If
not, external DTDs, and internal DTDs with external entities should be
documented as unsupported.

It doesn't seem to cope with internal DTDs at all (libxml2 limitation?):

SELECT * FROM xmltable('/' PASSING $XML$<?xml version="1.0"
standalone="yes" ?>
<!DOCTYPE foo [
<!ELEMENT foo (#PCDATA)>
<!ENTITY pg "PostgreSQL">
]>
<foo>Hello &pg;.</foo>
$XML$ COLUMNS foo text);

+ ERROR:  invalid XML content
+ LINE 1: SELECT * FROM xmltable('/' PASSING $XML$<?xml version="1.0" ...
+                                            ^
+ DETAIL:  line 2: StartTag: invalid element name
+ <!DOCTYPE foo [
+  ^
+ line 3: StartTag: invalid element name
+   <!ELEMENT foo (#PCDATA)>
+    ^
+ line 4: StartTag: invalid element name
+   <!ENTITY pg "PostgreSQL">
+    ^
+ line 6: Entity 'pg' not defined
+ <foo>Hello &pg;.</foo>
+                ^

libxml seems to support documents with internal DTDs:

$ xmllint --valid /tmp/x
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE foo [
<!ELEMENT foo (#PCDATA)>
<!ENTITY pg "PostgreSQL">
]>
<foo>Hello &pg;.</foo>

so presumably the issue lies in the xpath stuff? Note that it's not
even ignoring the DTD and choking on the undefined entity, it's
choking on the DTD its self.

OK, code comments:

In +ExecEvalTableExpr, shouldn't you be using PG_ENSURE_ERROR_CLEANUP
instead of a PG_TRY() / PG_CATCH() block?

I think the new way you handle the type stuff is much, much better,
and with comments to explain too. Thanks very much.

There's an oversight in tableexpr vs xmltable separation here:

+        case T_TableExpr:
+            *name = "xmltable";
+            return 2;

presumably you need to look at the node and decide what kind of table
expression it is or just use a generic "tableexpr".

Same problem here:

+        case T_TableExpr:
+            {
+                TableExpr  *te = (TableExpr *) node;
+
+                /* c_expr shoud be closed in brackets */
+                appendStringInfoString(buf, "XMLTABLE(");

This is correct, but not well commented - looks on XMLEXPR node - TableExpr
is a holder, but it is invisible for user. User running a XMLTABLE function
and should to see XMLTABLE. It will be more clean when we will support
JSON_TABLE function.

I don't have the libxml knowledge or remaining brain to usefully
evaluate the xpath and xml specifics in xpath.c today. It does strike
me that the new xpath parser should probably live in its own file,
though.

I'll try move it to separate file

I think this is all a big improvement. Barring the notes above and my
lack of review of the guts of the xml.c parts of it, I'm pretty happy
with what I see now.

Thank you. I hope so all major issues are solved. Probably some XML
specific related issues are there - but I am happy, so you have well XML
knowledge and you will test a corner cases.

Regards

Pavel

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#34Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#32)
Re: patch: function xmltable

Hi

2016-09-23 10:07 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

Did some docs copy-editing and integrated some examples.

Whoops, forgot to attach.

Rather than sending a whole new copy of the patch, here's a diff
against your patched tree of my changes so you can see what I've done
and apply the parts you want.

Note that I didn't updated the expected files.

I applied your patch - there is small misunderstanding. The PATH is
evaluated once for input row already. It is not clean in code, because it
is executor node started and running for all rows. I changed it in your
part of doc.

   to a simple value before calling the function. <literal>PATH</>
+      expressions are normally evaluated <emphasis>exactly once per result
row ## per input row
+      </emphasis>,

Regards

Pavel

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#35Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#31)
2 attachment(s)
Re: patch: function xmltable

Hi

2016-09-23 10:05 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

On 22 September 2016 at 02:31, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

another small update - fix XMLPath parser - support multibytes characters

I'm returning for another round of review.

The code doesn't handle the 5 XML built-in entities correctly in
text-typed output. It processes &apos; and &quot; but not &amp, &lt or
&gt; . See added test. I have not fixed this, but I think it's clearly
broken:

+ -- 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
+ -------
+  '
+  "
+  &amp;
+  &lt;
+  &gt;
+ (5 rows)

so I've adjusted the docs to claim that they're expanded. The code
needs fixing to avoid entity-escaping when the output column type is
not 'xml'.

fixed

&apos; and &quot; entities in xml-typed output are expanded, not
preserved. I don't know if this is intended but I suspect it is:

+ 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)

For the docs changes relevant to the above search for "The five
predefined XML entities". Adjust that bit of docs if I guessed wrong
about the intended behaviour.

The tests don't cover CDATA or PCDATA . I didn't try to add that, but
they should.

appended

Did some docs copy-editing and integrated some examples. Explained how
nested elements work, that multiple top level elements is an error,
etc. Explained the time-of-evaluation stuff. Pointed out that you can
refer to prior output columns in PATH and DEFAULT, since that's weird
and unusual compared to normal SQL. Documented handling of multiple
node matches, including the surprising results of somepath/text() on
<somepath>x<!--blah-->y</somepath>. Documented handling of nested
elements. Documented that xmltable works only on XML documents, not
fragments/forests.

I don't understand to this sentence: "It is possible for a PATH expression
to reference output columns that appear before it in the column-list, so
paths may be dynamically constructed based on other parts of the XML
document:"

Regarding evaluation time, it struck me that evaluating path
expressions once per row means the xpath must be parsed and processed
once per row. Isn't it desirable to store and re-use the preparsed
xpath? I don't think this is a major problem, since we can later
detect stable/immutable expressions including constants, evaluate only
once in that case, and cache. It's just worth thinking about.

Probably it is possible - it is exactly how you wrote - it needs to check
the change. We can try do some possible performance optimizations later -
without compatibility issues. Now, I prefer the most simple code.

a note: PATH expression is evaluated for any **input** row. In same moment
is processed row path expression and man XML document DOM parsing. So
overhead of PATH expression and PATH parsing should not be dominant.

The docs and tests don't seem to cover XML entities. What's the
behaviour there? Core XML only defines one entity, but if a schema
defines more how are they processed? The tests need to cover the
predefined entities &quot; &amp; &apos; &lt; and &gt; at least.

I don't understand, what you are propose here. ?? Please, can you send some
examples.

I have no idea whether the current code can fetch a DTD and use any
<!ENTITY > declarations to expand entities, but I'm guessing not? If
not, external DTDs, and internal DTDs with external entities should be
documented as unsupported.

It doesn't seem to cope with internal DTDs at all (libxml2 limitation?):

SELECT * FROM xmltable('/' PASSING $XML$<?xml version="1.0"
standalone="yes" ?>
<!DOCTYPE foo [
<!ELEMENT foo (#PCDATA)>
<!ENTITY pg "PostgreSQL">
]>
<foo>Hello &pg;.</foo>
$XML$ COLUMNS foo text);

+ ERROR:  invalid XML content
+ LINE 1: SELECT * FROM xmltable('/' PASSING $XML$<?xml version="1.0" ...
+                                            ^
+ DETAIL:  line 2: StartTag: invalid element name
+ <!DOCTYPE foo [
+  ^
+ line 3: StartTag: invalid element name
+   <!ELEMENT foo (#PCDATA)>
+    ^
+ line 4: StartTag: invalid element name
+   <!ENTITY pg "PostgreSQL">
+    ^
+ line 6: Entity 'pg' not defined
+ <foo>Hello &pg;.</foo>
+                ^

It is rejected before XMLTABLE function call

postgres=# select $XML$<?xml version="1.0" standalone="yes" ?>
postgres$# <!DOCTYPE foo [
postgres$# <!ELEMENT foo (#PCDATA)>
postgres$# <!ENTITY pg "PostgreSQL">
postgres$# ]>
postgres$# <foo>Hello &pg;.</foo>
postgres$# $XML$::xml;
ERROR: invalid XML content
LINE 1: select $XML$<?xml version="1.0" standalone="yes" ?>
^
DETAIL: line 2: StartTag: invalid element name
<!DOCTYPE foo [
^
line 3: StartTag: invalid element name
<!ELEMENT foo (#PCDATA)>
^
line 4: StartTag: invalid element name
<!ENTITY pg "PostgreSQL">
^
line 6: Entity 'pg' not defined
<foo>Hello &pg;.</foo>
^
It is disabled by default in libxml2. I found a function
xmlSubstituteEntitiesDefault http://www.xmlsoft.org/entities.html
http://www.xmlsoft.org/html/libxml-parser.html#xmlSubstituteEntitiesDefault

The default behave should be common for all PostgreSQL's libxml2 based
function - and then it is different topic - maybe part for PostgreSQL ToDo?
But I don't remember any user requests related to this issue.

libxml seems to support documents with internal DTDs:

$ xmllint --valid /tmp/x
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE foo [
<!ELEMENT foo (#PCDATA)>
<!ENTITY pg "PostgreSQL">
]>
<foo>Hello &pg;.</foo>

I removed this tests - it is not related to XMLTABLE function, but to
generic XML processing/validation.

so presumably the issue lies in the xpath stuff? Note that it's not
even ignoring the DTD and choking on the undefined entity, it's
choking on the DTD its self.

OK, code comments:

In +ExecEvalTableExpr, shouldn't you be using PG_ENSURE_ERROR_CLEANUP
instead of a PG_TRY() / PG_CATCH() block?

If I understand to doc, the PG_ENSURE_ERROR_CLEANUP should be used, when
you want to catch FATAL errors (and when you want to clean shared memory).
XMLTABLE doesn't use shared memory, and doesn't need to catch fatal errors.

I think the new way you handle the type stuff is much, much better,
and with comments to explain too. Thanks very much.

There's an oversight in tableexpr vs xmltable separation here:

+        case T_TableExpr:
+            *name = "xmltable";
+            return 2;

presumably you need to look at the node and decide what kind of table
expression it is or just use a generic "tableexpr".

Same problem here:

+        case T_TableExpr:
+            {
+                TableExpr  *te = (TableExpr *) node;
+
+                /* c_expr shoud be closed in brackets */
+                appendStringInfoString(buf, "XMLTABLE(");

commented

I don't have the libxml knowledge or remaining brain to usefully
evaluate the xpath and xml specifics in xpath.c today. It does strike
me that the new xpath parser should probably live in its own file,
though.

moved

I think this is all a big improvement. Barring the notes above and my
lack of review of the guts of the xml.c parts of it, I'm pretty happy
with what I see now.

new version is attached

Regards

Pavel

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

xmltable-10.patch.gzapplication/gzip; name=xmltable-10.patch.gzDownload
xmltable-diff10.difftext/x-patch; charset=US-ASCII; name=xmltable-diff10.diffDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f6bead6..92259fd 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10368,7 +10368,7 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
 </ROWS>
 ]]></screen>
 
-     following query produces result:
+     the following query produces the result:
 
 <screen><![CDATA[
 SELECT  xmltable.*
@@ -10438,7 +10438,8 @@ SELECT *
      <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.
+     <literal>AS</> aliases are not supported. The argument must be
+     a well-formed XML document; fragments/forests are not accepted.
     </para>
 
    <para>
@@ -10473,16 +10474,73 @@ SELECT *
     <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.
+     <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. It is possible for a <literal>PATH</> expression to
+     reference output columns that appear before it in the column-list, so
+     paths may be dynamically constructed based on other parts of the XML
+     document:
+     <programlisting>
+
+     </programlisting>
+    </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</>.
+     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>
@@ -10492,22 +10550,29 @@ SELECT *
      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>
-
-    <note>
-     <para>
-      Empty tag is translated as empty string (possible attribute xsi:nil
-      has not any effect. The XPath expression in PATH clause is evaluated
-      for any input row. The expression in DEFAULT expression is evaluated for
-      any missing value (for any output row).
-     </para>
-    </note>
+    </para>
    </sect3>
 
    <sect3 id="functions-xpath" xreflabel="XPATH">
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 7ce209d..4a345c4 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1838,6 +1838,10 @@ FigureColnameInternal(Node *node, char **name)
 			*name = "xmlserialize";
 			return 2;
 		case T_TableExpr:
+			/*
+			 * Make TableExpr act like a regular function. Only
+			 * XMLTABLE expr is supported in this moment.
+			 */
 			*name = "xmltable";
 			return 2;
 		default:
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 0f51275..5a3715c 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -29,7 +29,7 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
 	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
 	tsvector.o tsvector_op.o tsvector_parser.o \
 	txid.o uuid.o varbit.o varchar.o varlena.o version.o \
-	windowfuncs.o xid.o xml.o
+	windowfuncs.o xid.o xml.o xpath_parser.o
 
 like.o: like.c like_match.c
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3930452..7065415 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8285,6 +8285,11 @@ get_rule_expr(Node *node, deparse_context *context,
 			{
 				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(");
 
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index c931cee..aacbfe4 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -90,7 +90,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/xml.h"
-
+#include "utils/xpath_parser.h"
 
 /* GUC variables */
 int			xmlbinary;
@@ -4102,310 +4102,6 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 #ifdef USE_LIBXML
 
 /*
- * We need to work with XPath expression tokens. When expression
- * starting or finishing with nodenames, then we can use prefix
- * and suffix. When default namespace is defined, then we should to
- * enhance any nodename and attribute without namespace by default
- * namespace. The procession of XPath expression is linear.
- */
-
-typedef enum
-{
-	XPATH_TOKEN_NONE,
-	XPATH_TOKEN_NAME,
-	XPATH_TOKEN_STRING,
-	XPATH_TOKEN_NUMBER,
-	XPATH_TOKEN_OTHER
-}	XPathTokenType;
-
-typedef struct TokenInfo
-{
-	XPathTokenType ttype;
-	char	   *start;
-	int			length;
-}	XPathTokenInfo;
-
-#define TOKEN_STACK_SIZE		10
-
-typedef struct ParserData
-{
-	char	   *str;
-	char	   *cur;
-	XPathTokenInfo stack[TOKEN_STACK_SIZE];
-	int			stack_length;
-}	XPathParserData;
-
-/* Any high-bit-set character is OK (might be part of a multibyte char) */
-#define NODENAME_FIRSTCHAR(c)	 ((c) == '_' || (c) == '-' || \
-								 ((c) >= 'A' && (c) <= 'Z') || \
-								 ((c) >= 'a' && (c) <= 'z') || \
-								 (IS_HIGHBIT_SET(c)))
-
-#define IS_NODENAME_CHAR(c)		(NODENAME_FIRSTCHAR(c) || (c) == '.' || \
-								 ((c) >= '0' && (c) <= '9'))
-
-
-/*
- * Returns next char after last char of token
- */
-static char *
-getXPathToken(char *str, XPathTokenInfo * ti)
-{
-	/* skip initial spaces */
-	while (*str == ' ')
-		str++;
-
-	if (*str != '\0')
-	{
-		char		c = *str;
-
-		ti->start = str++;
-
-		if (c >= '0' && c <= '9')
-		{
-			while (*str >= '0' && *str <= '9')
-				str++;
-			if (*str == '.')
-			{
-				str++;
-				while (*str >= '0' && *str <= '9')
-					str++;
-			}
-			ti->ttype = XPATH_TOKEN_NUMBER;
-		}
-		else if (NODENAME_FIRSTCHAR(c))
-		{
-			while (IS_NODENAME_CHAR(*str))
-				str++;
-
-			ti->ttype = XPATH_TOKEN_NAME;
-		}
-		else if (c == '"')
-		{
-			while (*str != '\0')
-				if (*str++ == '"')
-					break;
-
-			ti->ttype = XPATH_TOKEN_STRING;
-		}
-		else
-			ti->ttype = XPATH_TOKEN_OTHER;
-
-		ti->length = str - ti->start;
-	}
-	else
-	{
-		ti->start = NULL;
-		ti->length = 0;
-
-		ti->ttype = XPATH_TOKEN_NONE;
-	}
-
-	return str;
-}
-
-/*
- * reset XPath parser stack
- */
-static void
-initXPathParser(XPathParserData * parser, char *str)
-{
-	parser->str = str;
-	parser->cur = str;
-	parser->stack_length = 0;
-}
-
-/*
- * Returns token from stack or read token
- */
-static void
-nextXPathToken(XPathParserData * parser, XPathTokenInfo * ti)
-{
-	if (parser->stack_length > 0)
-		memcpy(ti, &parser->stack[--parser->stack_length],
-			   sizeof(XPathTokenInfo));
-	else
-		parser->cur = getXPathToken(parser->cur, ti);
-}
-
-/*
- * Push token to stack
- */
-static void
-pushXPathToken(XPathParserData * parser, XPathTokenInfo * ti)
-{
-	if (parser->stack_length == TOKEN_STACK_SIZE)
-		elog(ERROR, "internal error");
-	memcpy(&parser->stack[parser->stack_length++], ti,
-		   sizeof(XPathTokenInfo));
-}
-
-/*
- * Write token to output string
- */
-static void
-writeXPathToken(StringInfo str, XPathTokenInfo * ti)
-{
-	Assert(ti->ttype != XPATH_TOKEN_NONE);
-
-	if (ti->ttype != XPATH_TOKEN_OTHER)
-		appendBinaryStringInfo(str, ti->start, ti->length);
-	else
-		appendStringInfoChar(str, *ti->start);
-}
-
-/*
- * Working horse for XPath transformation. When XPath starting by node name,
- * then prefix have to be applied. Any unqualified node name should be
- * qualified by default namespace. inside_predicate is true, when
- * _transformXPath is recursivly called because the predicate expression
- * was found.
- */
-static void
-_transformXPath(StringInfo str, XPathParserData * parser,
-				bool inside_predicate,
-				char *prefix, char *def_namespace_name)
-{
-	XPathTokenInfo t1,
-				t2;
-	bool		is_first_token = true;
-	bool		last_token_is_name = false;
-
-	nextXPathToken(parser, &t1);
-
-	while (t1.ttype != XPATH_TOKEN_NONE)
-	{
-		switch (t1.ttype)
-		{
-			case XPATH_TOKEN_NUMBER:
-			case XPATH_TOKEN_STRING:
-				last_token_is_name = false;
-				is_first_token = false;
-				writeXPathToken(str, &t1);
-				nextXPathToken(parser, &t1);
-				break;
-
-			case XPATH_TOKEN_NAME:
-				{
-					bool		is_qual_name = false;
-
-					/* inside predicate ignore keywords "and" "or" */
-					if (inside_predicate)
-					{
-						if ((strncmp(t1.start, "and", 3) == 0 && t1.length == 3) ||
-						 (strncmp(t1.start, "or", 2) == 0 && t1.length == 2))
-						{
-							writeXPathToken(str, &t1);
-							nextXPathToken(parser, &t1);
-							break;
-						}
-					}
-
-					last_token_is_name = true;
-					nextXPathToken(parser, &t2);
-					if (t2.ttype == XPATH_TOKEN_OTHER)
-					{
-						if (*t2.start == '(')
-							last_token_is_name = false;
-						else if (*t2.start == ':')
-							is_qual_name = true;
-					}
-
-					if (is_first_token && last_token_is_name && prefix != NULL)
-						appendStringInfoString(str, prefix);
-
-					if (last_token_is_name && !is_qual_name && def_namespace_name != NULL)
-						appendStringInfo(str, "%s:", def_namespace_name);
-
-					writeXPathToken(str, &t1);
-					is_first_token = false;
-
-					if (is_qual_name)
-					{
-						writeXPathToken(str, &t2);
-						nextXPathToken(parser, &t1);
-						if (t1.ttype == XPATH_TOKEN_NAME)
-							writeXPathToken(str, &t1);
-						else
-							pushXPathToken(parser, &t1);
-					}
-					else
-						pushXPathToken(parser, &t2);
-
-					nextXPathToken(parser, &t1);
-				}
-				break;
-
-			case XPATH_TOKEN_OTHER:
-				{
-					char		c = *t1.start;
-
-					is_first_token = false;
-
-					writeXPathToken(str, &t1);
-
-					if (c == '[')
-						_transformXPath(str, parser, true, NULL, def_namespace_name);
-					else
-					{
-						last_token_is_name = false;
-
-						if (c == ']' && inside_predicate)
-							return;
-
-						else if (c == '@')
-						{
-							nextXPathToken(parser, &t1);
-							if (t1.ttype == XPATH_TOKEN_NAME)
-							{
-								bool		is_qual_name = false;
-
-								nextXPathToken(parser, &t2);
-								if (t2.ttype == XPATH_TOKEN_OTHER && *t2.start == ':')
-									is_qual_name = true;
-
-								if (!is_qual_name && def_namespace_name != NULL)
-									appendStringInfo(str, "%s:", def_namespace_name);
-
-								writeXPathToken(str, &t1);
-								if (is_qual_name)
-								{
-									writeXPathToken(str, &t2);
-									nextXPathToken(parser, &t1);
-									if (t1.ttype == XPATH_TOKEN_NAME)
-										writeXPathToken(str, &t1);
-									else
-										pushXPathToken(parser, &t1);
-								}
-								else
-									pushXPathToken(parser, &t2);
-							}
-							else
-								pushXPathToken(parser, &t1);
-						}
-					}
-					nextXPathToken(parser, &t1);
-				}
-				break;
-
-			case XPATH_TOKEN_NONE:
-				elog(ERROR, "should not be here");
-		}
-	}
-}
-
-static void
-transformXPath(StringInfo str, char *xpath,
-			   char *prefix, char *def_namespace_name)
-{
-	XPathParserData parser;
-
-	initStringInfo(str);
-	initXPathParser(&parser, xpath);
-	_transformXPath(str, &parser, false, prefix, def_namespace_name);
-}
-
-/*
  * Functions for XmlTableBuilder
  *
  */
@@ -4840,7 +4536,8 @@ XmlTableGetValue(void *tcontext, int colnum, bool *isnull)
 							{
 								PG_TRY();
 								{
-									cstr = escape_xml((char *) str);
+									/* Copy string to PostgreSQL controlled memory */
+									cstr = pstrdup((char *) str);
 								}
 								PG_CATCH();
 								{
@@ -4848,14 +4545,14 @@ XmlTableGetValue(void *tcontext, int colnum, bool *isnull)
 									PG_RE_THROW();
 								}
 								PG_END_TRY();
+
+								xmlFree(str);
 							}
 							else
 							{
 								/* Return empty string when tag is empty */
 								cstr = pstrdup("");
 							}
-
-							xmlFree(str);
 						}
 						else
 						{
diff --git a/src/backend/utils/adt/xpath_parser.c b/src/backend/utils/adt/xpath_parser.c
new file mode 100644
index 0000000..a38798b
--- /dev/null
+++ b/src/backend/utils/adt/xpath_parser.c
@@ -0,0 +1,340 @@
+/*-------------------------------------------------------------------------
+ *
+ * xpath_parser.c
+ *	  XML XPath parser.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/utils/adt/xpath_parser.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * All PostgreSQL xml related functionality is based on libxml2 library.
+ * XPath support is not a exception. But libxml2 doesn't provide all functions
+ * necessary for implementation of XMLTABLE function. There is not API for
+ * access to XPath expression AST (abstract syntax tree), and there is not
+ * support for default namespaces in XPath expressions.
+ *
+ * These requests are implemented with simple XPath parser/preprocessor.
+ * This XPath parser transform XPath expression to another XPath expression
+ * used in libxml2 XPath evaluation. It doesn't replace libxml2 XPath parser
+ * or libxml2 XPath expression evaluation. Currently libxml2 is stable, but
+ * without any movement in implementation new or missing features.
+ */
+#include "postgres.h"
+#include "utils/xpath_parser.h"
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * We need to work with XPath expression tokens. When expression
+ * starting with nodename, then we can use prefix. When default
+ * namespace is defined, then we should to enhance any nodename
+ * and attribute without namespace by default namespace.
+ */
+
+typedef enum
+{
+	XPATH_TOKEN_NONE,
+	XPATH_TOKEN_NAME,
+	XPATH_TOKEN_STRING,
+	XPATH_TOKEN_NUMBER,
+	XPATH_TOKEN_OTHER
+}	XPathTokenType;
+
+typedef struct TokenInfo
+{
+	XPathTokenType ttype;
+	char	   *start;
+	int			length;
+}	XPathTokenInfo;
+
+#define TOKEN_STACK_SIZE		10
+
+typedef struct ParserData
+{
+	char	   *str;
+	char	   *cur;
+	XPathTokenInfo stack[TOKEN_STACK_SIZE];
+	int			stack_length;
+}	XPathParserData;
+
+/* Any high-bit-set character is OK (might be part of a multibyte char) */
+#define NODENAME_FIRSTCHAR(c)	 ((c) == '_' || (c) == '-' || \
+								 ((c) >= 'A' && (c) <= 'Z') || \
+								 ((c) >= 'a' && (c) <= 'z') || \
+								 (IS_HIGHBIT_SET(c)))
+
+#define IS_NODENAME_CHAR(c)		(NODENAME_FIRSTCHAR(c) || (c) == '.' || \
+								 ((c) >= '0' && (c) <= '9'))
+
+
+/*
+ * Returns next char after last char of token
+ */
+static char *
+getXPathToken(char *str, XPathTokenInfo * ti)
+{
+	/* skip initial spaces */
+	while (*str == ' ')
+		str++;
+
+	if (*str != '\0')
+	{
+		char		c = *str;
+
+		ti->start = str++;
+
+		if (c >= '0' && c <= '9')
+		{
+			while (*str >= '0' && *str <= '9')
+				str++;
+			if (*str == '.')
+			{
+				str++;
+				while (*str >= '0' && *str <= '9')
+					str++;
+			}
+			ti->ttype = XPATH_TOKEN_NUMBER;
+		}
+		else if (NODENAME_FIRSTCHAR(c))
+		{
+			while (IS_NODENAME_CHAR(*str))
+				str++;
+
+			ti->ttype = XPATH_TOKEN_NAME;
+		}
+		else if (c == '"')
+		{
+			while (*str != '\0')
+				if (*str++ == '"')
+					break;
+
+			ti->ttype = XPATH_TOKEN_STRING;
+		}
+		else
+			ti->ttype = XPATH_TOKEN_OTHER;
+
+		ti->length = str - ti->start;
+	}
+	else
+	{
+		ti->start = NULL;
+		ti->length = 0;
+
+		ti->ttype = XPATH_TOKEN_NONE;
+	}
+
+	return str;
+}
+
+/*
+ * reset XPath parser stack
+ */
+static void
+initXPathParser(XPathParserData * parser, char *str)
+{
+	parser->str = str;
+	parser->cur = str;
+	parser->stack_length = 0;
+}
+
+/*
+ * Returns token from stack or read token
+ */
+static void
+nextXPathToken(XPathParserData * parser, XPathTokenInfo * ti)
+{
+	if (parser->stack_length > 0)
+		memcpy(ti, &parser->stack[--parser->stack_length],
+			   sizeof(XPathTokenInfo));
+	else
+		parser->cur = getXPathToken(parser->cur, ti);
+}
+
+/*
+ * Push token to stack
+ */
+static void
+pushXPathToken(XPathParserData * parser, XPathTokenInfo * ti)
+{
+	if (parser->stack_length == TOKEN_STACK_SIZE)
+		elog(ERROR, "internal error");
+	memcpy(&parser->stack[parser->stack_length++], ti,
+		   sizeof(XPathTokenInfo));
+}
+
+/*
+ * Write token to output string
+ */
+static void
+writeXPathToken(StringInfo str, XPathTokenInfo * ti)
+{
+	Assert(ti->ttype != XPATH_TOKEN_NONE);
+
+	if (ti->ttype != XPATH_TOKEN_OTHER)
+		appendBinaryStringInfo(str, ti->start, ti->length);
+	else
+		appendStringInfoChar(str, *ti->start);
+}
+
+/*
+ * Working horse for XPath transformation. When XPath starting by node name,
+ * then prefix have to be applied. Any unqualified node name should be
+ * qualified by default namespace. inside_predicate is true, when
+ * _transformXPath is recursivly called because the predicate expression
+ * was found.
+ */
+static void
+_transformXPath(StringInfo str, XPathParserData * parser,
+				bool inside_predicate,
+				char *prefix, char *def_namespace_name)
+{
+	XPathTokenInfo t1,
+				t2;
+	bool		is_first_token = true;
+	bool		last_token_is_name = false;
+
+	nextXPathToken(parser, &t1);
+
+	while (t1.ttype != XPATH_TOKEN_NONE)
+	{
+		switch (t1.ttype)
+		{
+			case XPATH_TOKEN_NUMBER:
+			case XPATH_TOKEN_STRING:
+				last_token_is_name = false;
+				is_first_token = false;
+				writeXPathToken(str, &t1);
+				nextXPathToken(parser, &t1);
+				break;
+
+			case XPATH_TOKEN_NAME:
+				{
+					bool		is_qual_name = false;
+
+					/* inside predicate ignore keywords "and" "or" */
+					if (inside_predicate)
+					{
+						if ((strncmp(t1.start, "and", 3) == 0 && t1.length == 3) ||
+						 (strncmp(t1.start, "or", 2) == 0 && t1.length == 2))
+						{
+							writeXPathToken(str, &t1);
+							nextXPathToken(parser, &t1);
+							break;
+						}
+					}
+
+					last_token_is_name = true;
+					nextXPathToken(parser, &t2);
+					if (t2.ttype == XPATH_TOKEN_OTHER)
+					{
+						if (*t2.start == '(')
+							last_token_is_name = false;
+						else if (*t2.start == ':')
+							is_qual_name = true;
+					}
+
+					if (is_first_token && last_token_is_name && prefix != NULL)
+						appendStringInfoString(str, prefix);
+
+					if (last_token_is_name && !is_qual_name && def_namespace_name != NULL)
+						appendStringInfo(str, "%s:", def_namespace_name);
+
+					writeXPathToken(str, &t1);
+					is_first_token = false;
+
+					if (is_qual_name)
+					{
+						writeXPathToken(str, &t2);
+						nextXPathToken(parser, &t1);
+						if (t1.ttype == XPATH_TOKEN_NAME)
+							writeXPathToken(str, &t1);
+						else
+							pushXPathToken(parser, &t1);
+					}
+					else
+						pushXPathToken(parser, &t2);
+
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_OTHER:
+				{
+					char		c = *t1.start;
+
+					is_first_token = false;
+
+					writeXPathToken(str, &t1);
+
+					if (c == '[')
+						_transformXPath(str, parser, true, NULL, def_namespace_name);
+					else
+					{
+						last_token_is_name = false;
+
+						if (c == ']' && inside_predicate)
+							return;
+
+						else if (c == '@')
+						{
+							nextXPathToken(parser, &t1);
+							if (t1.ttype == XPATH_TOKEN_NAME)
+							{
+								bool		is_qual_name = false;
+
+								nextXPathToken(parser, &t2);
+								if (t2.ttype == XPATH_TOKEN_OTHER && *t2.start == ':')
+									is_qual_name = true;
+
+								if (!is_qual_name && def_namespace_name != NULL)
+									appendStringInfo(str, "%s:", def_namespace_name);
+
+								writeXPathToken(str, &t1);
+								if (is_qual_name)
+								{
+									writeXPathToken(str, &t2);
+									nextXPathToken(parser, &t1);
+									if (t1.ttype == XPATH_TOKEN_NAME)
+										writeXPathToken(str, &t1);
+									else
+										pushXPathToken(parser, &t1);
+								}
+								else
+									pushXPathToken(parser, &t2);
+							}
+							else
+								pushXPathToken(parser, &t1);
+						}
+					}
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_NONE:
+				elog(ERROR, "should not be here");
+		}
+	}
+}
+
+void
+transformXPath(StringInfo str, char *xpath,
+			   char *prefix, char *def_namespace_name)
+{
+	XPathParserData parser;
+
+	initStringInfo(str);
+	initXPathParser(&parser, xpath);
+	_transformXPath(str, &parser, false, prefix, def_namespace_name);
+}
+
+#endif
diff --git a/src/include/utils/xpath_parser.h b/src/include/utils/xpath_parser.h
new file mode 100644
index 0000000..c6fc532
--- /dev/null
+++ b/src/include/utils/xpath_parser.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * xpath_parser.h
+ *	  Declarations for XML XPath transformation.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/xml.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef XPATH_PARSER_H
+#define XPATH_PARSER_H
+
+#include "postgres.h"
+#include "lib/stringinfo.h"
+
+void transformXPath(StringInfo str, char *xpath,
+			   char *prefix, char *def_namespace_name);
+
+#endif   /* XPATH_PARSER_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 8ea477f..c8ac437 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1266,14 +1266,43 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"
   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?>bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
-    element     
-----------------
- a1aa2abbbbcccc
+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?>bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+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,
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index a185ef7..83909b9 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -1058,18 +1058,38 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"
 ----+--------------+-----------+---------
 (0 rows)
 
-SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?>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);
 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?>bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+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,
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 6e522a6..358fed4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -1245,14 +1245,43 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"
   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?>bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
-    element     
-----------------
- a1aa2abbbbcccc
+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?>bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+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,
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index b67af31..49f6bc4 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -393,8 +393,15 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"
 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?>bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
-SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?>bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+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.*
#36Craig Ringer
craig@2ndquadrant.com
In reply to: Pavel Stehule (#35)
Re: patch: function xmltable

On 24 September 2016 at 14:01, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Did some docs copy-editing and integrated some examples. Explained how
nested elements work, that multiple top level elements is an error,
etc. Explained the time-of-evaluation stuff. Pointed out that you can
refer to prior output columns in PATH and DEFAULT, since that's weird
and unusual compared to normal SQL. Documented handling of multiple
node matches, including the surprising results of somepath/text() on
<somepath>x<!--blah-->y</somepath>. Documented handling of nested
elements. Documented that xmltable works only on XML documents, not
fragments/forests.

I don't understand to this sentence: "It is possible for a PATH expression
to reference output columns that appear before it in the column-list, so
paths may be dynamically constructed based on other parts of the XML
document:"

The docs and tests don't seem to cover XML entities. What's the
behaviour there? Core XML only defines one entity, but if a schema
defines more how are they processed? The tests need to cover the
predefined entities &quot; &amp; &apos; &lt; and &gt; at least.

I don't understand, what you are propose here. ?? Please, can you send some
examples.

Per below - handling of DTD <!ENTITY> declarations, and the builtin
entity tests I already added tests for.

It doesn't seem to cope with internal DTDs at all (libxml2 limitation?):

SELECT * FROM xmltable('/' PASSING $XML$<?xml version="1.0"
standalone="yes" ?>
<!DOCTYPE foo [
<!ELEMENT foo (#PCDATA)>
<!ENTITY pg "PostgreSQL">
]>
<foo>Hello &pg;.</foo>
$XML$ COLUMNS foo text);

+ ERROR:  invalid XML content
+ LINE 1: SELECT * FROM xmltable('/' PASSING $XML$<?xml version="1.0" ...
+                                            ^
+ DETAIL:  line 2: StartTag: invalid element name
+ <!DOCTYPE foo [
+  ^
+ line 3: StartTag: invalid element name
+   <!ELEMENT foo (#PCDATA)>
+    ^
+ line 4: StartTag: invalid element name
+   <!ENTITY pg "PostgreSQL">
+    ^
+ line 6: Entity 'pg' not defined
+ <foo>Hello &pg;.</foo>
+                ^

It is rejected before XMLTABLE function call

postgres=# select $XML$<?xml version="1.0" standalone="yes" ?>
postgres$# <!DOCTYPE foo [
postgres$# <!ELEMENT foo (#PCDATA)>
postgres$# <!ENTITY pg "PostgreSQL">
postgres$# ]>
postgres$# <foo>Hello &pg;.</foo>
postgres$# $XML$::xml;
ERROR: invalid XML content
LINE 1: select $XML$<?xml version="1.0" standalone="yes" ?>
^
DETAIL: line 2: StartTag: invalid element name
<!DOCTYPE foo [

[snip]

It is disabled by default in libxml2. I found a function
xmlSubstituteEntitiesDefault http://www.xmlsoft.org/entities.html
http://www.xmlsoft.org/html/libxml-parser.html#xmlSubstituteEntitiesDefault

The default behave should be common for all PostgreSQL's libxml2 based
function - and then it is different topic - maybe part for PostgreSQL ToDo?
But I don't remember any user requests related to this issue.

OK, so it's not xmltable specific. Fine by me.

Somebody who cares can deal with it. There's clearly nobody breaking
down the walls wanting the feature.

I removed this tests - it is not related to XMLTABLE function, but to
generic XML processing/validation.

Good plan.

In +ExecEvalTableExpr, shouldn't you be using PG_ENSURE_ERROR_CLEANUP
instead of a PG_TRY() / PG_CATCH() block?

If I understand to doc, the PG_ENSURE_ERROR_CLEANUP should be used, when you
want to catch FATAL errors (and when you want to clean shared memory).
XMLTABLE doesn't use shared memory, and doesn't need to catch fatal errors.

Ok, makes sense.

I don't have the libxml knowledge or remaining brain to usefully
evaluate the xpath and xml specifics in xpath.c today. It does strike
me that the new xpath parser should probably live in its own file,
though.

moved

Thanks.

new version is attached

Great.

I'm marking this ready for committer at this point.

I think the XML parser likely needs a more close reading, so I'll ping
Peter E to see if he'll have a chance to check that bit out. But by
and large I think the issues have been ironed out - in terms of
functionality, structure and clarity I think it's looking solid.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#36)
Re: patch: function xmltable

2016-09-27 3:34 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

On 24 September 2016 at 14:01, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Did some docs copy-editing and integrated some examples. Explained how
nested elements work, that multiple top level elements is an error,
etc. Explained the time-of-evaluation stuff. Pointed out that you can
refer to prior output columns in PATH and DEFAULT, since that's weird
and unusual compared to normal SQL. Documented handling of multiple
node matches, including the surprising results of somepath/text() on
<somepath>x<!--blah-->y</somepath>. Documented handling of nested
elements. Documented that xmltable works only on XML documents, not
fragments/forests.

I don't understand to this sentence: "It is possible for a PATH

expression

to reference output columns that appear before it in the column-list, so
paths may be dynamically constructed based on other parts of the XML
document:"

The docs and tests don't seem to cover XML entities. What's the
behaviour there? Core XML only defines one entity, but if a schema
defines more how are they processed? The tests need to cover the
predefined entities &quot; &amp; &apos; &lt; and &gt; at least.

I don't understand, what you are propose here. ?? Please, can you send

some

examples.

Per below - handling of DTD <!ENTITY> declarations, and the builtin
entity tests I already added tests for.

It doesn't seem to cope with internal DTDs at all (libxml2 limitation?):

SELECT * FROM xmltable('/' PASSING $XML$<?xml version="1.0"
standalone="yes" ?>
<!DOCTYPE foo [
<!ELEMENT foo (#PCDATA)>
<!ENTITY pg "PostgreSQL">
]>
<foo>Hello &pg;.</foo>
$XML$ COLUMNS foo text);

+ ERROR:  invalid XML content
+ LINE 1: SELECT * FROM xmltable('/' PASSING $XML$<?xml version="1.0"

...

+                                            ^
+ DETAIL:  line 2: StartTag: invalid element name
+ <!DOCTYPE foo [
+  ^
+ line 3: StartTag: invalid element name
+   <!ELEMENT foo (#PCDATA)>
+    ^
+ line 4: StartTag: invalid element name
+   <!ENTITY pg "PostgreSQL">
+    ^
+ line 6: Entity 'pg' not defined
+ <foo>Hello &pg;.</foo>
+                ^

It is rejected before XMLTABLE function call

postgres=# select $XML$<?xml version="1.0" standalone="yes" ?>
postgres$# <!DOCTYPE foo [
postgres$# <!ELEMENT foo (#PCDATA)>
postgres$# <!ENTITY pg "PostgreSQL">
postgres$# ]>
postgres$# <foo>Hello &pg;.</foo>
postgres$# $XML$::xml;
ERROR: invalid XML content
LINE 1: select $XML$<?xml version="1.0" standalone="yes" ?>
^
DETAIL: line 2: StartTag: invalid element name
<!DOCTYPE foo [

[snip]

It is disabled by default in libxml2. I found a function
xmlSubstituteEntitiesDefault http://www.xmlsoft.org/entities.html
http://www.xmlsoft.org/html/libxml-parser.html#

xmlSubstituteEntitiesDefault

The default behave should be common for all PostgreSQL's libxml2 based
function - and then it is different topic - maybe part for PostgreSQL

ToDo?

But I don't remember any user requests related to this issue.

OK, so it's not xmltable specific. Fine by me.

Somebody who cares can deal with it. There's clearly nobody breaking
down the walls wanting the feature.

I removed this tests - it is not related to XMLTABLE function, but to
generic XML processing/validation.

Good plan.

In +ExecEvalTableExpr, shouldn't you be using PG_ENSURE_ERROR_CLEANUP
instead of a PG_TRY() / PG_CATCH() block?

If I understand to doc, the PG_ENSURE_ERROR_CLEANUP should be used, when

you

want to catch FATAL errors (and when you want to clean shared memory).
XMLTABLE doesn't use shared memory, and doesn't need to catch fatal

errors.

Ok, makes sense.

I don't have the libxml knowledge or remaining brain to usefully
evaluate the xpath and xml specifics in xpath.c today. It does strike
me that the new xpath parser should probably live in its own file,
though.

moved

Thanks.

new version is attached

Great.

I'm marking this ready for committer at this point.

Thank you very much

Regards

Pavel

Show quoted text

I think the XML parser likely needs a more close reading, so I'll ping
Peter E to see if he'll have a chance to check that bit out. But by
and large I think the issues have been ironed out - in terms of
functionality, structure and clarity I think it's looking solid.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#38Craig Ringer
craig@2ndquadrant.com
In reply to: Pavel Stehule (#35)
Re: patch: function xmltable

On 24 September 2016 at 14:01, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Did some docs copy-editing and integrated some examples. Explained how
nested elements work, that multiple top level elements is an error,
etc. Explained the time-of-evaluation stuff. Pointed out that you can
refer to prior output columns in PATH and DEFAULT, since that's weird
and unusual compared to normal SQL. Documented handling of multiple
node matches, including the surprising results of somepath/text() on
<somepath>x<!--blah-->y</somepath>. Documented handling of nested
elements. Documented that xmltable works only on XML documents, not
fragments/forests.

I don't understand to this sentence: "It is possible for a PATH expression
to reference output columns that appear before it in the column-list, so
paths may be dynamically constructed based on other parts of the XML
document:"

This was based on a misunderstanding of something you said earlier. I
thought the idea was to allow this to work:

SELECT * FROM xmltable('/x' PASSING
'<x><elemName>a</elemName><a>value</a></x>' COLUMNS elemName text,
extractedValue text PATH elemName);

... but it doesn't:

SELECT * FROM xmltable('/x' PASSING
'<x><elemName>a</elemName><a>value</a></x>' COLUMNS elemName text,
extractedValue text PATH elemName);
ERROR: column "elemname" does not exist
LINE 1: ...' COLUMNS elemName text, extractedValue text PATH elemName);

... so please delete that text. I thought I'd tested it but the state
of my tests dir says I just got distracted by another task at the
wrong time.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#39Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#38)
1 attachment(s)
Re: patch: function xmltable

2016-09-27 5:53 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

On 24 September 2016 at 14:01, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Did some docs copy-editing and integrated some examples. Explained how
nested elements work, that multiple top level elements is an error,
etc. Explained the time-of-evaluation stuff. Pointed out that you can
refer to prior output columns in PATH and DEFAULT, since that's weird
and unusual compared to normal SQL. Documented handling of multiple
node matches, including the surprising results of somepath/text() on
<somepath>x<!--blah-->y</somepath>. Documented handling of nested
elements. Documented that xmltable works only on XML documents, not
fragments/forests.

I don't understand to this sentence: "It is possible for a PATH

expression

to reference output columns that appear before it in the column-list, so
paths may be dynamically constructed based on other parts of the XML
document:"

This was based on a misunderstanding of something you said earlier. I
thought the idea was to allow this to work:

SELECT * FROM xmltable('/x' PASSING
'<x><elemName>a</elemName><a>value</a></x>' COLUMNS elemName text,
extractedValue text PATH elemName);

... but it doesn't:

SELECT * FROM xmltable('/x' PASSING
'<x><elemName>a</elemName><a>value</a></x>' COLUMNS elemName text,
extractedValue text PATH elemName);
ERROR: column "elemname" does not exist
LINE 1: ...' COLUMNS elemName text, extractedValue text PATH elemName);

... so please delete that text. I thought I'd tested it but the state
of my tests dir says I just got distracted by another task at the
wrong time.

deleted

Regards

Pavel

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

xmltable-11.patch.gzapplication/x-gzip; name=xmltable-11.patch.gzDownload
#40Michael Paquier
michael.paquier@gmail.com
In reply to: Pavel Stehule (#39)
Re: patch: function xmltable

On Tue, Sep 27, 2016 at 2:29 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

2016-09-27 5:53 GMT+02:00 Craig Ringer <craig@2ndquadrant.com>:

[...]
... so please delete that text. I thought I'd tested it but the state
of my tests dir says I just got distracted by another task at the
wrong time.

Moved patch to next CF with same status: ready for committer.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#41Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#39)
1 attachment(s)
Re: patch: function xmltable

Hi

new update - only doc

+    <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>

Regards

Pavel

Attachments:

xmltable-12.patch.gzapplication/x-gzip; name=xmltable-12.patch.gzDownload
#42Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#41)
Re: patch: function xmltable

I've been going over this patch. I think it'd be better to restructure
the <sect2> before adding the docs for this new function; I already
split it out, so don't do anything about this.

Next, looking at struct TableExprBuilder I noticed that the comments are
already obsolete, as they talk about function params that do not exist
(missing_columns) and they fail to mention the ones that do exist.
Also, function member SetContent is not documented at all. Overall,
these comments do not convey a lot -- apparently, whoever reads them is
already supposed to know how it works: "xyz sets a row generating
filter" doesn't tell me anything. Since this is API documentation, it
needs to be much clearer.

ExecEvalTableExpr and ExecEvalTableExprProtected have no comments
whatsoever. Needs fixed.

I wonder if it'd be a good idea to install TableExpr first without the
implementing XMLTABLE, so that it's clearer what is API and what is
implementation.

The number of new keywords in this patch is depressing. I suppose
there's no way around that -- as I understand, this is caused by the SQL
standard's definition of the syntax for this feature.

Have to go now for a bit -- will continue looking afterwards. Please
submit delta patches on top of your latest v12 to fix the comments I
mentioned.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#43Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#42)
1 attachment(s)
Re: patch: function xmltable

Hi

2016-11-17 19:22 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

I've been going over this patch. I think it'd be better to restructure
the <sect2> before adding the docs for this new function; I already
split it out, so don't do anything about this.

Next, looking at struct TableExprBuilder I noticed that the comments are
already obsolete, as they talk about function params that do not exist
(missing_columns) and they fail to mention the ones that do exist.
Also, function member SetContent is not documented at all. Overall,
these comments do not convey a lot -- apparently, whoever reads them is
already supposed to know how it works: "xyz sets a row generating
filter" doesn't tell me anything. Since this is API documentation, it
needs to be much clearer.

ExecEvalTableExpr and ExecEvalTableExprProtected have no comments
whatsoever. Needs fixed.

I am sending the patch with more comments - but it needs a care someone
with good English skills.

I wonder if it'd be a good idea to install TableExpr first without the
implementing XMLTABLE, so that it's clearer what is API and what is
implementation.

I am not sure about this step - the API is clean from name. In this moment,
for this API is not any other tests than XMLTABLE implementation.

The number of new keywords in this patch is depressing. I suppose
there's no way around that -- as I understand, this is caused by the SQL
standard's definition of the syntax for this feature.

Have to go now for a bit -- will continue looking afterwards. Please
submit delta patches on top of your latest v12 to fix the comments I
mentioned.

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

fix-comments.patchtext/x-patch; charset=US-ASCII; name=fix-comments.patchDownload
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index c386be1..76c2ec8 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -4505,8 +4505,18 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 }
 
 /* ----------------------------------------------------------------
- *		ExecEvalTableExpr
+ *		ExecEvalTableExprProtected and ExecEvalTableExpr
  *
+ * Evaluate a TableExpr node. ExecEvalTableExprProtected is called
+ * from ExecEvalTableExpr from PG_TRY(), PG_CATCH() block be ensured
+ * all allocated memory be released.
+ *
+ * It creates TableExprBuilder object, does all necessary settings and
+ * reads all tuples from this object. Is possible to go over this
+ * cycle more times per query - when LATERAL JOIN is used. On the end
+ * of cycle, the TableExprBuilder object is destroyed, state pointer
+ * to this object is cleaned, and related memory context is resetted.
+ * New call starts new cycle.
  * ----------------------------------------------------------------
  */
 static Datum
@@ -4682,6 +4692,12 @@ ExecEvalTableExprProtected(TableExprState * tstate,
 	return result;
 }
 
+/*
+ * ExecEvalTableExpr - this is envelop of ExecEvalTableExprProtected() function.
+ *
+ * This function ensures releasing all TableBuilder context and related
+ * memory context, when ExecEvalTableExprProtected fails on exception.
+ */
 static Datum
 ExecEvalTableExpr(TableExprState * tstate,
 				  ExprContext *econtext,
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
index ad5d8e2..3056317 100644
--- a/src/include/executor/tableexpr.h
+++ b/src/include/executor/tableexpr.h
@@ -20,27 +20,37 @@
  * for generating content of table-expression functions like
  * XMLTABLE.
  *
- * CreateContext is called before execution and it does query level
- * initialization. Returns pointer to private TableExprBuilder data
- * (context). A query context should be active in call time. A param
- * missing_columns is true, when a user doesn't specify returned
- * columns.
+ * The TableBuilder is initialized by calling CreateContext function
+ * at evaluation time. First parameter - tuple descriptor describes
+ * produced (expected) table. in_functions is a array of FmgrInfo input
+ * functions for types of columns of produced table. The typioparams
+ * is a array of typio Oids for types of columns of produced table.
+ * The created context is living in special memory context passed
+ * as last parameter.
  *
- * AddNs add namespace info when namespaces are is supported.
- * then passed namespace is default namespace. Namespaces should be
- * passed before Row/Column Paths setting. When name is NULL, then
- * related uri is default namespace.
+ * The SetContent function is used for passing input document to
+ * table builder. The table builder handler knows expected format
+ * and it can do some additional transformations that are not propagated
+ * out from table builder.
  *
- * SetRowPath sets a row generating filter.
+ * The AddNs add namespace info when namespaces are supported.
+ * Namespaces should be passed before Row/Column Paths setting.
+ * When name is NULL, then related uri is default namespace.
  *
- * SetColumnPath sets a column generating filter.
+ * The SetRowPath sets a row generating filter. This filter is used
+ * for separation of rows from document. Passed as cstring.
  *
- * FetchRow ensure loading row raleted data. Returns false, when
- * document doesn't containt any next data.
+ * The SetColumnPath sets a column generating filter. This filter is
+ * used for separating nth value from row. Passed as cstring.
  *
- * GetValue returns a value related to colnum column.
+ * The FetchRow ensure loading row raleted data. Returns false, when
+ * document doesn't containt any next row.
  *
- * DestroyContext - release all memory
+ * The GetValue returns a value related to colnum column.
+ *
+ * The DestroyContext - should to release all sources related to
+ * processing the document. Called when all rows are fetched or
+ * when a error is catched.
  */
 typedef struct TableExprBuilder
 {
#44Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#43)
Re: patch: function xmltable

Pavel Stehule wrote:

2016-11-17 19:22 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Next, looking at struct TableExprBuilder I noticed that the comments are
already obsolete, as they talk about function params that do not exist
(missing_columns) and they fail to mention the ones that do exist.
Also, function member SetContent is not documented at all. Overall,
these comments do not convey a lot -- apparently, whoever reads them is
already supposed to know how it works: "xyz sets a row generating
filter" doesn't tell me anything. Since this is API documentation, it
needs to be much clearer.

ExecEvalTableExpr and ExecEvalTableExprProtected have no comments
whatsoever. Needs fixed.

I am sending the patch with more comments - but it needs a care someone
with good English skills.

Thanks, I can help with that.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#45Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#43)
Re: patch: function xmltable

The SQL standard seems to require a comma after the XMLNAMESPACES
clause:

<XML table> ::=
XMLTABLE <left paren>
[ <XML namespace declaration> <comma> ]
<XML table row pattern>
[ <XML table argument list> ]
COLUMNS <XML table column definitions> <right paren>

I don't understand the reason for that, but I have added it:

| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ',' c_expr xmlexists_argument ')'
{
TableExpr *n = makeNode(TableExpr);
n->row_path = $8;
n->expr = $9;
n->cols = NIL;
n->namespaces = $5;
n->location = @1;
$$ = (Node *)n;
}
| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ',' c_expr xmlexists_argument COLUMNS TableExprColList ')'
{
TableExpr *n = makeNode(TableExpr);
n->row_path = $8;
n->expr = $9;
n->cols = $11;
n->namespaces = $5;
n->location = @1;
$$ = (Node *)n;
}
;

Another thing I did was remove the TableExprColOptionsOpt production; in
its place I added a third rule in TableExprCol for "ColId Typename
IsNotNull" (i.e. no options). This seems to reduce the size of the
generated gram.c a good dozen kB.

I didn't like much the use of c_expr in all these productions. As I
understand it, c_expr is mostly an implementation artifact and we should
be using a_expr or b_expr almost everywhere. I see that XMLEXISTS
already expanded the very limited use of c_expr there was; I would
prefer to fix that one too rather than replicate it here. TBH I'm not
sure I like that XMLTABLE is re-using xmlexists_argument.

Actually, is the existing XMLEXISTS production correct? What I see in
the standard is

<XML table row pattern> ::= <character string literal>

<XML table argument list> ::=
PASSING <XML table argument passing mechanism> <XML query argument>
[ { <comma> <XML query argument> }... ]

<XML table argument passing mechanism> ::= <XML passing mechanism>

<XML table column definitions> ::= <XML table column definition> [ { <comma> <XML table column definition> }... ]

<XML table column definition> ::=
<XML table ordinality column definition>
| <XML table regular column definition>

<XML table ordinality column definition> ::= <column name> FOR ORDINALITY

<XML table regular column definition> ::=
<column name> <data type> [ <XML passing mechanism> ]
[ <default clause> ]
[ PATH <XML table column pattern> ]

<XML table column pattern> ::= <character string literal>

so I think this resolves "PASSING BY {REF,VALUE} <XML query argument>", but what
we have in gram.y is:

/* We allow several variants for SQL and other compatibility. */
xmlexists_argument:
PASSING c_expr
{
$$ = $2;
}
| PASSING c_expr BY REF
{
$$ = $2;
}
| PASSING BY REF c_expr
{
$$ = $4;
}
| PASSING BY REF c_expr BY REF
{
$$ = $4;
}
;

I'm not sure why we allow "PASSING c_expr" at all. Maybe if BY VALUE/BY
REF is not specified, we should just not have PASSING at all?

If we extended this for XMLEXISTS for compatibility with some other
product, perhaps we should look into what that product supports for
XMLTABLE; maybe XMLTABLE does not need all the same options as
XMLEXISTS.

The fourth option seems very dubious to me. This was added by commit
641459f26, submitted here:
/messages/by-id/4C0F6DBF.9000001@mlfowler.com

... Hm, actually some perusal of the XMLEXISTS predicate in the standard
shows that it's quite a different thing from XMLTABLE. Maybe we
shouldn't reuse xmlexists_argument here at all.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#46Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#45)
Re: patch: function xmltable

2016-11-19 0:42 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

The SQL standard seems to require a comma after the XMLNAMESPACES
clause:

<XML table> ::=
XMLTABLE <left paren>
[ <XML namespace declaration> <comma> ]
<XML table row pattern>
[ <XML table argument list> ]
COLUMNS <XML table column definitions> <right paren>

I don't understand the reason for that, but I have added it:

| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList
')' ',' c_expr xmlexists_argument ')'
{
TableExpr *n = makeNode(TableExpr);
n->row_path = $8;
n->expr = $9;
n->cols = NIL;
n->namespaces = $5;
n->location = @1;
$$ = (Node *)n;
}
| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList
')' ',' c_expr xmlexists_argument COLUMNS TableExprColList ')'
{
TableExpr *n = makeNode(TableExpr);
n->row_path = $8;
n->expr = $9;
n->cols = $11;
n->namespaces = $5;
n->location = @1;
$$ = (Node *)n;
}
;

yes, looks my oversight - it is better

Another thing I did was remove the TableExprColOptionsOpt production; in
its place I added a third rule in TableExprCol for "ColId Typename
IsNotNull" (i.e. no options). This seems to reduce the size of the
generated gram.c a good dozen kB.

If I remember well - this was required by better compatibility with Oracle

ANSI SQL: colname type DEFAULT PATH
Oracle: colname PATH DEFAULT

My implementation allows both combinations - there are two reasons: 1. one
less issue when people does port from Oracle, 2. almost all examples of
XMLTABLE on a net are from Oracle - it can be unfriendly, when these
examples would not work on PG - there was discussion about this issue in
this mailing list

I didn't like much the use of c_expr in all these productions. As I
understand it, c_expr is mostly an implementation artifact and we should
be using a_expr or b_expr almost everywhere. I see that XMLEXISTS
already expanded the very limited use of c_expr there was; I would
prefer to fix that one too rather than replicate it here. TBH I'm not
sure I like that XMLTABLE is re-using xmlexists_argument.

There are two situations: c_expr as document content, and c_expr after
DEFAULT and PATH keywords. First probably can be fixed, second not, because
"PATH" is unreserved keyword only.

Actually, is the existing XMLEXISTS production correct? What I see in
the standard is

<XML table row pattern> ::= <character string literal>

<XML table argument list> ::=
PASSING <XML table argument passing mechanism> <XML query argument>
[ { <comma> <XML query argument> }... ]

<XML table argument passing mechanism> ::= <XML passing mechanism>

<XML table column definitions> ::= <XML table column definition> [ {
<comma> <XML table column definition> }... ]

<XML table column definition> ::=
<XML table ordinality column definition>
| <XML table regular column definition>

<XML table ordinality column definition> ::= <column name> FOR ORDINALITY

<XML table regular column definition> ::=
<column name> <data type> [ <XML passing mechanism> ]
[ <default clause> ]
[ PATH <XML table column pattern> ]

<XML table column pattern> ::= <character string literal>

so I think this resolves "PASSING BY {REF,VALUE} <XML query argument>",
but what
we have in gram.y is:

/* We allow several variants for SQL and other compatibility. */
xmlexists_argument:
PASSING c_expr
{
$$ = $2;
}
| PASSING c_expr BY REF
{
$$ = $2;
}
| PASSING BY REF c_expr
{
$$ = $4;
}
| PASSING BY REF c_expr BY REF
{
$$ = $4;
}
;

I'm not sure why we allow "PASSING c_expr" at all. Maybe if BY VALUE/BY
REF is not specified, we should just not have PASSING at all?

If we extended this for XMLEXISTS for compatibility with some other

product, perhaps we should look into what that product supports for
XMLTABLE; maybe XMLTABLE does not need all the same options as
XMLEXISTS.

The reason is a compatibility with other products - DB2. XMLTABLE uses same
options like XMLEXISTS. These options has zero value for Postgres, but its
are important - compatibility, and workable examples.

The fourth option seems very dubious to me. This was added by commit
641459f26, submitted here:
/messages/by-id/4C0F6DBF.9000001@mlfowler.com

... Hm, actually some perusal of the XMLEXISTS predicate in the standard
shows that it's quite a different thing from XMLTABLE. Maybe we
shouldn't reuse xmlexists_argument here at all.

not sure If I understand

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#47Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#46)
Re: patch: function xmltable

2016-11-19 5:19 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2016-11-19 0:42 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

The SQL standard seems to require a comma after the XMLNAMESPACES
clause:

<XML table> ::=
XMLTABLE <left paren>
[ <XML namespace declaration> <comma> ]
<XML table row pattern>
[ <XML table argument list> ]
COLUMNS <XML table column definitions> <right paren>

I don't understand the reason for that, but I have added it:

| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList
')' ',' c_expr xmlexists_argument ')'
{
TableExpr *n =
makeNode(TableExpr);
n->row_path = $8;
n->expr = $9;
n->cols = NIL;
n->namespaces = $5;
n->location = @1;
$$ = (Node *)n;
}
| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList
')' ',' c_expr xmlexists_argument COLUMNS TableExprColList ')'
{
TableExpr *n =
makeNode(TableExpr);
n->row_path = $8;
n->expr = $9;
n->cols = $11;
n->namespaces = $5;
n->location = @1;
$$ = (Node *)n;
}
;

yes, looks my oversight - it is better

Another thing I did was remove the TableExprColOptionsOpt production; in
its place I added a third rule in TableExprCol for "ColId Typename
IsNotNull" (i.e. no options). This seems to reduce the size of the
generated gram.c a good dozen kB.

If I remember well - this was required by better compatibility with Oracle

ANSI SQL: colname type DEFAULT PATH
Oracle: colname PATH DEFAULT

My implementation allows both combinations - there are two reasons: 1. one
less issue when people does port from Oracle, 2. almost all examples of
XMLTABLE on a net are from Oracle - it can be unfriendly, when these
examples would not work on PG - there was discussion about this issue in
this mailing list

I didn't like much the use of c_expr in all these productions. As I
understand it, c_expr is mostly an implementation artifact and we should
be using a_expr or b_expr almost everywhere. I see that XMLEXISTS
already expanded the very limited use of c_expr there was; I would
prefer to fix that one too rather than replicate it here. TBH I'm not
sure I like that XMLTABLE is re-using xmlexists_argument.

There are two situations: c_expr as document content, and c_expr after
DEFAULT and PATH keywords. First probably can be fixed, second not, because
"PATH" is unreserved keyword only.

It is not possible PASSING is unreserved keyword too.

Regards

Pavel

Show quoted text

Actually, is the existing XMLEXISTS production correct? What I see in
the standard is

<XML table row pattern> ::= <character string literal>

<XML table argument list> ::=
PASSING <XML table argument passing mechanism> <XML query argument>
[ { <comma> <XML query argument> }... ]

<XML table argument passing mechanism> ::= <XML passing mechanism>

<XML table column definitions> ::= <XML table column definition> [ {
<comma> <XML table column definition> }... ]

<XML table column definition> ::=
<XML table ordinality column definition>
| <XML table regular column definition>

<XML table ordinality column definition> ::= <column name> FOR ORDINALITY

<XML table regular column definition> ::=
<column name> <data type> [ <XML passing mechanism> ]
[ <default clause> ]
[ PATH <XML table column pattern> ]

<XML table column pattern> ::= <character string literal>

so I think this resolves "PASSING BY {REF,VALUE} <XML query argument>",
but what
we have in gram.y is:

/* We allow several variants for SQL and other compatibility. */
xmlexists_argument:
PASSING c_expr
{
$$ = $2;
}
| PASSING c_expr BY REF
{
$$ = $2;
}
| PASSING BY REF c_expr
{
$$ = $4;
}
| PASSING BY REF c_expr BY REF
{
$$ = $4;
}
;

I'm not sure why we allow "PASSING c_expr" at all. Maybe if BY VALUE/BY
REF is not specified, we should just not have PASSING at all?

If we extended this for XMLEXISTS for compatibility with some other

product, perhaps we should look into what that product supports for
XMLTABLE; maybe XMLTABLE does not need all the same options as
XMLEXISTS.

The reason is a compatibility with other products - DB2. XMLTABLE uses
same options like XMLEXISTS. These options has zero value for Postgres, but
its are important - compatibility, and workable examples.

The fourth option seems very dubious to me. This was added by commit
641459f26, submitted here:
/messages/by-id/4C0F6DBF.9000001@mlfowler.com

... Hm, actually some perusal of the XMLEXISTS predicate in the standard
shows that it's quite a different thing from XMLTABLE. Maybe we
shouldn't reuse xmlexists_argument here at all.

not sure If I understand

Regards

Pavel

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#48Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#41)
Re: patch: function xmltable

Something I just noticed is that transformTableExpr takes a TableExpr
node and returns another TableExpr node. That's unlike what we do in
other places, where the node returned is of a different type than the
input node. I'm not real clear what happens if you try to re-transform
a node that was already transformed, but it seems worth thinking about.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#48)
Re: patch: function xmltable

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

Something I just noticed is that transformTableExpr takes a TableExpr
node and returns another TableExpr node. That's unlike what we do in
other places, where the node returned is of a different type than the
input node. I'm not real clear what happens if you try to re-transform
a node that was already transformed, but it seems worth thinking about.

We're not 100% consistent on that --- there are cases such as RowExpr
and CaseExpr where the same struct type is used for pre-parse-analysis
and post-parse-analysis nodes. I think it's okay as long as the
information content isn't markedly different, ie the transformation
just consists of transforming all the sub-nodes.

Being able to behave sanely on a re-transformation used to be an
issue, but we no longer expect transformExpr to support that.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#50Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#49)
Re: patch: function xmltable

2016-11-21 21:16 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

Something I just noticed is that transformTableExpr takes a TableExpr
node and returns another TableExpr node. That's unlike what we do in
other places, where the node returned is of a different type than the
input node. I'm not real clear what happens if you try to re-transform
a node that was already transformed, but it seems worth thinking about.

We're not 100% consistent on that --- there are cases such as RowExpr
and CaseExpr where the same struct type is used for pre-parse-analysis
and post-parse-analysis nodes. I think it's okay as long as the
information content isn't markedly different, ie the transformation
just consists of transforming all the sub-nodes.

Being able to behave sanely on a re-transformation used to be an
issue, but we no longer expect transformExpr to support that.

I was not sure in this case - using new node was more clear for me -
safeguard against some uninitialized or untransformed value. There in only
few bytes memory more overhead.

regards

Pavel

Show quoted text

regards, tom lane

#51Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#50)
Re: patch: function xmltable

I found the whole TableExprGetTupleDesc() function a bit odd in
nodeFuncs.c, so I renamed it to ExecTypeFromTableExpr() and moved it to
execTuples.c -- but only because that's where ExecTypeFromTL and others
already live. I would have liked to move it to tupdesc.c instead, but
it requires knowledge of executor nodes, which is probably the reason
that ExecTypeFromTL is in execTuples. I think we'd eat that bit of
ugliness only because we're not the first. But anyway I quickly ran
into another problem.

I noticed that ExecTypeFromTableExpr is being called from the transform
phase, which is much earlier than the executor. I noticed because of
the warning that the above movement added to nodeFuncs.c,
src/backend/nodes/nodeFuncs.c:509:5: warning: implicit declaration of function 'ExecTypeFromTableExpr' [-Wimplicit-function-declaration]

so I thought, hm, is it okay to have parse analysis run an executor
function? (I suppose this is the reason you put it in nodeFuncs in the
first place). For fun, I tried this query under GDB, with a breakpoint
on exprTypmod():

SELECT X.*
FROM emp,
XMLTABLE ('//depts/dept/employee' passing doc
COLUMNS
empID INTEGER PATH '@id',
firstname int PATH 'name/first',
lastname VARCHAR(25) PATH 'name/last') AS X;

and sure enough, the type is resolved during parse analysis:

Breakpoint 1, exprTypmod (expr=expr@entry=0x1d23ad8)
at /pgsql/source/master/src/backend/nodes/nodeFuncs.c:283
283 if (!expr)
(gdb) print *expr
$2 = {type = T_TableExpr}
(gdb) bt
#0 exprTypmod (expr=expr@entry=0x1d23ad8)
at /pgsql/source/master/src/backend/nodes/nodeFuncs.c:283
#1 0x000000000080c500 in get_expr_result_type (expr=0x1d23ad8,
resultTypeId=0x7ffd482bfdb4, resultTupleDesc=0x7ffd482bfdb8)
at /pgsql/source/master/src/backend/utils/fmgr/funcapi.c:247
#2 0x000000000056de1b in expandRTE (rte=rte@entry=0x1d6b800, rtindex=2,
sublevels_up=0, location=location@entry=7,
include_dropped=include_dropped@entry=0 '\000',
colnames=colnames@entry=0x7ffd482bfe10, colvars=0x7ffd482bfe18)
at /pgsql/source/master/src/backend/parser/parse_relation.c:2052
#3 0x000000000056e131 in expandRelAttrs (pstate=pstate@entry=0x1d238a8,
rte=rte@entry=0x1d6b800, rtindex=<optimized out>,
sublevels_up=<optimized out>, location=location@entry=7)
at /pgsql/source/master/src/backend/parser/parse_relation.c:2435
#4 0x000000000056fa64 in ExpandSingleTable (pstate=pstate@entry=0x1d238a8,
rte=rte@entry=0x1d6b800, location=7,
make_target_entry=make_target_entry@entry=1 '\001')
at /pgsql/source/master/src/backend/parser/parse_target.c:1266
#5 0x000000000057135b in ExpandColumnRefStar (pstate=pstate@entry=0x1d238a8,
cref=0x1d22720, make_target_entry=make_target_entry@entry=1 '\001')
at /pgsql/source/master/src/backend/parser/parse_target.c:1158
#6 0x00000000005716f9 in transformTargetList (pstate=0x1d238a8,
targetlist=<optimized out>, exprKind=EXPR_KIND_SELECT_TARGET)

This seems fine I guess, and it seems to say that we ought to move the
code that generates the tupdesc to back parse analysis rather than
executor. Okay, fine. But let's find a better place than nodeFuncs.

But if I move the XMLTABLE() call to the target list instead, the type
is resolved at planner time:

SELECT
XMLTABLE ('/dept/employee' passing $$<dept bldg="114">
<employee id="903">
<name>
<first>Mary</first>
<last>Jones</last>
</name>
<office>415</office>
<phone>905-403-6112</phone>
<phone>647-504-4546</phone>
<salary currency="USD">64000</salary>
</employee>
</dept>$$
COLUMNS
empID INTEGER PATH '@id',
firstname varchar(4) PATH 'name/first',
lastname VARCHAR(25) PATH 'name/last') AS X;

Breakpoint 1, exprTypmod (expr=expr@entry=0x1d6bed8)
at /pgsql/source/master/src/backend/nodes/nodeFuncs.c:283
283 if (!expr)
(gdb) bt
#0 exprTypmod (expr=expr@entry=0x1d6bed8)
at /pgsql/source/master/src/backend/nodes/nodeFuncs.c:283
#1 0x0000000000654058 in set_pathtarget_cost_width (root=0x1d6bc68,
target=0x1d6c728)
at /pgsql/source/master/src/backend/optimizer/path/costsize.c:4729
#2 0x000000000066c197 in grouping_planner (root=0x1d6bc68,
inheritance_update=40 '(', inheritance_update@entry=0 '\000',
tuple_fraction=0.01, tuple_fraction@entry=0)
at /pgsql/source/master/src/backend/optimizer/plan/planner.c:1745
#3 0x000000000066ef64 in subquery_planner (glob=glob@entry=0x1d6bbd0,
parse=parse@entry=0x1d23818, parent_root=parent_root@entry=0x0,
hasRecursion=hasRecursion@entry=0 '\000',
tuple_fraction=tuple_fraction@entry=0)
at /pgsql/source/master/src/backend/optimizer/plan/planner.c:795
#4 0x000000000066fe5e in standard_planner (parse=0x1d23818,
cursorOptions=256, boundParams=<optimized out>)
at /pgsql/source/master/src/backend/optimizer/plan/planner.c:307

This is surprising, but I'm not sure it's wrong.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#51)
Re: patch: function xmltable

2016-11-22 21:47 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

I found the whole TableExprGetTupleDesc() function a bit odd in
nodeFuncs.c, so I renamed it to ExecTypeFromTableExpr() and moved it to
execTuples.c -- but only because that's where ExecTypeFromTL and others
already live. I would have liked to move it to tupdesc.c instead, but
it requires knowledge of executor nodes, which is probably the reason
that ExecTypeFromTL is in execTuples. I think we'd eat that bit of
ugliness only because we're not the first. But anyway I quickly ran
into another problem.

I noticed that ExecTypeFromTableExpr is being called from the transform
phase, which is much earlier than the executor. I noticed because of
the warning that the above movement added to nodeFuncs.c,
src/backend/nodes/nodeFuncs.c:509:5: warning: implicit declaration of
function 'ExecTypeFromTableExpr' [-Wimplicit-function-declaration]

The tuple descriptor should not be serialized.

When xmltable is called directly, then living tuple descriptor is used -
created in transform time. Another situation is when xmltable is used from
view, where transform time is skipped.

Originally I serialized generated type - but I had the problems with record
types - the current infrastructure expects serialization only real types.

My solution is a recheck of tuple descriptor in executor time. It is small
overhead - once per query - but it allows use xmltable from views without
necessity to specify returned columns explicitly.

so I thought, hm, is it okay to have parse analysis run an executor
function? (I suppose this is the reason you put it in nodeFuncs in the
first place). For fun, I tried this query under GDB, with a breakpoint
on exprTypmod():

SELECT X.*
FROM emp,
XMLTABLE ('//depts/dept/employee' passing doc
COLUMNS
empID INTEGER PATH '@id',
firstname int PATH 'name/first',
lastname VARCHAR(25) PATH 'name/last') AS X;

and sure enough, the type is resolved during parse analysis:

Breakpoint 1, exprTypmod (expr=expr@entry=0x1d23ad8)
at /pgsql/source/master/src/backend/nodes/nodeFuncs.c:283
283 if (!expr)
(gdb) print *expr
$2 = {type = T_TableExpr}
(gdb) bt
#0 exprTypmod (expr=expr@entry=0x1d23ad8)
at /pgsql/source/master/src/backend/nodes/nodeFuncs.c:283
#1 0x000000000080c500 in get_expr_result_type (expr=0x1d23ad8,
resultTypeId=0x7ffd482bfdb4, resultTupleDesc=0x7ffd482bfdb8)
at /pgsql/source/master/src/backend/utils/fmgr/funcapi.c:247
#2 0x000000000056de1b in expandRTE (rte=rte@entry=0x1d6b800, rtindex=2,
sublevels_up=0, location=location@entry=7,
include_dropped=include_dropped@entry=0 '\000',
colnames=colnames@entry=0x7ffd482bfe10, colvars=0x7ffd482bfe18)
at /pgsql/source/master/src/backend/parser/parse_relation.c:2052
#3 0x000000000056e131 in expandRelAttrs (pstate=pstate@entry=0x1d238a8,
rte=rte@entry=0x1d6b800, rtindex=<optimized out>,
sublevels_up=<optimized out>, location=location@entry=7)
at /pgsql/source/master/src/backend/parser/parse_relation.c:2435
#4 0x000000000056fa64 in ExpandSingleTable (pstate=pstate@entry=
0x1d238a8,
rte=rte@entry=0x1d6b800, location=7,
make_target_entry=make_target_entry@entry=1 '\001')
at /pgsql/source/master/src/backend/parser/parse_target.c:1266
#5 0x000000000057135b in ExpandColumnRefStar (pstate=pstate@entry=
0x1d238a8,
cref=0x1d22720, make_target_entry=make_target_entry@entry=1 '\001')
at /pgsql/source/master/src/backend/parser/parse_target.c:1158
#6 0x00000000005716f9 in transformTargetList (pstate=0x1d238a8,
targetlist=<optimized out>, exprKind=EXPR_KIND_SELECT_TARGET)

This seems fine I guess, and it seems to say that we ought to move the
code that generates the tupdesc to back parse analysis rather than
executor. Okay, fine. But let's find a better place than nodeFuncs.

But if I move the XMLTABLE() call to the target list instead, the type
is resolved at planner time:

SELECT
XMLTABLE ('/dept/employee' passing $$<dept bldg="114">
<employee id="903">
<name>
<first>Mary</first>
<last>Jones</last>
</name>
<office>415</office>
<phone>905-403-6112</phone>
<phone>647-504-4546</phone>
<salary currency="USD">64000</salary>
</employee>
</dept>$$
COLUMNS
empID INTEGER PATH '@id',
firstname varchar(4) PATH 'name/first',
lastname VARCHAR(25) PATH 'name/last') AS X;

Breakpoint 1, exprTypmod (expr=expr@entry=0x1d6bed8)
at /pgsql/source/master/src/backend/nodes/nodeFuncs.c:283
283 if (!expr)
(gdb) bt
#0 exprTypmod (expr=expr@entry=0x1d6bed8)
at /pgsql/source/master/src/backend/nodes/nodeFuncs.c:283
#1 0x0000000000654058 in set_pathtarget_cost_width (root=0x1d6bc68,
target=0x1d6c728)
at /pgsql/source/master/src/backend/optimizer/path/costsize.c:4729
#2 0x000000000066c197 in grouping_planner (root=0x1d6bc68,
inheritance_update=40 '(', inheritance_update@entry=0 '\000',
tuple_fraction=0.01, tuple_fraction@entry=0)
at /pgsql/source/master/src/backend/optimizer/plan/planner.c:1745
#3 0x000000000066ef64 in subquery_planner (glob=glob@entry=0x1d6bbd0,
parse=parse@entry=0x1d23818, parent_root=parent_root@entry=0x0,
hasRecursion=hasRecursion@entry=0 '\000',
tuple_fraction=tuple_fraction@entry=0)
at /pgsql/source/master/src/backend/optimizer/plan/planner.c:795
#4 0x000000000066fe5e in standard_planner (parse=0x1d23818,
cursorOptions=256, boundParams=<optimized out>)
at /pgsql/source/master/src/backend/optimizer/plan/planner.c:307

This is surprising, but I'm not sure it's wrong.

There are different processing for Set Returning nodes called from
paramlist and from tablelist. In last case the invokes exprTypmod early.

There is a different case, that you didn't check

CREATE VIEW x AS SELECT xmltable(...)
CREATE VIEW x1 AS SELECT * FROM xmltable(...)

close session

and in new session
SELECT * FROM x;
SELECT * FROM x1;

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#53Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#51)
Re: patch: function xmltable

I tried to see if a following RTE was able to "see" the entries created by
XMLTABLE, and sure it can:

SELECT X.*, generate_series
FROM emp,
XMLTABLE ('//depts/dept/employee' passing doc
COLUMNS
empID INTEGER PATH '@id',
firstname varchar(25) PATH 'name/first',
lastname VARCHAR(25) PATH 'name/last') AS X,
generate_series(900, empid);

empid │ firstname │ lastname │ generate_series
───────┼───────────┼──────────┼─────────────────
901 │ John │ Doe │ 900
901 │ John │ Doe │ 901
902 │ Peter │ Pan │ 900
902 │ Peter │ Pan │ 901
902 │ Peter │ Pan │ 902
903 │ Mary │ Jones │ 900
903 │ Mary │ Jones │ 901
903 │ Mary │ Jones │ 902
903 │ Mary │ Jones │ 903
(9 filas)

Cool.

I'm still wondering how this works. I'll continue to explore the patch
in order to figure out.

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#54Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#53)
Re: patch: function xmltable

Alvaro Herrera wrote:

I'm still wondering how this works. I'll continue to explore the patch
in order to figure out.

Ah, so it's already parsed as a "range function". That part looks good
to me.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#41)
Re: patch: function xmltable

Oh my, I just noticed we have a new xpath preprocessor in this patch
too. Where did this code come from -- did you write it all from
scratch?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#56Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#41)
Re: patch: function xmltable

Here's another version. Not there yet: need to move back the function
to create the tupdesc, as discussed. Not clear what's the best place,
however. I modified the grammar a bit (added the missing comma, removed
PATH as an unreserved keyword and just used IDENT, removed the "Opt"
version for column options), and reworked the comments in the transform
phase (I tweaked the code here and there mostly to move things to nicer
places, but it's pretty much the same code).

In the new xpath_parser.c file I think we should tidy things up a bit.
First, it needs more commentary on what the entry function actually
does, in detail. Also, IMO that function should be at the top of the
file, not at the bottom, followed by all its helpers. I would like some
more clarity on the provenance of all this code, just to assess the
probability of bugs; mostly as it's completely undocumented.

I don't like the docs either. I think we should have a complete
reference to the syntax, followed by examples, rather than letting the
examples drive the whole thing. I fixed the synopsis so that it's not
one very long line.

If you use "PATH '/'" for a column, you get the text for all the entries
in the whole XML, rather than the text for the particular row being
processed. Isn't that rather weird, or to put it differently, completely
wrong? I didn't find a way to obtain the whole XML row when you have
the COLUMNS option (which is what I was hoping for with the "PATH '/'").

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#57Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#56)
1 attachment(s)
Re: patch: function xmltable

Sorry, here's the patch. Power loss distracted me here.

By the way, the pgindent you did is slightly confused because you failed
to add the new struct types you define to typedefs.list.

I have not looked at the new xml.c code at all, yet.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-13.patchtext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2e64cc4..8d5bbd8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10324,50 +10324,53 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
   <sect2 id="functions-xml-processing">
    <title>Processing XML</title>
 
-   <indexterm>
-    <primary>XPath</primary>
-   </indexterm>
-
    <para>
     To process values of data type <type>xml</type>, PostgreSQL offers
-    the functions <function>xpath</function> and
+    the functions <function>xpath</function>,
     <function>xpath_exists</function>, which evaluate XPath 1.0
-    expressions.
+    expressions, and the <function>XMLTABLE</function> predicate.
    </para>
 
+   <sect3 id="functions-xml-processing-xpath">
+    <title><literal>xpath</literal></title>
+
+    <indexterm>
+     <primary>XPath</primary>
+    </indexterm>
+
 <synopsis>
 <function>xpath</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath</function> evaluates the XPath
-    expression <replaceable>xpath</replaceable> (a <type>text</> value)
-    against the XML value
-    <replaceable>xml</replaceable>.  It returns an array of XML values
-    corresponding to the node set produced by the XPath expression.
-    If the XPath expression returns a scalar value rather than a node set,
-    a single-element array is returned.
-   </para>
+    <para>
+     The function <function>xpath</function> evaluates the XPath
+     expression <replaceable>xpath</replaceable> (a <type>text</> value)
+     against the XML value
+     <replaceable>xml</replaceable>.  It returns an array of XML values
+     corresponding to the node set produced by the XPath expression.
+     If the XPath expression returns a scalar value rather than a node set,
+     a single-element array is returned.
+    </para>
 
-  <para>
-    The second argument must be a well formed XML document. In particular,
-    it must have a single root node element.
-  </para>
+    <para>
+     The second argument must be a well formed XML document. In particular,
+     it must have a single root node element.
+    </para>
 
-   <para>
-    The optional third argument of the function is an array of namespace
-    mappings.  This array should be a two-dimensional <type>text</> array with
-    the length of the second axis being equal to 2 (i.e., it should be an
-    array of arrays, each of which consists of exactly 2 elements).
-    The first element of each array entry is the namespace name (alias), the
-    second the namespace URI. It is not required that aliases provided in
-    this array be the same as those being used in the XML document itself (in
-    other words, both in the XML document and in the <function>xpath</function>
-    function context, aliases are <emphasis>local</>).
-   </para>
+    <para>
+     The optional third argument of the function is an array of namespace
+     mappings.  This array should be a two-dimensional <type>text</> array with
+     the length of the second axis being equal to 2 (i.e., it should be an
+     array of arrays, each of which consists of exactly 2 elements).
+     The first element of each array entry is the namespace name (alias), the
+     second the namespace URI. It is not required that aliases provided in
+     this array be the same as those being used in the XML document itself (in
+     other words, both in the XML document and in the <function>xpath</function>
+     function context, aliases are <emphasis>local</>).
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
              ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10377,10 +10380,10 @@ SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
 
-   <para>
-    To deal with default (anonymous) namespaces, do something like this:
+    <para>
+     To deal with default (anonymous) namespaces, do something like this:
 <screen><![CDATA[
 SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
              ARRAY[ARRAY['mydefns', 'http://example.com']]);
@@ -10390,27 +10393,31 @@ SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
+   </sect3>
 
-   <indexterm>
-    <primary>xpath_exists</primary>
-   </indexterm>
+   <sect3 id="functions-xml-processing-xpath-exists">
+    <title><literal>xpath_exists</literal></title>
+
+    <indexterm>
+     <primary>xpath_exists</primary>
+    </indexterm>
 
 <synopsis>
 <function>xpath_exists</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath_exists</function> is a specialized form
-    of the <function>xpath</function> function.  Instead of returning the
-    individual XML values that satisfy the XPath, this function returns a
-    Boolean indicating whether the query was satisfied or not.  This
-    function is equivalent to the standard <literal>XMLEXISTS</> predicate,
-    except that it also offers support for a namespace mapping argument.
-   </para>
+    <para>
+     The function <function>xpath_exists</function> is a specialized form
+     of the <function>xpath</function> function.  Instead of returning the
+     individual XML values that satisfy the XPath, this function returns a
+     Boolean indicating whether the query was satisfied or not.  This
+     function is equivalent to the standard <literal>XMLEXISTS</> predicate,
+     except that it also offers support for a namespace mapping argument.
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
                      ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10420,7 +10427,278 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
  t
 (1 row)
 ]]></screen>
+    </para>
+   </sect3>
+
+   <sect3 id="functions-xml-processing-xmltable">
+    <title><literal>xmltable</literal></title>
+
+    <indexterm>
+     <primary>xmltable</primary>
+    </indexterm>
+
+<synopsis>
+<function>xmltable</function>(
+    <optional>xmlnamespaces(<replaceable>namespace uri</replaceable> AS <replaceable>namespace name</replaceable>|DEFAULT <replaceable>namespace uri</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 specified with
+     <literal>DEFAULT</> <replaceable>namespace-URI</>.
+
+<screen><![CDATA[
+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');
+
+ 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')
+                '/rows/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 743e7d6..321cb77 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,9 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState * tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4504,234 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExprProtected
+ *
+ * Evaluate a TableExpr node.  This function may leak libxml memory, so it
+ * needs to be protected from exceptions.
+ *
+ * This function creates a TableExprBuilder object and applies all necessary
+ * settings; one tuple is returned per call.
+ *
+ * Is possible to go over this
+ * cycle more times per query - when LATERAL JOIN is used. On the end
+ * of cycle, the TableExprBuilder object is destroyed, state pointer
+ * to this object is cleaned, and related memory context is resetted.
+ * New call starts new cycle.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExprProtected(TableExprState * tstate,
+						   ExprContext *econtext,
+						   bool *isNull, ExprDoneCond *isDone)
+{
+	TupleDesc	tupdesc;
+	Datum		result;
+	int			i;
+	Datum		value;
+	bool		isnull;
+	const TableExprBuilder *builder;
+	void	   *builderCxt;
+
+	tupdesc = tstate->tupdesc;
+	builder = tstate->builder;
+	builderCxt = tstate->builderCxt;
+
+	/*
+	 * First time around?  Set up our calling context and evaluate the
+	 * document expression.
+	 */
+	if (builderCxt == NULL)
+	{
+		ListCell   *ns;
+
+		builderCxt = builder->CreateContext(tupdesc,
+											tstate->in_functions,
+											tstate->typioparams,
+											tstate->per_rowset_memory);
+		tstate->builderCxt = builderCxt;
+
+		/* Evaluate document expression first */
+		value = ExecEvalExpr(tstate->expr, econtext, &isnull, NULL);
+		if (isnull)
+		{
+			*isDone = ExprSingleResult;
+			*isNull = true;
+			return (Datum) 0;
+		}
+
+		/*
+		 * The content can be bigger document and transformation to cstring
+		 * can be expensive. The table builder is better place for this task -
+		 * pass value as Datum.
+		 */
+		builder->SetContent(builderCxt, value);
+
+		/* Evaluate namespace specifications */
+		foreach(ns, tstate->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			ExprState  *expr;
+			char	   *ns_name;
+			char	   *ns_uri;
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr *na = (NamedArgExpr *) n;
+
+				expr = (ExprState *) na->arg;
+				ns_name = na->name;
+			}
+			else
+			{
+				expr = (ExprState *) n;
+				ns_name = NULL;
+			}
+
+			value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("namespace uri must not be null")));
+			ns_uri = TextDatumGetCString(value);
+
+			builder->AddNS(builderCxt, ns_name, ns_uri);
+		}
+
+		/* Evaluate row path filter */
+		value = ExecEvalExpr(tstate->row_path_expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("row query must not be null")));
+		builder->SetRowPath(builderCxt, TextDatumGetCString(value));
+
+		/* Evaluate column paths */
+		for (i = 0; i < tstate->ncols; i++)
+		{
+			char	   *col_path;
+
+			if (tstate->col_path_expr[i] != NULL)
+			{
+				value = ExecEvalExpr(tstate->col_path_expr[i], econtext, &isnull, NULL);
+				if (isnull)
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("column path for column \"%s\" must not be null",
+							NameStr(tupdesc->attrs[i]->attname))));
+				col_path = TextDatumGetCString(value);
+			}
+			else
+				col_path = NameStr(tupdesc->attrs[i]->attname);
+
+			builder->SetColumnPath(builderCxt, col_path, i);
+		}
+	}
+
+	/* Now we can prepare result */
+	if (builder->FetchRow(builderCxt))
+	{
+		HeapTuple	tuple;
+		HeapTupleHeader dtuple;
+		Datum	   *values;
+		bool	   *nulls;
+
+		values = tstate->values;
+		nulls = tstate->nulls;
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			if (i != tstate->for_ordinality_col - 1)
+			{
+				values[i] = builder->GetValue(builderCxt, i, &isnull);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tupdesc->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+			else
+			{
+				values[i] = Int32GetDatum(++tstate->rownum);
+				nulls[i] = false;
+			}
+		}
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(dtuple, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type previously identified
+		 */
+		HeapTupleHeaderSetTypeId(dtuple, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(dtuple, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+
+		*isNull = false;
+		*isDone = ExprMultipleResult;
+
+		result = HeapTupleHeaderGetDatum(dtuple);
+	}
+	else
+	{
+		/* no more rows */
+		builder->DestroyContext(builderCxt);
+		tstate->builderCxt = NULL;
+
+		/* ensure releasing all memory */
+		MemoryContextReset(tstate->per_rowset_memory);
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	return result;
+}
+
+/*
+ * ExecEvalTableExpr - this is envelop of ExecEvalTableExprProtected() function.
+ *
+ * This function ensures releasing all TableBuilder context and related
+ * memory context, when ExecEvalTableExprProtected fails on exception.
+ */
+static Datum
+ExecEvalTableExpr(TableExprState * tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	/* Ensure releasing context every exception */
+	Datum		result;
+
+	PG_TRY();
+	{
+		result = ExecEvalTableExprProtected(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		if (tstate->builderCxt != NULL)
+		{
+			tstate->builder->DestroyContext(tstate->builderCxt);
+			tstate->builderCxt = NULL;
+		}
+
+		MemoryContextDelete(tstate->per_rowset_memory);
+		tstate->per_rowset_memory = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5494,109 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int			ncols;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->builderCxt = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->builder = &XmlTableBuilder;
+
+				/* XXX this assumes the anon record type has been registered */
+				tstate->tupdesc =
+					lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+												exprTypmod((Node *) te));
+				Assert(tstate->tupdesc->natts > 0);
+
+				ncols = tstate->tupdesc->natts;
+				tstate->values = palloc0(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid			in_funcid;
+
+					getTypeInputInfo(tstate->tupdesc->attrs[i]->atttypid,
+									 &in_funcid, &tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_path_expr = ExecInitExpr((Expr *) te->row_path, parent);
+				tstate->expr = ExecInitExpr((Expr *) te->expr, parent);
+
+				if (te->cols)
+				{
+					ListCell   *col;
+
+					Assert(ncols == list_length(te->cols));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_path_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+
+					i = 0;
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+															   parent);
+							tstate->col_path_expr[i] = ExecInitExpr((Expr *) tec->path_expr,
+																	parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->per_rowset_memory = AllocSetContextCreate(CurrentMemoryContext,
+											"XmlTable per rowgroup context",
+											ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..3f0b490 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,48 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+	TupleDesc	typeInfo;
+
+	if (te->cols != NIL)
+	{
+		ListCell   *col;
+		int			i = 1;
+
+		typeInfo = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(typeInfo,
+							   (AttrNumber) i,
+							   pstrdup(tec->colname),
+							   tec->typid,
+							   tec->typmod,
+							   0);
+			i++;
+		}
+	}
+	else
+	{
+		typeInfo = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(typeInfo, (AttrNumber) 1,
+						   "xmltable", XMLOID, -1, 0);
+	}
+
+	/* XXX OK to do this?  looks a bit out of place ... */
+	assign_record_type_typmod(typeInfo);
+
+	return typeInfo;
+}
+
+/*
  * ExecTypeSetColNames - set column names in a TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..b9c39d7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr * from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(row_path);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(cols);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol * from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn * from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4600,6 +4657,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5104,6 +5167,9 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..686bccb 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2615,6 +2615,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol * a, const TableExprRawCol * b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn * a, const TableExprColumn * b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2645,6 +2675,18 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr * a, const TableExpr * b)
+{
+	COMPARE_NODE_FIELD(row_path);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(cols);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2910,6 +2952,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3401,6 +3449,9 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..df357e1 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +258,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +499,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate a blessed tupdesc from the column definitions, and
+				 * returns its typmod.
+				 */
+				TupleDesc	tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +747,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 +951,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1155,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1587,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2249,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->path_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3069,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_path, te->row_path, Node *);
+				MUTATE(newnode->expr, te->expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->cols, te->cols, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->path_expr, tec->path_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3708,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..bb1b904 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_NODE_FIELD(row_path);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(cols);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3539,6 +3581,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..8a47a82 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,44 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(row_path);
+	READ_NODE_FIELD(expr);
+	READ_NODE_FIELD(cols);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(path_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2535,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9598f28..8bfd06d 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))
@@ -3581,6 +3587,33 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (tec->path_expr != NULL || tec->default_expr != NULL)
+				{
+					TableExprColumn *newtec = makeNode(TableExprColumn);
+
+					newtec->colname = tec->colname;
+					newtec->typid = tec->typid;
+					newtec->typmod = tec->typmod;
+					newtec->collation = tec->collation;
+					newtec->for_ordinality = tec->for_ordinality;
+					newtec->is_not_null = tec->is_not_null;
+					newtec->path_expr =
+						  eval_const_expressions_mutator((Node *) tec->path_expr,
+														  context);
+					newtec->default_expr =
+						  eval_const_expressions_mutator((Node *) tec->default_expr,
+														  context);
+					newtec->location = tec->location;
+
+					return (Node *) newtec;
+				}
+			}
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..2b71fdd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -546,6 +546,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	TableExprColList TableExprColOptions
+%type <node>	TableExprCol
+%type <defelt>	TableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -577,10 +584,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
 
@@ -650,8 +657,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
 
@@ -12611,6 +12618,165 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $8;
+					n->expr = $9;
+					n->cols = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $8;
+					n->expr = $9;
+					n->cols = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+TableExprColList: TableExprCol							{ $$ = list_make1($1); }
+			| TableExprColList ',' TableExprCol			{ $$ = lappend($1, $3); }
+		;
+
+TableExprCol:
+			ColId Typename IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $3;
+					rawc->path_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+			| ColId Typename TableExprColOptions IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $4;
+					rawc->path_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->path_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->path_expr = defel->arg;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+TableExprColOptions: TableExprColOption					{ $$ = list_make1($1); }
+			| TableExprColOptions TableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+TableExprColOption:
+			IDENT a_expr
+				{
+					$$ = makeDefElem($1, $2, @1);
+				}
+			| DEFAULT a_expr
+				{
+					$$ = makeDefElem("default", $2, @1);
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			a_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT a_expr
+				{
+					$$ = $2;
+				}
 		;
 
 /*
@@ -13749,6 +13915,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14050,10 +14217,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..12f3b36 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1095,9 +1095,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1105,9 +1105,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1116,7 +1116,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1143,6 +1143,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+								targetTypeId, -1,
+								constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..d6a0559 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr * te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -365,6 +367,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2677,6 +2683,182 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 	return (Node *) newc;
 }
 
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr* te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	const char *constructName;
+	Oid			exprType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	exprType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, te->location);
+
+	/* Transform the row-generating Xpath expression ... */
+	Assert(te->row_path != NULL);
+	newte->row_path = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_path),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML itself */
+	Assert(te->expr != NULL);
+	newte->expr = coerce_to_specific_type(pstate,
+									  transformExprRecurse(pstate, te->expr),
+										  exprType,
+										  constructName);
+
+	/* Columns, if any, also need to be transformed */
+	if (te->cols != NIL)
+	{
+		ListCell   *col;
+		bool		for_ordinality = false;
+		char	  **colnames;
+		int			i = 0;
+
+		colnames = palloc(sizeof(char *) * list_length(te->cols));
+
+		/*
+		 * For each TableExprRawCol in the input TableExpr, generate one
+		 * TableExprColumn in the output TableExpr.
+		 */
+		foreach(col, te->cols)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc;
+			Oid			typid;
+			int32		typmod;
+			int			j;
+
+			/* Only one FOR ORDINALITY column is allowed; verify */
+			if (rawc->for_ordinality)
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+			}
+
+			newc = makeNode(TableExprColumn);
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			/*
+			 * Determine the type and typmod for the new column.
+			 * FOR ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				newc->typid = INT4OID;
+				newc->typmod = -1;
+			}
+			else
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &newc->typid, &newc->typmod);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->path_expr)
+				newc->path_expr = coerce_to_specific_type(pstate,
+														  transformExprRecurse(pstate, rawc->path_expr),
+														  TEXTOID,
+														  constructName);
+			if (rawc->default_expr)
+				newc->default_expr = coerce_to_specific_type_typmod(pstate,
+																	transformExprRecurse(pstate, rawc->default_expr),
+																	newc->typid, newc->typmod,
+																	constructName);
+
+
+			newte->cols = lappend(newte->cols, newc);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(colnames[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			colnames[i] = rawc->colname;
+
+			i++;
+		}
+
+		pfree(colnames);
+	}
+	else
+		newte->cols = NIL;
+
+	/* Namespaces, if any, also need to be transformed */
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		bool		found_default_namespace = false;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr *na = (NamedArgExpr *) n;
+				int			i;
+
+				/* make sure namespace names are unique */
+				for (i = 0; i < nnames; i++)
+					if (strcmp(nsnames[i], na->name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the namespace \"%s\" is not unique",
+										na->name),
+								 parser_errposition(pstate, na->location)));
+				nsnames[nnames++] = na->name;
+
+				na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+														   TEXTOID,
+														   constructName);
+			}
+			else
+			{
+				/* Only one DEFAULT namespace is allowed; verify */
+				if (found_default_namespace)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one default namespace is allowed"),
+							 parser_errposition(pstate, exprLocation(n))));
+				found_default_namespace = true;
+				n = coerce_to_specific_type(pstate,
+											transformExprRecurse(pstate, n),
+											TEXTOID,
+											constructName);
+			}
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(nsnames);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
 /*
  * Transform a "row compare-op row" construct
  *
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index a76c33f..edafb9a 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1848,6 +1848,13 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+			/*
+			 * Make TableExpr act like a regular function. Only
+			 * XMLTABLE expr is supported in this moment.
+			 */
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 0f51275..5a3715c 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -29,7 +29,7 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
 	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
 	tsvector.o tsvector_op.o tsvector_parser.o \
 	txid.o uuid.o varbit.o varchar.o varlena.o version.o \
-	windowfuncs.o xid.o xml.o
+	windowfuncs.o xid.o xml.o xpath_parser.o
 
 like.o: like.c like_match.c
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..92d5dd9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8303,6 +8304,105 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/*
+				 * Deparse TableExpr - the is only one TableExpr producer,
+				 * the function XMLTABLE.
+				 */
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_path, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->cols != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->path_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->path_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..e979cf0 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"
@@ -89,6 +90,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/xml.h"
+#include "utils/xpath_parser.h"
 
 
 /* GUC variables */
@@ -165,6 +167,29 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+static void *XmlTableCreateContext(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparms,
+					  MemoryContext mcxt);
+static void XmlTableSetContent(void *tcontext, Datum value);
+static void XmlTableAddNS(void *tcontext, char *name, char *uri);
+static void XmlTableSetRowPath(void *tcontext, char *path);
+static void XmlTableSetColumnPath(void *tcontext, char *path, int colnum);
+static bool XmlTableFetchRow(void *tcontext);
+static Datum XmlTableGetValue(void *tcontext, int colnum, bool *isnull);
+static void XmlTableDestroyContext(void *tcontext);
+
+const TableExprBuilder XmlTableBuilder =
+{
+	XmlTableCreateContext,
+	XmlTableSetContent,
+	XmlTableAddNS,
+	XmlTableSetRowPath,
+	XmlTableSetColumnPath,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyContext
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4090,588 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Functions for XmlTableBuilder
+ *
+ */
+typedef struct XmlTableContext
+{
+	TupleDesc	tupdesc;
+	MemoryContext per_rowset_memory;
+	char	   *def_namespace_name;
+	FmgrInfo   *in_functions;
+	Oid		   *typioparams;
+	long int	rc;
+	PgXmlErrorContext *xmlerrcxt;
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr	doc;
+	xmlXPathContextPtr xpathcxt;
+	xmlXPathCompExprPtr xpathcomp;
+	xmlXPathObjectPtr xpathobj;
+	xmlXPathCompExprPtr *xpathscomp;
+}	XmlTableContext;
+
+/*
+ * Convert XML node to cstring (dump subtree in case of element,
+ * return value otherwise)
+ */
+static char *
+xml_xmlnodetostr(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt)
+{
+	char	   *result;
+
+	if (cur->type == XML_ELEMENT_NODE)
+	{
+		xmlBufferPtr buf;
+		xmlNodePtr	cur_copy;
+
+		buf = xmlBufferCreate();
+
+		/*
+		 * The result of xmlNodeDump() won't contain namespace definitions
+		 * from parent nodes, but xmlCopyNode() duplicates a node along with
+		 * its required namespace definitions.
+		 */
+		cur_copy = xmlCopyNode(cur, 1);
+
+		if (cur_copy == NULL)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not copy node");
+
+		PG_TRY();
+		{
+			xmlNodeDump(buf, NULL, cur_copy, 0, 1);
+			result = pstrdup((const char *) xmlBufferContent(buf));
+		}
+		PG_CATCH();
+		{
+			xmlFreeNode(cur_copy);
+			xmlBufferFree(buf);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+		xmlFreeNode(cur_copy);
+		xmlBufferFree(buf);
+	}
+	else
+	{
+		xmlChar    *str;
+
+		str = xmlXPathCastNodeToString(cur);
+		PG_TRY();
+		{
+			/* Here we rely on XML having the same representation as TEXT */
+			char	   *escaped = escape_xml((char *) str);
+
+			result = escaped;
+		}
+		PG_CATCH();
+		{
+			xmlFree(str);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+		xmlFree(str);
+	}
+
+	return result;
+}
+
+/*
+ * Secure cstring for usage in libxml2
+ */
+static xmlChar *
+to_xmlstr(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+#endif
+
+/*
+ * Create XmlTableContext for XmlTable builder. Initialize XML parser.
+ */
+static void *
+XmlTableCreateContext(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparams,
+					  MemoryContext per_rowset_memory)
+{
+#ifdef USE_LIBXML
+	MemoryContext oldcxt;
+	XmlTableContext *result = NULL;
+	PgXmlErrorContext *xmlerrcxt = NULL;
+	volatile xmlParserCtxtPtr ctxt = NULL;
+
+	oldcxt = MemoryContextSwitchTo(per_rowset_memory);
+
+	result = palloc0(sizeof(struct XmlTableContext));
+	result->tupdesc = tupdesc;
+	result->per_rowset_memory = per_rowset_memory;
+	result->in_functions = in_functions;
+	result->typioparams = typioparams;
+	result->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * tupdesc->natts);
+
+	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+	PG_TRY();
+	{
+		xmlInitParser();
+
+		ctxt = xmlNewParserCtxt();
+		if (ctxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate parser context");
+	}
+	PG_CATCH();
+	{
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	result->xmlerrcxt = xmlerrcxt;
+	result->ctxt = ctxt;
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(void *tcontext, Datum value)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	xmltype    *xmlval = DatumGetXmlP(value);
+	xmlChar    *xstr;
+	int			length;
+	volatile xmlDocPtr doc = NULL;
+	volatile xmlXPathContextPtr xpathcxt = NULL;
+	MemoryContext oldcxt;
+
+	length = VARSIZE(xmlval) - VARHDRSZ;
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+
+	xstr = to_xmlstr(VARDATA(xmlval), length);
+
+	PG_TRY();
+	{
+		doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL, 0);
+		if (doc == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+						"could not parse XML document");
+		xpathcxt = xmlXPathNewContext(doc);
+		if (xpathcxt == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate XPath context");
+		xpathcxt->node = xmlDocGetRootElement(doc);
+		if (xpathcxt->node == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not find root XML element");
+	}
+	PG_CATCH();
+	{
+		if (xpathcxt != NULL)
+			xmlXPathFreeContext(xpathcxt);
+		if (doc != NULL)
+			xmlFreeDoc(doc);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xtCxt->doc = doc;
+	xtCxt->xpathcxt = xpathcxt;
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+#define DEFAULT_NAMESPACE_NAME		"pgdefnamespace"
+
+/*
+ * XmlTableAddNS - add namespace. use fake name when the name
+ * is null, and store this name as default namespace.
+ */
+static void
+XmlTableAddNS(void *tcontext, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	if (name == NULL)
+	{
+		Assert(xtCxt->def_namespace_name == NULL);
+
+		name = DEFAULT_NAMESPACE_NAME;
+		xtCxt->def_namespace_name = name;
+	}
+
+	if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+						   to_xmlstr(name, strlen(name)),
+						   to_xmlstr(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowPath
+ */
+static void
+XmlTableSetRowPath(void *tcontext, char *path)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	xmlChar    *xstr;
+	MemoryContext oldcxt;
+	StringInfoData str;
+
+	if (path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("row path filter must not be empty string")));
+
+	transformXPath(&str, path, NULL, xtCxt->def_namespace_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+	xstr = to_xmlstr(str.data, str.len);
+
+	xtCxt->xpathcomp = xmlXPathCompile(xstr);
+	if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetColumnPath
+ */
+static void
+XmlTableSetColumnPath(void *tcontext, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	MemoryContext oldcxt;
+	StringInfoData str;
+	xmlChar    *xstr;
+
+	if (path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("column path filter must not be empty string")));
+
+	transformXPath(&str, path,
+				   "./",
+				   xtCxt->def_namespace_name);
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+	xstr = to_xmlstr(str.data, str.len);
+
+	xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+	if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableFetchRow - returns true when is possible to read
+ * values from XML document.
+ *
+ * First call evaluates row path over content.
+ * Next step and at other calls increment row counter and
+ * compare it with number values in result set.
+ */
+static bool
+XmlTableFetchRow(void *tcontext)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	/*
+	 * XmlTable returns table - set of composite values. The error context,
+	 * is used for producement more values, between two calls, there can be
+	 * created and used another libxml2 error context. It is libxml2 global
+	 * value, so it should be refreshed any time before any libxml2 usage,
+	 * that is finished by returning some value.
+	 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		xtCxt->rc = 0;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+}
+
+/*
+ * Apply column path on current node and returns result.
+ * Allows multivalue when expected type is XML.
+ * When COLUMNS part was not entered, returns current node.
+ */
+static Datum
+XmlTableGetValue(void *tcontext, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+	if (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = NULL;
+
+		/* fast leaving */
+		if (cur->type != XML_ELEMENT_NODE)
+			elog(ERROR, "unexpected xmlNode type");
+
+		PG_TRY();
+		{
+			/* Set current node as entry point for XPath evaluation */
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+			/* Evaluate column path */
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+			if (column_xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+				xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+							"could not create XPath object");
+
+			if (column_xpathobj->type == XPATH_NODESET)
+			{
+				int			count;
+
+				if (column_xpathobj->nodesetval != NULL)
+					count = column_xpathobj->nodesetval->nodeNr;
+				else
+					count = 0;
+
+				if (count > 0)
+				{
+					Oid		targettypid = xtCxt->tupdesc->attrs[colnum]->atttypid;
+
+					if (count == 1)
+					{
+						if (targettypid != XMLOID)
+						{
+							xmlChar *str;
+
+							str  = xmlNodeListGetString(xtCxt->doc,
+								column_xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+																			1);
+
+							if (str != NULL)
+							{
+								PG_TRY();
+								{
+									/* Copy string to PostgreSQL controlled memory */
+									cstr = pstrdup((char *) str);
+								}
+								PG_CATCH();
+								{
+									xmlFree(str);
+									PG_RE_THROW();
+								}
+								PG_END_TRY();
+
+								xmlFree(str);
+							}
+							else
+							{
+								/* Return empty string when tag is empty */
+								cstr = pstrdup("");
+							}
+						}
+						else
+						{
+							/* simple case, result is one value */
+							cstr = xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[0],
+													xtCxt->xmlerrcxt);
+						}
+					}
+					else
+					{
+						StringInfoData str;
+						int			i;
+
+						/*
+						 * When result is not one value, then we can work with
+						 * concated values. But it requires XML as expected
+						 * type.
+						 */
+						if (targettypid != XMLOID)
+							ereport(ERROR,
+									(errcode(ERRCODE_CARDINALITY_VIOLATION),
+									 errmsg("more than one value returned by column XPath expression")));
+
+						/* Concate serialized values */
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+												   xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														  xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[colnum],
+											   cstr,
+											   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[colnum],
+										 (char *) column_xpathobj->stringval,
+										   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+				*isnull = false;
+			}
+			else
+				elog(ERROR, "unexpected XPath object type");
+
+			xmlXPathFreeObject(column_xpathobj);
+			column_xpathobj = NULL;
+		}
+		PG_CATCH();
+		{
+			if (column_xpathobj != NULL)
+				xmlXPathFreeObject(column_xpathobj);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+	else
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+		result = InputFunctionCall(&xtCxt->in_functions[0],
+								   cstr,
+								   xtCxt->typioparams[0],
+								   -1); /* target type is XML always */
+		*isnull = false;
+	}
+
+	if (cstr != NULL)
+		pfree(cstr);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * Release all libxml2 memory and related resources
+ */
+static void
+XmlTableDestroyContext(void *tcontext)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int			i;
+
+		for (i = 0; i < xtCxt->tupdesc->natts; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
diff --git a/src/backend/utils/adt/xpath_parser.c b/src/backend/utils/adt/xpath_parser.c
new file mode 100644
index 0000000..ff8aabf
--- /dev/null
+++ b/src/backend/utils/adt/xpath_parser.c
@@ -0,0 +1,337 @@
+/*-------------------------------------------------------------------------
+ *
+ * xpath_parser.c
+ *	  XML XPath parser.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/utils/adt/xpath_parser.c
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "utils/xpath_parser.h"
+
+/*
+ * All PostgreSQL XML related functionality is based on libxml2 library, and
+ * XPath support is not an exception.  However, libxml2 doesn't provide the
+ * functions necessary to implement an XMLTABLE function. There is no API to
+ * access the XPath expression AST (abstract syntax tree), and there is no
+ * support for default namespaces in XPath expressions.
+ *
+ * Those functionalities are implemented with a simple XPath parser/
+ * preprocessor.  This XPath parser transforms a XPath expression to another
+ * XPath expression that can be used by libxml2 XPath evaluation. It doesn't
+ * replace libxml2 XPath parser or libxml2 XPath expression evaluation.
+ */
+
+/*
+ * support functions for XMLTABLE function
+ */
+#ifdef USE_LIBXML
+
+/*
+ * We need to work with XPath expression tokens.  When expression starting with
+ * nodename, then we can use prefix.  When default namespace is defined, then we
+ * should to enhance any nodename and attribute without namespace by default
+ * namespace.
+ */
+
+typedef enum
+{
+	XPATH_TOKEN_NONE,
+	XPATH_TOKEN_NAME,
+	XPATH_TOKEN_STRING,
+	XPATH_TOKEN_NUMBER,
+	XPATH_TOKEN_OTHER
+}	XPathTokenType;
+
+typedef struct TokenInfo
+{
+	XPathTokenType ttype;
+	char	   *start;
+	int			length;
+}	XPathTokenInfo;
+
+#define TOKEN_STACK_SIZE		10
+
+typedef struct ParserData
+{
+	char	   *str;
+	char	   *cur;
+	XPathTokenInfo stack[TOKEN_STACK_SIZE];
+	int			stack_length;
+}	XPathParserData;
+
+/* Any high-bit-set character is OK (might be part of a multibyte char) */
+#define NODENAME_FIRSTCHAR(c)	 ((c) == '_' || (c) == '-' || \
+								 ((c) >= 'A' && (c) <= 'Z') || \
+								 ((c) >= 'a' && (c) <= 'z') || \
+								 (IS_HIGHBIT_SET(c)))
+
+#define IS_NODENAME_CHAR(c)		(NODENAME_FIRSTCHAR(c) || (c) == '.' || \
+								 ((c) >= '0' && (c) <= '9'))
+
+
+/*
+ * Returns next char after last char of token
+ */
+static char *
+getXPathToken(char *str, XPathTokenInfo * ti)
+{
+	/* skip initial spaces */
+	while (*str == ' ')
+		str++;
+
+	if (*str != '\0')
+	{
+		char		c = *str;
+
+		ti->start = str++;
+
+		if (c >= '0' && c <= '9')
+		{
+			while (*str >= '0' && *str <= '9')
+				str++;
+			if (*str == '.')
+			{
+				str++;
+				while (*str >= '0' && *str <= '9')
+					str++;
+			}
+			ti->ttype = XPATH_TOKEN_NUMBER;
+		}
+		else if (NODENAME_FIRSTCHAR(c))
+		{
+			while (IS_NODENAME_CHAR(*str))
+				str++;
+
+			ti->ttype = XPATH_TOKEN_NAME;
+		}
+		else if (c == '"')
+		{
+			while (*str != '\0')
+				if (*str++ == '"')
+					break;
+
+			ti->ttype = XPATH_TOKEN_STRING;
+		}
+		else
+			ti->ttype = XPATH_TOKEN_OTHER;
+
+		ti->length = str - ti->start;
+	}
+	else
+	{
+		ti->start = NULL;
+		ti->length = 0;
+
+		ti->ttype = XPATH_TOKEN_NONE;
+	}
+
+	return str;
+}
+
+/*
+ * reset XPath parser stack
+ */
+static void
+initXPathParser(XPathParserData * parser, char *str)
+{
+	parser->str = str;
+	parser->cur = str;
+	parser->stack_length = 0;
+}
+
+/*
+ * Returns token from stack or read token
+ */
+static void
+nextXPathToken(XPathParserData * parser, XPathTokenInfo * ti)
+{
+	if (parser->stack_length > 0)
+		memcpy(ti, &parser->stack[--parser->stack_length],
+			   sizeof(XPathTokenInfo));
+	else
+		parser->cur = getXPathToken(parser->cur, ti);
+}
+
+/*
+ * Push token to stack
+ */
+static void
+pushXPathToken(XPathParserData * parser, XPathTokenInfo * ti)
+{
+	if (parser->stack_length == TOKEN_STACK_SIZE)
+		elog(ERROR, "internal error");
+	memcpy(&parser->stack[parser->stack_length++], ti,
+		   sizeof(XPathTokenInfo));
+}
+
+/*
+ * Write token to output string
+ */
+static void
+writeXPathToken(StringInfo str, XPathTokenInfo * ti)
+{
+	Assert(ti->ttype != XPATH_TOKEN_NONE);
+
+	if (ti->ttype != XPATH_TOKEN_OTHER)
+		appendBinaryStringInfo(str, ti->start, ti->length);
+	else
+		appendStringInfoChar(str, *ti->start);
+}
+
+/*
+ * Working horse for XPath transformation. When XPath starting by node name,
+ * then prefix have to be applied. Any unqualified node name should be
+ * qualified by default namespace. inside_predicate is true, when
+ * _transformXPath is recursivly called because the predicate expression
+ * was found.
+ */
+static void
+_transformXPath(StringInfo str, XPathParserData * parser,
+				bool inside_predicate,
+				char *prefix, char *def_namespace_name)
+{
+	XPathTokenInfo t1,
+				t2;
+	bool		is_first_token = true;
+	bool		last_token_is_name = false;
+
+	nextXPathToken(parser, &t1);
+
+	while (t1.ttype != XPATH_TOKEN_NONE)
+	{
+		switch (t1.ttype)
+		{
+			case XPATH_TOKEN_NUMBER:
+			case XPATH_TOKEN_STRING:
+				last_token_is_name = false;
+				is_first_token = false;
+				writeXPathToken(str, &t1);
+				nextXPathToken(parser, &t1);
+				break;
+
+			case XPATH_TOKEN_NAME:
+				{
+					bool		is_qual_name = false;
+
+					/* inside predicate ignore keywords "and" "or" */
+					if (inside_predicate)
+					{
+						if ((strncmp(t1.start, "and", 3) == 0 && t1.length == 3) ||
+						 (strncmp(t1.start, "or", 2) == 0 && t1.length == 2))
+						{
+							writeXPathToken(str, &t1);
+							nextXPathToken(parser, &t1);
+							break;
+						}
+					}
+
+					last_token_is_name = true;
+					nextXPathToken(parser, &t2);
+					if (t2.ttype == XPATH_TOKEN_OTHER)
+					{
+						if (*t2.start == '(')
+							last_token_is_name = false;
+						else if (*t2.start == ':')
+							is_qual_name = true;
+					}
+
+					if (is_first_token && last_token_is_name && prefix != NULL)
+						appendStringInfoString(str, prefix);
+
+					if (last_token_is_name && !is_qual_name && def_namespace_name != NULL)
+						appendStringInfo(str, "%s:", def_namespace_name);
+
+					writeXPathToken(str, &t1);
+					is_first_token = false;
+
+					if (is_qual_name)
+					{
+						writeXPathToken(str, &t2);
+						nextXPathToken(parser, &t1);
+						if (t1.ttype == XPATH_TOKEN_NAME)
+							writeXPathToken(str, &t1);
+						else
+							pushXPathToken(parser, &t1);
+					}
+					else
+						pushXPathToken(parser, &t2);
+
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_OTHER:
+				{
+					char		c = *t1.start;
+
+					is_first_token = false;
+
+					writeXPathToken(str, &t1);
+
+					if (c == '[')
+						_transformXPath(str, parser, true, NULL, def_namespace_name);
+					else
+					{
+						last_token_is_name = false;
+
+						if (c == ']' && inside_predicate)
+							return;
+
+						else if (c == '@')
+						{
+							nextXPathToken(parser, &t1);
+							if (t1.ttype == XPATH_TOKEN_NAME)
+							{
+								bool		is_qual_name = false;
+
+								nextXPathToken(parser, &t2);
+								if (t2.ttype == XPATH_TOKEN_OTHER && *t2.start == ':')
+									is_qual_name = true;
+
+								if (!is_qual_name && def_namespace_name != NULL)
+									appendStringInfo(str, "%s:", def_namespace_name);
+
+								writeXPathToken(str, &t1);
+								if (is_qual_name)
+								{
+									writeXPathToken(str, &t2);
+									nextXPathToken(parser, &t1);
+									if (t1.ttype == XPATH_TOKEN_NAME)
+										writeXPathToken(str, &t1);
+									else
+										pushXPathToken(parser, &t1);
+								}
+								else
+									pushXPathToken(parser, &t2);
+							}
+							else
+								pushXPathToken(parser, &t1);
+						}
+					}
+					nextXPathToken(parser, &t1);
+				}
+				break;
+
+			case XPATH_TOKEN_NONE:
+				elog(ERROR, "should not be here");
+		}
+	}
+}
+
+void
+transformXPath(StringInfo str, char *xpath,
+			   char *prefix, char *def_namespace_name)
+{
+	XPathParserData parser;
+
+	initStringInfo(str);
+	initXPathParser(&parser, xpath);
+	_transformXPath(str, &parser, false, prefix, def_namespace_name);
+}
+
+#endif
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c6d69bc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -272,6 +272,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
 extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..12791ab
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/primnodes.h"
+
+/*
+ * This structure holds a collection of function pointers used
+ * for generating content of table-expression functions like
+ * XMLTABLE.
+ *
+ * The TableBuilder is initialized by calling CreateContext function
+ * at evaluation time. First parameter - tuple descriptor describes
+ * produced (expected) table. in_functions is a array of FmgrInfo input
+ * functions for types of columns of produced table. The typioparams
+ * is a array of typio Oids for types of columns of produced table.
+ * The created context is living in special memory context passed
+ * as last parameter.
+ *
+ * The SetContent function is used for passing input document to
+ * table builder. The table builder handler knows expected format
+ * and it can do some additional transformations that are not propagated
+ * out from table builder.
+ *
+ * The AddNs add namespace info when namespaces are supported.
+ * Namespaces should be passed before Row/Column Paths setting.
+ * When name is NULL, then related uri is default namespace.
+ *
+ * The SetRowPath sets a row generating filter. This filter is used
+ * for separation of rows from document. Passed as cstring.
+ *
+ * The SetColumnPath sets a column generating filter. This filter is
+ * used for separating nth value from row. Passed as cstring.
+ *
+ * The FetchRow ensure loading row raleted data. Returns false, when
+ * document doesn't containt any next row.
+ *
+ * The GetValue returns a value related to colnum column.
+ *
+ * The DestroyContext - should to release all sources related to
+ * processing the document. Called when all rows are fetched or
+ * when a error is catched.
+ */
+typedef struct TableExprBuilder
+{
+	void	   *(*CreateContext) (TupleDesc tupdesc,
+									FmgrInfo *in_functions, Oid *typioparams,
+											MemoryContext per_rowset_memory);
+	void		(*SetContent) (void *tcontext, Datum value);
+	void		(*AddNS) (void *tcontext, char *name, char *uri);
+	void		(*SetRowPath) (void *tcontext, char *path);
+	void		(*SetColumnPath) (void *tcontext, char *path, int colnum);
+	bool		(*FetchRow) (void *tcontext);
+	Datum		(*GetValue) (void *tcontext, int colnum, bool *isnull);
+	void		(*DestroyContext) (void *tcontext);
+}	TableExprBuilder;
+
+#endif		/* TABLEEXPR_H */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..523afc8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "executor/instrument.h"
+#include "executor/tableexpr.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
@@ -1011,6 +1012,36 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+/* ----------------
+ *		TableExprState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	TupleDesc	tupdesc;		/* cache */
+	int			ncols;			/* number of declared columns */
+	int			for_ordinality_col;		/* number of oridinality column,
+										 * started by 1 */
+	int			rownum;			/* row counter - for ordinality column */
+	ExprState  *row_path_expr;	/* row xpath expression */
+	ExprState  *expr;			/* processed data */
+	ExprState **def_expr;		/* array of expressions for default value */
+	ExprState **col_path_expr;	/* array of expressions for path value */
+	bool	   *not_null;		/* for any column info if NULL is allowed or
+								 * not */
+	Datum	   *values;			/* prealloc buffer */
+	bool	   *nulls;			/* prealloc buffer */
+	FmgrInfo   *in_functions;	/* array of infunction for any column */
+	Oid		   *typioparams;	/* array of typIOParam for any column */
+	const TableExprBuilder *builder;	/* pointers to builder functions */
+	void	   *builderCxt;		/* data for content builder */
+
+	MemoryContext per_rowset_memory;
+}	TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b6e5151 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -454,6 +457,7 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..21859cc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,27 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - raw column definition in table-expression functions
+ *		like XMLTABLE.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions.
+ *
+ * If for_ordinality is true (FOR ORDINALITY), then the column is an int4
+ * column and the rest of the fields are ignored.
+ */
+typedef struct TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;
+	bool		for_ordinality;
+	TypeName   *typeName;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..3fcf546 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,44 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - holds data for table-expression functions like XMLTABLE
+ *
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Node	   *row_path;		/* row xpath query */
+	Node	   *expr;			/* processed data */
+	List	   *cols;			/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+}	TableExpr;
+
+/*----------
+ * TableExprColumn - a column definition in table-expression functions.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions
+ * and just doesn't make sense here.
+ *
+ * Raw form of this node is TableExprRawCol node - different in
+ * typeName field. After transformation we use typid, typmod.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;
+	Oid			typid;
+	int32		typmod;
+	Oid			collation;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+}	TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..de4b8b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -441,10 +442,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..1229b76 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,6 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern const TableExprBuilder XmlTableBuilder;
+
 #endif   /* XML_H */
diff --git a/src/include/utils/xpath_parser.h b/src/include/utils/xpath_parser.h
new file mode 100644
index 0000000..c6fc532
--- /dev/null
+++ b/src/include/utils/xpath_parser.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * xpath_parser.h
+ *	  Declarations for XML XPath transformation.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/xml.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef XPATH_PARSER_H
+#define XPATH_PARSER_H
+
+#include "postgres.h"
+#include "lib/stringinfo.h"
+
+void transformXPath(StringInfo str, char *xpath,
+			   char *prefix, char *def_namespace_name);
+
+#endif   /* XPATH_PARSER_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..3f14b3b 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,418 @@ 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');
+ a  
+----
+ 10
+(1 row)
+
+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..8987558 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,326 @@ 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:  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 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..f591be3 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,417 @@ 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');
+ a  
+----
+ 10
+(1 row)
+
+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;
#58Andrew Dunstan
andrew@dunslane.net
In reply to: Alvaro Herrera (#57)
Re: patch: function xmltable

On 11/23/2016 06:31 PM, Alvaro Herrera wrote:

Sorry, here's the patch. Power loss distracted me here.

By the way, the pgindent you did is slightly confused because you failed
to add the new struct types you define to typedefs.list.

Tips on how to use pgindent for developers:

<http://adpgtech.blogspot.com/2015/05/running-pgindent-on-non-core-code-or.html&gt;

cheers

andrew

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#59Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#56)
Re: patch: function xmltable

Alvaro Herrera wrote:

If you use "PATH '/'" for a column, you get the text for all the entries
in the whole XML, rather than the text for the particular row being
processed. Isn't that rather weird, or to put it differently, completely
wrong? I didn't find a way to obtain the whole XML row when you have
the COLUMNS option (which is what I was hoping for with the "PATH '/'").

Ah, apparently you need to use type XML for that column in order for
this to happen. Example:

insert into emp values ($$
<depts >
<dept bldg="102">
<employee id="905">
<name>
<first>John</first>
<last>Doew</last>
</name>
<office>344</office>
<salary currency="USD">55000</salary>
</employee>

<employee id="908">
<name>
<first>Peter</first>
<last>Panw</last>
</name>
<office>216</office>
<phone>905-416-5004</phone>
</employee>
</dept>

<dept bldg="115">
<employee id="909">
<name>
<first>Mary</first>
<last>Jonesw</last>
</name>
<office>415</office>
<phone>905-403-6112</phone>
<phone>647-504-4546</phone>
<salary currency="USD">64000</salary>
</employee>
</dept>
</depts>
$$);

Note the weird salary_amount value here:

SELECT x.*
FROM emp,
XMLTABLE ('//depts/dept/employee' passing doc
COLUMNS
i for ordinality,
empID int PATH '@id',
firstname varchar(25) PATH 'name/first' default 'FOOBAR',
lastname VARCHAR(25) PATH 'name/last',
salary xml path 'concat(salary/text(), salary/@currency)' default 'DONT KNOW', salary_amount xml path '/' )
WITH ORDINALITY
AS X (i, a, b, c) limit 1;
i │ a │ b │ c │ salary │ salary_amount │ ordinality
───┼─────┼──────┼──────┼──────────┼───────────────────────┼────────────
1 │ 905 │ John │ Doew │ 55000USD │ ↵│ 1
│ │ │ │ │ ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ John ↵│
│ │ │ │ │ Doew ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ 344 ↵│
│ │ │ │ │ 55000 ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ Peter ↵│
│ │ │ │ │ Panw ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ 216 ↵│
│ │ │ │ │ 905-416-5004↵│
│ │ │ │ │ ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ Mary ↵│
│ │ │ │ │ Jonesw ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ 415 ↵│
│ │ │ │ │ 905-403-6112↵│
│ │ │ │ │ 647-504-4546↵│
│ │ │ │ │ 64000 ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ ↵│
│ │ │ │ │ │
(1 fila)

If you declare salary_amount to be text instead, it doesn't happen anymore.
Apparently if you put it in a namespace, it doesn't hapen either.

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#60Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#55)
Re: patch: function xmltable

Hi

2016-11-24 0:13 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Oh my, I just noticed we have a new xpath preprocessor in this patch
too. Where did this code come from -- did you write it all from
scratch?

I wrote it from scratch - libxml2 has not any API for iteration over XPath
expression (different than iteration over XPath expression result), and
what I have info, there will not be any new API in libxml2.

There are two purposes:

Safe manipulation with XPath expression prefixes - ANSI SQL design
implicitly expects some prefix, but it can be used manually. The prefix
should not be used twice and in some situations, when it can breaks the
expression.

Second goal is support default namespaces - when we needed parser for first
task, then the enhancing for this task was not too much lines more.

This parser can be used for enhancing current XPath function - default
namespaces are pretty nice, when you have to use namespaces.

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#61Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#56)
Re: patch: function xmltable

2016-11-24 0:29 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Here's another version. Not there yet: need to move back the function
to create the tupdesc, as discussed. Not clear what's the best place,
however. I modified the grammar a bit (added the missing comma, removed
PATH as an unreserved keyword and just used IDENT, removed the "Opt"
version for column options), and reworked the comments in the transform
phase (I tweaked the code here and there mostly to move things to nicer
places, but it's pretty much the same code).

In the new xpath_parser.c file I think we should tidy things up a bit.
First, it needs more commentary on what the entry function actually
does, in detail. Also, IMO that function should be at the top of the
file, not at the bottom, followed by all its helpers. I would like some
more clarity on the provenance of all this code, just to assess the
probability of bugs; mostly as it's completely undocumented.

I don't like the docs either. I think we should have a complete
reference to the syntax, followed by examples, rather than letting the
examples drive the whole thing. I fixed the synopsis so that it's not
one very long line.

If you use "PATH '/'" for a column, you get the text for all the entries
in the whole XML, rather than the text for the particular row being
processed. Isn't that rather weird, or to put it differently, completely
wrong? I didn't find a way to obtain the whole XML row when you have
the COLUMNS option (which is what I was hoping for with the "PATH '/'").

This is a libxml2 behave

Postprocessing only check result and try to push the result to expected
types.

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#62Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#60)
Re: patch: function xmltable

2016-11-24 5:52 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

2016-11-24 0:13 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Oh my, I just noticed we have a new xpath preprocessor in this patch
too. Where did this code come from -- did you write it all from
scratch?

I wrote it from scratch - libxml2 has not any API for iteration over XPath
expression (different than iteration over XPath expression result), and
what I have info, there will not be any new API in libxml2.

There are two purposes:

Safe manipulation with XPath expression prefixes - ANSI SQL design
implicitly expects some prefix, but it can be used manually. The prefix
should not be used twice and in some situations, when it can breaks the
expression.

Implicit prefix for column PATH expressions is "./". An user can use it
explicitly.

In my initial patches, the manipulations with XPath expression was more
complex - now, it can be reduced - but then we lost default namespaces
support, what is nice feature, supported other providers.

Show quoted text

Second goal is support default namespaces - when we needed parser for
first task, then the enhancing for this task was not too much lines more.

This parser can be used for enhancing current XPath function - default
namespaces are pretty nice, when you have to use namespaces.

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#63Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#60)
Re: patch: function xmltable

Pavel Stehule wrote:

Hi

2016-11-24 0:13 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Oh my, I just noticed we have a new xpath preprocessor in this patch
too. Where did this code come from -- did you write it all from
scratch?

I wrote it from scratch - libxml2 has not any API for iteration over XPath
expression (different than iteration over XPath expression result), and
what I have info, there will not be any new API in libxml2.

Okay, I agree that the default namespace stuff looks worthwhile in the
long run. But I don't have enough time to review the xpath parser stuff
in the current commitfest, and I think it needs at the very least a lot
of additional code commentary.

However I think the rest of it can reasonably go in -- I mean the SQL
parse of it, analysis, executor. Let me propose this: you split the
patch, leaving the xpath_parser.c stuff out and XMLNAMESPACES DEFAULT,
and we introduce just the TableExpr stuff plus the XMLTABLE function. I
can commit that part in the current commitfest, and we leave the
xpath_parser plus associated features for the upcoming commitfest.

Deal?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#64Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#63)
Re: patch: function xmltable

2016-11-24 18:29 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

Hi

2016-11-24 0:13 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Oh my, I just noticed we have a new xpath preprocessor in this patch
too. Where did this code come from -- did you write it all from
scratch?

I wrote it from scratch - libxml2 has not any API for iteration over

XPath

expression (different than iteration over XPath expression result), and
what I have info, there will not be any new API in libxml2.

Okay, I agree that the default namespace stuff looks worthwhile in the
long run. But I don't have enough time to review the xpath parser stuff
in the current commitfest, and I think it needs at the very least a lot
of additional code commentary.

However I think the rest of it can reasonably go in -- I mean the SQL
parse of it, analysis, executor. Let me propose this: you split the
patch, leaving the xpath_parser.c stuff out and XMLNAMESPACES DEFAULT,
and we introduce just the TableExpr stuff plus the XMLTABLE function. I
can commit that part in the current commitfest, and we leave the
xpath_parser plus associated features for the upcoming commitfest.

Deal?

ok

can me send your last work?

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#65Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#64)
Re: patch: function xmltable

Pavel Stehule wrote:

can me send your last work?

Sure, it's in the archives --
/messages/by-id/20161123233130.oqf7jl6czehy5fiw@alvherre.pgsql

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#66Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#63)
Re: patch: function xmltable

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

Pavel Stehule wrote:

2016-11-24 0:13 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Oh my, I just noticed we have a new xpath preprocessor in this patch
too. Where did this code come from -- did you write it all from
scratch?

I wrote it from scratch - libxml2 has not any API for iteration over XPath
expression (different than iteration over XPath expression result), and
what I have info, there will not be any new API in libxml2.

Okay, I agree that the default namespace stuff looks worthwhile in the
long run. But I don't have enough time to review the xpath parser stuff
in the current commitfest, and I think it needs at the very least a lot
of additional code commentary.

contrib/xml2 has always relied on libxslt for xpath functionality.
Can we do that here instead of writing, debugging, and documenting
a pile of new code?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#67Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#66)
Re: patch: function xmltable

2016-11-24 18:51 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

Pavel Stehule wrote:

2016-11-24 0:13 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Oh my, I just noticed we have a new xpath preprocessor in this patch
too. Where did this code come from -- did you write it all from
scratch?

I wrote it from scratch - libxml2 has not any API for iteration over

XPath

expression (different than iteration over XPath expression result), and
what I have info, there will not be any new API in libxml2.

Okay, I agree that the default namespace stuff looks worthwhile in the
long run. But I don't have enough time to review the xpath parser stuff
in the current commitfest, and I think it needs at the very least a lot
of additional code commentary.

contrib/xml2 has always relied on libxslt for xpath functionality.
Can we do that here instead of writing, debugging, and documenting
a pile of new code?

I am sorry - I don't see it. There is nothing complex manipulation with
XPath expressions.

Regards

Pavel

Show quoted text

regards, tom lane

#68Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#65)
1 attachment(s)
Re: patch: function xmltable

2016-11-24 18:41 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

can me send your last work?

Sure, it's in the archives --
/messages/by-id/20161123233130.
oqf7jl6czehy5fiw@alvherre.pgsql

Here is updated patch without default namespace support (and without XPath
expression transformation).

Due last changes in parser
https://github.com/postgres/postgres/commit/906bfcad7ba7cb3863fe0e2a7810be8e3cd84fbd
I had to use c_expr on other positions ( xmlnamespace definition).

I don't think it is limit - in 99% there will be const literal.

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-14.patchtext/x-patch; charset=US-ASCII; name=xmltable-14.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2e64cc4..5bb1027 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10324,50 +10324,53 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
   <sect2 id="functions-xml-processing">
    <title>Processing XML</title>
 
-   <indexterm>
-    <primary>XPath</primary>
-   </indexterm>
-
    <para>
     To process values of data type <type>xml</type>, PostgreSQL offers
-    the functions <function>xpath</function> and
+    the functions <function>xpath</function>,
     <function>xpath_exists</function>, which evaluate XPath 1.0
-    expressions.
+    expressions, and the <function>XMLTABLE</function> predicate.
    </para>
 
+   <sect3 id="functions-xml-processing-xpath">
+    <title><literal>xpath</literal></title>
+
+    <indexterm>
+     <primary>XPath</primary>
+    </indexterm>
+
 <synopsis>
 <function>xpath</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath</function> evaluates the XPath
-    expression <replaceable>xpath</replaceable> (a <type>text</> value)
-    against the XML value
-    <replaceable>xml</replaceable>.  It returns an array of XML values
-    corresponding to the node set produced by the XPath expression.
-    If the XPath expression returns a scalar value rather than a node set,
-    a single-element array is returned.
-   </para>
+    <para>
+     The function <function>xpath</function> evaluates the XPath
+     expression <replaceable>xpath</replaceable> (a <type>text</> value)
+     against the XML value
+     <replaceable>xml</replaceable>.  It returns an array of XML values
+     corresponding to the node set produced by the XPath expression.
+     If the XPath expression returns a scalar value rather than a node set,
+     a single-element array is returned.
+    </para>
 
-  <para>
-    The second argument must be a well formed XML document. In particular,
-    it must have a single root node element.
-  </para>
+    <para>
+     The second argument must be a well formed XML document. In particular,
+     it must have a single root node element.
+    </para>
 
-   <para>
-    The optional third argument of the function is an array of namespace
-    mappings.  This array should be a two-dimensional <type>text</> array with
-    the length of the second axis being equal to 2 (i.e., it should be an
-    array of arrays, each of which consists of exactly 2 elements).
-    The first element of each array entry is the namespace name (alias), the
-    second the namespace URI. It is not required that aliases provided in
-    this array be the same as those being used in the XML document itself (in
-    other words, both in the XML document and in the <function>xpath</function>
-    function context, aliases are <emphasis>local</>).
-   </para>
+    <para>
+     The optional third argument of the function is an array of namespace
+     mappings.  This array should be a two-dimensional <type>text</> array with
+     the length of the second axis being equal to 2 (i.e., it should be an
+     array of arrays, each of which consists of exactly 2 elements).
+     The first element of each array entry is the namespace name (alias), the
+     second the namespace URI. It is not required that aliases provided in
+     this array be the same as those being used in the XML document itself (in
+     other words, both in the XML document and in the <function>xpath</function>
+     function context, aliases are <emphasis>local</>).
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
              ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10377,10 +10380,10 @@ SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
 
-   <para>
-    To deal with default (anonymous) namespaces, do something like this:
+    <para>
+     To deal with default (anonymous) namespaces, do something like this:
 <screen><![CDATA[
 SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
              ARRAY[ARRAY['mydefns', 'http://example.com']]);
@@ -10390,27 +10393,31 @@ SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
+   </sect3>
 
-   <indexterm>
-    <primary>xpath_exists</primary>
-   </indexterm>
+   <sect3 id="functions-xml-processing-xpath-exists">
+    <title><literal>xpath_exists</literal></title>
+
+    <indexterm>
+     <primary>xpath_exists</primary>
+    </indexterm>
 
 <synopsis>
 <function>xpath_exists</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath_exists</function> is a specialized form
-    of the <function>xpath</function> function.  Instead of returning the
-    individual XML values that satisfy the XPath, this function returns a
-    Boolean indicating whether the query was satisfied or not.  This
-    function is equivalent to the standard <literal>XMLEXISTS</> predicate,
-    except that it also offers support for a namespace mapping argument.
-   </para>
+    <para>
+     The function <function>xpath_exists</function> is a specialized form
+     of the <function>xpath</function> function.  Instead of returning the
+     individual XML values that satisfy the XPath, this function returns a
+     Boolean indicating whether the query was satisfied or not.  This
+     function is equivalent to the standard <literal>XMLEXISTS</> predicate,
+     except that it also offers support for a namespace mapping argument.
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
                      ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10420,7 +10427,278 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
  t
 (1 row)
 ]]></screen>
+    </para>
+   </sect3>
+
+   <sect3 id="functions-xml-processing-xmltable">
+    <title><literal>xmltable</literal></title>
+
+    <indexterm>
+     <primary>xmltable</primary>
+    </indexterm>
+
+<synopsis>
+<function>xmltable</function>(
+    <optional>xmlnamespaces(<replaceable>namespace uri</replaceable> AS <replaceable>namespace name</replaceable><optional>, ...</optional>)</optional>
+    <replaceable>rowexpr</replaceable> PASSING <optional>BY REF</optional> <replaceable>xml</replaceable> <optional>BY REF</optional>
+    <optional>COLUMNS <replaceable>name</replaceable> <replaceable>type</replaceable>
+        <optional>PATH <replaceable>columnexpr</replaceable></optional>
+        <optional>DEFAULT <replaceable>expr</replaceable></optional>
+        <optional>NOT NULL | NULL</optional> <optional>, ...</optional></optional>
+)
+</synopsis>
+
+    <para>
+     The <function>xmltable</function> produces a table based on the
+     passed XML value, an xpath filter to extract rows, and an
+     optional set of column definitions.
+    </para>
+
+    <para>
+     For XML document:
+  <screen><![CDATA[
+<ROWS>
+  <ROW id="1">
+    <COUNTRY_ID>AU</COUNTRY_ID>
+    <COUNTRY_NAME>Australia</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="2">
+    <COUNTRY_ID>CN</COUNTRY_ID>
+    <COUNTRY_NAME>China</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="3">
+    <COUNTRY_ID>HK</COUNTRY_ID>
+    <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="4">
+    <COUNTRY_ID>IN</COUNTRY_ID>
+    <COUNTRY_NAME>India</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="5">
+    <COUNTRY_ID>JP</COUNTRY_ID>
+    <COUNTRY_NAME>Japan</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+  </ROW>
+  <ROW id="6">
+    <COUNTRY_ID>SG</COUNTRY_ID>
+    <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+  </ROW>
+</ROWS>
+]]></screen>
+
+     the following query produces the result:
+
+<screen><![CDATA[
+SELECT  xmltable.*
+  FROM (SELECT data FROM xmldata) x,
+        LATERAL xmltable('//ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  country_name text PATH 'COUNTRY_NAME',
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE[@unit = "km"]/text()',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+ id | country_name | country_id | region_id | size | unit | premier_name  
+----+--------------+------------+-----------+------+------+---------------
+  1 | Australia    | AU         |         3 |      |      | not specified
+  2 | China        | CN         |         3 |      |      | not specified
+  3 | HongKong     | HK         |         3 |      |      | not specified
+  4 | India        | IN         |         3 |      |      | not specified
+  5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 | Singapore    | SG         |         3 |  791 | km   | not specified
+]]></screen>
+    </para>
+
+    <para>
+     The optional <literal>xmlnamespaces</> clause is a comma-separated list of
+     namespaces of the form <replaceable>namespace-URI</> <literal>AS</>
+     <replaceable>namespace-name</>. It specifies the XML namespaces used in
+     the document and their aliases. The default namespace is not supported
+     yet.
+
+<screen><![CDATA[
+SELECT *
+  FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS ns)
+                '/ns:rows/ns:row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                COLUMNS a int PATH 'a');
+
+ a  
+----
+ 10
+]]></screen>
    </para>
+
+    <para>
+     The required <replaceable>rowexpr</> argument is an xpath
+     expression that is evaluated against the supplied XML document to
+     obtain an ordered sequence of XML nodes. This sequence is what
+     <function>xmltable</> transforms into output rows.
+    </para>
+
+    <note>
+     <para>
+      The SQL/XML standard specifies the the
+      <function>xmltable</function> construct should take an XQuery
+      expression as the first argument, but PostgreSQL currently only
+      supports XPath, which is a subset of XQuery.
+     </para>
+    </note>
+
+    <para>
+     The <literal>PASSING</> <replaceable>xml</> clause provides the
+     XML document to operate on.
+     The <literal>BY REF</literal> clauses have no effect in
+     PostgreSQL, but are allowed for SQL conformance and compatibility
+     with other implementations. Per the SQL/XML standard, the first
+     <literal>BY REF</literal> is required, the second is optional.
+     Passing <literal>BY VALUE</> is not supported. Multiple
+     comma-separated terms in the PASSING clause are not supported.
+     <literal>AS</> aliases are not supported. The argument must be
+     a well-formed XML document; fragments/forests are not accepted.
+    </para>
+
+    <para>
+     The optional <literal>COLUMNS</literal> clause specifies the list
+     of colums in the generated table. If the whole
+     <literal>COLUMNS</> list is omitted, then the result set is a
+     single column where each row is a field of <literal>xml</> type
+     containing the data matched by the <replaceable>rowexpr</>.
+     Otherwise each entry describes a single column. See the syntax
+     summary above for the format. The column name and type are
+     required; the path and not-null specifier are optional.
+ <screen><![CDATA[
+SELECT *
+  FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y' AS ns)
+                '/ns:rows/ns:row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>');
+
+           xmltable           
+------------------------------
+ <a xmlns="http://x.y">10</a>
+]]></screen>
+    </para>
+
+    <note>
+     <para>
+      Column names and types are identifiers so unlike normal
+      function arguments they may not be specified as bind parameters,
+      column references, or expressions. The path and default are
+      ordinary values.
+     </para>
+    </note>
+
+    <para>
+     The <literal>PATH</> for a column is an xpath expression that is
+     evaluated for each row, relative to the result of the
+     <replaceable>rowexpr</>, to find the value of the column.
+     If no <literal>PATH</> is given then the column name is used as an
+     implicit path.
+    </para>
+
+    <para>
+     The text body of the XML matched by the <literal>PATH</> expression is
+     used as the column value. Multiple <literal>text()</literal> nodes within
+     an element are concatenated in order. Any child elements, processing
+     instructions, and comments are ignored, but the text contents of child
+     elements are concatenated to the result:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--x-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+element
+-----------------
+ a1aa2a   bbbbcccc
+ (1 row)
+]]></programlisting>
+     Note that the whitespace-only <literal>text()</> node between two non-text
+     elements is preserved, and that leading whitespace on a <literal>text()</>
+     node is not flattened.
+    </para>
+    
+    <para>
+     A <literal>PATH</> expression that matches multiple top-level elements
+     results in an error:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>1</element><element>2</element></root>' COLUMNS element text);
+ERROR:  more than one value returned by column XPath expression
+]]></programlisting>
+     This applies to <literal>text()</> nodes too, so avoid using explicit text nodes
+     in your path expressions unless this behaviour is desired:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a</element></root>' COLUMNS element text PATH 'element/text()');
+ERROR:  more than one value returned by column XPath expression
+]]></programlisting>
+     If the <literal>PATH</> matches an empty tag the result is an empty string
+     (not <literal>NULL</>).  Any <literal>xsi:nil</> attributes are ignored.
+    </para>
+
+    <para>
+     The five predefined XML entities <literal>&apos;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 743e7d6..321cb77 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,9 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState * tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4504,234 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExprProtected
+ *
+ * Evaluate a TableExpr node.  This function may leak libxml memory, so it
+ * needs to be protected from exceptions.
+ *
+ * This function creates a TableExprBuilder object and applies all necessary
+ * settings; one tuple is returned per call.
+ *
+ * Is possible to go over this
+ * cycle more times per query - when LATERAL JOIN is used. On the end
+ * of cycle, the TableExprBuilder object is destroyed, state pointer
+ * to this object is cleaned, and related memory context is resetted.
+ * New call starts new cycle.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExprProtected(TableExprState * tstate,
+						   ExprContext *econtext,
+						   bool *isNull, ExprDoneCond *isDone)
+{
+	TupleDesc	tupdesc;
+	Datum		result;
+	int			i;
+	Datum		value;
+	bool		isnull;
+	const TableExprBuilder *builder;
+	void	   *builderCxt;
+
+	tupdesc = tstate->tupdesc;
+	builder = tstate->builder;
+	builderCxt = tstate->builderCxt;
+
+	/*
+	 * First time around?  Set up our calling context and evaluate the
+	 * document expression.
+	 */
+	if (builderCxt == NULL)
+	{
+		ListCell   *ns;
+
+		builderCxt = builder->CreateContext(tupdesc,
+											tstate->in_functions,
+											tstate->typioparams,
+											tstate->per_rowset_memory);
+		tstate->builderCxt = builderCxt;
+
+		/* Evaluate document expression first */
+		value = ExecEvalExpr(tstate->expr, econtext, &isnull, NULL);
+		if (isnull)
+		{
+			*isDone = ExprSingleResult;
+			*isNull = true;
+			return (Datum) 0;
+		}
+
+		/*
+		 * The content can be bigger document and transformation to cstring
+		 * can be expensive. The table builder is better place for this task -
+		 * pass value as Datum.
+		 */
+		builder->SetContent(builderCxt, value);
+
+		/* Evaluate namespace specifications */
+		foreach(ns, tstate->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			ExprState  *expr;
+			char	   *ns_name;
+			char	   *ns_uri;
+
+			if (IsA(n, NamedArgExpr))
+			{
+				NamedArgExpr *na = (NamedArgExpr *) n;
+
+				expr = (ExprState *) na->arg;
+				ns_name = na->name;
+			}
+			else
+			{
+				expr = (ExprState *) n;
+				ns_name = NULL;
+			}
+
+			value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("namespace uri must not be null")));
+			ns_uri = TextDatumGetCString(value);
+
+			builder->AddNS(builderCxt, ns_name, ns_uri);
+		}
+
+		/* Evaluate row path filter */
+		value = ExecEvalExpr(tstate->row_path_expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("row query must not be null")));
+		builder->SetRowPath(builderCxt, TextDatumGetCString(value));
+
+		/* Evaluate column paths */
+		for (i = 0; i < tstate->ncols; i++)
+		{
+			char	   *col_path;
+
+			if (tstate->col_path_expr[i] != NULL)
+			{
+				value = ExecEvalExpr(tstate->col_path_expr[i], econtext, &isnull, NULL);
+				if (isnull)
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("column path for column \"%s\" must not be null",
+							NameStr(tupdesc->attrs[i]->attname))));
+				col_path = TextDatumGetCString(value);
+			}
+			else
+				col_path = NameStr(tupdesc->attrs[i]->attname);
+
+			builder->SetColumnPath(builderCxt, col_path, i);
+		}
+	}
+
+	/* Now we can prepare result */
+	if (builder->FetchRow(builderCxt))
+	{
+		HeapTuple	tuple;
+		HeapTupleHeader dtuple;
+		Datum	   *values;
+		bool	   *nulls;
+
+		values = tstate->values;
+		nulls = tstate->nulls;
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			if (i != tstate->for_ordinality_col - 1)
+			{
+				values[i] = builder->GetValue(builderCxt, i, &isnull);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tupdesc->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+			else
+			{
+				values[i] = Int32GetDatum(++tstate->rownum);
+				nulls[i] = false;
+			}
+		}
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(dtuple, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type previously identified
+		 */
+		HeapTupleHeaderSetTypeId(dtuple, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(dtuple, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+
+		*isNull = false;
+		*isDone = ExprMultipleResult;
+
+		result = HeapTupleHeaderGetDatum(dtuple);
+	}
+	else
+	{
+		/* no more rows */
+		builder->DestroyContext(builderCxt);
+		tstate->builderCxt = NULL;
+
+		/* ensure releasing all memory */
+		MemoryContextReset(tstate->per_rowset_memory);
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	return result;
+}
+
+/*
+ * ExecEvalTableExpr - this is envelop of ExecEvalTableExprProtected() function.
+ *
+ * This function ensures releasing all TableBuilder context and related
+ * memory context, when ExecEvalTableExprProtected fails on exception.
+ */
+static Datum
+ExecEvalTableExpr(TableExprState * tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	/* Ensure releasing context every exception */
+	Datum		result;
+
+	PG_TRY();
+	{
+		result = ExecEvalTableExprProtected(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		if (tstate->builderCxt != NULL)
+		{
+			tstate->builder->DestroyContext(tstate->builderCxt);
+			tstate->builderCxt = NULL;
+		}
+
+		MemoryContextDelete(tstate->per_rowset_memory);
+		tstate->per_rowset_memory = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5494,109 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int			ncols;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->builderCxt = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->builder = &XmlTableBuilder;
+
+				/* XXX this assumes the anon record type has been registered */
+				tstate->tupdesc =
+					lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+												exprTypmod((Node *) te));
+				Assert(tstate->tupdesc->natts > 0);
+
+				ncols = tstate->tupdesc->natts;
+				tstate->values = palloc0(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid			in_funcid;
+
+					getTypeInputInfo(tstate->tupdesc->attrs[i]->atttypid,
+									 &in_funcid, &tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_path_expr = ExecInitExpr((Expr *) te->row_path, parent);
+				tstate->expr = ExecInitExpr((Expr *) te->expr, parent);
+
+				if (te->cols)
+				{
+					ListCell   *col;
+
+					Assert(ncols == list_length(te->cols));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_path_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+
+					i = 0;
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+															   parent);
+							tstate->col_path_expr[i] = ExecInitExpr((Expr *) tec->path_expr,
+																	parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->per_rowset_memory = AllocSetContextCreate(CurrentMemoryContext,
+											"XmlTable per rowgroup context",
+											ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..3f0b490 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,48 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+	TupleDesc	typeInfo;
+
+	if (te->cols != NIL)
+	{
+		ListCell   *col;
+		int			i = 1;
+
+		typeInfo = CreateTemplateTupleDesc(list_length(te->cols), false);
+
+		foreach(col, te->cols)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(typeInfo,
+							   (AttrNumber) i,
+							   pstrdup(tec->colname),
+							   tec->typid,
+							   tec->typmod,
+							   0);
+			i++;
+		}
+	}
+	else
+	{
+		typeInfo = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(typeInfo, (AttrNumber) 1,
+						   "xmltable", XMLOID, -1, 0);
+	}
+
+	/* XXX OK to do this?  looks a bit out of place ... */
+	assign_record_type_typmod(typeInfo);
+
+	return typeInfo;
+}
+
+/*
  * ExecTypeSetColNames - set column names in a TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..b9c39d7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr * from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(row_path);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(cols);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol * from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn * from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(path_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4600,6 +4657,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5104,6 +5167,9 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..686bccb 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2615,6 +2615,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol * a, const TableExprRawCol * b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn * a, const TableExprColumn * b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(path_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2645,6 +2675,18 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr * a, const TableExpr * b)
+{
+	COMPARE_NODE_FIELD(row_path);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(cols);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2910,6 +2952,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3401,6 +3449,9 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..df357e1 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +258,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +499,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate a blessed tupdesc from the column definitions, and
+				 * returns its typmod.
+				 */
+				TupleDesc	tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +747,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 +951,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1155,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1587,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2249,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->path_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3069,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_path, te->row_path, Node *);
+				MUTATE(newnode->expr, te->expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->cols, te->cols, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->path_expr, tec->path_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3708,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_path, context))
+					return true;
+				if (walker(te->expr, context))
+					return true;
+				if (walker(te->cols, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..bb1b904 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_NODE_FIELD(row_path);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(cols);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol * node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(path_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3539,6 +3581,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..8a47a82 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,44 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(row_path);
+	READ_NODE_FIELD(expr);
+	READ_NODE_FIELD(cols);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(path_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2535,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9598f28..8bfd06d 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))
@@ -3581,6 +3587,33 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (tec->path_expr != NULL || tec->default_expr != NULL)
+				{
+					TableExprColumn *newtec = makeNode(TableExprColumn);
+
+					newtec->colname = tec->colname;
+					newtec->typid = tec->typid;
+					newtec->typmod = tec->typmod;
+					newtec->collation = tec->collation;
+					newtec->for_ordinality = tec->for_ordinality;
+					newtec->is_not_null = tec->is_not_null;
+					newtec->path_expr =
+						  eval_const_expressions_mutator((Node *) tec->path_expr,
+														  context);
+					newtec->default_expr =
+						  eval_const_expressions_mutator((Node *) tec->default_expr,
+														  context);
+					newtec->location = tec->location;
+
+					return (Node *) newtec;
+				}
+			}
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 367bc2e..8a77682 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -545,6 +545,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	TableExprColList TableExprColOptions
+%type <node>	TableExprCol
+%type <defelt>	TableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -576,10 +583,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
-	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
-	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-	CROSS CSV CUBE CURRENT_P
+	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS
+	COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION
+	CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST
+	CREATE CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -649,8 +656,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
-	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
-	XMLPI XMLROOT XMLSERIALIZE
+	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
+	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
 
 	YEAR_P YES_P
 
@@ -12578,6 +12585,168 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $3;
+					n->expr = $4;
+					n->cols = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $8;
+					n->expr = $9;
+					n->cols = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS TableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_path = $8;
+					n->expr = $9;
+					n->cols = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+TableExprColList: TableExprCol							{ $$ = list_make1($1); }
+			| TableExprColList ',' TableExprCol			{ $$ = lappend($1, $3); }
+		;
+
+TableExprCol:
+			ColId Typename IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $3;
+					rawc->path_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+			| ColId Typename TableExprColOptions IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $4;
+					rawc->path_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->path_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->path_expr = defel->arg;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+TableExprColOptions: TableExprColOption					{ $$ = list_make1($1); }
+			| TableExprColOptions TableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+TableExprColOption:
+			IDENT c_expr
+				{
+					$$ = makeDefElem($1, $2, @1);
+				}
+			| DEFAULT c_expr
+				{
+					$$ = makeDefElem("default", $2, @1);
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			c_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT a_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
 		;
 
 /*
@@ -13686,6 +13855,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13987,10 +14157,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..12f3b36 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1095,9 +1095,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1105,9 +1105,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1116,7 +1116,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1143,6 +1143,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+								targetTypeId, -1,
+								constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 17d1cbf..91378b0 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr * te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2764,6 +2770,165 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 	return (Node *) newc;
 }
 
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr* te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	const char *constructName;
+	Oid			exprType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	exprType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, te->location);
+
+	/* Transform the row-generating Xpath expression ... */
+	Assert(te->row_path != NULL);
+	newte->row_path = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_path),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML itself */
+	Assert(te->expr != NULL);
+	newte->expr = coerce_to_specific_type(pstate,
+									  transformExprRecurse(pstate, te->expr),
+										  exprType,
+										  constructName);
+
+	/* Columns, if any, also need to be transformed */
+	if (te->cols != NIL)
+	{
+		ListCell   *col;
+		bool		for_ordinality = false;
+		char	  **colnames;
+		int			i = 0;
+
+		colnames = palloc(sizeof(char *) * list_length(te->cols));
+
+		/*
+		 * For each TableExprRawCol in the input TableExpr, generate one
+		 * TableExprColumn in the output TableExpr.
+		 */
+		foreach(col, te->cols)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc;
+			Oid			typid;
+			int32		typmod;
+			int			j;
+
+			/* Only one FOR ORDINALITY column is allowed; verify */
+			if (rawc->for_ordinality)
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+			}
+
+			newc = makeNode(TableExprColumn);
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			/*
+			 * Determine the type and typmod for the new column.
+			 * FOR ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				newc->typid = INT4OID;
+				newc->typmod = -1;
+			}
+			else
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &newc->typid, &newc->typmod);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->path_expr)
+				newc->path_expr = coerce_to_specific_type(pstate,
+														  transformExprRecurse(pstate, rawc->path_expr),
+														  TEXTOID,
+														  constructName);
+			if (rawc->default_expr)
+				newc->default_expr = coerce_to_specific_type_typmod(pstate,
+																	transformExprRecurse(pstate, rawc->default_expr),
+																	newc->typid, newc->typmod,
+																	constructName);
+
+
+			newte->cols = lappend(newte->cols, newc);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(colnames[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			colnames[i] = rawc->colname;
+
+			i++;
+		}
+
+		pfree(colnames);
+	}
+	else
+		newte->cols = NIL;
+
+	/* Namespaces, if any, also need to be transformed */
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the namespace \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+						  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(nsnames);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
 /*
  * Transform a "row compare-op row" construct
  *
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index d440dec..828ae9b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,13 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+			/*
+			 * Make TableExpr act like a regular function. Only
+			 * XMLTABLE expr is supported in this moment.
+			 */
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..54f808e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8306,6 +8307,105 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/*
+				 * Deparse TableExpr - the is only one TableExpr producer,
+				 * the function XMLTABLE.
+				 */
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_path, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->cols != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->cols)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->path_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->path_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..21466f0 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -73,6 +73,7 @@
 #include "commands/dbcommands.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "executor/tableexpr.h"
 #include "fmgr.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
@@ -165,6 +166,29 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+static void *XmlTableCreateContext(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparms,
+					  MemoryContext mcxt);
+static void XmlTableSetContent(void *tcontext, Datum value);
+static void XmlTableAddNS(void *tcontext, char *name, char *uri);
+static void XmlTableSetRowPath(void *tcontext, char *path);
+static void XmlTableSetColumnPath(void *tcontext, char *path, int colnum);
+static bool XmlTableFetchRow(void *tcontext);
+static Datum XmlTableGetValue(void *tcontext, int colnum, bool *isnull);
+static void XmlTableDestroyContext(void *tcontext);
+
+const TableExprBuilder XmlTableBuilder =
+{
+	XmlTableCreateContext,
+	XmlTableSetContent,
+	XmlTableAddNS,
+	XmlTableSetRowPath,
+	XmlTableSetColumnPath,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyContext
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4089,570 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Functions for XmlTableBuilder
+ *
+ */
+typedef struct XmlTableContext
+{
+	TupleDesc	tupdesc;
+	MemoryContext per_rowset_memory;
+	char	   *def_namespace_name;
+	FmgrInfo   *in_functions;
+	Oid		   *typioparams;
+	long int	rc;
+	PgXmlErrorContext *xmlerrcxt;
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr	doc;
+	xmlXPathContextPtr xpathcxt;
+	xmlXPathCompExprPtr xpathcomp;
+	xmlXPathObjectPtr xpathobj;
+	xmlXPathCompExprPtr *xpathscomp;
+}	XmlTableContext;
+
+/*
+ * Convert XML node to cstring (dump subtree in case of element,
+ * return value otherwise)
+ */
+static char *
+xml_xmlnodetostr(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt)
+{
+	char	   *result;
+
+	if (cur->type == XML_ELEMENT_NODE)
+	{
+		xmlBufferPtr buf;
+		xmlNodePtr	cur_copy;
+
+		buf = xmlBufferCreate();
+
+		/*
+		 * The result of xmlNodeDump() won't contain namespace definitions
+		 * from parent nodes, but xmlCopyNode() duplicates a node along with
+		 * its required namespace definitions.
+		 */
+		cur_copy = xmlCopyNode(cur, 1);
+
+		if (cur_copy == NULL)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not copy node");
+
+		PG_TRY();
+		{
+			xmlNodeDump(buf, NULL, cur_copy, 0, 1);
+			result = pstrdup((const char *) xmlBufferContent(buf));
+		}
+		PG_CATCH();
+		{
+			xmlFreeNode(cur_copy);
+			xmlBufferFree(buf);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+		xmlFreeNode(cur_copy);
+		xmlBufferFree(buf);
+	}
+	else
+	{
+		xmlChar    *str;
+
+		str = xmlXPathCastNodeToString(cur);
+		PG_TRY();
+		{
+			/* Here we rely on XML having the same representation as TEXT */
+			char	   *escaped = escape_xml((char *) str);
+
+			result = escaped;
+		}
+		PG_CATCH();
+		{
+			xmlFree(str);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+		xmlFree(str);
+	}
+
+	return result;
+}
+
+/*
+ * Secure cstring for usage in libxml2
+ */
+static xmlChar *
+to_xmlstr(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+#endif
+
+/*
+ * Create XmlTableContext for XmlTable builder. Initialize XML parser.
+ */
+static void *
+XmlTableCreateContext(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparams,
+					  MemoryContext per_rowset_memory)
+{
+#ifdef USE_LIBXML
+	MemoryContext oldcxt;
+	XmlTableContext *result = NULL;
+	PgXmlErrorContext *xmlerrcxt = NULL;
+	volatile xmlParserCtxtPtr ctxt = NULL;
+
+	oldcxt = MemoryContextSwitchTo(per_rowset_memory);
+
+	result = palloc0(sizeof(struct XmlTableContext));
+	result->tupdesc = tupdesc;
+	result->per_rowset_memory = per_rowset_memory;
+	result->in_functions = in_functions;
+	result->typioparams = typioparams;
+	result->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * tupdesc->natts);
+
+	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+	PG_TRY();
+	{
+		xmlInitParser();
+
+		ctxt = xmlNewParserCtxt();
+		if (ctxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate parser context");
+	}
+	PG_CATCH();
+	{
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	result->xmlerrcxt = xmlerrcxt;
+	result->ctxt = ctxt;
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(void *tcontext, Datum value)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	xmltype    *xmlval = DatumGetXmlP(value);
+	xmlChar    *xstr;
+	int			length;
+	volatile xmlDocPtr doc = NULL;
+	volatile xmlXPathContextPtr xpathcxt = NULL;
+	MemoryContext oldcxt;
+
+	length = VARSIZE(xmlval) - VARHDRSZ;
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+
+	xstr = to_xmlstr(VARDATA(xmlval), length);
+
+	PG_TRY();
+	{
+		doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL, 0);
+		if (doc == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+						"could not parse XML document");
+		xpathcxt = xmlXPathNewContext(doc);
+		if (xpathcxt == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate XPath context");
+		xpathcxt->node = xmlDocGetRootElement(doc);
+		if (xpathcxt->node == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not find root XML element");
+	}
+	PG_CATCH();
+	{
+		if (xpathcxt != NULL)
+			xmlXPathFreeContext(xpathcxt);
+		if (doc != NULL)
+			xmlFreeDoc(doc);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xtCxt->doc = doc;
+	xtCxt->xpathcxt = xpathcxt;
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableAddNS - add namespace. use fake name when the name
+ * is null, and store this name as default namespace.
+ */
+static void
+XmlTableAddNS(void *tcontext, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+						   to_xmlstr(name, strlen(name)),
+						   to_xmlstr(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowPath
+ */
+static void
+XmlTableSetRowPath(void *tcontext, char *path)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	xmlChar    *xstr;
+	MemoryContext oldcxt;
+
+	if (path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("row path filter must not be empty string")));
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+	xstr = to_xmlstr(path, strlen(path));
+
+	xtCxt->xpathcomp = xmlXPathCompile(xstr);
+	if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetColumnPath
+ */
+static void
+XmlTableSetColumnPath(void *tcontext, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	MemoryContext oldcxt;
+	xmlChar    *xstr;
+
+	if (path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("column path filter must not be empty string")));
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+	xstr = to_xmlstr(path, strlen(path));
+
+	xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+	if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableFetchRow - returns true when is possible to read
+ * values from XML document.
+ *
+ * First call evaluates row path over content.
+ * Next step and at other calls increment row counter and
+ * compare it with number values in result set.
+ */
+static bool
+XmlTableFetchRow(void *tcontext)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	/*
+	 * XmlTable returns table - set of composite values. The error context,
+	 * is used for producement more values, between two calls, there can be
+	 * created and used another libxml2 error context. It is libxml2 global
+	 * value, so it should be refreshed any time before any libxml2 usage,
+	 * that is finished by returning some value.
+	 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memory);
+
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		xtCxt->rc = 0;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+}
+
+/*
+ * Apply column path on current node and returns result.
+ * Allows multivalue when expected type is XML.
+ * When COLUMNS part was not entered, returns current node.
+ */
+static Datum
+XmlTableGetValue(void *tcontext, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+	if (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = NULL;
+
+		/* fast leaving */
+		if (cur->type != XML_ELEMENT_NODE)
+			elog(ERROR, "unexpected xmlNode type");
+
+		PG_TRY();
+		{
+			/* Set current node as entry point for XPath evaluation */
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+			/* Evaluate column path */
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+			if (column_xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+				xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+							"could not create XPath object");
+
+			if (column_xpathobj->type == XPATH_NODESET)
+			{
+				int			count;
+
+				if (column_xpathobj->nodesetval != NULL)
+					count = column_xpathobj->nodesetval->nodeNr;
+				else
+					count = 0;
+
+				if (count > 0)
+				{
+					Oid		targettypid = xtCxt->tupdesc->attrs[colnum]->atttypid;
+
+					if (count == 1)
+					{
+						if (targettypid != XMLOID)
+						{
+							xmlChar *str;
+
+							str  = xmlNodeListGetString(xtCxt->doc,
+								column_xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+																			1);
+
+							if (str != NULL)
+							{
+								PG_TRY();
+								{
+									/* Copy string to PostgreSQL controlled memory */
+									cstr = pstrdup((char *) str);
+								}
+								PG_CATCH();
+								{
+									xmlFree(str);
+									PG_RE_THROW();
+								}
+								PG_END_TRY();
+
+								xmlFree(str);
+							}
+							else
+							{
+								/* Return empty string when tag is empty */
+								cstr = pstrdup("");
+							}
+						}
+						else
+						{
+							/* simple case, result is one value */
+							cstr = xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[0],
+													xtCxt->xmlerrcxt);
+						}
+					}
+					else
+					{
+						StringInfoData str;
+						int			i;
+
+						/*
+						 * When result is not one value, then we can work with
+						 * concated values. But it requires XML as expected
+						 * type.
+						 */
+						if (targettypid != XMLOID)
+							ereport(ERROR,
+									(errcode(ERRCODE_CARDINALITY_VIOLATION),
+									 errmsg("more than one value returned by column XPath expression")));
+
+						/* Concate serialized values */
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+												   xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														  xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[colnum],
+											   cstr,
+											   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[colnum],
+										 (char *) column_xpathobj->stringval,
+										   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+				*isnull = false;
+			}
+			else
+				elog(ERROR, "unexpected XPath object type");
+
+			xmlXPathFreeObject(column_xpathobj);
+			column_xpathobj = NULL;
+		}
+		PG_CATCH();
+		{
+			if (column_xpathobj != NULL)
+				xmlXPathFreeObject(column_xpathobj);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+	else
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+		result = InputFunctionCall(&xtCxt->in_functions[0],
+								   cstr,
+								   xtCxt->typioparams[0],
+								   -1); /* target type is XML always */
+		*isnull = false;
+	}
+
+	if (cstr != NULL)
+		pfree(cstr);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * Release all libxml2 memory and related resources
+ */
+static void
+XmlTableDestroyContext(void *tcontext)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) tcontext;
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int			i;
+
+		for (i = 0; i < xtCxt->tupdesc->natts; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c6d69bc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -272,6 +272,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
 extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..12791ab
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/primnodes.h"
+
+/*
+ * This structure holds a collection of function pointers used
+ * for generating content of table-expression functions like
+ * XMLTABLE.
+ *
+ * The TableBuilder is initialized by calling CreateContext function
+ * at evaluation time. First parameter - tuple descriptor describes
+ * produced (expected) table. in_functions is a array of FmgrInfo input
+ * functions for types of columns of produced table. The typioparams
+ * is a array of typio Oids for types of columns of produced table.
+ * The created context is living in special memory context passed
+ * as last parameter.
+ *
+ * The SetContent function is used for passing input document to
+ * table builder. The table builder handler knows expected format
+ * and it can do some additional transformations that are not propagated
+ * out from table builder.
+ *
+ * The AddNs add namespace info when namespaces are supported.
+ * Namespaces should be passed before Row/Column Paths setting.
+ * When name is NULL, then related uri is default namespace.
+ *
+ * The SetRowPath sets a row generating filter. This filter is used
+ * for separation of rows from document. Passed as cstring.
+ *
+ * The SetColumnPath sets a column generating filter. This filter is
+ * used for separating nth value from row. Passed as cstring.
+ *
+ * The FetchRow ensure loading row raleted data. Returns false, when
+ * document doesn't containt any next row.
+ *
+ * The GetValue returns a value related to colnum column.
+ *
+ * The DestroyContext - should to release all sources related to
+ * processing the document. Called when all rows are fetched or
+ * when a error is catched.
+ */
+typedef struct TableExprBuilder
+{
+	void	   *(*CreateContext) (TupleDesc tupdesc,
+									FmgrInfo *in_functions, Oid *typioparams,
+											MemoryContext per_rowset_memory);
+	void		(*SetContent) (void *tcontext, Datum value);
+	void		(*AddNS) (void *tcontext, char *name, char *uri);
+	void		(*SetRowPath) (void *tcontext, char *path);
+	void		(*SetColumnPath) (void *tcontext, char *path, int colnum);
+	bool		(*FetchRow) (void *tcontext);
+	Datum		(*GetValue) (void *tcontext, int colnum, bool *isnull);
+	void		(*DestroyContext) (void *tcontext);
+}	TableExprBuilder;
+
+#endif		/* TABLEEXPR_H */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..523afc8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "executor/instrument.h"
+#include "executor/tableexpr.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
@@ -1011,6 +1012,36 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+/* ----------------
+ *		TableExprState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	TupleDesc	tupdesc;		/* cache */
+	int			ncols;			/* number of declared columns */
+	int			for_ordinality_col;		/* number of oridinality column,
+										 * started by 1 */
+	int			rownum;			/* row counter - for ordinality column */
+	ExprState  *row_path_expr;	/* row xpath expression */
+	ExprState  *expr;			/* processed data */
+	ExprState **def_expr;		/* array of expressions for default value */
+	ExprState **col_path_expr;	/* array of expressions for path value */
+	bool	   *not_null;		/* for any column info if NULL is allowed or
+								 * not */
+	Datum	   *values;			/* prealloc buffer */
+	bool	   *nulls;			/* prealloc buffer */
+	FmgrInfo   *in_functions;	/* array of infunction for any column */
+	Oid		   *typioparams;	/* array of typIOParam for any column */
+	const TableExprBuilder *builder;	/* pointers to builder functions */
+	void	   *builderCxt;		/* data for content builder */
+
+	MemoryContext per_rowset_memory;
+}	TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b6e5151 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -454,6 +457,7 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..21859cc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,27 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - raw column definition in table-expression functions
+ *		like XMLTABLE.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions.
+ *
+ * If for_ordinality is true (FOR ORDINALITY), then the column is an int4
+ * column and the rest of the fields are ignored.
+ */
+typedef struct TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;
+	bool		for_ordinality;
+	TypeName   *typeName;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..3fcf546 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,44 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - holds data for table-expression functions like XMLTABLE
+ *
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Node	   *row_path;		/* row xpath query */
+	Node	   *expr;			/* processed data */
+	List	   *cols;			/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+}	TableExpr;
+
+/*----------
+ * TableExprColumn - a column definition in table-expression functions.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions
+ * and just doesn't make sense here.
+ *
+ * Raw form of this node is TableExprRawCol node - different in
+ * typeName field. After transformation we use typid, typmod.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;
+	Oid			typid;
+	int32		typmod;
+	Oid			collation;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *path_expr;
+	Node	   *default_expr;
+	int			location;
+}	TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..de4b8b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -441,10 +442,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..1229b76 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,6 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern const TableExprBuilder XmlTableBuilder;
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..bdbfc03 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,416 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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;
#69Michael Paquier
michael.paquier@gmail.com
In reply to: Pavel Stehule (#67)
Re: patch: function xmltable

On Fri, Nov 25, 2016 at 3:31 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

2016-11-24 18:51 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:

contrib/xml2 has always relied on libxslt for xpath functionality.
Can we do that here instead of writing, debugging, and documenting
a pile of new code?

I am sorry - I don't see it. There is nothing complex manipulation with
XPath expressions.

You are missing the point here, which is to make the implementation
footprint as light as possible, especially if the added functionality
is already present in a dependency that Postgres can be linked to. OK,
libxslt can only be linked with contrib/xml2/ now, but it would be at
least worth looking at how much the current patch can be simplified
for things like transformTableExpr or XmlTableGetValue by relying on
some existing routines. Nit: I did not look at the patch in details,
but I find the size of the latest version sent, 167kB, scary as it
complicates review and increases the likeliness of bugs.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#70Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#69)
Re: patch: function xmltable

Michael Paquier wrote:

Nit: I did not look at the patch in details,
but I find the size of the latest version sent, 167kB, scary as it
complicates review and increases the likeliness of bugs.

Here's the stat. Note that removing the functionality as discussed
would remove all of xpath_parser.c but I think the rest of it remains
pretty much unchanged. So it's clearly a large patch, but there are
large docs and tests too, not just code.

doc/src/sgml/func.sgml | 376 ++++++++++++++++++---
src/backend/executor/execQual.c | 335 +++++++++++++++++++
src/backend/executor/execTuples.c | 42 +++
src/backend/nodes/copyfuncs.c | 66 ++++
src/backend/nodes/equalfuncs.c | 51 +++
src/backend/nodes/nodeFuncs.c | 100 ++++++
src/backend/nodes/outfuncs.c | 51 +++
src/backend/nodes/readfuncs.c | 42 +++
src/backend/optimizer/util/clauses.c | 33 ++
src/backend/parser/gram.y | 181 ++++++++++-
src/backend/parser/parse_coerce.c | 33 +-
src/backend/parser/parse_expr.c | 182 +++++++++++
src/backend/parser/parse_target.c | 7 +
src/backend/utils/adt/Makefile | 2 +-
src/backend/utils/adt/ruleutils.c | 100 ++++++
src/backend/utils/adt/xml.c | 610 +++++++++++++++++++++++++++++++++++
src/backend/utils/adt/xpath_parser.c | 337 +++++++++++++++++++
src/backend/utils/fmgr/funcapi.c | 13 +
src/include/executor/executor.h | 1 +
src/include/executor/tableexpr.h | 69 ++++
src/include/funcapi.h | 1 -
src/include/nodes/execnodes.h | 31 ++
src/include/nodes/nodes.h | 4 +
src/include/nodes/parsenodes.h | 21 ++
src/include/nodes/primnodes.h | 40 +++
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_coerce.h | 4 +
src/include/utils/xml.h | 2 +
src/include/utils/xpath_parser.h | 24 ++
src/test/regress/expected/xml.out | 415 ++++++++++++++++++++++++
src/test/regress/expected/xml_1.out | 323 +++++++++++++++++++
src/test/regress/expected/xml_2.out | 414 ++++++++++++++++++++++++
src/test/regress/sql/xml.sql | 170 ++++++++++
33 files changed, 4019 insertions(+), 64 deletions(-)

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#71Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#70)
Re: patch: function xmltable

2016-11-25 3:31 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Michael Paquier wrote:

Nit: I did not look at the patch in details,
but I find the size of the latest version sent, 167kB, scary as it
complicates review and increases the likeliness of bugs.

Here's the stat. Note that removing the functionality as discussed
would remove all of xpath_parser.c but I think the rest of it remains
pretty much unchanged. So it's clearly a large patch, but there are
large docs and tests too, not just code.

yes, lot of is regress tests (expected part is 3x) - and XMLTABLE function
is not trivial.

lot of code is mechanical - nodes related. The really complex part is only
in xml.c. There is one longer function only - the complexity is based on
mapping libxml2 result to PostgreSQL result (with catching exceptions due
releasing libxml2 sources).

The all changes are well isolated - there is less risk to break some other.

Show quoted text

doc/src/sgml/func.sgml | 376 ++++++++++++++++++---
src/backend/executor/execQual.c | 335 +++++++++++++++++++
src/backend/executor/execTuples.c | 42 +++
src/backend/nodes/copyfuncs.c | 66 ++++
src/backend/nodes/equalfuncs.c | 51 +++
src/backend/nodes/nodeFuncs.c | 100 ++++++
src/backend/nodes/outfuncs.c | 51 +++
src/backend/nodes/readfuncs.c | 42 +++
src/backend/optimizer/util/clauses.c | 33 ++
src/backend/parser/gram.y | 181 ++++++++++-
src/backend/parser/parse_coerce.c | 33 +-
src/backend/parser/parse_expr.c | 182 +++++++++++
src/backend/parser/parse_target.c | 7 +
src/backend/utils/adt/Makefile | 2 +-
src/backend/utils/adt/ruleutils.c | 100 ++++++
src/backend/utils/adt/xml.c | 610 ++++++++++++++++++++++++++++++
+++++
src/backend/utils/adt/xpath_parser.c | 337 +++++++++++++++++++
src/backend/utils/fmgr/funcapi.c | 13 +
src/include/executor/executor.h | 1 +
src/include/executor/tableexpr.h | 69 ++++
src/include/funcapi.h | 1 -
src/include/nodes/execnodes.h | 31 ++
src/include/nodes/nodes.h | 4 +
src/include/nodes/parsenodes.h | 21 ++
src/include/nodes/primnodes.h | 40 +++
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_coerce.h | 4 +
src/include/utils/xml.h | 2 +
src/include/utils/xpath_parser.h | 24 ++
src/test/regress/expected/xml.out | 415 ++++++++++++++++++++++++
src/test/regress/expected/xml_1.out | 323 +++++++++++++++++++
src/test/regress/expected/xml_2.out | 414 ++++++++++++++++++++++++
src/test/regress/sql/xml.sql | 170 ++++++++++
33 files changed, 4019 insertions(+), 64 deletions(-)

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#72Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#71)
Re: patch: function xmltable

2016-11-25 7:44 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2016-11-25 3:31 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Michael Paquier wrote:

Nit: I did not look at the patch in details,
but I find the size of the latest version sent, 167kB, scary as it
complicates review and increases the likeliness of bugs.

Here's the stat. Note that removing the functionality as discussed
would remove all of xpath_parser.c but I think the rest of it remains
pretty much unchanged. So it's clearly a large patch, but there are
large docs and tests too, not just code.

yes, lot of is regress tests (expected part is 3x) - and XMLTABLE function
is not trivial.

regress tests are about 50%

Show quoted text

lot of code is mechanical - nodes related. The really complex part is only
in xml.c. There is one longer function only - the complexity is based on
mapping libxml2 result to PostgreSQL result (with catching exceptions due
releasing libxml2 sources).

The all changes are well isolated - there is less risk to break some
other.

doc/src/sgml/func.sgml | 376 ++++++++++++++++++---
src/backend/executor/execQual.c | 335 +++++++++++++++++++
src/backend/executor/execTuples.c | 42 +++
src/backend/nodes/copyfuncs.c | 66 ++++
src/backend/nodes/equalfuncs.c | 51 +++
src/backend/nodes/nodeFuncs.c | 100 ++++++
src/backend/nodes/outfuncs.c | 51 +++
src/backend/nodes/readfuncs.c | 42 +++
src/backend/optimizer/util/clauses.c | 33 ++
src/backend/parser/gram.y | 181 ++++++++++-
src/backend/parser/parse_coerce.c | 33 +-
src/backend/parser/parse_expr.c | 182 +++++++++++
src/backend/parser/parse_target.c | 7 +
src/backend/utils/adt/Makefile | 2 +-
src/backend/utils/adt/ruleutils.c | 100 ++++++
src/backend/utils/adt/xml.c | 610 ++++++++++++++++++++++++++++++
+++++
src/backend/utils/adt/xpath_parser.c | 337 +++++++++++++++++++
src/backend/utils/fmgr/funcapi.c | 13 +
src/include/executor/executor.h | 1 +
src/include/executor/tableexpr.h | 69 ++++
src/include/funcapi.h | 1 -
src/include/nodes/execnodes.h | 31 ++
src/include/nodes/nodes.h | 4 +
src/include/nodes/parsenodes.h | 21 ++
src/include/nodes/primnodes.h | 40 +++
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_coerce.h | 4 +
src/include/utils/xml.h | 2 +
src/include/utils/xpath_parser.h | 24 ++
src/test/regress/expected/xml.out | 415 ++++++++++++++++++++++++
src/test/regress/expected/xml_1.out | 323 +++++++++++++++++++
src/test/regress/expected/xml_2.out | 414 ++++++++++++++++++++++++
src/test/regress/sql/xml.sql | 170 ++++++++++
33 files changed, 4019 insertions(+), 64 deletions(-)

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#73Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#68)
Re: patch: function xmltable

Pavel Stehule wrote:

Here is updated patch without default namespace support (and without XPath
expression transformation).

Due last changes in parser
https://github.com/postgres/postgres/commit/906bfcad7ba7cb3863fe0e2a7810be8e3cd84fbd
I had to use c_expr on other positions ( xmlnamespace definition).

I don't think it is limit - in 99% there will be const literal.

Argh. I can't avoid the feeling that I'm missing some parser trickery
here. We have the XMLNAMESPACES keyword and the clause-terminating
comma to protect these clauses, there must be a way to define this piece
of the grammar so that there's no conflict, without losing the freedom
in the expressions. But I don't see how. Now I agree that xml
namespace definitions are going to be string literals in almost all
cases (or in extra sophisticated cases, column names) ... it's probably
better to spend the bison-fu in the document expression or the column
options, or better yet the xmlexists_argument stuff. But I don't see
possibility of improvements in any of those places, so let's put it
aside -- we can improve later, if need arises.

In any case, it looks like we can change c_expr to b_expr in a few
places, which is good because then operators work (in particular, unless
I misread the grammar, foo||bar doesn't work with c_expr and does work
with b_expr, which seems the most useful in this case). Also, it makes
no sense to support (in the namespaces clause) DEFAULT a_expr if the
IDENT case uses only b_expr, so let's reduce both to just b_expr.

While I'm looking at node definitions, I see a few things that could use
some naming improvement. For example, "expr" for TableExpr is a bit
unexpressive. We could use "document_expr" there, perhaps. "row_path"
seems fixated on the XML case and the expression be path; let's use
"row_expr" there. And "cols" could be "column_exprs" perhaps. (All
those renames cause fall-out in various node-related files, so let's
think carefully to avoid renaming them multiple times.)

In primnodes, you kept the comment that says "xpath". Please update
that to not-just-XML reality.

Please fix the comment in XmlTableAddNs; NULL is no longer a valid value.

parse_expr.c has two unused variables; please remove them.

This test in ExecEvalTableExprProtected looks weird:
if (i != tstate->for_ordinality_col - 1)
please change to comparing "i + 1" (convert array index into attribute
number), and invert the boolean expression, leaving the for_ordinality
case on top and the rest in the "else". That seems easier to read.
Also, we customarily use post-increment (rownum++) instead of pre-incr.

In execQual.c I think it's neater to have ExecEvalTableExpr go before
its subroutine. Actually, I wonder whether it is really necessary to
have a subroutine in the first place; you could just move the entire
contents of that subroutine to within the PG_TRY block instead. The
only thing you lose is one indentation level. I'm not sure about this
one, but it's worth considering.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#74Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#73)
1 attachment(s)
Re: patch: function xmltable

2016-11-28 23:34 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

Here is updated patch without default namespace support (and without

XPath

expression transformation).

Due last changes in parser
https://github.com/postgres/postgres/commit/

906bfcad7ba7cb3863fe0e2a7810be8e3cd84fbd

I had to use c_expr on other positions ( xmlnamespace definition).

I don't think it is limit - in 99% there will be const literal.

Argh. I can't avoid the feeling that I'm missing some parser trickery
here. We have the XMLNAMESPACES keyword and the clause-terminating
comma to protect these clauses, there must be a way to define this piece
of the grammar so that there's no conflict, without losing the freedom
in the expressions. But I don't see how. Now I agree that xml
namespace definitions are going to be string literals in almost all
cases (or in extra sophisticated cases, column names) ... it's probably
better to spend the bison-fu in the document expression or the column
options, or better yet the xmlexists_argument stuff. But I don't see
possibility of improvements in any of those places, so let's put it
aside -- we can improve later, if need arises.

The problem is in unreserved keyword "PASSING" probably.

In any case, it looks like we can change c_expr to b_expr in a few
places, which is good because then operators work (in particular, unless
I misread the grammar, foo||bar doesn't work with c_expr and does work
with b_expr, which seems the most useful in this case). Also, it makes
no sense to support (in the namespaces clause) DEFAULT a_expr if the
IDENT case uses only b_expr, so let's reduce both to just b_expr.

I changed all what was possible to b_expr.

While I'm looking at node definitions, I see a few things that could use
some naming improvement. For example, "expr" for TableExpr is a bit
unexpressive. We could use "document_expr" there, perhaps. "row_path"
seems fixated on the XML case and the expression be path; let's use
"row_expr" there. And "cols" could be "column_exprs" perhaps. (All
those renames cause fall-out in various node-related files, so let's
think carefully to avoid renaming them multiple times.)

Columns is not only expr - list - so I renamed it to "columns". Other
renamed like you proposed

In primnodes, you kept the comment that says "xpath". Please update
that to not-just-XML reality.

fixed

Please fix the comment in XmlTableAddNs; NULL is no longer a valid value.

fixed

parse_expr.c has two unused variables; please remove them.

fixed

This test in ExecEvalTableExprProtected looks weird:
if (i != tstate->for_ordinality_col - 1)
please change to comparing "i + 1" (convert array index into attribute
number), and invert the boolean expression, leaving the for_ordinality
case on top and the rest in the "else". That seems easier to read.
Also, we customarily use post-increment (rownum++) instead of pre-incr.

fiexed

In execQual.c I think it's neater to have ExecEvalTableExpr go before
its subroutine. Actually, I wonder whether it is really necessary to
have a subroutine in the first place; you could just move the entire
contents of that subroutine to within the PG_TRY block instead. The
only thing you lose is one indentation level. I'm not sure about this
one, but it's worth considering.

done

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Regards

Pavel

Attachments:

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

On 30 November 2016 at 05:36, Pavel Stehule <pavel.stehule@gmail.com> wrote:

The problem is in unreserved keyword "PASSING" probably.

Yeah, I think that's what I hit when trying to change it.

Can't you just parenthesize the expression to use operators like ||
etc? If so, not a big deal.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#76Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#75)
Re: patch: function xmltable

2016-11-30 2:40 GMT+01:00 Craig Ringer <craig@2ndquadrant.com>:

On 30 November 2016 at 05:36, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

The problem is in unreserved keyword "PASSING" probably.

Yeah, I think that's what I hit when trying to change it.

Can't you just parenthesize the expression to use operators like ||
etc? If so, not a big deal.

???

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#77Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#76)
Re: patch: function xmltable

Pavel Stehule wrote:

2016-11-30 2:40 GMT+01:00 Craig Ringer <craig@2ndquadrant.com>:

On 30 November 2016 at 05:36, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

The problem is in unreserved keyword "PASSING" probably.

Yeah, I think that's what I hit when trying to change it.

Can't you just parenthesize the expression to use operators like ||
etc? If so, not a big deal.

???

"'(' a_expr ')'" is a c_expr; Craig suggests that we can just tell users
to manually add parens around any expressions that they want to use.
That's not necessary most of the time since we've been able to use
b_expr in most places.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#78Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#77)
Re: patch: function xmltable

2016-11-30 13:38 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2016-11-30 2:40 GMT+01:00 Craig Ringer <craig@2ndquadrant.com>:

On 30 November 2016 at 05:36, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

The problem is in unreserved keyword "PASSING" probably.

Yeah, I think that's what I hit when trying to change it.

Can't you just parenthesize the expression to use operators like ||
etc? If so, not a big deal.

???

"'(' a_expr ')'" is a c_expr; Craig suggests that we can just tell users
to manually add parens around any expressions that they want to use.
That's not necessary most of the time since we've been able to use
b_expr in most places.

Now I understand

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#79Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#76)
Re: patch: function xmltable

Dne 30. 11. 2016 14:53 napsal uživatel "Pavel Stehule" <
pavel.stehule@gmail.com>:

2016-11-30 13:38 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2016-11-30 2:40 GMT+01:00 Craig Ringer <craig@2ndquadrant.com>:

On 30 November 2016 at 05:36, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

The problem is in unreserved keyword "PASSING" probably.

Yeah, I think that's what I hit when trying to change it.

Can't you just parenthesize the expression to use operators like ||
etc? If so, not a big deal.

???

"'(' a_expr ')'" is a c_expr; Craig suggests that we can just tell users
to manually add parens around any expressions that they want to use.
That's not necessary most of the time since we've been able to use
b_expr in most places.

still there are one c_expr, but without new reserved word there are not
change to reduce it.

Show quoted text

Now I understand

Regards

Pavel

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#80Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Pavel Stehule (#79)
Re: patch: function xmltable

On Thu, Dec 1, 2016 at 2:21 AM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Dne 30. 11. 2016 14:53 napsal uživatel "Pavel Stehule" <
pavel.stehule@gmail.com>:

2016-11-30 13:38 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2016-11-30 2:40 GMT+01:00 Craig Ringer <craig@2ndquadrant.com>:

On 30 November 2016 at 05:36, Pavel Stehule <

pavel.stehule@gmail.com>

wrote:

The problem is in unreserved keyword "PASSING" probably.

Yeah, I think that's what I hit when trying to change it.

Can't you just parenthesize the expression to use operators like ||
etc? If so, not a big deal.

???

"'(' a_expr ')'" is a c_expr; Craig suggests that we can just tell users
to manually add parens around any expressions that they want to use.
That's not necessary most of the time since we've been able to use
b_expr in most places.

still there are one c_expr, but without new reserved word there are not
change to reduce it.

Moved to next CF with the same status (ready for committer).

Regards,
Hari Babu
Fujitsu Australia

#81Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#74)
Re: patch: function xmltable

Hm, you omitted tableexpr.h from the v15 patch ...

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#82Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#81)
1 attachment(s)
Re: patch: function xmltable

2016-12-02 17:25 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Hm, you omitted tableexpr.h from the v15 patch ...

I am sorry

should be ok now

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

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

Here's version 17. I have made significant changes here.

1. Restructure the execQual code. Instead of a PG_TRY wrapper, I have
split this code in three pieces; there's the main code with the PG_TRY
wrappers and is mainly in charge of the builderCxt pointer. In the
previous coding there was a shim that examined builderCxt but was not
responsible for setting it up, which was ugly. The second part is the
"initializer" which sets the row and column filters and does namespace
processing. The third part is the "FetchRow" logic. It seems to me
much cleaner this way.

2. rename the "builder" stuff to use the "routine" terminology. This is
in line with what we do for other function-pointer-filled structs, such
as FdwRoutine, IndexAmRoutine etc. I also cleaned up the names a bit
more.

3. Added a magic number to the table builder context struct, so that we
can barf appropriately. This is in line with PgXmlErrorContext --
mostly for future-proofing. I didn't test this too hard. Also, moved
the XmlTableContext struct declaration nearer the top of the file, as is
customary. (We don't really need it that way, since the functions are
all declared taking void *, but it seems cleaner to me anyway).

4. I added, edited, and fixed a large number of code comments.

This is looking much better now, but it still needs at least the
following changes.

First, we need to fix is the per_rowset_memcxt thingy. I think the way
it's currently being used is rather ugly; it looks to me like the memory
context does not belong into the XmlTableContext struct at all.
Instead, the executor code should keep the memcxt pointer in a state
struct of its own, and it should be the executor's responsibility to
change to the appropriate context before calling the table builder
functions. In particular, this means that the table context can no
longer be a void * pointer; it needs to be a struct that's defined by
the executor (probably a primnodes.h one). The void * pointer is
stashed inside that struct. Also, the "routine" pointer should not be
part of the void * struct, but of the executor's struct. So the
execQual code can switch to the memory context, and destroy it
appropriately.

Second, we should make gram.y set a new "function type" value in the
TableExpr it creates, so that the downstream code (transformTableExpr,
ExecInitExpr, ruleutils.c) really knows that the given function is
XmlTableExpr, instead of guessing just because it's the only implemented
case. Probably this "function type" is an enum (currently with a single
value TableExprTypeXml or something like that) in primnodes.

Finally, there's the pending task of renaming and moving
ExecTypeFromTableExpr to some better place. Not sure that moving it
back to nodeFuncs is a nice idea. Looks to me like calling it from
ExprTypmod is a rather ugly idea.

Hmm, ruleutils ... not sure what to think of this one.

The typedefs.list changes are just used to pgindent the affected code
correctly. It's not for commit.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-Create-sect3-in-the-functions-xml-section.patchtext/plain; charset=us-asciiDownload
From fa46252e326ab16b8921fe8b7f09dace1b9d8ea7 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 16 Nov 2016 17:46:14 -0300
Subject: [PATCH 1/2] Create <sect3> in the functions-xml section

This is so that a new XMLTABLE sect3 can be added nicely later.

Author: Craig Ringer
---
 doc/src/sgml/func.sgml | 104 ++++++++++++++++++++++++++-----------------------
 1 file changed, 56 insertions(+), 48 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index eca98df..43db430 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10325,10 +10325,6 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
   <sect2 id="functions-xml-processing">
    <title>Processing XML</title>
 
-   <indexterm>
-    <primary>XPath</primary>
-   </indexterm>
-
    <para>
     To process values of data type <type>xml</type>, PostgreSQL offers
     the functions <function>xpath</function> and
@@ -10336,39 +10332,46 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
     expressions.
    </para>
 
+   <sect3 id="functions-xml-processing-xpath">
+    <title><literal>xpath</literal></title>
+
+    <indexterm>
+     <primary>XPath</primary>
+    </indexterm>
+
 <synopsis>
 <function>xpath</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath</function> evaluates the XPath
-    expression <replaceable>xpath</replaceable> (a <type>text</> value)
-    against the XML value
-    <replaceable>xml</replaceable>.  It returns an array of XML values
-    corresponding to the node set produced by the XPath expression.
-    If the XPath expression returns a scalar value rather than a node set,
-    a single-element array is returned.
-   </para>
+    <para>
+     The function <function>xpath</function> evaluates the XPath
+     expression <replaceable>xpath</replaceable> (a <type>text</> value)
+     against the XML value
+     <replaceable>xml</replaceable>.  It returns an array of XML values
+     corresponding to the node set produced by the XPath expression.
+     If the XPath expression returns a scalar value rather than a node set,
+     a single-element array is returned.
+    </para>
 
-  <para>
-    The second argument must be a well formed XML document. In particular,
-    it must have a single root node element.
-  </para>
+    <para>
+     The second argument must be a well formed XML document. In particular,
+     it must have a single root node element.
+    </para>
 
-   <para>
-    The optional third argument of the function is an array of namespace
-    mappings.  This array should be a two-dimensional <type>text</> array with
-    the length of the second axis being equal to 2 (i.e., it should be an
-    array of arrays, each of which consists of exactly 2 elements).
-    The first element of each array entry is the namespace name (alias), the
-    second the namespace URI. It is not required that aliases provided in
-    this array be the same as those being used in the XML document itself (in
-    other words, both in the XML document and in the <function>xpath</function>
-    function context, aliases are <emphasis>local</>).
-   </para>
+    <para>
+     The optional third argument of the function is an array of namespace
+     mappings.  This array should be a two-dimensional <type>text</> array with
+     the length of the second axis being equal to 2 (i.e., it should be an
+     array of arrays, each of which consists of exactly 2 elements).
+     The first element of each array entry is the namespace name (alias), the
+     second the namespace URI. It is not required that aliases provided in
+     this array be the same as those being used in the XML document itself (in
+     other words, both in the XML document and in the <function>xpath</function>
+     function context, aliases are <emphasis>local</>).
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
              ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10378,10 +10381,10 @@ SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
 
-   <para>
-    To deal with default (anonymous) namespaces, do something like this:
+    <para>
+     To deal with default (anonymous) namespaces, do something like this:
 <screen><![CDATA[
 SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
              ARRAY[ARRAY['mydefns', 'http://example.com']]);
@@ -10391,27 +10394,31 @@ SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
+   </sect3>
 
-   <indexterm>
-    <primary>xpath_exists</primary>
-   </indexterm>
+   <sect3 id="functions-xml-processing-xpath-exists">
+    <title><literal>xpath_exists</literal></title>
+
+    <indexterm>
+     <primary>xpath_exists</primary>
+    </indexterm>
 
 <synopsis>
 <function>xpath_exists</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath_exists</function> is a specialized form
-    of the <function>xpath</function> function.  Instead of returning the
-    individual XML values that satisfy the XPath, this function returns a
-    Boolean indicating whether the query was satisfied or not.  This
-    function is equivalent to the standard <literal>XMLEXISTS</> predicate,
-    except that it also offers support for a namespace mapping argument.
-   </para>
+    <para>
+     The function <function>xpath_exists</function> is a specialized form
+     of the <function>xpath</function> function.  Instead of returning the
+     individual XML values that satisfy the XPath, this function returns a
+     Boolean indicating whether the query was satisfied or not.  This
+     function is equivalent to the standard <literal>XMLEXISTS</> predicate,
+     except that it also offers support for a namespace mapping argument.
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
                      ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10421,7 +10428,8 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
  t
 (1 row)
 ]]></screen>
-   </para>
+    </para>
+   </sect3>
   </sect2>
 
   <sect2 id="functions-xml-mapping">
-- 
2.1.4

0002-xmltable-17.patchtext/plain; charset=us-asciiDownload
From 344ab51f71f5f92b46ff41046f289a31df494eb7 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 2 Dec 2016 19:15:47 -0300
Subject: [PATCH 2/2] xmltable 17

---
 doc/src/sgml/func.sgml               | 274 ++++++++++++++-
 src/backend/executor/execQual.c      | 349 +++++++++++++++++++
 src/backend/executor/execTuples.c    |  42 +++
 src/backend/nodes/copyfuncs.c        |  66 ++++
 src/backend/nodes/equalfuncs.c       |  51 +++
 src/backend/nodes/nodeFuncs.c        | 100 ++++++
 src/backend/nodes/outfuncs.c         |  51 +++
 src/backend/nodes/readfuncs.c        |  42 +++
 src/backend/optimizer/util/clauses.c |  33 ++
 src/backend/parser/gram.y            | 184 +++++++++-
 src/backend/parser/parse_coerce.c    |  33 +-
 src/backend/parser/parse_expr.c      | 163 +++++++++
 src/backend/parser/parse_target.c    |   8 +
 src/backend/utils/adt/ruleutils.c    | 100 ++++++
 src/backend/utils/adt/xml.c          | 633 +++++++++++++++++++++++++++++++++++
 src/backend/utils/fmgr/funcapi.c     |  13 +
 src/include/executor/executor.h      |   1 +
 src/include/executor/tableexpr.h     |  67 ++++
 src/include/funcapi.h                |   1 -
 src/include/nodes/execnodes.h        |  32 ++
 src/include/nodes/nodes.h            |   4 +
 src/include/nodes/parsenodes.h       |  21 ++
 src/include/nodes/primnodes.h        |  40 +++
 src/include/parser/kwlist.h          |   3 +
 src/include/parser/parse_coerce.h    |   4 +
 src/include/utils/xml.h              |   2 +
 src/test/regress/expected/xml.out    | 412 +++++++++++++++++++++++
 src/test/regress/expected/xml_1.out  | 321 ++++++++++++++++++
 src/test/regress/expected/xml_2.out  | 412 +++++++++++++++++++++++
 src/test/regress/sql/xml.sql         | 170 ++++++++++
 src/tools/pgindent/typedefs.list     |  10 +
 31 files changed, 3626 insertions(+), 16 deletions(-)
 create mode 100644 src/include/executor/tableexpr.h

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 43db430..d1c12a3 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 743e7d6..8fcbaac 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,13 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum value);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4508,243 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	Datum		result;
+	const TableExprRoutine *routine = tstate->routine;
+
+	PG_TRY();
+	{
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->builderCxt == NULL)
+		{
+			Datum		value;
+
+			tstate->builderCxt =
+				routine->CreateContext(tstate->tupdesc,
+									   tstate->in_functions,
+									   tstate->typioparams,
+									   tstate->per_rowset_memcxt);
+
+			/*
+			 * If evaluating the document expression returns NULL, the table
+			 * expression is empty and we return immediately.
+			 */
+			value = ExecEvalExpr(tstate->doc_expr, econtext, isNull, NULL);
+			if (*isNull)
+			{
+				*isDone = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any error are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->builderCxt != NULL)
+		{
+			routine->DestroyContext(tstate->builderCxt);
+			tstate->builderCxt = NULL;
+		}
+
+		MemoryContextDelete(tstate->per_rowset_memcxt);
+		tstate->per_rowset_memcxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum value)
+{
+	const TableExprRoutine *routine = tstate->routine;
+	ListCell   *cell;
+	bool		isnull;
+	int			i;
+
+	/*
+	 * 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.
+	 */
+	routine->SetContent(tstate->builderCxt, value);
+
+	/* Evaluate namespace specifications */
+	foreach(cell, tstate->namespaces)
+	{
+		Node	   *n = (Node *) lfirst(cell);
+		ExprState  *expr;
+		char	   *ns_name;
+		char	   *ns_uri;
+
+		if (IsA(n, NamedArgExpr))
+		{
+			NamedArgExpr *na = (NamedArgExpr *) n;
+
+			expr = (ExprState *) na->arg;
+			ns_name = na->name;
+		}
+		else
+		{
+			expr = (ExprState *) n;
+			ns_name = NULL;
+		}
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		routine->DeclareNamespace(tstate->builderCxt, ns_name, ns_uri);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->row_expr, econtext, &isnull, NULL);
+	if (isnull)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("row filter expression must not be null")));
+	routine->SetRowFilter(tstate->builderCxt, TextDatumGetCString(value));
+
+	/*
+	 * Install the column filter expressions into the table builder context.
+	 * If an expression is given, use that; otherwise the column name itself
+	 * is the column filter.
+	 */
+	for (i = 0; i < tstate->ncols; i++)
+	{
+		char	   *col_filter;
+
+		if (tstate->col_expr[i] != NULL)
+		{
+			value = ExecEvalExpr(tstate->col_expr[i], econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("column filter expression must not be null"),
+						 errdetail("Filter for column \"%s\" is null.",
+								   NameStr(tstate->tupdesc->attrs[i]->attname))));
+			col_filter = TextDatumGetCString(value);
+		}
+		else
+			col_filter = NameStr(tstate->tupdesc->attrs[i]->attname);
+
+		routine->SetColumnFilter(tstate->builderCxt, col_filter, i);
+	}
+}
+
+/*
+ * Fetch one more row from a TableExpr table builder.
+ */
+static Datum
+tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone)
+{
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+	void	   *builderCxt;
+
+	builderCxt = tstate->builderCxt;
+
+	/* Prepare one more row */
+	if (routine->FetchRow(builderCxt))
+	{
+		TupleDesc	tupdesc = tstate->tupdesc;
+		Datum	   *values = tstate->values;
+		bool	   *nulls = tstate->nulls;
+		HeapTuple	tuple;
+		HeapTupleHeader dtuple;
+		int			i;
+		bool		isnull;
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			if (i + 1 == tstate->for_ordinality_col)
+			{
+				values[i] = Int32GetDatum(tstate->rownum++);
+				nulls[i] = false;
+			}
+			else
+			{
+				values[i] = routine->GetValue(builderCxt, i, &isnull);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tupdesc->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+		}
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(dtuple, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type previously identified
+		 */
+		HeapTupleHeaderSetTypeId(dtuple, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(dtuple, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+
+		*isNull = false;
+		*isDone = ExprMultipleResult;
+
+		result = HeapTupleHeaderGetDatum(dtuple);
+	}
+	else
+	{
+		/* no more rows */
+		routine->DestroyContext(builderCxt);
+		tstate->builderCxt = NULL;
+
+		/* make sure all memory is released */
+		MemoryContextReset(tstate->per_rowset_memcxt);
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5507,110 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int			ncols;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->builderCxt = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				/* XXX this assumes the anon record type has been registered */
+				tstate->tupdesc =
+					lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+												exprTypmod((Node *) te));
+				Assert(tstate->tupdesc->natts > 0);
+
+				ncols = tstate->tupdesc->natts;
+				tstate->values = palloc0(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid			in_funcid;
+
+					getTypeInputInfo(tstate->tupdesc->attrs[i]->atttypid,
+									 &in_funcid, &tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_expr = ExecInitExpr((Expr *) te->row_expr, parent);
+				tstate->doc_expr = ExecInitExpr((Expr *) te->document_expr, parent);
+
+				if (te->columns)
+				{
+					ListCell   *col;
+
+					Assert(ncols == list_length(te->columns));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+					tstate->rownum = 1;
+
+					i = 0;
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+															   parent);
+							tstate->col_expr[i] = ExecInitExpr((Expr *) tec->column_expr,
+															   parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->per_rowset_memcxt = AllocSetContextCreate(CurrentMemoryContext,
+											 "XmlTable per rowgroup context",
+													 ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..8c722ed 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,48 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+	TupleDesc	typeInfo;
+
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		int			i = 1;
+
+		typeInfo = CreateTemplateTupleDesc(list_length(te->columns), false);
+
+		foreach(col, te->columns)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(typeInfo,
+							   (AttrNumber) i,
+							   pstrdup(tec->colname),
+							   tec->typid,
+							   tec->typmod,
+							   0);
+			i++;
+		}
+	}
+	else
+	{
+		typeInfo = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(typeInfo, (AttrNumber) 1,
+						   "xmltable", XMLOID, -1, 0);
+	}
+
+	/* XXX OK to do this?  looks a bit out of place ... */
+	assign_record_type_typmod(typeInfo);
+
+	return typeInfo;
+}
+
+/*
  * ExecTypeSetColNames - set column names in a TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..36b4f26 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(row_expr);
+	COPY_NODE_FIELD(document_expr);
+	COPY_NODE_FIELD(columns);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4600,6 +4657,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5104,6 +5167,9 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..6971086 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2615,6 +2615,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2645,6 +2675,18 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(row_expr);
+	COMPARE_NODE_FIELD(document_expr);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2910,6 +2952,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3401,6 +3449,9 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..6db3ccf 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +258,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +499,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate a blessed tupdesc from the column definitions, and
+				 * returns its typmod.
+				 */
+				TupleDesc	tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +747,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 +951,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1155,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1587,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2249,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->column_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3069,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_expr, te->row_expr, Node *);
+				MUTATE(newnode->document_expr, te->document_expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->columns, te->columns, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->column_expr, tec->column_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3708,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..d5d5e81 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_NODE_FIELD(row_expr);
+	WRITE_NODE_FIELD(document_expr);
+	WRITE_NODE_FIELD(columns);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3539,6 +3581,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..ed71fc6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,44 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(row_expr);
+	READ_NODE_FIELD(document_expr);
+	READ_NODE_FIELD(columns);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(column_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2535,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9af29dd..5d5fd41 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -829,6 +829,12 @@ expression_returns_set_rows_walker(Node *node, double *count)
 			*count *= get_func_rows(expr->opfuncid);
 		}
 	}
+	if (IsA(node, TableExpr))
+	{
+		/* we have not any method how to estimate it, use default */
+		*count = 1000;
+		return false;
+	}
 
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
@@ -3598,6 +3604,33 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (tec->column_expr != NULL || tec->default_expr != NULL)
+				{
+					TableExprColumn *newtec = makeNode(TableExprColumn);
+
+					newtec->colname = tec->colname;
+					newtec->typid = tec->typid;
+					newtec->typmod = tec->typmod;
+					newtec->collation = tec->collation;
+					newtec->for_ordinality = tec->for_ordinality;
+					newtec->is_not_null = tec->is_not_null;
+					newtec->column_expr =
+						eval_const_expressions_mutator((Node *) tec->column_expr,
+													   context);
+					newtec->default_expr =
+						eval_const_expressions_mutator((Node *) tec->default_expr,
+													   context);
+					newtec->location = tec->location;
+
+					return (Node *) newtec;
+				}
+			}
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d6274b4..91f8a9a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -545,6 +545,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	XmlTableExprColList XmlTableExprColOptions
+%type <node>	XmlTableExprCol
+%type <defelt>	XmlTableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -576,10 +583,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
-	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
-	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-	CROSS CSV CUBE CURRENT_P
+	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS
+	COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION
+	CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST
+	CREATE CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -649,8 +656,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
-	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
-	XMLPI XMLROOT XMLSERIALIZE
+	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
+	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
 
 	YEAR_P YES_P
 
@@ -12588,6 +12595,168 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+XmlTableExprColList: XmlTableExprCol						{ $$ = list_make1($1); }
+			| XmlTableExprColList ',' XmlTableExprCol		{ $$ = lappend($1, $3); }
+		;
+
+XmlTableExprCol:
+			ColId Typename IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $3;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+			| ColId Typename XmlTableExprColOptions IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $4;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->column_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->column_expr = defel->arg;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+XmlTableExprColOptions: XmlTableExprColOption				{ $$ = list_make1($1); }
+			| XmlTableExprColOptions XmlTableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+XmlTableExprColOption:
+			IDENT b_expr
+				{
+					$$ = makeDefElem($1, $2, @1);
+				}
+			| DEFAULT b_expr
+				{
+					$$ = makeDefElem("default", $2, @1);
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			b_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT b_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
 		;
 
 /*
@@ -13696,6 +13865,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13997,10 +14167,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..06739f6 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1095,9 +1095,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1105,9 +1105,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1116,7 +1116,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1143,6 +1143,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 17d1cbf..6551075 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2764,6 +2770,163 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 	return (Node *) newc;
 }
 
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	const char *constructName;
+	Oid			exprType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	exprType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, te->location);
+
+	/* Transform the row-generating Xpath expression ... */
+	Assert(te->row_expr != NULL);
+	newte->row_expr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_expr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML itself */
+	Assert(te->document_expr != NULL);
+	newte->document_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, te->document_expr),
+												   exprType,
+												   constructName);
+
+	/* Columns, if any, also need to be transformed */
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		bool		for_ordinality = false;
+		char	  **colnames;
+		int			i = 0;
+
+		colnames = palloc(sizeof(char *) * list_length(te->columns));
+
+		/*
+		 * For each TableExprRawCol in the input TableExpr, generate one
+		 * TableExprColumn in the output TableExpr.
+		 */
+		foreach(col, te->columns)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc;
+			int			j;
+
+			/* Only one FOR ORDINALITY column is allowed; verify */
+			if (rawc->for_ordinality)
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+			}
+
+			newc = makeNode(TableExprColumn);
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				newc->typid = INT4OID;
+				newc->typmod = -1;
+			}
+			else
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &newc->typid, &newc->typmod);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->column_expr)
+				newc->column_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, rawc->column_expr),
+															TEXTOID,
+															constructName);
+			if (rawc->default_expr)
+				newc->default_expr = coerce_to_specific_type_typmod(pstate,
+							transformExprRecurse(pstate, rawc->default_expr),
+												   newc->typid, newc->typmod,
+															  constructName);
+
+
+			newte->columns = lappend(newte->columns, newc);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(colnames[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			colnames[i] = rawc->colname;
+
+			i++;
+		}
+
+		pfree(colnames);
+	}
+	else
+		newte->columns = NIL;
+
+	/* Namespaces, if any, also need to be transformed */
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the namespace \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(nsnames);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
 /*
  * Transform a "row compare-op row" construct
  *
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index d440dec..d59d83f 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,14 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+
+			/*
+			 * Make TableExpr act like a regular function. Only XMLTABLE expr
+			 * is supported in this moment.
+			 */
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..ef12407 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8306,6 +8307,105 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/*
+				 * Deparse TableExpr - the is only one TableExpr producer, the
+				 * function XMLTABLE.
+				 */
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_expr, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->document_expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->columns != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->column_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->column_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..54b143a 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,52 @@ 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 XmlTableContext
+{
+	int			magic;
+	TupleDesc	tupdesc;
+	MemoryContext per_rowset_memcxt;
+	char	   *def_namespace_name;
+	FmgrInfo   *in_functions;
+	Oid		   *typioparams;
+	long int	rc;
+	PgXmlErrorContext *xmlerrcxt;
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr	doc;
+	xmlXPathContextPtr xpathcxt;
+	xmlXPathCompExprPtr xpathcomp;
+	xmlXPathObjectPtr xpathobj;
+	xmlXPathCompExprPtr *xpathscomp;
+} XmlTableContext;
+#endif
+
+static void *XmlTableCreateContext(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparms,
+					  MemoryContext mcxt);
+static void XmlTableSetContent(void *txpr, Datum value);
+static void XmlTableDeclareNamespace(void *txpr, char *name, char *uri);
+static void XmlTableSetRowFilter(void *txpr, char *path);
+static void XmlTableSetColumnFilter(void *txpr, char *path, int colnum);
+static bool XmlTableFetchRow(void *txpr);
+static Datum XmlTableGetValue(void *txpr, int colnum, bool *isnull);
+static void XmlTableDestroyContext(void *txpr);
+
+const TableExprRoutine XmlTableExprRoutine =
+{
+	XmlTableCreateContext,
+	XmlTableSetContent,
+	XmlTableDeclareNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyContext
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4112,589 @@ 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
+
+/*
+ * Create XmlTableContext for XmlTable builder. Initialize XML parser.
+ */
+static void *
+XmlTableCreateContext(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparams,
+					  MemoryContext per_rowset_memcxt)
+{
+#ifdef USE_LIBXML
+	MemoryContext oldcxt;
+	XmlTableContext *result = NULL;
+	PgXmlErrorContext *xmlerrcxt = NULL;
+	volatile xmlParserCtxtPtr ctxt = NULL;
+
+	oldcxt = MemoryContextSwitchTo(per_rowset_memcxt);
+
+	result = palloc0(sizeof(XmlTableContext));
+	result->magic = XMLTABLE_CONTEXT_MAGIC;
+	result->tupdesc = tupdesc;
+	result->per_rowset_memcxt = per_rowset_memcxt;
+	result->in_functions = in_functions;
+	result->typioparams = typioparams;
+	result->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * tupdesc->natts);
+
+	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+	PG_TRY();
+	{
+		xmlInitParser();
+
+		ctxt = xmlNewParserCtxt();
+		if (ctxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate parser context");
+	}
+	PG_CATCH();
+	{
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	result->xmlerrcxt = xmlerrcxt;
+	result->ctxt = ctxt;
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(void *txpr, Datum value)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) txpr;
+	xmltype    *xmlval = DatumGetXmlP(value);
+	xmlChar    *xstr;
+	int			length;
+	volatile xmlDocPtr doc = NULL;
+	volatile xmlXPathContextPtr xpathcxt = NULL;
+	MemoryContext oldcxt;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetContent called with invalid XmlTableContext");
+
+	length = VARSIZE(xmlval) - VARHDRSZ;
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memcxt);
+
+	xstr = to_xmlstr(VARDATA(xmlval), length);
+
+	PG_TRY();
+	{
+		doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL, 0);
+		if (doc == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+						"could not parse XML document");
+		xpathcxt = xmlXPathNewContext(doc);
+		if (xpathcxt == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate XPath context");
+		xpathcxt->node = xmlDocGetRootElement(doc);
+		if (xpathcxt->node == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not find root XML element");
+	}
+	PG_CATCH();
+	{
+		if (xpathcxt != NULL)
+			xmlXPathFreeContext(xpathcxt);
+		if (doc != NULL)
+			xmlFreeDoc(doc);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xtCxt->doc = doc;
+	xtCxt->xpathcxt = xpathcxt;
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableDeclareNamespace.  Add a namespace declaration.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableDeclareNamespace(void *txpr, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) txpr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid XmlTableContext");
+
+	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(void *txpr, char *path)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) txpr;
+	xmlChar    *xstr;
+	MemoryContext oldcxt;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetRowFilter called with invalid XmlTableContext");
+
+	if (path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("row path filter must not be empty string")));
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memcxt);
+	xstr = to_xmlstr(path, strlen(path));
+
+	xtCxt->xpathcomp = xmlXPathCompile(xstr);
+	if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetColumnFilter
+ */
+static void
+XmlTableSetColumnFilter(void *txpr, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) txpr;
+	MemoryContext oldcxt;
+	xmlChar    *xstr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid XmlTableContext");
+
+	if (path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("column path filter must not be empty string")));
+
+	oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memcxt);
+	xstr = to_xmlstr(path, strlen(path));
+
+	xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+	if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+
+	MemoryContextSwitchTo(oldcxt);
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableFetchRow - returns true when is possible to read
+ * values from XML document.
+ *
+ * First call evaluates row path over content.
+ * Next step and at other calls increment row counter and
+ * compare it with number values in result set.
+ */
+static bool
+XmlTableFetchRow(void *txpr)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) txpr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableFetchRow called with invalid XmlTableContext");
+
+	/*
+	 * XmlTable returns table - set of composite values. The error context, is
+	 * used for producement more values, between two calls, there can be
+	 * created and used another libxml2 error context. It is libxml2 global
+	 * value, so it should be refreshed any time before any libxml2 usage,
+	 * that is finished by returning some value.
+	 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(xtCxt->per_rowset_memcxt);
+
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		xtCxt->rc = 0;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+}
+
+/*
+ * Apply column path on current node and returns result.
+ * Allows multivalue when expected type is XML.
+ * When COLUMNS part was not entered, returns current node.
+ */
+static Datum
+XmlTableGetValue(void *txpr, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) txpr;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableGetValue called with invalid XmlTableContext");
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+	if (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = NULL;
+
+		/* fast leaving */
+		if (cur->type != XML_ELEMENT_NODE)
+			elog(ERROR, "unexpected xmlNode type");
+
+		PG_TRY();
+		{
+			/* Set current node as entry point for XPath evaluation */
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+			/* Evaluate column path */
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+			if (column_xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+				xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+							"could not create XPath object");
+
+			if (column_xpathobj->type == XPATH_NODESET)
+			{
+				int			count;
+
+				if (column_xpathobj->nodesetval != NULL)
+					count = column_xpathobj->nodesetval->nodeNr;
+				else
+					count = 0;
+
+				if (count > 0)
+				{
+					Oid			targettypid = xtCxt->tupdesc->attrs[colnum]->atttypid;
+
+					if (count == 1)
+					{
+						if (targettypid != XMLOID)
+						{
+							xmlChar    *str;
+
+							str = xmlNodeListGetString(xtCxt->doc,
+													   column_xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+													   1);
+
+							if (str != NULL)
+							{
+								PG_TRY();
+								{
+									/*
+									 * Copy string to PostgreSQL controlled
+									 * memory
+									 */
+									cstr = pstrdup((char *) str);
+								}
+								PG_CATCH();
+								{
+									xmlFree(str);
+									PG_RE_THROW();
+								}
+								PG_END_TRY();
+
+								xmlFree(str);
+							}
+							else
+							{
+								/* Return empty string when tag is empty */
+								cstr = pstrdup("");
+							}
+						}
+						else
+						{
+							/* simple case, result is one value */
+							cstr = xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[0],
+													xtCxt->xmlerrcxt);
+						}
+					}
+					else
+					{
+						StringInfoData str;
+						int			i;
+
+						/*
+						 * When result is not one value, then we can work with
+						 * concated values. But it requires XML as expected
+						 * type.
+						 */
+						if (targettypid != XMLOID)
+							ereport(ERROR,
+									(errcode(ERRCODE_CARDINALITY_VIOLATION),
+									 errmsg("more than one value returned by column XPath expression")));
+
+						/* Concate serialized values */
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+												   xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														  xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[colnum],
+											   cstr,
+											   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[colnum],
+										 (char *) column_xpathobj->stringval,
+										   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+				*isnull = false;
+			}
+			else
+				elog(ERROR, "unexpected XPath object type");
+
+			xmlXPathFreeObject(column_xpathobj);
+			column_xpathobj = NULL;
+		}
+		PG_CATCH();
+		{
+			if (column_xpathobj != NULL)
+				xmlXPathFreeObject(column_xpathobj);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+	else
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+		result = InputFunctionCall(&xtCxt->in_functions[0],
+								   cstr,
+								   xtCxt->typioparams[0],
+								   -1); /* target type is XML always */
+		*isnull = false;
+	}
+
+	if (cstr != NULL)
+		pfree(cstr);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * Release all libxml2 memory and related resources
+ */
+static void
+XmlTableDestroyContext(void *txpr)
+{
+#ifdef USE_LIBXML
+	XmlTableContext *xtCxt = (XmlTableContext *) txpr;
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDestroyContext called with invalid XmlTableContext");
+
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int			i;
+
+		for (i = 0; i < xtCxt->tupdesc->natts; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+	/* not valid anymore */
+	xtCxt->magic = 0;
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c6d69bc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -272,6 +272,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
 extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..3739f8d
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "access/tupdesc.h"
+#include "fmgr.h"
+
+/*
+ * TableExprRoutine holds function pointers used for generating content of
+ * table-expression functions, such as XMLTABLE.
+ *
+ * CreateContext creates the table builder context.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed as
+ * created by the executor scaffold.  The memory context being passed is an
+ * IMO bogus thing that shall be fixed by Pavel.
+ *
+ * SetContent is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * DeclareNamespace 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.
+ *
+ * DestroyContext 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	   *(*CreateContext) (TupleDesc tupdesc,
+									FmgrInfo *in_functions, Oid *typioparams,
+											MemoryContext per_rowset_memory);
+	void		(*SetContent) (void *txpr, Datum value);
+	void		(*DeclareNamespace) (void *txpr, char *name, char *uri);
+	void		(*SetRowFilter) (void *txpr, char *path);
+	void		(*SetColumnFilter) (void *txpr, char *path, int colnum);
+	bool		(*FetchRow) (void *txpr);
+	Datum		(*GetValue) (void *txpr, int colnum, bool *isnull);
+	void		(*DestroyContext) (void *txpr);
+} TableExprRoutine;
+
+#endif   /* TABLEEXPR_H */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8004d85..3955c15 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "executor/instrument.h"
+#include "executor/tableexpr.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
@@ -1011,6 +1012,37 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+/* ----------------
+ *		TableExprState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	TupleDesc	tupdesc;		/* cache */
+	int			ncols;			/* number of declared columns */
+	int			for_ordinality_col;		/* number of oridinality column,
+										 * started by 1 */
+	int			rownum;			/* row counter - for ordinality column */
+	ExprState  *row_expr;		/* row xpath expression */
+	ExprState  *doc_expr;		/* processed data */
+	ExprState **def_expr;		/* array of expressions for default value */
+	ExprState **col_expr;		/* array of expressions for path value */
+	bool	   *not_null;		/* for any column info if NULL is allowed or
+								 * not */
+	Datum	   *values;			/* prealloc buffer */
+	bool	   *nulls;			/* prealloc buffer */
+	FmgrInfo   *in_functions;	/* array of infunction for any column */
+	Oid		   *typioparams;	/* array of typIOParam for any column */
+	/* XXX move to better position */
+	const TableExprRoutine *routine;	/* pointers to builder functions */
+	void	   *builderCxt;		/* data for content builder */
+
+	MemoryContext per_rowset_memcxt;
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b6e5151 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -454,6 +457,7 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..2608562 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,27 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - raw column definition in table-expression functions
+ *		like XMLTABLE.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions.
+ *
+ * If for_ordinality is true (FOR ORDINALITY), then the column is an int4
+ * column and the rest of the fields are ignored.
+ */
+typedef struct TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	TypeName   *typeName;		/* type name of generated column */
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..61b2241 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,44 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - holds data for table-expression functions like XMLTABLE
+ *
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Node	   *row_expr;		/* row filter expression */
+	Node	   *document_expr;	/* processed data */
+	List	   *columns;		/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+} TableExpr;
+
+/*----------
+ * TableExprColumn - a column definition in table-expression functions.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions
+ * and just doesn't make sense here.
+ *
+ * Raw form of this node is TableExprRawCol node - different in
+ * typeName field. After transformation we use typid, typmod.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	Oid			typid;			/* typid of generated column */
+	int32		typmod;
+	Oid			collation;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..de4b8b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -441,10 +442,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..3c12df0 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,6 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern const TableExprRoutine XmlTableExprRoutine;
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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 c680216..720dbdf 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1,3 +1,13 @@
+XmlTableContext
+TableExprBuilder
+TableExprState
+TableExprRawCol
+TableExpr
+TableExprColumn
+SQLValueFunction
+max_parallel_hazard_context
+TriggerTransition
+SQLValueFunctionOp
 ABITVEC
 ACCESS_ALLOWED_ACE
 ACL_SIZE_INFORMATION
-- 
2.1.4

#84Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#83)
Re: patch: function xmltable

Hi

2016-12-02 23:25 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Here's version 17. I have made significant changes here.

1. Restructure the execQual code. Instead of a PG_TRY wrapper, I have
split this code in three pieces; there's the main code with the PG_TRY
wrappers and is mainly in charge of the builderCxt pointer. In the
previous coding there was a shim that examined builderCxt but was not
responsible for setting it up, which was ugly. The second part is the
"initializer" which sets the row and column filters and does namespace
processing. The third part is the "FetchRow" logic. It seems to me
much cleaner this way.

2. rename the "builder" stuff to use the "routine" terminology. This is
in line with what we do for other function-pointer-filled structs, such
as FdwRoutine, IndexAmRoutine etc. I also cleaned up the names a bit
more.

3. Added a magic number to the table builder context struct, so that we
can barf appropriately. This is in line with PgXmlErrorContext --
mostly for future-proofing. I didn't test this too hard. Also, moved
the XmlTableContext struct declaration nearer the top of the file, as is
customary. (We don't really need it that way, since the functions are
all declared taking void *, but it seems cleaner to me anyway).

4. I added, edited, and fixed a large number of code comments.

This is looking much better now, but it still needs at least the
following changes.

First, we need to fix is the per_rowset_memcxt thingy. I think the way
it's currently being used is rather ugly; it looks to me like the memory
context does not belong into the XmlTableContext struct at all.
Instead, the executor code should keep the memcxt pointer in a state
struct of its own, and it should be the executor's responsibility to
change to the appropriate context before calling the table builder
functions. In particular, this means that the table context can no
longer be a void * pointer; it needs to be a struct that's defined by
the executor (probably a primnodes.h one). The void * pointer is
stashed inside that struct. Also, the "routine" pointer should not be
part of the void * struct, but of the executor's struct. So the
execQual code can switch to the memory context, and destroy it
appropriately.

Second, we should make gram.y set a new "function type" value in the
TableExpr it creates, so that the downstream code (transformTableExpr,
ExecInitExpr, ruleutils.c) really knows that the given function is
XmlTableExpr, instead of guessing just because it's the only implemented
case. Probably this "function type" is an enum (currently with a single
value TableExprTypeXml or something like that) in primnodes.

It has sense - I was not sure about it - because currently it is only one
value, you mentioned it.

Finally, there's the pending task of renaming and moving
ExecTypeFromTableExpr to some better place. Not sure that moving it
back to nodeFuncs is a nice idea. Looks to me like calling it from
ExprTypmod is a rather ugly idea.

The code is related to prim nodes - it is used more times than in executor.

Hmm, ruleutils ... not sure what to think of this one.

it is little bit more complex - but it is related to complexity of XMLTABLE

The typedefs.list changes are just used to pgindent the affected code
correctly. It's not for commit.

The documentation is very precious. Nice

+    /* XXX OK to do this?  looks a bit out of place ... */
+    assign_record_type_typmod(typeInfo);

I am thinking it is ok. It is tupdesc without fixed typid, typname used in
returned value - you should to register this tupdesc in typcache.

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#85Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#84)
Re: patch: function xmltable

Pavel Stehule wrote:

2016-12-02 23:25 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

This is looking much better now, but it still needs at least the
following changes.

First, we need to fix is the per_rowset_memcxt thingy. I think the way
it's currently being used is rather ugly; it looks to me like the memory
context does not belong into the XmlTableContext struct at all.
Instead, the executor code should keep the memcxt pointer in a state
struct of its own, and it should be the executor's responsibility to
change to the appropriate context before calling the table builder
functions. In particular, this means that the table context can no
longer be a void * pointer; it needs to be a struct that's defined by
the executor (probably a primnodes.h one). The void * pointer is
stashed inside that struct. Also, the "routine" pointer should not be
part of the void * struct, but of the executor's struct. So the
execQual code can switch to the memory context, and destroy it
appropriately.

Second, we should make gram.y set a new "function type" value in the
TableExpr it creates, so that the downstream code (transformTableExpr,
ExecInitExpr, ruleutils.c) really knows that the given function is
XmlTableExpr, instead of guessing just because it's the only implemented
case. Probably this "function type" is an enum (currently with a single
value TableExprTypeXml or something like that) in primnodes.

It has sense - I was not sure about it - because currently it is only one
value, you mentioned it.

True. This is a minor point.

Are you able to do the memory context change I describe?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#86Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#85)
1 attachment(s)
Re: patch: function xmltable

2016-12-03 16:03 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2016-12-02 23:25 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

This is looking much better now, but it still needs at least the
following changes.

First, we need to fix is the per_rowset_memcxt thingy. I think the way
it's currently being used is rather ugly; it looks to me like the

memory

context does not belong into the XmlTableContext struct at all.
Instead, the executor code should keep the memcxt pointer in a state
struct of its own, and it should be the executor's responsibility to
change to the appropriate context before calling the table builder
functions. In particular, this means that the table context can no
longer be a void * pointer; it needs to be a struct that's defined by
the executor (probably a primnodes.h one). The void * pointer is
stashed inside that struct. Also, the "routine" pointer should not be
part of the void * struct, but of the executor's struct. So the
execQual code can switch to the memory context, and destroy it
appropriately.

Second, we should make gram.y set a new "function type" value in the
TableExpr it creates, so that the downstream code (transformTableExpr,
ExecInitExpr, ruleutils.c) really knows that the given function is
XmlTableExpr, instead of guessing just because it's the only

implemented

case. Probably this "function type" is an enum (currently with a

single

value TableExprTypeXml or something like that) in primnodes.

It has sense - I was not sure about it - because currently it is only one
value, you mentioned it.

True. This is a minor point.

Are you able to do the memory context change I describe?

I am not sure if I understand well to your ideas - please, check attached
patch.

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-18.patchtext/x-patch; charset=US-ASCII; name=xmltable-18.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index eca98df..d1c12a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10325,50 +10325,53 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
   <sect2 id="functions-xml-processing">
    <title>Processing XML</title>
 
-   <indexterm>
-    <primary>XPath</primary>
-   </indexterm>
-
    <para>
     To process values of data type <type>xml</type>, PostgreSQL offers
-    the functions <function>xpath</function> and
+    the functions <function>xpath</function>,
     <function>xpath_exists</function>, which evaluate XPath 1.0
-    expressions.
+    expressions, and the <function>XMLTABLE</function> predicate.
    </para>
 
+   <sect3 id="functions-xml-processing-xpath">
+    <title><literal>xpath</literal></title>
+
+    <indexterm>
+     <primary>XPath</primary>
+    </indexterm>
+
 <synopsis>
 <function>xpath</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath</function> evaluates the XPath
-    expression <replaceable>xpath</replaceable> (a <type>text</> value)
-    against the XML value
-    <replaceable>xml</replaceable>.  It returns an array of XML values
-    corresponding to the node set produced by the XPath expression.
-    If the XPath expression returns a scalar value rather than a node set,
-    a single-element array is returned.
-   </para>
+    <para>
+     The function <function>xpath</function> evaluates the XPath
+     expression <replaceable>xpath</replaceable> (a <type>text</> value)
+     against the XML value
+     <replaceable>xml</replaceable>.  It returns an array of XML values
+     corresponding to the node set produced by the XPath expression.
+     If the XPath expression returns a scalar value rather than a node set,
+     a single-element array is returned.
+    </para>
 
-  <para>
-    The second argument must be a well formed XML document. In particular,
-    it must have a single root node element.
-  </para>
+    <para>
+     The second argument must be a well formed XML document. In particular,
+     it must have a single root node element.
+    </para>
 
-   <para>
-    The optional third argument of the function is an array of namespace
-    mappings.  This array should be a two-dimensional <type>text</> array with
-    the length of the second axis being equal to 2 (i.e., it should be an
-    array of arrays, each of which consists of exactly 2 elements).
-    The first element of each array entry is the namespace name (alias), the
-    second the namespace URI. It is not required that aliases provided in
-    this array be the same as those being used in the XML document itself (in
-    other words, both in the XML document and in the <function>xpath</function>
-    function context, aliases are <emphasis>local</>).
-   </para>
+    <para>
+     The optional third argument of the function is an array of namespace
+     mappings.  This array should be a two-dimensional <type>text</> array with
+     the length of the second axis being equal to 2 (i.e., it should be an
+     array of arrays, each of which consists of exactly 2 elements).
+     The first element of each array entry is the namespace name (alias), the
+     second the namespace URI. It is not required that aliases provided in
+     this array be the same as those being used in the XML document itself (in
+     other words, both in the XML document and in the <function>xpath</function>
+     function context, aliases are <emphasis>local</>).
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
              ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10378,10 +10381,10 @@ SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
 
-   <para>
-    To deal with default (anonymous) namespaces, do something like this:
+    <para>
+     To deal with default (anonymous) namespaces, do something like this:
 <screen><![CDATA[
 SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
              ARRAY[ARRAY['mydefns', 'http://example.com']]);
@@ -10391,27 +10394,31 @@ SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
+   </sect3>
 
-   <indexterm>
-    <primary>xpath_exists</primary>
-   </indexterm>
+   <sect3 id="functions-xml-processing-xpath-exists">
+    <title><literal>xpath_exists</literal></title>
+
+    <indexterm>
+     <primary>xpath_exists</primary>
+    </indexterm>
 
 <synopsis>
 <function>xpath_exists</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath_exists</function> is a specialized form
-    of the <function>xpath</function> function.  Instead of returning the
-    individual XML values that satisfy the XPath, this function returns a
-    Boolean indicating whether the query was satisfied or not.  This
-    function is equivalent to the standard <literal>XMLEXISTS</> predicate,
-    except that it also offers support for a namespace mapping argument.
-   </para>
+    <para>
+     The function <function>xpath_exists</function> is a specialized form
+     of the <function>xpath</function> function.  Instead of returning the
+     individual XML values that satisfy the XPath, this function returns a
+     Boolean indicating whether the query was satisfied or not.  This
+     function is equivalent to the standard <literal>XMLEXISTS</> predicate,
+     except that it also offers support for a namespace mapping argument.
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
                      ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10421,7 +10428,278 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
  t
 (1 row)
 ]]></screen>
+    </para>
+   </sect3>
+
+   <sect3 id="functions-xml-processing-xmltable">
+    <title><literal>xmltable</literal></title>
+
+    <indexterm>
+     <primary>xmltable</primary>
+    </indexterm>
+
+<synopsis>
+<function>xmltable</function>(
+    <optional>xmlnamespaces(<replaceable>namespace uri</replaceable> AS <replaceable>namespace name</replaceable><optional>, ...</optional>)</optional>
+    <replaceable>rowexpr</replaceable> PASSING <optional>BY REF</optional> <replaceable>xml</replaceable> <optional>BY REF</optional>
+    <optional>COLUMNS <replaceable>name</replaceable> <replaceable>type</replaceable>
+        <optional>PATH <replaceable>columnexpr</replaceable></optional>
+        <optional>DEFAULT <replaceable>expr</replaceable></optional>
+        <optional>NOT NULL | NULL</optional> <optional>, ...</optional></optional>
+)
+</synopsis>
+
+    <para>
+     The <function>xmltable</function> produces a table based on the
+     passed XML value, an xpath filter to extract rows, and an
+     optional set of column definitions.
+    </para>
+
+    <para>
+     For XML document:
+  <screen><![CDATA[
+<ROWS>
+  <ROW id="1">
+    <COUNTRY_ID>AU</COUNTRY_ID>
+    <COUNTRY_NAME>Australia</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="2">
+    <COUNTRY_ID>CN</COUNTRY_ID>
+    <COUNTRY_NAME>China</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="3">
+    <COUNTRY_ID>HK</COUNTRY_ID>
+    <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="4">
+    <COUNTRY_ID>IN</COUNTRY_ID>
+    <COUNTRY_NAME>India</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="5">
+    <COUNTRY_ID>JP</COUNTRY_ID>
+    <COUNTRY_NAME>Japan</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+  </ROW>
+  <ROW id="6">
+    <COUNTRY_ID>SG</COUNTRY_ID>
+    <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+  </ROW>
+</ROWS>
+]]></screen>
+
+     the following query produces the result:
+
+<screen><![CDATA[
+SELECT  xmltable.*
+  FROM (SELECT data FROM xmldata) x,
+        LATERAL xmltable('//ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  country_name text PATH 'COUNTRY_NAME',
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE[@unit = "km"]/text()',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+ id | country_name | country_id | region_id | size | unit | premier_name  
+----+--------------+------------+-----------+------+------+---------------
+  1 | Australia    | AU         |         3 |      |      | not specified
+  2 | China        | CN         |         3 |      |      | not specified
+  3 | HongKong     | HK         |         3 |      |      | not specified
+  4 | India        | IN         |         3 |      |      | not specified
+  5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 | Singapore    | SG         |         3 |  791 | km   | not specified
+]]></screen>
+    </para>
+
+    <para>
+     The optional <literal>xmlnamespaces</> clause is a comma-separated list of
+     namespaces of the form <replaceable>namespace-URI</> <literal>AS</>
+     <replaceable>namespace-name</>. It specifies the XML namespaces used in
+     the document and their aliases. The default namespace is not supported
+     yet.
+
+<screen><![CDATA[
+SELECT *
+  FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS ns)
+                '/ns:rows/ns:row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                COLUMNS a int PATH 'a');
+
+ a  
+----
+ 10
+]]></screen>
    </para>
+
+    <para>
+     The required <replaceable>rowexpr</> argument is an xpath
+     expression that is evaluated against the supplied XML document to
+     obtain an ordered sequence of XML nodes. This sequence is what
+     <function>xmltable</> transforms into output rows.
+    </para>
+
+    <note>
+     <para>
+      The SQL/XML standard specifies the the
+      <function>xmltable</function> construct should take an XQuery
+      expression as the first argument, but PostgreSQL currently only
+      supports XPath, which is a subset of XQuery.
+     </para>
+    </note>
+
+    <para>
+     The <literal>PASSING</> <replaceable>xml</> clause provides the
+     XML document to operate on.
+     The <literal>BY REF</literal> clauses have no effect in
+     PostgreSQL, but are allowed for SQL conformance and compatibility
+     with other implementations. Per the SQL/XML standard, the first
+     <literal>BY REF</literal> is required, the second is optional.
+     Passing <literal>BY VALUE</> is not supported. Multiple
+     comma-separated terms in the PASSING clause are not supported.
+     <literal>AS</> aliases are not supported. The argument must be
+     a well-formed XML document; fragments/forests are not accepted.
+    </para>
+
+    <para>
+     The optional <literal>COLUMNS</literal> clause specifies the list
+     of colums in the generated table. If the whole
+     <literal>COLUMNS</> list is omitted, then the result set is a
+     single column where each row is a field of <literal>xml</> type
+     containing the data matched by the <replaceable>rowexpr</>.
+     Otherwise each entry describes a single column. See the syntax
+     summary above for the format. The column name and type are
+     required; the path and not-null specifier are optional.
+ <screen><![CDATA[
+SELECT *
+  FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y' AS ns)
+                '/ns:rows/ns:row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>');
+
+           xmltable           
+------------------------------
+ <a xmlns="http://x.y">10</a>
+]]></screen>
+    </para>
+
+    <note>
+     <para>
+      Column names and types are identifiers so unlike normal
+      function arguments they may not be specified as bind parameters,
+      column references, or expressions. The path and default are
+      ordinary values.
+     </para>
+    </note>
+
+    <para>
+     The <literal>PATH</> for a column is an xpath expression that is
+     evaluated for each row, relative to the result of the
+     <replaceable>rowexpr</>, to find the value of the column.
+     If no <literal>PATH</> is given then the column name is used as an
+     implicit path.
+    </para>
+
+    <para>
+     The text body of the XML matched by the <literal>PATH</> expression is
+     used as the column value. Multiple <literal>text()</literal> nodes within
+     an element are concatenated in order. Any child elements, processing
+     instructions, and comments are ignored, but the text contents of child
+     elements are concatenated to the result:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--x-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+element
+-----------------
+ a1aa2a   bbbbcccc
+ (1 row)
+]]></programlisting>
+     Note that the whitespace-only <literal>text()</> node between two non-text
+     elements is preserved, and that leading whitespace on a <literal>text()</>
+     node is not flattened.
+    </para>
+    
+    <para>
+     A <literal>PATH</> expression that matches multiple top-level elements
+     results in an error:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>1</element><element>2</element></root>' COLUMNS element text);
+ERROR:  more than one value returned by column XPath expression
+]]></programlisting>
+     This applies to <literal>text()</> nodes too, so avoid using explicit text nodes
+     in your path expressions unless this behaviour is desired:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a</element></root>' COLUMNS element text PATH 'element/text()');
+ERROR:  more than one value returned by column XPath expression
+]]></programlisting>
+     If the <literal>PATH</> matches an empty tag the result is an empty string
+     (not <literal>NULL</>).  Any <literal>xsi:nil</> attributes are ignored.
+    </para>
+
+    <para>
+     The five predefined XML entities <literal>&apos;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 743e7d6..600974c 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -43,6 +43,7 @@
 #include "catalog/pg_type.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSubplan.h"
+#include "executor/tableexpr.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -189,7 +190,23 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
-
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum value);
+static TableExprBuilder tabexpr_CreateBuilder(TableExprState *tstate);
+static void tabexpr_SetContent(TableExprState *tstate, Datum value);
+static void tabexpr_DeclareNamespace(TableExprState *tstate,
+				  char *name, char *uri);
+static void tabexpr_SetRowFilter(TableExprState *tstate, char *filter);
+static void tabexpr_SetColumnFilter(TableExprState *tstate, char *filter,
+					  int colnum);
+static bool tabexpr_FetchRow(TableExprState *tstate);
+static Datum tabexpr_GetValue(TableExprState *tstate, int colnum, bool *isnull);
+static void tabexpr_DestroyBuilder(TableExprState *tstate);
 
 /* ----------------------------------------------------------------
  *		ExecEvalExpr routines
@@ -4500,6 +4517,353 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	Datum		result;
+
+	PG_TRY();
+	{
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->builder == NULL)
+		{
+			Datum		value;
+
+			/* Create table expression builder */
+			tstate->builder = tabexpr_CreateBuilder(tstate);
+
+			/*
+			 * If evaluating the document expression returns NULL, the table
+			 * expression is empty and we return immediately.
+			 */
+			value = ExecEvalExpr(tstate->doc_expr, econtext, isNull, NULL);
+			if (*isNull)
+			{
+				*isDone = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any error are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->builder != NULL)
+		{
+			tabexpr_DestroyBuilder(tstate);
+			tstate->builder = NULL;
+		}
+
+		MemoryContextDelete(tstate->buildercxt);
+		tstate->buildercxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum value)
+{
+	ListCell   *cell;
+	bool		isnull;
+	int			i;
+
+	/*
+	 * 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
+	 */
+	tabexpr_SetContent(tstate, value);
+
+	/* Evaluate namespace specifications */
+	foreach(cell, tstate->namespaces)
+	{
+		Node	   *n = (Node *) lfirst(cell);
+		ExprState  *expr;
+		char	   *ns_name;
+		char	   *ns_uri;
+
+		if (IsA(n, NamedArgExpr))
+		{
+			NamedArgExpr *na = (NamedArgExpr *) n;
+
+			expr = (ExprState *) na->arg;
+			ns_name = na->name;
+		}
+		else
+		{
+			expr = (ExprState *) n;
+			ns_name = NULL;
+		}
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		tabexpr_DeclareNamespace(tstate, ns_name, ns_uri);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->row_expr, econtext, &isnull, NULL);
+	if (isnull)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("row filter expression must not be null")));
+
+	tabexpr_SetRowFilter(tstate, TextDatumGetCString(value));
+
+	/*
+	 * Install the column filter expressions into the table builder context.
+	 * If an expression is given, use that; otherwise the column name itself
+	 * is the column filter.
+	 */
+	for (i = 0; i < tstate->ncols; i++)
+	{
+		char	   *col_filter;
+
+		if (tstate->col_expr[i] != NULL)
+		{
+			value = ExecEvalExpr(tstate->col_expr[i], econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("column filter expression must not be null"),
+						 errdetail("Filter for column \"%s\" is null.",
+								   NameStr(tstate->tupdesc->attrs[i]->attname))));
+			col_filter = TextDatumGetCString(value);
+		}
+		else
+			col_filter = NameStr(tstate->tupdesc->attrs[i]->attname);
+
+		tabexpr_SetColumnFilter(tstate, col_filter, i);
+	}
+}
+
+/*
+ * Fetch one more row from a TableExpr table builder.
+ */
+static Datum
+tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone)
+{
+	Datum		result;
+
+	/* Prepare one more row */
+	if (tabexpr_FetchRow(tstate))
+	{
+		TupleDesc	tupdesc = tstate->tupdesc;
+		Datum	   *values = tstate->values;
+		bool	   *nulls = tstate->nulls;
+		HeapTuple	tuple;
+		HeapTupleHeader dtuple;
+		int			i;
+		bool		isnull;
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			if (i + 1 == tstate->for_ordinality_col)
+			{
+				values[i] = Int32GetDatum(tstate->rownum++);
+				nulls[i] = false;
+			}
+			else
+			{
+				values[i] = tabexpr_GetValue(tstate, i, &isnull);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tupdesc->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+		}
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(dtuple, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type previously identified
+		 */
+		HeapTupleHeaderSetTypeId(dtuple, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(dtuple, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+
+		*isNull = false;
+		*isDone = ExprMultipleResult;
+
+		result = HeapTupleHeaderGetDatum(dtuple);
+	}
+	else
+	{
+		/* no more rows */
+		tabexpr_DestroyBuilder(tstate);
+		tstate->builder = NULL;
+
+		/* make sure all memory is released */
+		MemoryContextReset(tstate->buildercxt);
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	return result;
+}
+
+/*
+ * Switch to builder memory context and evaluate CreateBuilder routine
+ */
+static TableExprBuilder
+tabexpr_CreateBuilder(TableExprState *tstate)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+	TableExprBuilder	result;
+
+	result = routine->CreateBuilder(tstate->tupdesc,
+									  tstate->in_functions,
+									  tstate->typioparams);
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
+
+/*
+ * Switch to builder memory context and evaluate SetContent routine
+ */
+static void
+tabexpr_SetContent(TableExprState *tstate, Datum value)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+
+	routine->SetContent(tstate->builder, value);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Switch to builder memory context and evaluate DeclareNamespace routine
+ */
+static void
+tabexpr_DeclareNamespace(TableExprState *tstate, char *name, char *uri)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+
+	routine->DeclareNamespace(tstate->builder, name, uri);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Switch to builder memory context and evaluate SetRowFilter routine
+ */
+static void
+tabexpr_SetRowFilter(TableExprState *tstate, char *filter)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+
+	routine->SetRowFilter(tstate->builder, filter);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Switch to builder memory context and evaluate SetColumnFilter routine
+ */
+static void
+tabexpr_SetColumnFilter(TableExprState *tstate, char *filter,
+					  int colnum)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+
+	routine->SetColumnFilter(tstate->builder, filter, colnum);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Switch to builder memory context and evaluate FetchRow routine
+ */
+static bool
+tabexpr_FetchRow(TableExprState *tstate)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+	bool		result;
+
+	result = routine->FetchRow(tstate->builder);
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
+
+/*
+ * Switch to builder memory context and evaluate GetValue routine
+ */
+static Datum
+tabexpr_GetValue(TableExprState *tstate, int colnum, bool *isnull)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+
+	result = routine->GetValue(tstate->builder, colnum, isnull);
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
+
+/*
+ * Switch to builder memory context and evaluate DestroyBuilder routine
+ */
+static void
+tabexpr_DestroyBuilder(TableExprState *tstate)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+
+	routine->DestroyBuilder(tstate->builder);
+	MemoryContextSwitchTo(oldcxt);
+}
+
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5626,110 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int			ncols;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->builder = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				/* XXX this assumes the anon record type has been registered */
+				tstate->tupdesc =
+					lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+												exprTypmod((Node *) te));
+				Assert(tstate->tupdesc->natts > 0);
+
+				ncols = tstate->tupdesc->natts;
+				tstate->values = palloc0(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid			in_funcid;
+
+					getTypeInputInfo(tstate->tupdesc->attrs[i]->atttypid,
+									 &in_funcid, &tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_expr = ExecInitExpr((Expr *) te->row_expr, parent);
+				tstate->doc_expr = ExecInitExpr((Expr *) te->document_expr, parent);
+
+				if (te->columns)
+				{
+					ListCell   *col;
+
+					Assert(ncols == list_length(te->columns));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+					tstate->rownum = 1;
+
+					i = 0;
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+															   parent);
+							tstate->col_expr[i] = ExecInitExpr((Expr *) tec->column_expr,
+															   parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->buildercxt = AllocSetContextCreate(CurrentMemoryContext,
+											 "TableExpr builder context",
+													 ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..8c722ed 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,48 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+	TupleDesc	typeInfo;
+
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		int			i = 1;
+
+		typeInfo = CreateTemplateTupleDesc(list_length(te->columns), false);
+
+		foreach(col, te->columns)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(typeInfo,
+							   (AttrNumber) i,
+							   pstrdup(tec->colname),
+							   tec->typid,
+							   tec->typmod,
+							   0);
+			i++;
+		}
+	}
+	else
+	{
+		typeInfo = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(typeInfo, (AttrNumber) 1,
+						   "xmltable", XMLOID, -1, 0);
+	}
+
+	/* XXX OK to do this?  looks a bit out of place ... */
+	assign_record_type_typmod(typeInfo);
+
+	return typeInfo;
+}
+
+/*
  * ExecTypeSetColNames - set column names in a TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..36b4f26 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(row_expr);
+	COPY_NODE_FIELD(document_expr);
+	COPY_NODE_FIELD(columns);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4600,6 +4657,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5104,6 +5167,9 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..6971086 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2615,6 +2615,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2645,6 +2675,18 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(row_expr);
+	COMPARE_NODE_FIELD(document_expr);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2910,6 +2952,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3401,6 +3449,9 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 20e2dbd..592760f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -17,6 +17,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "executor/executor.h"
 #include "fmgr.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..6db3ccf 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +258,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +499,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate a blessed tupdesc from the column definitions, and
+				 * returns its typmod.
+				 */
+				TupleDesc	tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +747,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 +951,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1155,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1587,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2249,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->column_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3069,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_expr, te->row_expr, Node *);
+				MUTATE(newnode->document_expr, te->document_expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->columns, te->columns, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->column_expr, tec->column_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3708,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..d5d5e81 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_NODE_FIELD(row_expr);
+	WRITE_NODE_FIELD(document_expr);
+	WRITE_NODE_FIELD(columns);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3539,6 +3581,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..ed71fc6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,44 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(row_expr);
+	READ_NODE_FIELD(document_expr);
+	READ_NODE_FIELD(columns);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(column_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2535,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9af29dd..5d5fd41 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -829,6 +829,12 @@ expression_returns_set_rows_walker(Node *node, double *count)
 			*count *= get_func_rows(expr->opfuncid);
 		}
 	}
+	if (IsA(node, TableExpr))
+	{
+		/* we have not any method how to estimate it, use default */
+		*count = 1000;
+		return false;
+	}
 
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
@@ -3598,6 +3604,33 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (tec->column_expr != NULL || tec->default_expr != NULL)
+				{
+					TableExprColumn *newtec = makeNode(TableExprColumn);
+
+					newtec->colname = tec->colname;
+					newtec->typid = tec->typid;
+					newtec->typmod = tec->typmod;
+					newtec->collation = tec->collation;
+					newtec->for_ordinality = tec->for_ordinality;
+					newtec->is_not_null = tec->is_not_null;
+					newtec->column_expr =
+						eval_const_expressions_mutator((Node *) tec->column_expr,
+													   context);
+					newtec->default_expr =
+						eval_const_expressions_mutator((Node *) tec->default_expr,
+													   context);
+					newtec->location = tec->location;
+
+					return (Node *) newtec;
+				}
+			}
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d6274b4..91f8a9a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -545,6 +545,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	XmlTableExprColList XmlTableExprColOptions
+%type <node>	XmlTableExprCol
+%type <defelt>	XmlTableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -576,10 +583,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
-	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
-	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-	CROSS CSV CUBE CURRENT_P
+	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS
+	COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION
+	CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST
+	CREATE CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -649,8 +656,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
-	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
-	XMLPI XMLROOT XMLSERIALIZE
+	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
+	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
 
 	YEAR_P YES_P
 
@@ -12588,6 +12595,168 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+XmlTableExprColList: XmlTableExprCol						{ $$ = list_make1($1); }
+			| XmlTableExprColList ',' XmlTableExprCol		{ $$ = lappend($1, $3); }
+		;
+
+XmlTableExprCol:
+			ColId Typename IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $3;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+			| ColId Typename XmlTableExprColOptions IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $4;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->column_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->column_expr = defel->arg;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+XmlTableExprColOptions: XmlTableExprColOption				{ $$ = list_make1($1); }
+			| XmlTableExprColOptions XmlTableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+XmlTableExprColOption:
+			IDENT b_expr
+				{
+					$$ = makeDefElem($1, $2, @1);
+				}
+			| DEFAULT b_expr
+				{
+					$$ = makeDefElem("default", $2, @1);
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			b_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT b_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
 		;
 
 /*
@@ -13696,6 +13865,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13997,10 +14167,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..06739f6 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1095,9 +1095,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1105,9 +1105,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1116,7 +1116,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1143,6 +1143,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 17d1cbf..6551075 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2764,6 +2770,163 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 	return (Node *) newc;
 }
 
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	const char *constructName;
+	Oid			exprType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	exprType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, te->location);
+
+	/* Transform the row-generating Xpath expression ... */
+	Assert(te->row_expr != NULL);
+	newte->row_expr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_expr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML itself */
+	Assert(te->document_expr != NULL);
+	newte->document_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, te->document_expr),
+												   exprType,
+												   constructName);
+
+	/* Columns, if any, also need to be transformed */
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		bool		for_ordinality = false;
+		char	  **colnames;
+		int			i = 0;
+
+		colnames = palloc(sizeof(char *) * list_length(te->columns));
+
+		/*
+		 * For each TableExprRawCol in the input TableExpr, generate one
+		 * TableExprColumn in the output TableExpr.
+		 */
+		foreach(col, te->columns)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc;
+			int			j;
+
+			/* Only one FOR ORDINALITY column is allowed; verify */
+			if (rawc->for_ordinality)
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+			}
+
+			newc = makeNode(TableExprColumn);
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				newc->typid = INT4OID;
+				newc->typmod = -1;
+			}
+			else
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &newc->typid, &newc->typmod);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->column_expr)
+				newc->column_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, rawc->column_expr),
+															TEXTOID,
+															constructName);
+			if (rawc->default_expr)
+				newc->default_expr = coerce_to_specific_type_typmod(pstate,
+							transformExprRecurse(pstate, rawc->default_expr),
+												   newc->typid, newc->typmod,
+															  constructName);
+
+
+			newte->columns = lappend(newte->columns, newc);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(colnames[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			colnames[i] = rawc->colname;
+
+			i++;
+		}
+
+		pfree(colnames);
+	}
+	else
+		newte->columns = NIL;
+
+	/* Namespaces, if any, also need to be transformed */
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the namespace \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(nsnames);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
 /*
  * Transform a "row compare-op row" construct
  *
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index d440dec..d59d83f 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,14 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+
+			/*
+			 * Make TableExpr act like a regular function. Only XMLTABLE expr
+			 * is supported in this moment.
+			 */
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..ef12407 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8306,6 +8307,105 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/*
+				 * Deparse TableExpr - the is only one TableExpr producer, the
+				 * function XMLTABLE.
+				 */
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_expr, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->document_expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->columns != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->column_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->column_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..a4b20f3 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,51 @@ 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;
+	TupleDesc	tupdesc;
+	MemoryContext per_rowset_memcxt;
+	char	   *def_namespace_name;
+	FmgrInfo   *in_functions;
+	Oid		   *typioparams;
+	long int	rc;
+	PgXmlErrorContext *xmlerrcxt;
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr	doc;
+	xmlXPathContextPtr xpathcxt;
+	xmlXPathCompExprPtr xpathcomp;
+	xmlXPathObjectPtr xpathobj;
+	xmlXPathCompExprPtr *xpathscomp;
+} XmlTableBuilderData;
+#endif
+
+static TableExprBuilder XmlTableCreateBuilder(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparms);
+static void XmlTableSetContent(TableExprBuilder tebuilder, Datum value);
+static void XmlTableDeclareNamespace(TableExprBuilder tebuilder, char *name, char *uri);
+static void XmlTableSetRowFilter(TableExprBuilder tebuilder, char *path);
+static void XmlTableSetColumnFilter(TableExprBuilder tebuilder, char *path, int colnum);
+static bool XmlTableFetchRow(TableExprBuilder tebuilder);
+static Datum XmlTableGetValue(TableExprBuilder tebuilder, int colnum, bool *isnull);
+static void XmlTableDestroyBuilder(TableExprBuilder tebuilder);
+
+const TableExprRoutine XmlTableExprRoutine =
+{
+	XmlTableCreateBuilder,
+	XmlTableSetContent,
+	XmlTableDeclareNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyBuilder
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4111,568 @@ 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
+
+/*
+ * Create XmlTableBuilderData for XmlTable builder. Initialize XML parser.
+ */
+static TableExprBuilder
+XmlTableCreateBuilder(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparams)
+{
+#ifdef USE_LIBXML
+	TableExprBuilder result;
+	XmlTableBuilderData *xtCxt = NULL;
+	PgXmlErrorContext *xmlerrcxt = NULL;
+	volatile xmlParserCtxtPtr ctxt = NULL;
+
+	xtCxt = palloc0(sizeof(XmlTableBuilderData));
+	xtCxt->magic = XMLTABLE_CONTEXT_MAGIC;
+	xtCxt->tupdesc = tupdesc;
+	xtCxt->in_functions = in_functions;
+	xtCxt->typioparams = typioparams;
+	xtCxt->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * tupdesc->natts);
+
+	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+	PG_TRY();
+	{
+		xmlInitParser();
+
+		ctxt = xmlNewParserCtxt();
+		if (ctxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate parser context");
+	}
+	PG_CATCH();
+	{
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xtCxt->xmlerrcxt = xmlerrcxt;
+	xtCxt->ctxt = ctxt;
+
+	result = palloc(sizeof(TableExprBuilderData));
+	result->private_data = xtCxt;
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(TableExprBuilder tebuilder, Datum value)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	xmltype    *xmlval = DatumGetXmlP(value);
+	xmlChar    *xstr;
+	int			length;
+	volatile xmlDocPtr doc = NULL;
+	volatile xmlXPathContextPtr xpathcxt = NULL;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetContent called with invalid XmlTableBuilderData");
+
+	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 */
+}
+
+/*
+ * XmlTableDeclareNamespace.  Add a namespace declaration.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableDeclareNamespace(TableExprBuilder tebuilder, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid XmlTableBuilderData");
+
+	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(TableExprBuilder tebuilder, char *path)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	xmlChar    *xstr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetRowFilter called with invalid XmlTableBuilderData");
+
+	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
+ */
+static void
+XmlTableSetColumnFilter(TableExprBuilder tebuilder, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	xmlChar    *xstr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid XmlTableBuilderData");
+
+	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
+	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(TableExprBuilder tebuilder)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableFetchRow called with invalid XmlTableBuilderData");
+
+	/*
+	 * 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->rc = 0;
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+}
+
+/*
+ * Apply column path on current node and returns result.
+ * Allows multivalue when expected type is XML.
+ * When COLUMNS part was not entered, returns current node.
+ */
+static Datum
+XmlTableGetValue(TableExprBuilder tebuilder, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableGetValue called with invalid XmlTableBuilderData");
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+	if (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = NULL;
+
+		/* fast leaving */
+		if (cur->type != XML_ELEMENT_NODE)
+			elog(ERROR, "unexpected xmlNode type");
+
+		PG_TRY();
+		{
+			/* Set current node as entry point for XPath evaluation */
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+			/* Evaluate column path */
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+			if (column_xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+				xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+							"could not create XPath object");
+
+			if (column_xpathobj->type == XPATH_NODESET)
+			{
+				int			count;
+
+				if (column_xpathobj->nodesetval != NULL)
+					count = column_xpathobj->nodesetval->nodeNr;
+				else
+					count = 0;
+
+				if (count > 0)
+				{
+					Oid			targettypid = xtCxt->tupdesc->attrs[colnum]->atttypid;
+
+					if (count == 1)
+					{
+						if (targettypid != XMLOID)
+						{
+							xmlChar    *str;
+
+							str = xmlNodeListGetString(xtCxt->doc,
+													   column_xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+													   1);
+
+							if (str != NULL)
+							{
+								PG_TRY();
+								{
+									/*
+									 * Copy string to PostgreSQL controlled
+									 * memory
+									 */
+									cstr = pstrdup((char *) str);
+								}
+								PG_CATCH();
+								{
+									xmlFree(str);
+									PG_RE_THROW();
+								}
+								PG_END_TRY();
+
+								xmlFree(str);
+							}
+							else
+							{
+								/* Return empty string when tag is empty */
+								cstr = pstrdup("");
+							}
+						}
+						else
+						{
+							/* simple case, result is one value */
+							cstr = xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[0],
+													xtCxt->xmlerrcxt);
+						}
+					}
+					else
+					{
+						StringInfoData str;
+						int			i;
+
+						/*
+						 * When result is not one value, then we can work with
+						 * concated values. But it requires XML as expected
+						 * type.
+						 */
+						if (targettypid != XMLOID)
+							ereport(ERROR,
+									(errcode(ERRCODE_CARDINALITY_VIOLATION),
+									 errmsg("more than one value returned by column XPath expression")));
+
+						/* Concate serialized values */
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+												   xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														  xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[colnum],
+											   cstr,
+											   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[colnum],
+										 (char *) column_xpathobj->stringval,
+										   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+				*isnull = false;
+			}
+			else
+				elog(ERROR, "unexpected XPath object type");
+
+			xmlXPathFreeObject(column_xpathobj);
+			column_xpathobj = NULL;
+		}
+		PG_CATCH();
+		{
+			if (column_xpathobj != NULL)
+				xmlXPathFreeObject(column_xpathobj);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+	else
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+		result = InputFunctionCall(&xtCxt->in_functions[0],
+								   cstr,
+								   xtCxt->typioparams[0],
+								   -1); /* target type is XML always */
+		*isnull = false;
+	}
+
+	if (cstr != NULL)
+		pfree(cstr);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * Release all libxml2 memory and related resources
+ */
+static void
+XmlTableDestroyBuilder(TableExprBuilder tebuilder)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDestroyContext called with invalid XmlTableBuilderData");
+
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int			i;
+
+		for (i = 0; i < xtCxt->tupdesc->natts; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+	/* not valid anymore */
+	xtCxt->magic = 0;
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c6d69bc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -272,6 +272,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
 extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..6516a33
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,73 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "access/tupdesc.h"
+#include "fmgr.h"
+
+/*
+ * TableExprRoutine holds function pointers used for generating content of
+ * table-expression functions, such as XMLTABLE.
+ *
+ * CreateContext creates the table builder context.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed as
+ * created by the executor scaffold.  The memory context being passed is an
+ * IMO bogus thing that shall be fixed by Pavel.
+ *
+ * SetContent is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * DeclareNamespace 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.
+ *
+ * DestroyContext 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 TableExprBuilderData
+{
+	void	   *private_data;	/* Opaque data used by table builder */
+} TableExprBuilderData;
+
+typedef struct TableExprBuilderData *TableExprBuilder;
+
+typedef struct TableExprRoutine
+{
+	TableExprBuilder	(*CreateBuilder) (TupleDesc tupdesc,
+									FmgrInfo *in_functions, Oid *typioparams);
+	void		(*SetContent) (TableExprBuilder tebuilder, Datum value);
+	void		(*DeclareNamespace) (TableExprBuilder tebuilder, char *name, char *uri);
+	void		(*SetRowFilter) (TableExprBuilder tebuilder, char *path);
+	void		(*SetColumnFilter) (TableExprBuilder tebuilder, char *path, int colnum);
+	bool		(*FetchRow) (TableExprBuilder tebuilder);
+	Datum		(*GetValue) (TableExprBuilder tebuilder, int colnum, bool *isnull);
+	void		(*DestroyBuilder) (TableExprBuilder tebuilder);
+} TableExprRoutine;
+
+#endif   /* TABLEEXPR_H */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8004d85..49cff48 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "executor/instrument.h"
+#include "executor/tableexpr.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
@@ -1011,6 +1012,36 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+/* ----------------
+ *		TableExprState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	TupleDesc	tupdesc;		/* cache */
+	int			ncols;			/* number of declared columns */
+	int			for_ordinality_col;		/* number of oridinality column,
+										 * started by 1 */
+	int			rownum;			/* row counter - for ordinality column */
+	ExprState  *row_expr;		/* row xpath expression */
+	ExprState  *doc_expr;		/* processed data */
+	ExprState **def_expr;		/* array of expressions for default value */
+	ExprState **col_expr;		/* array of expressions for path value */
+	bool	   *not_null;		/* for any column info if NULL is allowed or
+								 * not */
+	Datum	   *values;			/* prealloc buffer */
+	bool	   *nulls;			/* prealloc buffer */
+	FmgrInfo   *in_functions;	/* array of infunction for any column */
+	Oid		   *typioparams;	/* array of typIOParam for any column */
+	/* XXX move to better position */
+	const TableExprRoutine *routine;	/* pointers to builder functions */
+	TableExprBuilder	builder;		/* data for content builder */
+	MemoryContext buildercxt;		/* memory context used by builder */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b6e5151 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -454,6 +457,7 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..2608562 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,27 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - raw column definition in table-expression functions
+ *		like XMLTABLE.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions.
+ *
+ * If for_ordinality is true (FOR ORDINALITY), then the column is an int4
+ * column and the rest of the fields are ignored.
+ */
+typedef struct TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	TypeName   *typeName;		/* type name of generated column */
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..61b2241 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,44 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - holds data for table-expression functions like XMLTABLE
+ *
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Node	   *row_expr;		/* row filter expression */
+	Node	   *document_expr;	/* processed data */
+	List	   *columns;		/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+} TableExpr;
+
+/*----------
+ * TableExprColumn - a column definition in table-expression functions.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions
+ * and just doesn't make sense here.
+ *
+ * Raw form of this node is TableExprRawCol node - different in
+ * typeName field. After transformation we use typid, typmod.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	Oid			typid;			/* typid of generated column */
+	int32		typmod;
+	Oid			collation;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..de4b8b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -441,10 +442,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..3c12df0 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,6 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern const TableExprRoutine XmlTableExprRoutine;
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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 c680216..720dbdf 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1,3 +1,13 @@
+XmlTableContext
+TableExprBuilder
+TableExprState
+TableExprRawCol
+TableExpr
+TableExprColumn
+SQLValueFunction
+max_parallel_hazard_context
+TriggerTransition
+SQLValueFunctionOp
 ABITVEC
 ACCESS_ALLOWED_ACE
 ACL_SIZE_INFORMATION
#87Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#86)
1 attachment(s)
Re: patch: function xmltable

2016-12-04 23:00 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2016-12-03 16:03 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2016-12-02 23:25 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

This is looking much better now, but it still needs at least the
following changes.

First, we need to fix is the per_rowset_memcxt thingy. I think the

way

it's currently being used is rather ugly; it looks to me like the

memory

context does not belong into the XmlTableContext struct at all.
Instead, the executor code should keep the memcxt pointer in a state
struct of its own, and it should be the executor's responsibility to
change to the appropriate context before calling the table builder
functions. In particular, this means that the table context can no
longer be a void * pointer; it needs to be a struct that's defined by
the executor (probably a primnodes.h one). The void * pointer is
stashed inside that struct. Also, the "routine" pointer should not be
part of the void * struct, but of the executor's struct. So the
execQual code can switch to the memory context, and destroy it
appropriately.

Second, we should make gram.y set a new "function type" value in the
TableExpr it creates, so that the downstream code (transformTableExpr,
ExecInitExpr, ruleutils.c) really knows that the given function is
XmlTableExpr, instead of guessing just because it's the only

implemented

case. Probably this "function type" is an enum (currently with a

single

value TableExprTypeXml or something like that) in primnodes.

It has sense - I was not sure about it - because currently it is only

one

value, you mentioned it.

True. This is a minor point.

Are you able to do the memory context change I describe?

I am not sure if I understand well to your ideas - please, check attached
patch.

attached patch without your patch 0001

Regards

Pavel

Show quoted text

Regards

Pavel

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-19.patchtext/x-patch; charset=US-ASCII; name=xmltable-19.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 43db430..d1c12a3 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 743e7d6..600974c 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -43,6 +43,7 @@
 #include "catalog/pg_type.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSubplan.h"
+#include "executor/tableexpr.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -189,7 +190,23 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
-
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum value);
+static TableExprBuilder tabexpr_CreateBuilder(TableExprState *tstate);
+static void tabexpr_SetContent(TableExprState *tstate, Datum value);
+static void tabexpr_DeclareNamespace(TableExprState *tstate,
+				  char *name, char *uri);
+static void tabexpr_SetRowFilter(TableExprState *tstate, char *filter);
+static void tabexpr_SetColumnFilter(TableExprState *tstate, char *filter,
+					  int colnum);
+static bool tabexpr_FetchRow(TableExprState *tstate);
+static Datum tabexpr_GetValue(TableExprState *tstate, int colnum, bool *isnull);
+static void tabexpr_DestroyBuilder(TableExprState *tstate);
 
 /* ----------------------------------------------------------------
  *		ExecEvalExpr routines
@@ -4500,6 +4517,353 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	Datum		result;
+
+	PG_TRY();
+	{
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->builder == NULL)
+		{
+			Datum		value;
+
+			/* Create table expression builder */
+			tstate->builder = tabexpr_CreateBuilder(tstate);
+
+			/*
+			 * If evaluating the document expression returns NULL, the table
+			 * expression is empty and we return immediately.
+			 */
+			value = ExecEvalExpr(tstate->doc_expr, econtext, isNull, NULL);
+			if (*isNull)
+			{
+				*isDone = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any error are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->builder != NULL)
+		{
+			tabexpr_DestroyBuilder(tstate);
+			tstate->builder = NULL;
+		}
+
+		MemoryContextDelete(tstate->buildercxt);
+		tstate->buildercxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum value)
+{
+	ListCell   *cell;
+	bool		isnull;
+	int			i;
+
+	/*
+	 * 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
+	 */
+	tabexpr_SetContent(tstate, value);
+
+	/* Evaluate namespace specifications */
+	foreach(cell, tstate->namespaces)
+	{
+		Node	   *n = (Node *) lfirst(cell);
+		ExprState  *expr;
+		char	   *ns_name;
+		char	   *ns_uri;
+
+		if (IsA(n, NamedArgExpr))
+		{
+			NamedArgExpr *na = (NamedArgExpr *) n;
+
+			expr = (ExprState *) na->arg;
+			ns_name = na->name;
+		}
+		else
+		{
+			expr = (ExprState *) n;
+			ns_name = NULL;
+		}
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		tabexpr_DeclareNamespace(tstate, ns_name, ns_uri);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->row_expr, econtext, &isnull, NULL);
+	if (isnull)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("row filter expression must not be null")));
+
+	tabexpr_SetRowFilter(tstate, TextDatumGetCString(value));
+
+	/*
+	 * Install the column filter expressions into the table builder context.
+	 * If an expression is given, use that; otherwise the column name itself
+	 * is the column filter.
+	 */
+	for (i = 0; i < tstate->ncols; i++)
+	{
+		char	   *col_filter;
+
+		if (tstate->col_expr[i] != NULL)
+		{
+			value = ExecEvalExpr(tstate->col_expr[i], econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("column filter expression must not be null"),
+						 errdetail("Filter for column \"%s\" is null.",
+								   NameStr(tstate->tupdesc->attrs[i]->attname))));
+			col_filter = TextDatumGetCString(value);
+		}
+		else
+			col_filter = NameStr(tstate->tupdesc->attrs[i]->attname);
+
+		tabexpr_SetColumnFilter(tstate, col_filter, i);
+	}
+}
+
+/*
+ * Fetch one more row from a TableExpr table builder.
+ */
+static Datum
+tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone)
+{
+	Datum		result;
+
+	/* Prepare one more row */
+	if (tabexpr_FetchRow(tstate))
+	{
+		TupleDesc	tupdesc = tstate->tupdesc;
+		Datum	   *values = tstate->values;
+		bool	   *nulls = tstate->nulls;
+		HeapTuple	tuple;
+		HeapTupleHeader dtuple;
+		int			i;
+		bool		isnull;
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			if (i + 1 == tstate->for_ordinality_col)
+			{
+				values[i] = Int32GetDatum(tstate->rownum++);
+				nulls[i] = false;
+			}
+			else
+			{
+				values[i] = tabexpr_GetValue(tstate, i, &isnull);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tupdesc->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+		}
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(dtuple, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type previously identified
+		 */
+		HeapTupleHeaderSetTypeId(dtuple, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(dtuple, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+
+		*isNull = false;
+		*isDone = ExprMultipleResult;
+
+		result = HeapTupleHeaderGetDatum(dtuple);
+	}
+	else
+	{
+		/* no more rows */
+		tabexpr_DestroyBuilder(tstate);
+		tstate->builder = NULL;
+
+		/* make sure all memory is released */
+		MemoryContextReset(tstate->buildercxt);
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	return result;
+}
+
+/*
+ * Switch to builder memory context and evaluate CreateBuilder routine
+ */
+static TableExprBuilder
+tabexpr_CreateBuilder(TableExprState *tstate)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+	TableExprBuilder	result;
+
+	result = routine->CreateBuilder(tstate->tupdesc,
+									  tstate->in_functions,
+									  tstate->typioparams);
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
+
+/*
+ * Switch to builder memory context and evaluate SetContent routine
+ */
+static void
+tabexpr_SetContent(TableExprState *tstate, Datum value)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+
+	routine->SetContent(tstate->builder, value);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Switch to builder memory context and evaluate DeclareNamespace routine
+ */
+static void
+tabexpr_DeclareNamespace(TableExprState *tstate, char *name, char *uri)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+
+	routine->DeclareNamespace(tstate->builder, name, uri);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Switch to builder memory context and evaluate SetRowFilter routine
+ */
+static void
+tabexpr_SetRowFilter(TableExprState *tstate, char *filter)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+
+	routine->SetRowFilter(tstate->builder, filter);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Switch to builder memory context and evaluate SetColumnFilter routine
+ */
+static void
+tabexpr_SetColumnFilter(TableExprState *tstate, char *filter,
+					  int colnum)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+
+	routine->SetColumnFilter(tstate->builder, filter, colnum);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Switch to builder memory context and evaluate FetchRow routine
+ */
+static bool
+tabexpr_FetchRow(TableExprState *tstate)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+	bool		result;
+
+	result = routine->FetchRow(tstate->builder);
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
+
+/*
+ * Switch to builder memory context and evaluate GetValue routine
+ */
+static Datum
+tabexpr_GetValue(TableExprState *tstate, int colnum, bool *isnull)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+
+	result = routine->GetValue(tstate->builder, colnum, isnull);
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
+
+/*
+ * Switch to builder memory context and evaluate DestroyBuilder routine
+ */
+static void
+tabexpr_DestroyBuilder(TableExprState *tstate)
+{
+	MemoryContext		oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	const TableExprRoutine *routine = tstate->routine;
+
+	routine->DestroyBuilder(tstate->builder);
+	MemoryContextSwitchTo(oldcxt);
+}
+
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5626,110 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int			ncols;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->builder = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				/* XXX this assumes the anon record type has been registered */
+				tstate->tupdesc =
+					lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+												exprTypmod((Node *) te));
+				Assert(tstate->tupdesc->natts > 0);
+
+				ncols = tstate->tupdesc->natts;
+				tstate->values = palloc0(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid			in_funcid;
+
+					getTypeInputInfo(tstate->tupdesc->attrs[i]->atttypid,
+									 &in_funcid, &tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_expr = ExecInitExpr((Expr *) te->row_expr, parent);
+				tstate->doc_expr = ExecInitExpr((Expr *) te->document_expr, parent);
+
+				if (te->columns)
+				{
+					ListCell   *col;
+
+					Assert(ncols == list_length(te->columns));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+					tstate->rownum = 1;
+
+					i = 0;
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+															   parent);
+							tstate->col_expr[i] = ExecInitExpr((Expr *) tec->column_expr,
+															   parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->buildercxt = AllocSetContextCreate(CurrentMemoryContext,
+											 "TableExpr builder context",
+													 ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..8c722ed 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,48 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+	TupleDesc	typeInfo;
+
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		int			i = 1;
+
+		typeInfo = CreateTemplateTupleDesc(list_length(te->columns), false);
+
+		foreach(col, te->columns)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(typeInfo,
+							   (AttrNumber) i,
+							   pstrdup(tec->colname),
+							   tec->typid,
+							   tec->typmod,
+							   0);
+			i++;
+		}
+	}
+	else
+	{
+		typeInfo = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(typeInfo, (AttrNumber) 1,
+						   "xmltable", XMLOID, -1, 0);
+	}
+
+	/* XXX OK to do this?  looks a bit out of place ... */
+	assign_record_type_typmod(typeInfo);
+
+	return typeInfo;
+}
+
+/*
  * ExecTypeSetColNames - set column names in a TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..36b4f26 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(row_expr);
+	COPY_NODE_FIELD(document_expr);
+	COPY_NODE_FIELD(columns);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4600,6 +4657,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5104,6 +5167,9 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..6971086 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2615,6 +2615,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2645,6 +2675,18 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(row_expr);
+	COMPARE_NODE_FIELD(document_expr);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2910,6 +2952,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3401,6 +3449,9 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 20e2dbd..592760f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -17,6 +17,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "executor/executor.h"
 #include "fmgr.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..6db3ccf 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +258,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +499,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate a blessed tupdesc from the column definitions, and
+				 * returns its typmod.
+				 */
+				TupleDesc	tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +747,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 +951,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1155,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1587,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2249,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->column_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3069,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_expr, te->row_expr, Node *);
+				MUTATE(newnode->document_expr, te->document_expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->columns, te->columns, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->column_expr, tec->column_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3708,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..d5d5e81 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_NODE_FIELD(row_expr);
+	WRITE_NODE_FIELD(document_expr);
+	WRITE_NODE_FIELD(columns);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3539,6 +3581,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..ed71fc6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,44 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(row_expr);
+	READ_NODE_FIELD(document_expr);
+	READ_NODE_FIELD(columns);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(column_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2535,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9af29dd..5d5fd41 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -829,6 +829,12 @@ expression_returns_set_rows_walker(Node *node, double *count)
 			*count *= get_func_rows(expr->opfuncid);
 		}
 	}
+	if (IsA(node, TableExpr))
+	{
+		/* we have not any method how to estimate it, use default */
+		*count = 1000;
+		return false;
+	}
 
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
@@ -3598,6 +3604,33 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (tec->column_expr != NULL || tec->default_expr != NULL)
+				{
+					TableExprColumn *newtec = makeNode(TableExprColumn);
+
+					newtec->colname = tec->colname;
+					newtec->typid = tec->typid;
+					newtec->typmod = tec->typmod;
+					newtec->collation = tec->collation;
+					newtec->for_ordinality = tec->for_ordinality;
+					newtec->is_not_null = tec->is_not_null;
+					newtec->column_expr =
+						eval_const_expressions_mutator((Node *) tec->column_expr,
+													   context);
+					newtec->default_expr =
+						eval_const_expressions_mutator((Node *) tec->default_expr,
+													   context);
+					newtec->location = tec->location;
+
+					return (Node *) newtec;
+				}
+			}
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d6274b4..91f8a9a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -545,6 +545,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	XmlTableExprColList XmlTableExprColOptions
+%type <node>	XmlTableExprCol
+%type <defelt>	XmlTableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -576,10 +583,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
-	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
-	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-	CROSS CSV CUBE CURRENT_P
+	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS
+	COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION
+	CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST
+	CREATE CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -649,8 +656,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
-	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
-	XMLPI XMLROOT XMLSERIALIZE
+	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
+	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
 
 	YEAR_P YES_P
 
@@ -12588,6 +12595,168 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+XmlTableExprColList: XmlTableExprCol						{ $$ = list_make1($1); }
+			| XmlTableExprColList ',' XmlTableExprCol		{ $$ = lappend($1, $3); }
+		;
+
+XmlTableExprCol:
+			ColId Typename IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $3;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+			| ColId Typename XmlTableExprColOptions IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $4;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->column_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->column_expr = defel->arg;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+XmlTableExprColOptions: XmlTableExprColOption				{ $$ = list_make1($1); }
+			| XmlTableExprColOptions XmlTableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+XmlTableExprColOption:
+			IDENT b_expr
+				{
+					$$ = makeDefElem($1, $2, @1);
+				}
+			| DEFAULT b_expr
+				{
+					$$ = makeDefElem("default", $2, @1);
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			b_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT b_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
 		;
 
 /*
@@ -13696,6 +13865,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13997,10 +14167,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..06739f6 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1095,9 +1095,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1105,9 +1105,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1116,7 +1116,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1143,6 +1143,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 17d1cbf..6551075 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2764,6 +2770,163 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 	return (Node *) newc;
 }
 
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	const char *constructName;
+	Oid			exprType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	exprType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, te->location);
+
+	/* Transform the row-generating Xpath expression ... */
+	Assert(te->row_expr != NULL);
+	newte->row_expr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_expr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML itself */
+	Assert(te->document_expr != NULL);
+	newte->document_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, te->document_expr),
+												   exprType,
+												   constructName);
+
+	/* Columns, if any, also need to be transformed */
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		bool		for_ordinality = false;
+		char	  **colnames;
+		int			i = 0;
+
+		colnames = palloc(sizeof(char *) * list_length(te->columns));
+
+		/*
+		 * For each TableExprRawCol in the input TableExpr, generate one
+		 * TableExprColumn in the output TableExpr.
+		 */
+		foreach(col, te->columns)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc;
+			int			j;
+
+			/* Only one FOR ORDINALITY column is allowed; verify */
+			if (rawc->for_ordinality)
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+			}
+
+			newc = makeNode(TableExprColumn);
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				newc->typid = INT4OID;
+				newc->typmod = -1;
+			}
+			else
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &newc->typid, &newc->typmod);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->column_expr)
+				newc->column_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, rawc->column_expr),
+															TEXTOID,
+															constructName);
+			if (rawc->default_expr)
+				newc->default_expr = coerce_to_specific_type_typmod(pstate,
+							transformExprRecurse(pstate, rawc->default_expr),
+												   newc->typid, newc->typmod,
+															  constructName);
+
+
+			newte->columns = lappend(newte->columns, newc);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(colnames[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			colnames[i] = rawc->colname;
+
+			i++;
+		}
+
+		pfree(colnames);
+	}
+	else
+		newte->columns = NIL;
+
+	/* Namespaces, if any, also need to be transformed */
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the namespace \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(nsnames);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
 /*
  * Transform a "row compare-op row" construct
  *
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index d440dec..d59d83f 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,14 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+
+			/*
+			 * Make TableExpr act like a regular function. Only XMLTABLE expr
+			 * is supported in this moment.
+			 */
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..ef12407 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8306,6 +8307,105 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/*
+				 * Deparse TableExpr - the is only one TableExpr producer, the
+				 * function XMLTABLE.
+				 */
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_expr, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->document_expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->columns != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->column_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->column_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..a4b20f3 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,51 @@ 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;
+	TupleDesc	tupdesc;
+	MemoryContext per_rowset_memcxt;
+	char	   *def_namespace_name;
+	FmgrInfo   *in_functions;
+	Oid		   *typioparams;
+	long int	rc;
+	PgXmlErrorContext *xmlerrcxt;
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr	doc;
+	xmlXPathContextPtr xpathcxt;
+	xmlXPathCompExprPtr xpathcomp;
+	xmlXPathObjectPtr xpathobj;
+	xmlXPathCompExprPtr *xpathscomp;
+} XmlTableBuilderData;
+#endif
+
+static TableExprBuilder XmlTableCreateBuilder(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparms);
+static void XmlTableSetContent(TableExprBuilder tebuilder, Datum value);
+static void XmlTableDeclareNamespace(TableExprBuilder tebuilder, char *name, char *uri);
+static void XmlTableSetRowFilter(TableExprBuilder tebuilder, char *path);
+static void XmlTableSetColumnFilter(TableExprBuilder tebuilder, char *path, int colnum);
+static bool XmlTableFetchRow(TableExprBuilder tebuilder);
+static Datum XmlTableGetValue(TableExprBuilder tebuilder, int colnum, bool *isnull);
+static void XmlTableDestroyBuilder(TableExprBuilder tebuilder);
+
+const TableExprRoutine XmlTableExprRoutine =
+{
+	XmlTableCreateBuilder,
+	XmlTableSetContent,
+	XmlTableDeclareNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyBuilder
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4111,568 @@ 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
+
+/*
+ * Create XmlTableBuilderData for XmlTable builder. Initialize XML parser.
+ */
+static TableExprBuilder
+XmlTableCreateBuilder(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparams)
+{
+#ifdef USE_LIBXML
+	TableExprBuilder result;
+	XmlTableBuilderData *xtCxt = NULL;
+	PgXmlErrorContext *xmlerrcxt = NULL;
+	volatile xmlParserCtxtPtr ctxt = NULL;
+
+	xtCxt = palloc0(sizeof(XmlTableBuilderData));
+	xtCxt->magic = XMLTABLE_CONTEXT_MAGIC;
+	xtCxt->tupdesc = tupdesc;
+	xtCxt->in_functions = in_functions;
+	xtCxt->typioparams = typioparams;
+	xtCxt->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * tupdesc->natts);
+
+	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+	PG_TRY();
+	{
+		xmlInitParser();
+
+		ctxt = xmlNewParserCtxt();
+		if (ctxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate parser context");
+	}
+	PG_CATCH();
+	{
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xtCxt->xmlerrcxt = xmlerrcxt;
+	xtCxt->ctxt = ctxt;
+
+	result = palloc(sizeof(TableExprBuilderData));
+	result->private_data = xtCxt;
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(TableExprBuilder tebuilder, Datum value)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	xmltype    *xmlval = DatumGetXmlP(value);
+	xmlChar    *xstr;
+	int			length;
+	volatile xmlDocPtr doc = NULL;
+	volatile xmlXPathContextPtr xpathcxt = NULL;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetContent called with invalid XmlTableBuilderData");
+
+	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 */
+}
+
+/*
+ * XmlTableDeclareNamespace.  Add a namespace declaration.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableDeclareNamespace(TableExprBuilder tebuilder, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid XmlTableBuilderData");
+
+	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(TableExprBuilder tebuilder, char *path)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	xmlChar    *xstr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetRowFilter called with invalid XmlTableBuilderData");
+
+	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
+ */
+static void
+XmlTableSetColumnFilter(TableExprBuilder tebuilder, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	xmlChar    *xstr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid XmlTableBuilderData");
+
+	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
+	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(TableExprBuilder tebuilder)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableFetchRow called with invalid XmlTableBuilderData");
+
+	/*
+	 * 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->rc = 0;
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->rc++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+}
+
+/*
+ * Apply column path on current node and returns result.
+ * Allows multivalue when expected type is XML.
+ * When COLUMNS part was not entered, returns current node.
+ */
+static Datum
+XmlTableGetValue(TableExprBuilder tebuilder, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableGetValue called with invalid XmlTableBuilderData");
+
+	Assert(xtCxt->xpathobj && xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->rc - 1];
+
+	if (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = NULL;
+
+		/* fast leaving */
+		if (cur->type != XML_ELEMENT_NODE)
+			elog(ERROR, "unexpected xmlNode type");
+
+		PG_TRY();
+		{
+			/* Set current node as entry point for XPath evaluation */
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+			/* Evaluate column path */
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+			if (column_xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+				xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+							"could not create XPath object");
+
+			if (column_xpathobj->type == XPATH_NODESET)
+			{
+				int			count;
+
+				if (column_xpathobj->nodesetval != NULL)
+					count = column_xpathobj->nodesetval->nodeNr;
+				else
+					count = 0;
+
+				if (count > 0)
+				{
+					Oid			targettypid = xtCxt->tupdesc->attrs[colnum]->atttypid;
+
+					if (count == 1)
+					{
+						if (targettypid != XMLOID)
+						{
+							xmlChar    *str;
+
+							str = xmlNodeListGetString(xtCxt->doc,
+													   column_xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+													   1);
+
+							if (str != NULL)
+							{
+								PG_TRY();
+								{
+									/*
+									 * Copy string to PostgreSQL controlled
+									 * memory
+									 */
+									cstr = pstrdup((char *) str);
+								}
+								PG_CATCH();
+								{
+									xmlFree(str);
+									PG_RE_THROW();
+								}
+								PG_END_TRY();
+
+								xmlFree(str);
+							}
+							else
+							{
+								/* Return empty string when tag is empty */
+								cstr = pstrdup("");
+							}
+						}
+						else
+						{
+							/* simple case, result is one value */
+							cstr = xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[0],
+													xtCxt->xmlerrcxt);
+						}
+					}
+					else
+					{
+						StringInfoData str;
+						int			i;
+
+						/*
+						 * When result is not one value, then we can work with
+						 * concated values. But it requires XML as expected
+						 * type.
+						 */
+						if (targettypid != XMLOID)
+							ereport(ERROR,
+									(errcode(ERRCODE_CARDINALITY_VIOLATION),
+									 errmsg("more than one value returned by column XPath expression")));
+
+						/* Concate serialized values */
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+												   xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														  xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[colnum],
+											   cstr,
+											   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[colnum],
+										 (char *) column_xpathobj->stringval,
+										   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+				*isnull = false;
+			}
+			else
+				elog(ERROR, "unexpected XPath object type");
+
+			xmlXPathFreeObject(column_xpathobj);
+			column_xpathobj = NULL;
+		}
+		PG_CATCH();
+		{
+			if (column_xpathobj != NULL)
+				xmlXPathFreeObject(column_xpathobj);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+	else
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+		result = InputFunctionCall(&xtCxt->in_functions[0],
+								   cstr,
+								   xtCxt->typioparams[0],
+								   -1); /* target type is XML always */
+		*isnull = false;
+	}
+
+	if (cstr != NULL)
+		pfree(cstr);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * Release all libxml2 memory and related resources
+ */
+static void
+XmlTableDestroyBuilder(TableExprBuilder tebuilder)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDestroyContext called with invalid XmlTableBuilderData");
+
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int			i;
+
+		for (i = 0; i < xtCxt->tupdesc->natts; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+	/* not valid anymore */
+	xtCxt->magic = 0;
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c6d69bc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -272,6 +272,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
 extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..6516a33
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,73 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "access/tupdesc.h"
+#include "fmgr.h"
+
+/*
+ * TableExprRoutine holds function pointers used for generating content of
+ * table-expression functions, such as XMLTABLE.
+ *
+ * CreateContext creates the table builder context.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed as
+ * created by the executor scaffold.  The memory context being passed is an
+ * IMO bogus thing that shall be fixed by Pavel.
+ *
+ * SetContent is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * DeclareNamespace 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.
+ *
+ * DestroyContext 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 TableExprBuilderData
+{
+	void	   *private_data;	/* Opaque data used by table builder */
+} TableExprBuilderData;
+
+typedef struct TableExprBuilderData *TableExprBuilder;
+
+typedef struct TableExprRoutine
+{
+	TableExprBuilder	(*CreateBuilder) (TupleDesc tupdesc,
+									FmgrInfo *in_functions, Oid *typioparams);
+	void		(*SetContent) (TableExprBuilder tebuilder, Datum value);
+	void		(*DeclareNamespace) (TableExprBuilder tebuilder, char *name, char *uri);
+	void		(*SetRowFilter) (TableExprBuilder tebuilder, char *path);
+	void		(*SetColumnFilter) (TableExprBuilder tebuilder, char *path, int colnum);
+	bool		(*FetchRow) (TableExprBuilder tebuilder);
+	Datum		(*GetValue) (TableExprBuilder tebuilder, int colnum, bool *isnull);
+	void		(*DestroyBuilder) (TableExprBuilder tebuilder);
+} TableExprRoutine;
+
+#endif   /* TABLEEXPR_H */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8004d85..49cff48 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "executor/instrument.h"
+#include "executor/tableexpr.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
@@ -1011,6 +1012,36 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+/* ----------------
+ *		TableExprState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	TupleDesc	tupdesc;		/* cache */
+	int			ncols;			/* number of declared columns */
+	int			for_ordinality_col;		/* number of oridinality column,
+										 * started by 1 */
+	int			rownum;			/* row counter - for ordinality column */
+	ExprState  *row_expr;		/* row xpath expression */
+	ExprState  *doc_expr;		/* processed data */
+	ExprState **def_expr;		/* array of expressions for default value */
+	ExprState **col_expr;		/* array of expressions for path value */
+	bool	   *not_null;		/* for any column info if NULL is allowed or
+								 * not */
+	Datum	   *values;			/* prealloc buffer */
+	bool	   *nulls;			/* prealloc buffer */
+	FmgrInfo   *in_functions;	/* array of infunction for any column */
+	Oid		   *typioparams;	/* array of typIOParam for any column */
+	/* XXX move to better position */
+	const TableExprRoutine *routine;	/* pointers to builder functions */
+	TableExprBuilder	builder;		/* data for content builder */
+	MemoryContext buildercxt;		/* memory context used by builder */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b6e5151 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -454,6 +457,7 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..2608562 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,27 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - raw column definition in table-expression functions
+ *		like XMLTABLE.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions.
+ *
+ * If for_ordinality is true (FOR ORDINALITY), then the column is an int4
+ * column and the rest of the fields are ignored.
+ */
+typedef struct TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	TypeName   *typeName;		/* type name of generated column */
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..61b2241 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,44 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - holds data for table-expression functions like XMLTABLE
+ *
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Node	   *row_expr;		/* row filter expression */
+	Node	   *document_expr;	/* processed data */
+	List	   *columns;		/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+} TableExpr;
+
+/*----------
+ * TableExprColumn - a column definition in table-expression functions.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions
+ * and just doesn't make sense here.
+ *
+ * Raw form of this node is TableExprRawCol node - different in
+ * typeName field. After transformation we use typid, typmod.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	Oid			typid;			/* typid of generated column */
+	int32		typmod;
+	Oid			collation;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..de4b8b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -441,10 +442,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..3c12df0 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,6 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern const TableExprRoutine XmlTableExprRoutine;
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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 c680216..720dbdf 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1,3 +1,13 @@
+XmlTableContext
+TableExprBuilder
+TableExprState
+TableExprRawCol
+TableExpr
+TableExprColumn
+SQLValueFunction
+max_parallel_hazard_context
+TriggerTransition
+SQLValueFunctionOp
 ABITVEC
 ACCESS_ALLOWED_ACE
 ACL_SIZE_INFORMATION
#88Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#87)
Re: patch: function xmltable

Pavel Stehule wrote:

2016-12-04 23:00 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

I am not sure if I understand well to your ideas - please, check attached
patch.

Thanks, that's what I meant, but I think you went a bit overboard
creating new functions in execQual -- seems to me it would work just
fine to have the memory switches in the same function, rather than
having a number of separate functions just to change the context then
call the method. Please remove these shim functions.

Also, you forgot to remove the now-unused per_rowset_memcxt struct member.

Also, please rename "rc" to something more meaningful -- maybe
"rowcount" is good enough. And "doc" would perhaps be better as
"document".

I'm not completely sure the structs are really sensible yet. I may do
some more changes tomorrow.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#89Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#88)
1 attachment(s)
Re: patch: function xmltable

2016-12-05 0:45 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2016-12-04 23:00 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

I am not sure if I understand well to your ideas - please, check

attached

patch.

Thanks, that's what I meant, but I think you went a bit overboard
creating new functions in execQual -- seems to me it would work just
fine to have the memory switches in the same function, rather than
having a number of separate functions just to change the context then
call the method. Please remove these shim functions.

done

Also, you forgot to remove the now-unused per_rowset_memcxt struct member.

done

Also, please rename "rc" to something more meaningful -- maybe
"rowcount" is good enough. And "doc" would perhaps be better as
"document".

done

Regards

Pavel

Show quoted text

I'm not completely sure the structs are really sensible yet. I may do
some more changes tomorrow.

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-20.patchtext/x-patch; charset=US-ASCII; name=xmltable-20.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 43db430..d1c12a3 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 743e7d6..7ba089c 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -43,6 +43,7 @@
 #include "catalog/pg_type.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSubplan.h"
+#include "executor/tableexpr.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -189,7 +190,13 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
-
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum value);
 
 /* ----------------------------------------------------------------
  *		ExecEvalExpr routines
@@ -4500,6 +4507,267 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	const TableExprRoutine *routine;
+	MemoryContext		oldcxt;
+	Datum		result;
+
+	routine = tstate->routine;
+
+	PG_TRY();
+	{
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->builder == NULL)
+		{
+			Datum		value;
+
+			/* Create table expression builder */
+			oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+			tstate->builder = routine->CreateBuilder(tstate->tupdesc,
+										  tstate->in_functions,
+										  tstate->typioparams);
+			MemoryContextSwitchTo(oldcxt);
+
+			/*
+			 * If evaluating the document expression returns NULL, the table
+			 * expression is empty and we return immediately.
+			 */
+			value = ExecEvalExpr(tstate->doc_expr, econtext, isNull, NULL);
+			if (*isNull)
+			{
+				*isDone = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any error are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->builder != NULL)
+		{
+			routine->DestroyBuilder(tstate->builder);
+			tstate->builder = NULL;
+		}
+
+		MemoryContextDelete(tstate->buildercxt);
+		tstate->buildercxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum value)
+{
+	const TableExprRoutine *routine;
+	MemoryContext		oldcxt;
+	ListCell   *cell;
+	bool		isnull;
+	int			i;
+
+	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->SetContent(tstate->builder, value);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Evaluate namespace specifications */
+	foreach(cell, tstate->namespaces)
+	{
+		Node	   *n = (Node *) lfirst(cell);
+		ExprState  *expr;
+		char	   *ns_name;
+		char	   *ns_uri;
+
+		if (IsA(n, NamedArgExpr))
+		{
+			NamedArgExpr *na = (NamedArgExpr *) n;
+
+			expr = (ExprState *) na->arg;
+			ns_name = na->name;
+		}
+		else
+		{
+			expr = (ExprState *) n;
+			ns_name = NULL;
+		}
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->DeclareNamespace(tstate->builder, ns_name, ns_uri);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->row_expr, 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->builder, 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.
+	 */
+	for (i = 0; i < tstate->ncols; i++)
+	{
+		char	   *col_filter;
+
+		if (tstate->col_expr[i] != NULL)
+		{
+			value = ExecEvalExpr(tstate->col_expr[i], econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("column filter expression must not be null"),
+						 errdetail("Filter for column \"%s\" is null.",
+								   NameStr(tstate->tupdesc->attrs[i]->attname))));
+			col_filter = TextDatumGetCString(value);
+		}
+		else
+			col_filter = NameStr(tstate->tupdesc->attrs[i]->attname);
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->SetColumnFilter(tstate->builder, col_filter, i);
+		MemoryContextSwitchTo(oldcxt);
+	}
+}
+
+/*
+ * 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->builder))
+	{
+		TupleDesc	tupdesc = tstate->tupdesc;
+		Datum	   *values = tstate->values;
+		bool	   *nulls = tstate->nulls;
+		HeapTuple	tuple;
+		HeapTupleHeader dtuple;
+		int			i;
+		bool		isnull;
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			if (i + 1 == tstate->for_ordinality_col)
+			{
+				values[i] = Int32GetDatum(tstate->rownum++);
+				nulls[i] = false;
+			}
+			else
+			{
+				values[i] = routine->GetValue(tstate->builder, i, &isnull);
+				MemoryContextSwitchTo(oldcxt);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tupdesc->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+		}
+
+		MemoryContextSwitchTo(oldcxt);
+
+		tuple = heap_form_tuple(tupdesc, values, nulls);
+		dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+		memcpy(dtuple, tuple->t_data, tuple->t_len);
+
+		/*
+		 * Label the datum with the composite type previously identified
+		 */
+		HeapTupleHeaderSetTypeId(dtuple, tupdesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(dtuple, tupdesc->tdtypmod);
+
+		heap_freetuple(tuple);
+
+		*isNull = false;
+		*isDone = ExprMultipleResult;
+
+		result = HeapTupleHeaderGetDatum(dtuple);
+	}
+	else
+	{
+		/* no more rows */
+		routine->DestroyBuilder(tstate->builder);
+		tstate->builder = NULL;
+
+		/* make sure all memory is released */
+		MemoryContextReset(tstate->buildercxt);
+
+		*isNull = true;
+		*isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	/* push back short life memory context */
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5262,6 +5530,110 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				int			ncols;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->builder = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				/* XXX this assumes the anon record type has been registered */
+				tstate->tupdesc =
+					lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+												exprTypmod((Node *) te));
+				Assert(tstate->tupdesc->natts > 0);
+
+				ncols = tstate->tupdesc->natts;
+				tstate->values = palloc0(sizeof(Datum) * ncols);
+				tstate->nulls = palloc(sizeof(bool) * ncols);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+				tstate->typioparams = palloc(sizeof(Oid) * ncols);
+
+				for (i = 0; i < ncols; i++)
+				{
+					Oid			in_funcid;
+
+					getTypeInputInfo(tstate->tupdesc->attrs[i]->atttypid,
+									 &in_funcid, &tstate->typioparams[i]);
+					fmgr_info(in_funcid, &tstate->in_functions[i]);
+				}
+
+				tstate->row_expr = ExecInitExpr((Expr *) te->row_expr, parent);
+				tstate->doc_expr = ExecInitExpr((Expr *) te->document_expr, parent);
+
+				if (te->columns)
+				{
+					ListCell   *col;
+
+					Assert(ncols == list_length(te->columns));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->col_expr = palloc0(sizeof(ExprState *) * ncols);
+					tstate->not_null = palloc0(sizeof(bool) * ncols);
+					tstate->ncols = ncols;
+					tstate->rownum = 1;
+
+					i = 0;
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+															   parent);
+							tstate->col_expr[i] = ExecInitExpr((Expr *) tec->column_expr,
+															   parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->buildercxt = AllocSetContextCreate(CurrentMemoryContext,
+											 "TableExpr builder context",
+													 ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..8c722ed 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,48 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+	TupleDesc	typeInfo;
+
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		int			i = 1;
+
+		typeInfo = CreateTemplateTupleDesc(list_length(te->columns), false);
+
+		foreach(col, te->columns)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(typeInfo,
+							   (AttrNumber) i,
+							   pstrdup(tec->colname),
+							   tec->typid,
+							   tec->typmod,
+							   0);
+			i++;
+		}
+	}
+	else
+	{
+		typeInfo = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(typeInfo, (AttrNumber) 1,
+						   "xmltable", XMLOID, -1, 0);
+	}
+
+	/* XXX OK to do this?  looks a bit out of place ... */
+	assign_record_type_typmod(typeInfo);
+
+	return typeInfo;
+}
+
+/*
  * ExecTypeSetColNames - set column names in a TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..36b4f26 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(row_expr);
+	COPY_NODE_FIELD(document_expr);
+	COPY_NODE_FIELD(columns);
+	COPY_NODE_FIELD(namespaces);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4600,6 +4657,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5104,6 +5167,9 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..6971086 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2615,6 +2615,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2645,6 +2675,18 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(row_expr);
+	COMPARE_NODE_FIELD(document_expr);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2910,6 +2952,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3401,6 +3449,9 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 20e2dbd..592760f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -17,6 +17,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "executor/executor.h"
 #include "fmgr.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..6db3ccf 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +258,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +499,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate a blessed tupdesc from the column definitions, and
+				 * returns its typmod.
+				 */
+				TupleDesc	tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +747,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 +951,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1155,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1587,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2249,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->column_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3069,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_expr, te->row_expr, Node *);
+				MUTATE(newnode->document_expr, te->document_expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->columns, te->columns, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->column_expr, tec->column_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3708,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..d5d5e81 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_NODE_FIELD(row_expr);
+	WRITE_NODE_FIELD(document_expr);
+	WRITE_NODE_FIELD(columns);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3539,6 +3581,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..ed71fc6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,44 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(row_expr);
+	READ_NODE_FIELD(document_expr);
+	READ_NODE_FIELD(columns);
+	READ_NODE_FIELD(namespaces);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(column_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2535,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9af29dd..5d5fd41 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -829,6 +829,12 @@ expression_returns_set_rows_walker(Node *node, double *count)
 			*count *= get_func_rows(expr->opfuncid);
 		}
 	}
+	if (IsA(node, TableExpr))
+	{
+		/* we have not any method how to estimate it, use default */
+		*count = 1000;
+		return false;
+	}
 
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
@@ -3598,6 +3604,33 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (tec->column_expr != NULL || tec->default_expr != NULL)
+				{
+					TableExprColumn *newtec = makeNode(TableExprColumn);
+
+					newtec->colname = tec->colname;
+					newtec->typid = tec->typid;
+					newtec->typmod = tec->typmod;
+					newtec->collation = tec->collation;
+					newtec->for_ordinality = tec->for_ordinality;
+					newtec->is_not_null = tec->is_not_null;
+					newtec->column_expr =
+						eval_const_expressions_mutator((Node *) tec->column_expr,
+													   context);
+					newtec->default_expr =
+						eval_const_expressions_mutator((Node *) tec->default_expr,
+													   context);
+					newtec->location = tec->location;
+
+					return (Node *) newtec;
+				}
+			}
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d6274b4..91f8a9a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -545,6 +545,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	XmlTableExprColList XmlTableExprColOptions
+%type <node>	XmlTableExprCol
+%type <defelt>	XmlTableExprColOption
+%type <boolean>	IsNotNull
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -576,10 +583,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
-	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
-	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-	CROSS CSV CUBE CURRENT_P
+	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS
+	COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION
+	CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST
+	CREATE CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -649,8 +656,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
-	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
-	XMLPI XMLROOT XMLSERIALIZE
+	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
+	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
 
 	YEAR_P YES_P
 
@@ -12588,6 +12595,168 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+XmlTableExprColList: XmlTableExprCol						{ $$ = list_make1($1); }
+			| XmlTableExprColList ',' XmlTableExprCol		{ $$ = lappend($1, $3); }
+		;
+
+XmlTableExprCol:
+			ColId Typename IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $3;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+			| ColId Typename XmlTableExprColOptions IsNotNull
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = $4;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->column_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->column_expr = defel->arg;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+XmlTableExprColOptions: XmlTableExprColOption				{ $$ = list_make1($1); }
+			| XmlTableExprColOptions XmlTableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+XmlTableExprColOption:
+			IDENT b_expr
+				{
+					$$ = makeDefElem($1, $2, @1);
+				}
+			| DEFAULT b_expr
+				{
+					$$ = makeDefElem("default", $2, @1);
+				}
+		;
+
+IsNotNull: NOT NULL_P									{ $$ = true; }
+			| NULL_P									{ $$ = false; }
+			| /* EMPTY */								{ $$ = false; }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			b_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT b_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
 		;
 
 /*
@@ -13696,6 +13865,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13997,10 +14167,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..06739f6 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1095,9 +1095,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1105,9 +1105,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1116,7 +1116,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1143,6 +1143,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 17d1cbf..6551075 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2764,6 +2770,163 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 	return (Node *) newc;
 }
 
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	const char *constructName;
+	Oid			exprType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	exprType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, te->location);
+
+	/* Transform the row-generating Xpath expression ... */
+	Assert(te->row_expr != NULL);
+	newte->row_expr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_expr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML itself */
+	Assert(te->document_expr != NULL);
+	newte->document_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, te->document_expr),
+												   exprType,
+												   constructName);
+
+	/* Columns, if any, also need to be transformed */
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		bool		for_ordinality = false;
+		char	  **colnames;
+		int			i = 0;
+
+		colnames = palloc(sizeof(char *) * list_length(te->columns));
+
+		/*
+		 * For each TableExprRawCol in the input TableExpr, generate one
+		 * TableExprColumn in the output TableExpr.
+		 */
+		foreach(col, te->columns)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc;
+			int			j;
+
+			/* Only one FOR ORDINALITY column is allowed; verify */
+			if (rawc->for_ordinality)
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+			}
+
+			newc = makeNode(TableExprColumn);
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				newc->typid = INT4OID;
+				newc->typmod = -1;
+			}
+			else
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &newc->typid, &newc->typmod);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->column_expr)
+				newc->column_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, rawc->column_expr),
+															TEXTOID,
+															constructName);
+			if (rawc->default_expr)
+				newc->default_expr = coerce_to_specific_type_typmod(pstate,
+							transformExprRecurse(pstate, rawc->default_expr),
+												   newc->typid, newc->typmod,
+															  constructName);
+
+
+			newte->columns = lappend(newte->columns, newc);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(colnames[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			colnames[i] = rawc->colname;
+
+			i++;
+		}
+
+		pfree(colnames);
+	}
+	else
+		newte->columns = NIL;
+
+	/* Namespaces, if any, also need to be transformed */
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the namespace \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(nsnames);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
 /*
  * Transform a "row compare-op row" construct
  *
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index d440dec..d59d83f 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,14 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+
+			/*
+			 * Make TableExpr act like a regular function. Only XMLTABLE expr
+			 * is supported in this moment.
+			 */
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..ef12407 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8306,6 +8307,105 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/*
+				 * Deparse TableExpr - the is only one TableExpr producer, the
+				 * function XMLTABLE.
+				 */
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_expr, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->document_expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->columns != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->column_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->column_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..9ab68ed 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,50 @@ 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;
+	TupleDesc	tupdesc;
+	char	   *def_namespace_name;
+	FmgrInfo   *in_functions;
+	Oid		   *typioparams;
+	long int	row_count;
+	PgXmlErrorContext *xmlerrcxt;
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr	doc;
+	xmlXPathContextPtr xpathcxt;
+	xmlXPathCompExprPtr xpathcomp;
+	xmlXPathObjectPtr xpathobj;
+	xmlXPathCompExprPtr *xpathscomp;
+} XmlTableBuilderData;
+#endif
+
+static TableExprBuilder XmlTableCreateBuilder(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparms);
+static void XmlTableSetContent(TableExprBuilder tebuilder, Datum value);
+static void XmlTableDeclareNamespace(TableExprBuilder tebuilder, char *name, char *uri);
+static void XmlTableSetRowFilter(TableExprBuilder tebuilder, char *path);
+static void XmlTableSetColumnFilter(TableExprBuilder tebuilder, char *path, int colnum);
+static bool XmlTableFetchRow(TableExprBuilder tebuilder);
+static Datum XmlTableGetValue(TableExprBuilder tebuilder, int colnum, bool *isnull);
+static void XmlTableDestroyBuilder(TableExprBuilder tebuilder);
+
+const TableExprRoutine XmlTableExprRoutine =
+{
+	XmlTableCreateBuilder,
+	XmlTableSetContent,
+	XmlTableDeclareNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyBuilder
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4110,568 @@ 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
+
+/*
+ * Create XmlTableBuilderData for XmlTable builder. Initialize XML parser.
+ */
+static TableExprBuilder
+XmlTableCreateBuilder(TupleDesc tupdesc,
+					  FmgrInfo *in_functions, Oid *typioparams)
+{
+#ifdef USE_LIBXML
+	TableExprBuilder result;
+	XmlTableBuilderData *xtCxt = NULL;
+	PgXmlErrorContext *xmlerrcxt = NULL;
+	volatile xmlParserCtxtPtr ctxt = NULL;
+
+	xtCxt = palloc0(sizeof(XmlTableBuilderData));
+	xtCxt->magic = XMLTABLE_CONTEXT_MAGIC;
+	xtCxt->tupdesc = tupdesc;
+	xtCxt->in_functions = in_functions;
+	xtCxt->typioparams = typioparams;
+	xtCxt->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * tupdesc->natts);
+
+	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+	PG_TRY();
+	{
+		xmlInitParser();
+
+		ctxt = xmlNewParserCtxt();
+		if (ctxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate parser context");
+	}
+	PG_CATCH();
+	{
+		if (ctxt != NULL)
+			xmlFreeParserCtxt(ctxt);
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xtCxt->xmlerrcxt = xmlerrcxt;
+	xtCxt->ctxt = ctxt;
+
+	result = palloc(sizeof(TableExprBuilderData));
+	result->private_data = xtCxt;
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(TableExprBuilder tebuilder, Datum value)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	xmltype    *xmlval = DatumGetXmlP(value);
+	xmlChar    *xstr;
+	int			length;
+	volatile xmlDocPtr doc = NULL;
+	volatile xmlXPathContextPtr xpathcxt = NULL;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetContent called with invalid XmlTableBuilderData");
+
+	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 */
+}
+
+/*
+ * XmlTableDeclareNamespace.  Add a namespace declaration.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableDeclareNamespace(TableExprBuilder tebuilder, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid XmlTableBuilderData");
+
+	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(TableExprBuilder tebuilder, char *path)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	xmlChar    *xstr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetRowFilter called with invalid XmlTableBuilderData");
+
+	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
+ */
+static void
+XmlTableSetColumnFilter(TableExprBuilder tebuilder, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	xmlChar    *xstr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid XmlTableBuilderData");
+
+	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
+	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(TableExprBuilder tebuilder)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableFetchRow called with invalid XmlTableBuilderData");
+
+	/*
+	 * 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(TableExprBuilder tebuilder, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableGetValue called with invalid XmlTableBuilderData");
+
+	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 (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = NULL;
+
+		/* fast leaving */
+		if (cur->type != XML_ELEMENT_NODE)
+			elog(ERROR, "unexpected xmlNode type");
+
+		PG_TRY();
+		{
+			/* Set current node as entry point for XPath evaluation */
+			xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+			/* Evaluate column path */
+			column_xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+			if (column_xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+				xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+							"could not create XPath object");
+
+			if (column_xpathobj->type == XPATH_NODESET)
+			{
+				int			count;
+
+				if (column_xpathobj->nodesetval != NULL)
+					count = column_xpathobj->nodesetval->nodeNr;
+				else
+					count = 0;
+
+				if (count > 0)
+				{
+					Oid			targettypid = xtCxt->tupdesc->attrs[colnum]->atttypid;
+
+					if (count == 1)
+					{
+						if (targettypid != XMLOID)
+						{
+							xmlChar    *str;
+
+							str = xmlNodeListGetString(xtCxt->doc,
+													   column_xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+													   1);
+
+							if (str != NULL)
+							{
+								PG_TRY();
+								{
+									/*
+									 * Copy string to PostgreSQL controlled
+									 * memory
+									 */
+									cstr = pstrdup((char *) str);
+								}
+								PG_CATCH();
+								{
+									xmlFree(str);
+									PG_RE_THROW();
+								}
+								PG_END_TRY();
+
+								xmlFree(str);
+							}
+							else
+							{
+								/* Return empty string when tag is empty */
+								cstr = pstrdup("");
+							}
+						}
+						else
+						{
+							/* simple case, result is one value */
+							cstr = xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[0],
+													xtCxt->xmlerrcxt);
+						}
+					}
+					else
+					{
+						StringInfoData str;
+						int			i;
+
+						/*
+						 * When result is not one value, then we can work with
+						 * concated values. But it requires XML as expected
+						 * type.
+						 */
+						if (targettypid != XMLOID)
+							ereport(ERROR,
+									(errcode(ERRCODE_CARDINALITY_VIOLATION),
+									 errmsg("more than one value returned by column XPath expression")));
+
+						/* Concate serialized values */
+						initStringInfo(&str);
+						for (i = 0; i < count; i++)
+						{
+							appendStringInfoString(&str,
+												   xml_xmlnodetostr(column_xpathobj->nodesetval->nodeTab[i],
+														  xtCxt->xmlerrcxt));
+						}
+						cstr = str.data;
+					}
+
+					result = InputFunctionCall(&xtCxt->in_functions[colnum],
+											   cstr,
+											   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+					*isnull = false;
+				}
+				else
+					*isnull = true;
+			}
+			else if (column_xpathobj->type == XPATH_STRING)
+			{
+				result = InputFunctionCall(&xtCxt->in_functions[colnum],
+										 (char *) column_xpathobj->stringval,
+										   xtCxt->typioparams[colnum],
+								   xtCxt->tupdesc->attrs[colnum]->atttypmod);
+				*isnull = false;
+			}
+			else
+				elog(ERROR, "unexpected XPath object type");
+
+			xmlXPathFreeObject(column_xpathobj);
+			column_xpathobj = NULL;
+		}
+		PG_CATCH();
+		{
+			if (column_xpathobj != NULL)
+				xmlXPathFreeObject(column_xpathobj);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+	else
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		cstr = xml_xmlnodetostr(cur, xtCxt->xmlerrcxt);
+		result = InputFunctionCall(&xtCxt->in_functions[0],
+								   cstr,
+								   xtCxt->typioparams[0],
+								   -1); /* target type is XML always */
+		*isnull = false;
+	}
+
+	if (cstr != NULL)
+		pfree(cstr);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * Release all libxml2 memory and related resources
+ */
+static void
+XmlTableDestroyBuilder(TableExprBuilder tebuilder)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt = (XmlTableBuilderData *) tebuilder->private_data;
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	/* Defend against someone passing us a bogus context struct */
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDestroyContext called with invalid XmlTableBuilderData");
+
+	if (xtCxt->xpathscomp != NULL)
+	{
+		int			i;
+
+		for (i = 0; i < xtCxt->tupdesc->natts; i++)
+			if (xtCxt->xpathscomp[i] != NULL)
+				xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
+	}
+
+	if (xtCxt->xpathobj != NULL)
+		xmlXPathFreeObject(xtCxt->xpathobj);
+	if (xtCxt->xpathcomp != NULL)
+		xmlXPathFreeCompExpr(xtCxt->xpathcomp);
+	if (xtCxt->xpathcxt != NULL)
+		xmlXPathFreeContext(xtCxt->xpathcxt);
+	if (xtCxt->doc != NULL)
+		xmlFreeDoc(xtCxt->doc);
+	if (xtCxt->ctxt != NULL)
+		xmlFreeParserCtxt(xtCxt->ctxt);
+
+	pg_xml_done(xtCxt->xmlerrcxt, true);
+
+	/* not valid anymore */
+	xtCxt->magic = 0;
+
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c6d69bc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -272,6 +272,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
 extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..6516a33
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,73 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "access/tupdesc.h"
+#include "fmgr.h"
+
+/*
+ * TableExprRoutine holds function pointers used for generating content of
+ * table-expression functions, such as XMLTABLE.
+ *
+ * CreateContext creates the table builder context.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed as
+ * created by the executor scaffold.  The memory context being passed is an
+ * IMO bogus thing that shall be fixed by Pavel.
+ *
+ * SetContent is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * DeclareNamespace 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.
+ *
+ * DestroyContext 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 TableExprBuilderData
+{
+	void	   *private_data;	/* Opaque data used by table builder */
+} TableExprBuilderData;
+
+typedef struct TableExprBuilderData *TableExprBuilder;
+
+typedef struct TableExprRoutine
+{
+	TableExprBuilder	(*CreateBuilder) (TupleDesc tupdesc,
+									FmgrInfo *in_functions, Oid *typioparams);
+	void		(*SetContent) (TableExprBuilder tebuilder, Datum value);
+	void		(*DeclareNamespace) (TableExprBuilder tebuilder, char *name, char *uri);
+	void		(*SetRowFilter) (TableExprBuilder tebuilder, char *path);
+	void		(*SetColumnFilter) (TableExprBuilder tebuilder, char *path, int colnum);
+	bool		(*FetchRow) (TableExprBuilder tebuilder);
+	Datum		(*GetValue) (TableExprBuilder tebuilder, int colnum, bool *isnull);
+	void		(*DestroyBuilder) (TableExprBuilder tebuilder);
+} TableExprRoutine;
+
+#endif   /* TABLEEXPR_H */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8004d85..49cff48 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "executor/instrument.h"
+#include "executor/tableexpr.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
@@ -1011,6 +1012,36 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+/* ----------------
+ *		TableExprState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableExprState
+{
+	ExprState	xprstate;
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	TupleDesc	tupdesc;		/* cache */
+	int			ncols;			/* number of declared columns */
+	int			for_ordinality_col;		/* number of oridinality column,
+										 * started by 1 */
+	int			rownum;			/* row counter - for ordinality column */
+	ExprState  *row_expr;		/* row xpath expression */
+	ExprState  *doc_expr;		/* processed data */
+	ExprState **def_expr;		/* array of expressions for default value */
+	ExprState **col_expr;		/* array of expressions for path value */
+	bool	   *not_null;		/* for any column info if NULL is allowed or
+								 * not */
+	Datum	   *values;			/* prealloc buffer */
+	bool	   *nulls;			/* prealloc buffer */
+	FmgrInfo   *in_functions;	/* array of infunction for any column */
+	Oid		   *typioparams;	/* array of typIOParam for any column */
+	/* XXX move to better position */
+	const TableExprRoutine *routine;	/* pointers to builder functions */
+	TableExprBuilder	builder;		/* data for content builder */
+	MemoryContext buildercxt;		/* memory context used by builder */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b6e5151 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -454,6 +457,7 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..2608562 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,27 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - raw column definition in table-expression functions
+ *		like XMLTABLE.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions.
+ *
+ * If for_ordinality is true (FOR ORDINALITY), then the column is an int4
+ * column and the rest of the fields are ignored.
+ */
+typedef struct TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	TypeName   *typeName;		/* type name of generated column */
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..61b2241 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,44 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - holds data for table-expression functions like XMLTABLE
+ *
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	Node	   *row_expr;		/* row filter expression */
+	Node	   *document_expr;	/* processed data */
+	List	   *columns;		/* columns definitions */
+	List	   *namespaces;		/* list of namespaces */
+	int			location;
+} TableExpr;
+
+/*----------
+ * TableExprColumn - a column definition in table-expression functions.
+ *
+ * We can't re-use ColumnDef here; the utility command column
+ * definition has different set of attributes than table-expressions
+ * and just doesn't make sense here.
+ *
+ * Raw form of this node is TableExprRawCol node - different in
+ * typeName field. After transformation we use typid, typmod.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	Oid			typid;			/* typid of generated column */
+	int32		typmod;
+	Oid			collation;
+	bool		for_ordinality;
+	bool		is_not_null;
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..de4b8b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -441,10 +442,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..3c12df0 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -109,4 +109,6 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern const TableExprRoutine XmlTableExprRoutine;
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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 c680216..720dbdf 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1,3 +1,13 @@
+XmlTableContext
+TableExprBuilder
+TableExprState
+TableExprRawCol
+TableExpr
+TableExprColumn
+SQLValueFunction
+max_parallel_hazard_context
+TriggerTransition
+SQLValueFunctionOp
 ABITVEC
 ACCESS_ALLOWED_ACE
 ACL_SIZE_INFORMATION
#90Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#89)
2 attachment(s)
Re: patch: function xmltable

Here's v21.

* I changed the grammar by moving the NOT NULL to the column options,
and removing the IsNotNull production. It wasn't nice that "NOT NULL
DEFAULT 0" was not accepted, which it is with the new representation.

* The tuple that's returned is natively a TupleTableSlot inside the
table builder, not directly a HeapTuple. That stuff was ugly and wasn't
using the proper abstraction anyway.

* I changed the signatures of the methods so that they receive
TableExprState, and restructured the "opaque" data to be inside
TableExprState. Now we don't need to have things such as the tupdesc or
the input functions be repeated in the opaque struct. Instead they
belong to the TableExprState and the methods can read them from there.

I managed to break the case with no COLUMNS. Probably related to the
tupdesc changes. It now crashes the regression test. Too tired to
debug now; care to take a look? The other stuff seems to run fine,
though of course the regression test crashes in the middle, so perhaps
there are other problems.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-21.patchtext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 43db430..d1c12a3 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 743e7d6..c322012 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,13 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum value);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4508,253 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+
+	PG_TRY();
+	{
+		MemoryContext oldcxt;
+
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->opaque == NULL)
+		{
+			Datum		value;
+
+			/* Fill in table builder opaque area */
+			oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+			routine->InitializeBuilder(tstate);
+			MemoryContextSwitchTo(oldcxt);
+
+			/*
+			 * If evaluating the document expression returns NULL, the table
+			 * expression is empty and we return immediately.
+			 */
+			value = ExecEvalExpr(tstate->doc_expr, econtext, isNull, NULL);
+			if (*isNull)
+			{
+				*isDone = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any errors are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->opaque != NULL)
+		{
+			routine->DestroyBuilder(tstate);
+			ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		}
+
+		MemoryContextDelete(tstate->buildercxt);
+		tstate->buildercxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum value)
+{
+	const TableExprRoutine *routine;
+	MemoryContext oldcxt;
+	ListCell   *cell;
+	bool		isnull;
+	int			i;
+
+	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->SetContent(tstate, value);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Evaluate namespace specifications */
+	foreach(cell, tstate->namespaces)
+	{
+		Node	   *n = (Node *) lfirst(cell);
+		ExprState  *expr;
+		char	   *ns_name;
+		char	   *ns_uri;
+
+		if (IsA(n, NamedArgExpr))
+		{
+			NamedArgExpr *na = (NamedArgExpr *) n;
+
+			expr = (ExprState *) na->arg;
+			ns_name = na->name;
+		}
+		else
+		{
+			expr = (ExprState *) n;
+			ns_name = NULL;
+		}
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->DeclareNamespace(tstate, ns_name, ns_uri);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->row_expr, 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.
+	 */
+	for (i = 0; i < tstate->resultSlot->tts_tupleDescriptor->natts; i++)
+	{
+		char	   *col_filter;
+
+		if (tstate->col_expr[i] != NULL)
+		{
+			value = ExecEvalExpr(tstate->col_expr[i], econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("column filter expression must not be null"),
+						 errdetail("Filter for column \"%s\" is null.",
+								   NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname))));
+			col_filter = TextDatumGetCString(value);
+		}
+		else
+			col_filter = NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname);
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->SetColumnFilter(tstate, col_filter, i);
+		MemoryContextSwitchTo(oldcxt);
+	}
+}
+
+/*
+ * 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))
+	{
+		Datum	   *values = tstate->resultSlot->tts_values;
+		bool	   *nulls = tstate->resultSlot->tts_isnull;
+		int			i;
+		bool		isnull;
+		int			natts = tstate->resultSlot->tts_tupleDescriptor->natts;
+
+		ExecClearTuple(tstate->resultSlot);
+
+		for (i = 0; i < natts; i++)
+		{
+			if (i + 1 == tstate->for_ordinality_col)
+			{
+				values[i] = Int32GetDatum(tstate->rownum++);
+				nulls[i] = false;
+			}
+			else
+			{
+				MemoryContextSwitchTo(tstate->buildercxt);
+				values[i] = routine->GetValue(tstate, i, &isnull);
+				MemoryContextSwitchTo(oldcxt);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+		}
+
+		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
@@ -5262,6 +5517,108 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				TupleDesc	tupdesc;
+				int			natts;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->opaque = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				/* XXX this assumes the anon record type has been registered */
+				tupdesc = lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+													exprTypmod((Node *) te));
+				natts = tupdesc->natts;
+				Assert(natts > 0);
+				tstate->resultSlot = MakeSingleTupleTableSlot(tupdesc);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * natts);
+				tstate->typioparams = palloc(sizeof(Oid) * natts);
+
+				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]);
+				}
+
+				tstate->row_expr = ExecInitExpr((Expr *) te->row_expr, parent);
+				tstate->doc_expr = ExecInitExpr((Expr *) te->document_expr, parent);
+
+				if (te->columns)
+				{
+					ListCell   *col;
+
+					Assert(natts == list_length(te->columns));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * natts);
+					tstate->col_expr = palloc0(sizeof(ExprState *) * natts);
+					tstate->not_null = palloc0(sizeof(bool) * natts);
+					tstate->rownum = 1;
+
+					i = 0;
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+															   parent);
+							tstate->col_expr[i] = ExecInitExpr((Expr *) tec->column_expr,
+															   parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..8c722ed 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,48 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+	TupleDesc	typeInfo;
+
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		int			i = 1;
+
+		typeInfo = CreateTemplateTupleDesc(list_length(te->columns), false);
+
+		foreach(col, te->columns)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(typeInfo,
+							   (AttrNumber) i,
+							   pstrdup(tec->colname),
+							   tec->typid,
+							   tec->typmod,
+							   0);
+			i++;
+		}
+	}
+	else
+	{
+		typeInfo = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(typeInfo, (AttrNumber) 1,
+						   "xmltable", XMLOID, -1, 0);
+	}
+
+	/* XXX OK to do this?  looks a bit out of place ... */
+	assign_record_type_typmod(typeInfo);
+
+	return typeInfo;
+}
+
+/*
  * ExecTypeSetColNames - set column names in a TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..c04febb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(namespaces);
+	COPY_NODE_FIELD(document_expr);
+	COPY_NODE_FIELD(row_expr);
+	COPY_NODE_FIELD(columns);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4600,6 +4657,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5104,6 +5167,9 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..238fc30 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2615,6 +2615,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2645,6 +2675,18 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(row_expr);
+	COMPARE_NODE_FIELD(document_expr);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2910,6 +2952,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3401,6 +3449,9 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 20e2dbd..592760f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -17,6 +17,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "executor/executor.h"
 #include "fmgr.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..6db3ccf 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +258,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +499,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate a blessed tupdesc from the column definitions, and
+				 * returns its typmod.
+				 */
+				TupleDesc	tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +747,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 +951,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1155,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1587,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2249,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->column_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3069,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_expr, te->row_expr, Node *);
+				MUTATE(newnode->document_expr, te->document_expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->columns, te->columns, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->column_expr, tec->column_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3708,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..a3ee9bd 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_NODE_FIELD(document_expr);
+	WRITE_NODE_FIELD(row_expr);
+	WRITE_NODE_FIELD(columns);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3539,6 +3581,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..1aed810 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,44 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(namespaces);
+	READ_NODE_FIELD(document_expr);
+	READ_NODE_FIELD(row_expr);
+	READ_NODE_FIELD(columns);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(column_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2535,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9af29dd..5d5fd41 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -829,6 +829,12 @@ expression_returns_set_rows_walker(Node *node, double *count)
 			*count *= get_func_rows(expr->opfuncid);
 		}
 	}
+	if (IsA(node, TableExpr))
+	{
+		/* we have not any method how to estimate it, use default */
+		*count = 1000;
+		return false;
+	}
 
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
@@ -3598,6 +3604,33 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (tec->column_expr != NULL || tec->default_expr != NULL)
+				{
+					TableExprColumn *newtec = makeNode(TableExprColumn);
+
+					newtec->colname = tec->colname;
+					newtec->typid = tec->typid;
+					newtec->typmod = tec->typmod;
+					newtec->collation = tec->collation;
+					newtec->for_ordinality = tec->for_ordinality;
+					newtec->is_not_null = tec->is_not_null;
+					newtec->column_expr =
+						eval_const_expressions_mutator((Node *) tec->column_expr,
+													   context);
+					newtec->default_expr =
+						eval_const_expressions_mutator((Node *) tec->default_expr,
+													   context);
+					newtec->location = tec->location;
+
+					return (Node *) newtec;
+				}
+			}
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d6274b4..277fae8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -545,6 +545,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	XmlTableExprColList XmlTableExprColOptions
+%type <node>	XmlTableExprCol
+%type <defelt>	XmlTableExprColOption
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -576,10 +582,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
-	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
-	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-	CROSS CSV CUBE CURRENT_P
+	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS
+	COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION
+	CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST
+	CREATE CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -649,8 +655,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
 
@@ -12588,6 +12594,174 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+XmlTableExprColList: XmlTableExprCol						{ $$ = list_make1($1); }
+			| XmlTableExprColList ',' XmlTableExprCol		{ $$ = lappend($1, $3); }
+		;
+
+XmlTableExprCol:
+			ColId Typename
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = false;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+			| ColId Typename XmlTableExprColOptions
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+					bool				nullability_seen = false;
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = false;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->column_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->column_expr = defel->arg;
+						}
+						else 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\"", rawc->colname),
+										 parser_errposition(defel->location)));
+							rawc->is_not_null = intVal(defel->arg);
+							nullability_seen = true;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+XmlTableExprColOptions: XmlTableExprColOption				{ $$ = list_make1($1); }
+			| XmlTableExprColOptions XmlTableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+XmlTableExprColOption:
+			IDENT b_expr
+				{ $$ = makeDefElem($1, $2, @1); }
+			| DEFAULT b_expr
+				{ $$ = makeDefElem("default", $2, @1); }
+			| NOT NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(false), @1); }
+			| NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(true), @1); }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			b_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT b_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
 		;
 
 /*
@@ -13696,6 +13870,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13997,10 +14172,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..06739f6 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1095,9 +1095,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1105,9 +1105,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1116,7 +1116,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1143,6 +1143,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 17d1cbf..42269a8 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2765,6 +2771,172 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a table expression.
+ *
+ * Transform the namespace clauses, the document-generating expression, the
+ * row-generating expression, the column-generating expressions, and the
+ * default value expressions.
+ */
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	const char *constructName;
+	Oid			exprType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	exprType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, te->location);
+
+	/* Transform the row-generating Xpath expression ... */
+	Assert(te->row_expr != NULL);
+	newte->row_expr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_expr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML itself */
+	Assert(te->document_expr != NULL);
+	newte->document_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, te->document_expr),
+												   exprType,
+												   constructName);
+
+	/* Columns, if any, also need to be transformed */
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		List	   *transformlist = NIL;
+		bool		for_ordinality = false;
+		char	  **colnames;
+		int			i = 0;
+
+		colnames = palloc(sizeof(char *) * list_length(te->columns));
+
+		/*
+		 * For each TableExprRawCol in the input TableExpr, generate one
+		 * TableExprColumn in the output TableExpr.
+		 */
+		foreach(col, te->columns)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc;
+			int			j;
+
+			/* Only one FOR ORDINALITY column is allowed; verify */
+			if (rawc->for_ordinality)
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+			}
+
+			newc = makeNode(TableExprColumn);
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				newc->typid = INT4OID;
+				newc->typmod = -1;
+			}
+			else
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &newc->typid, &newc->typmod);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->column_expr)
+				newc->column_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, rawc->column_expr),
+															TEXTOID,
+															constructName);
+			if (rawc->default_expr)
+				newc->default_expr = coerce_to_specific_type_typmod(pstate,
+							transformExprRecurse(pstate, rawc->default_expr),
+												   newc->typid, newc->typmod,
+															  constructName);
+
+
+			transformlist = lappend(transformlist, newc);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(colnames[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			colnames[i] = rawc->colname;
+
+			i++;
+		}
+
+		newte->columns = transformlist;
+		pfree(colnames);
+	}
+	else
+		newte->columns = NIL;
+
+	/* Namespaces, if any, also need to be transformed */
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("namespace name \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(nsnames);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
+/*
  * Transform a "row compare-op row" construct
  *
  * 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 d440dec..d59d83f 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,14 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+
+			/*
+			 * Make TableExpr act like a regular function. Only XMLTABLE expr
+			 * is supported in this moment.
+			 */
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..ef12407 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8306,6 +8307,105 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/*
+				 * Deparse TableExpr - the is only one TableExpr producer, the
+				 * function XMLTABLE.
+				 */
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_expr, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->document_expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->columns != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->column_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->column_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..b9e5f18 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 XmlTableInitializeBuilder(TableExprState *state);
+static void XmlTableSetContent(TableExprState *state, Datum value);
+static void XmlTableDeclareNamespace(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 =
+{
+	XmlTableInitializeBuilder,
+	XmlTableSetContent,
+	XmlTableDeclareNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyBuilder
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4109,585 @@ 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
+XmlTableInitializeBuilder(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 */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+
+	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 */
+}
+
+/*
+ * XmlTableDeclareNamespace.  Add a namespace declaration.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableDeclareNamespace(TableExprState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid TableExprState");
+
+	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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetRowFilter called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetRowFilter called with invalid TableExprState");
+
+	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
+ */
+static void
+XmlTableSetColumnFilter(TableExprState *state, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	xmlChar    *xstr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid TableExprState");
+
+	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
+	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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableFetchRow called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableFetchRow called with invalid TableExprState");
+
+	/*
+	 * 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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableGetValue called with invalid TableExprState");
+
+	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 (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = 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
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		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 = (XmlTableBuilderData *) state->opaque;
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableDestroyBuilder called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDestroyBuilder called with invalid TableExprState");
+
+	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 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c6d69bc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -272,6 +272,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
 extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..34f5f29
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "access/tupdesc.h"
+#include "fmgr.h"
+
+/*
+ * TableExprRoutine holds function pointers used for generating content of
+ * table-expression functions, such as XMLTABLE.
+ *
+ * CreateContext creates the table builder context.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed as
+ * created by the executor scaffold.  The memory context being passed is an
+ * IMO bogus thing that shall be fixed by Pavel.
+ *
+ * SetContent is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * DeclareNamespace 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.
+ *
+ * DestroyContext 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		(*InitializeBuilder) (TableExprState *state);
+	void		(*SetContent) (TableExprState *state, Datum value);
+	void		(*DeclareNamespace) (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/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8004d85..a1ff800 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1011,6 +1011,30 @@ 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;
+	struct TableExprRoutine *routine;	/* table builder methods */
+	MemoryContext buildercxt;	/* memory context used by builder */
+	void	   *opaque;			/* content builder private space */
+	TupleTableSlot *resultSlot; /* output tuple table slot */
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	FmgrInfo   *in_functions;	/* input function for each column */
+	Oid		   *typioparams;	/* typioparam for each column */
+	AttrNumber	for_ordinality_col;		/* FOR ORDINALITY column */
+	int			rownum;			/* row number to be output next */
+	ExprState  *doc_expr;		/* state for document expression */
+	ExprState  *row_expr;		/* state for row-generating expression */
+	ExprState **col_expr;		/* state for column-generating expression */
+	ExprState **def_expr;		/* state for column default values */
+	bool	   *not_null;		/* nullability flag for each column */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b6e5151 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -454,6 +457,7 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..bf42692 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,23 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - 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 TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	TypeName   *typeName;		/* type of generated column */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..9e149e2 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,37 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - for table expressions, such as XMLTABLE.
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	List	   *namespaces;		/* list of namespaces */
+	Node	   *document_expr;	/* processed data */
+	Node	   *row_expr;		/* row filter expression */
+	List	   *columns;		/* columns definitions */
+	int			location;		/* token location, or -1 if unknown */
+} TableExpr;
+
+/*----------
+ * TableExprColumn - one column in a table expression.
+ *		Raw form of this is TableExprRawCol.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	Oid			typid;			/* type of generated column */
+	int32		typmod;			/* typmod of generated column */
+	Oid			collation;		/* column collation */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..de4b8b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -441,10 +442,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..4ed5297 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..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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 c680216..099c6ee 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
xmltable-21.patchtext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 43db430..d1c12a3 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 743e7d6..c322012 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,13 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum value);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4508,253 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+
+	PG_TRY();
+	{
+		MemoryContext oldcxt;
+
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->opaque == NULL)
+		{
+			Datum		value;
+
+			/* Fill in table builder opaque area */
+			oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+			routine->InitializeBuilder(tstate);
+			MemoryContextSwitchTo(oldcxt);
+
+			/*
+			 * If evaluating the document expression returns NULL, the table
+			 * expression is empty and we return immediately.
+			 */
+			value = ExecEvalExpr(tstate->doc_expr, econtext, isNull, NULL);
+			if (*isNull)
+			{
+				*isDone = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any errors are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->opaque != NULL)
+		{
+			routine->DestroyBuilder(tstate);
+			ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		}
+
+		MemoryContextDelete(tstate->buildercxt);
+		tstate->buildercxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum value)
+{
+	const TableExprRoutine *routine;
+	MemoryContext oldcxt;
+	ListCell   *cell;
+	bool		isnull;
+	int			i;
+
+	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->SetContent(tstate, value);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Evaluate namespace specifications */
+	foreach(cell, tstate->namespaces)
+	{
+		Node	   *n = (Node *) lfirst(cell);
+		ExprState  *expr;
+		char	   *ns_name;
+		char	   *ns_uri;
+
+		if (IsA(n, NamedArgExpr))
+		{
+			NamedArgExpr *na = (NamedArgExpr *) n;
+
+			expr = (ExprState *) na->arg;
+			ns_name = na->name;
+		}
+		else
+		{
+			expr = (ExprState *) n;
+			ns_name = NULL;
+		}
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->DeclareNamespace(tstate, ns_name, ns_uri);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->row_expr, 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.
+	 */
+	for (i = 0; i < tstate->resultSlot->tts_tupleDescriptor->natts; i++)
+	{
+		char	   *col_filter;
+
+		if (tstate->col_expr[i] != NULL)
+		{
+			value = ExecEvalExpr(tstate->col_expr[i], econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("column filter expression must not be null"),
+						 errdetail("Filter for column \"%s\" is null.",
+								   NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname))));
+			col_filter = TextDatumGetCString(value);
+		}
+		else
+			col_filter = NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname);
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->SetColumnFilter(tstate, col_filter, i);
+		MemoryContextSwitchTo(oldcxt);
+	}
+}
+
+/*
+ * 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))
+	{
+		Datum	   *values = tstate->resultSlot->tts_values;
+		bool	   *nulls = tstate->resultSlot->tts_isnull;
+		int			i;
+		bool		isnull;
+		int			natts = tstate->resultSlot->tts_tupleDescriptor->natts;
+
+		ExecClearTuple(tstate->resultSlot);
+
+		for (i = 0; i < natts; i++)
+		{
+			if (i + 1 == tstate->for_ordinality_col)
+			{
+				values[i] = Int32GetDatum(tstate->rownum++);
+				nulls[i] = false;
+			}
+			else
+			{
+				MemoryContextSwitchTo(tstate->buildercxt);
+				values[i] = routine->GetValue(tstate, i, &isnull);
+				MemoryContextSwitchTo(oldcxt);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+		}
+
+		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
@@ -5262,6 +5517,108 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				TupleDesc	tupdesc;
+				int			natts;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->opaque = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				/* XXX this assumes the anon record type has been registered */
+				tupdesc = lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+													exprTypmod((Node *) te));
+				natts = tupdesc->natts;
+				Assert(natts > 0);
+				tstate->resultSlot = MakeSingleTupleTableSlot(tupdesc);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * natts);
+				tstate->typioparams = palloc(sizeof(Oid) * natts);
+
+				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]);
+				}
+
+				tstate->row_expr = ExecInitExpr((Expr *) te->row_expr, parent);
+				tstate->doc_expr = ExecInitExpr((Expr *) te->document_expr, parent);
+
+				if (te->columns)
+				{
+					ListCell   *col;
+
+					Assert(natts == list_length(te->columns));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * natts);
+					tstate->col_expr = palloc0(sizeof(ExprState *) * natts);
+					tstate->not_null = palloc0(sizeof(bool) * natts);
+					tstate->rownum = 1;
+
+					i = 0;
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+															   parent);
+							tstate->col_expr[i] = ExecInitExpr((Expr *) tec->column_expr,
+															   parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..8c722ed 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,48 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+	TupleDesc	typeInfo;
+
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		int			i = 1;
+
+		typeInfo = CreateTemplateTupleDesc(list_length(te->columns), false);
+
+		foreach(col, te->columns)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(typeInfo,
+							   (AttrNumber) i,
+							   pstrdup(tec->colname),
+							   tec->typid,
+							   tec->typmod,
+							   0);
+			i++;
+		}
+	}
+	else
+	{
+		typeInfo = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(typeInfo, (AttrNumber) 1,
+						   "xmltable", XMLOID, -1, 0);
+	}
+
+	/* XXX OK to do this?  looks a bit out of place ... */
+	assign_record_type_typmod(typeInfo);
+
+	return typeInfo;
+}
+
+/*
  * ExecTypeSetColNames - set column names in a TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..c04febb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(namespaces);
+	COPY_NODE_FIELD(document_expr);
+	COPY_NODE_FIELD(row_expr);
+	COPY_NODE_FIELD(columns);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4600,6 +4657,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5104,6 +5167,9 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..238fc30 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2615,6 +2615,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2645,6 +2675,18 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(row_expr);
+	COMPARE_NODE_FIELD(document_expr);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2910,6 +2952,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3401,6 +3449,9 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 20e2dbd..592760f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -17,6 +17,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "executor/executor.h"
 #include "fmgr.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..6db3ccf 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +258,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +499,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate a blessed tupdesc from the column definitions, and
+				 * returns its typmod.
+				 */
+				TupleDesc	tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +747,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 +951,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1155,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1587,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2249,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->column_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3069,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_expr, te->row_expr, Node *);
+				MUTATE(newnode->document_expr, te->document_expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->columns, te->columns, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->column_expr, tec->column_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3708,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..a3ee9bd 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_NODE_FIELD(document_expr);
+	WRITE_NODE_FIELD(row_expr);
+	WRITE_NODE_FIELD(columns);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3539,6 +3581,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..1aed810 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,44 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(namespaces);
+	READ_NODE_FIELD(document_expr);
+	READ_NODE_FIELD(row_expr);
+	READ_NODE_FIELD(columns);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(column_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2535,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9af29dd..5d5fd41 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -829,6 +829,12 @@ expression_returns_set_rows_walker(Node *node, double *count)
 			*count *= get_func_rows(expr->opfuncid);
 		}
 	}
+	if (IsA(node, TableExpr))
+	{
+		/* we have not any method how to estimate it, use default */
+		*count = 1000;
+		return false;
+	}
 
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
@@ -3598,6 +3604,33 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (tec->column_expr != NULL || tec->default_expr != NULL)
+				{
+					TableExprColumn *newtec = makeNode(TableExprColumn);
+
+					newtec->colname = tec->colname;
+					newtec->typid = tec->typid;
+					newtec->typmod = tec->typmod;
+					newtec->collation = tec->collation;
+					newtec->for_ordinality = tec->for_ordinality;
+					newtec->is_not_null = tec->is_not_null;
+					newtec->column_expr =
+						eval_const_expressions_mutator((Node *) tec->column_expr,
+													   context);
+					newtec->default_expr =
+						eval_const_expressions_mutator((Node *) tec->default_expr,
+													   context);
+					newtec->location = tec->location;
+
+					return (Node *) newtec;
+				}
+			}
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d6274b4..277fae8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -545,6 +545,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	XmlTableExprColList XmlTableExprColOptions
+%type <node>	XmlTableExprCol
+%type <defelt>	XmlTableExprColOption
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -576,10 +582,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
-	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
-	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-	CROSS CSV CUBE CURRENT_P
+	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS
+	COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION
+	CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST
+	CREATE CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -649,8 +655,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
 
@@ -12588,6 +12594,174 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+XmlTableExprColList: XmlTableExprCol						{ $$ = list_make1($1); }
+			| XmlTableExprColList ',' XmlTableExprCol		{ $$ = lappend($1, $3); }
+		;
+
+XmlTableExprCol:
+			ColId Typename
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = false;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+			| ColId Typename XmlTableExprColOptions
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+					bool				nullability_seen = false;
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = false;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->column_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->column_expr = defel->arg;
+						}
+						else 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\"", rawc->colname),
+										 parser_errposition(defel->location)));
+							rawc->is_not_null = intVal(defel->arg);
+							nullability_seen = true;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+XmlTableExprColOptions: XmlTableExprColOption				{ $$ = list_make1($1); }
+			| XmlTableExprColOptions XmlTableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+XmlTableExprColOption:
+			IDENT b_expr
+				{ $$ = makeDefElem($1, $2, @1); }
+			| DEFAULT b_expr
+				{ $$ = makeDefElem("default", $2, @1); }
+			| NOT NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(false), @1); }
+			| NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(true), @1); }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			b_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT b_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
 		;
 
 /*
@@ -13696,6 +13870,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13997,10 +14172,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..06739f6 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1095,9 +1095,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1105,9 +1105,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1116,7 +1116,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1143,6 +1143,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 17d1cbf..42269a8 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2765,6 +2771,172 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a table expression.
+ *
+ * Transform the namespace clauses, the document-generating expression, the
+ * row-generating expression, the column-generating expressions, and the
+ * default value expressions.
+ */
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	const char *constructName;
+	Oid			exprType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	exprType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, te->location);
+
+	/* Transform the row-generating Xpath expression ... */
+	Assert(te->row_expr != NULL);
+	newte->row_expr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_expr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML itself */
+	Assert(te->document_expr != NULL);
+	newte->document_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, te->document_expr),
+												   exprType,
+												   constructName);
+
+	/* Columns, if any, also need to be transformed */
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		List	   *transformlist = NIL;
+		bool		for_ordinality = false;
+		char	  **colnames;
+		int			i = 0;
+
+		colnames = palloc(sizeof(char *) * list_length(te->columns));
+
+		/*
+		 * For each TableExprRawCol in the input TableExpr, generate one
+		 * TableExprColumn in the output TableExpr.
+		 */
+		foreach(col, te->columns)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc;
+			int			j;
+
+			/* Only one FOR ORDINALITY column is allowed; verify */
+			if (rawc->for_ordinality)
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+			}
+
+			newc = makeNode(TableExprColumn);
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				newc->typid = INT4OID;
+				newc->typmod = -1;
+			}
+			else
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &newc->typid, &newc->typmod);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->column_expr)
+				newc->column_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, rawc->column_expr),
+															TEXTOID,
+															constructName);
+			if (rawc->default_expr)
+				newc->default_expr = coerce_to_specific_type_typmod(pstate,
+							transformExprRecurse(pstate, rawc->default_expr),
+												   newc->typid, newc->typmod,
+															  constructName);
+
+
+			transformlist = lappend(transformlist, newc);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(colnames[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			colnames[i] = rawc->colname;
+
+			i++;
+		}
+
+		newte->columns = transformlist;
+		pfree(colnames);
+	}
+	else
+		newte->columns = NIL;
+
+	/* Namespaces, if any, also need to be transformed */
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("namespace name \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(nsnames);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
+/*
  * Transform a "row compare-op row" construct
  *
  * 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 d440dec..d59d83f 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,14 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+
+			/*
+			 * Make TableExpr act like a regular function. Only XMLTABLE expr
+			 * is supported in this moment.
+			 */
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..ef12407 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8306,6 +8307,105 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/*
+				 * Deparse TableExpr - the is only one TableExpr producer, the
+				 * function XMLTABLE.
+				 */
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_expr, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->document_expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->columns != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->column_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->column_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..b9e5f18 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 XmlTableInitializeBuilder(TableExprState *state);
+static void XmlTableSetContent(TableExprState *state, Datum value);
+static void XmlTableDeclareNamespace(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 =
+{
+	XmlTableInitializeBuilder,
+	XmlTableSetContent,
+	XmlTableDeclareNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyBuilder
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4109,585 @@ 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
+XmlTableInitializeBuilder(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 */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+
+	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 */
+}
+
+/*
+ * XmlTableDeclareNamespace.  Add a namespace declaration.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableDeclareNamespace(TableExprState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid TableExprState");
+
+	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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetRowFilter called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetRowFilter called with invalid TableExprState");
+
+	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
+ */
+static void
+XmlTableSetColumnFilter(TableExprState *state, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	xmlChar    *xstr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid TableExprState");
+
+	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
+	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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableFetchRow called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableFetchRow called with invalid TableExprState");
+
+	/*
+	 * 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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableGetValue called with invalid TableExprState");
+
+	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 (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = 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
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		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 = (XmlTableBuilderData *) state->opaque;
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableDestroyBuilder called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDestroyBuilder called with invalid TableExprState");
+
+	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 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c6d69bc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -272,6 +272,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
 extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..34f5f29
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "access/tupdesc.h"
+#include "fmgr.h"
+
+/*
+ * TableExprRoutine holds function pointers used for generating content of
+ * table-expression functions, such as XMLTABLE.
+ *
+ * CreateContext creates the table builder context.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed as
+ * created by the executor scaffold.  The memory context being passed is an
+ * IMO bogus thing that shall be fixed by Pavel.
+ *
+ * SetContent is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * DeclareNamespace 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.
+ *
+ * DestroyContext 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		(*InitializeBuilder) (TableExprState *state);
+	void		(*SetContent) (TableExprState *state, Datum value);
+	void		(*DeclareNamespace) (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/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8004d85..a1ff800 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1011,6 +1011,30 @@ 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;
+	struct TableExprRoutine *routine;	/* table builder methods */
+	MemoryContext buildercxt;	/* memory context used by builder */
+	void	   *opaque;			/* content builder private space */
+	TupleTableSlot *resultSlot; /* output tuple table slot */
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	FmgrInfo   *in_functions;	/* input function for each column */
+	Oid		   *typioparams;	/* typioparam for each column */
+	AttrNumber	for_ordinality_col;		/* FOR ORDINALITY column */
+	int			rownum;			/* row number to be output next */
+	ExprState  *doc_expr;		/* state for document expression */
+	ExprState  *row_expr;		/* state for row-generating expression */
+	ExprState **col_expr;		/* state for column-generating expression */
+	ExprState **def_expr;		/* state for column default values */
+	bool	   *not_null;		/* nullability flag for each column */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b6e5151 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -454,6 +457,7 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..bf42692 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,23 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - 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 TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	TypeName   *typeName;		/* type of generated column */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..9e149e2 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,37 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - for table expressions, such as XMLTABLE.
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	List	   *namespaces;		/* list of namespaces */
+	Node	   *document_expr;	/* processed data */
+	Node	   *row_expr;		/* row filter expression */
+	List	   *columns;		/* columns definitions */
+	int			location;		/* token location, or -1 if unknown */
+} TableExpr;
+
+/*----------
+ * TableExprColumn - one column in a table expression.
+ *		Raw form of this is TableExprRawCol.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	Oid			typid;			/* type of generated column */
+	int32		typmod;			/* typmod of generated column */
+	Oid			collation;		/* column collation */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..de4b8b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -441,10 +442,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..4ed5297 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..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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 c680216..099c6ee 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
#91Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#90)
1 attachment(s)
Re: patch: function xmltable

2016-12-07 8:14 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Here's v21.

* I changed the grammar by moving the NOT NULL to the column options,
and removing the IsNotNull production. It wasn't nice that "NOT NULL
DEFAULT 0" was not accepted, which it is with the new representation.

* The tuple that's returned is natively a TupleTableSlot inside the
table builder, not directly a HeapTuple. That stuff was ugly and wasn't
using the proper abstraction anyway.

* I changed the signatures of the methods so that they receive
TableExprState, and restructured the "opaque" data to be inside
TableExprState. Now we don't need to have things such as the tupdesc or
the input functions be repeated in the opaque struct. Instead they
belong to the TableExprState and the methods can read them from there.

I managed to break the case with no COLUMNS. Probably related to the
tupdesc changes. It now crashes the regression test. Too tired to
debug now; care to take a look? The other stuff seems to run fine,
though of course the regression test crashes in the middle, so perhaps
there are other problems.

I fixed two issues.

1. there are not columns data when there are not any explicit column - fixed

2. there was reverse setting in NOT NULL flag

all tests passed now

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-22.patchtext/x-patch; charset=US-ASCII; name=xmltable-22.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 43db430..d1c12a3 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 743e7d6..51357ad 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,13 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum value);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4508,256 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+
+	PG_TRY();
+	{
+		MemoryContext oldcxt;
+
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->opaque == NULL)
+		{
+			Datum		value;
+
+			/* Fill in table builder opaque area */
+			oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+			routine->InitializeBuilder(tstate);
+			MemoryContextSwitchTo(oldcxt);
+
+			/*
+			 * If evaluating the document expression returns NULL, the table
+			 * expression is empty and we return immediately.
+			 */
+			value = ExecEvalExpr(tstate->doc_expr, econtext, isNull, NULL);
+			if (*isNull)
+			{
+				*isDone = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any errors are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->opaque != NULL)
+		{
+			routine->DestroyBuilder(tstate);
+			ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		}
+
+		MemoryContextDelete(tstate->buildercxt);
+		tstate->buildercxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum value)
+{
+	const TableExprRoutine *routine;
+	MemoryContext oldcxt;
+	ListCell   *cell;
+	bool		isnull;
+	int			i;
+
+	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->SetContent(tstate, value);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Evaluate namespace specifications */
+	foreach(cell, tstate->namespaces)
+	{
+		Node	   *n = (Node *) lfirst(cell);
+		ExprState  *expr;
+		char	   *ns_name;
+		char	   *ns_uri;
+
+		if (IsA(n, NamedArgExpr))
+		{
+			NamedArgExpr *na = (NamedArgExpr *) n;
+
+			expr = (ExprState *) na->arg;
+			ns_name = na->name;
+		}
+		else
+		{
+			expr = (ExprState *) n;
+			ns_name = NULL;
+		}
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->DeclareNamespace(tstate, ns_name, ns_uri);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->row_expr, 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.
+	 */
+	if (tstate->ncolumns > 0)
+	{
+		for (i = 0; i < tstate->resultSlot->tts_tupleDescriptor->natts; i++)
+		{
+			char	   *col_filter;
+
+			if (tstate->col_expr[i] != NULL)
+			{
+				value = ExecEvalExpr(tstate->col_expr[i], econtext, &isnull, NULL);
+				if (isnull)
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("column filter expression must not be null"),
+							 errdetail("Filter for column \"%s\" is null.",
+					   NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname))));
+				col_filter = TextDatumGetCString(value);
+			}
+			else
+				col_filter = NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname);
+
+			MemoryContextSwitchTo(tstate->buildercxt);
+			routine->SetColumnFilter(tstate, col_filter, i);
+			MemoryContextSwitchTo(oldcxt);
+		}
+	}
+}
+
+/*
+ * 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))
+	{
+		Datum	   *values = tstate->resultSlot->tts_values;
+		bool	   *nulls = tstate->resultSlot->tts_isnull;
+		int			i;
+		bool		isnull;
+		int			natts = tstate->resultSlot->tts_tupleDescriptor->natts;
+
+		ExecClearTuple(tstate->resultSlot);
+
+		for (i = 0; i < natts; i++)
+		{
+			if (i + 1 == tstate->for_ordinality_col)
+			{
+				values[i] = Int32GetDatum(tstate->rownum++);
+				nulls[i] = false;
+			}
+			else
+			{
+				MemoryContextSwitchTo(tstate->buildercxt);
+				values[i] = routine->GetValue(tstate, i, &isnull);
+				MemoryContextSwitchTo(oldcxt);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+		}
+
+		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
@@ -5262,6 +5520,111 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				TupleDesc	tupdesc;
+				int			natts;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->opaque = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				/* XXX this assumes the anon record type has been registered */
+				tupdesc = lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+													exprTypmod((Node *) te));
+				natts = tupdesc->natts;
+				Assert(natts > 0);
+				tstate->resultSlot = MakeSingleTupleTableSlot(tupdesc);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * natts);
+				tstate->typioparams = palloc(sizeof(Oid) * natts);
+
+				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]);
+				}
+
+				tstate->row_expr = ExecInitExpr((Expr *) te->row_expr, parent);
+				tstate->doc_expr = ExecInitExpr((Expr *) te->document_expr, parent);
+
+				if (te->columns)
+				{
+					ListCell   *col;
+
+					Assert(natts == list_length(te->columns));
+
+					tstate->def_expr = palloc0(sizeof(ExprState *) * natts);
+					tstate->col_expr = palloc0(sizeof(ExprState *) * natts);
+					tstate->not_null = palloc0(sizeof(bool) * natts);
+					tstate->rownum = 1;
+					tstate->ncolumns = natts;
+
+					i = 0;
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!tec->for_ordinality)
+						{
+							tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+															   parent);
+							tstate->col_expr[i] = ExecInitExpr((Expr *) tec->column_expr,
+															   parent);
+							tstate->not_null[i] = tec->is_not_null;
+						}
+						else
+							tstate->for_ordinality_col = i + 1;
+
+						i++;
+					}
+				}
+				else
+					tstate->ncolumns = 0;
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..8c722ed 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,48 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+	TupleDesc	typeInfo;
+
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		int			i = 1;
+
+		typeInfo = CreateTemplateTupleDesc(list_length(te->columns), false);
+
+		foreach(col, te->columns)
+		{
+			TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+			TupleDescInitEntry(typeInfo,
+							   (AttrNumber) i,
+							   pstrdup(tec->colname),
+							   tec->typid,
+							   tec->typmod,
+							   0);
+			i++;
+		}
+	}
+	else
+	{
+		typeInfo = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(typeInfo, (AttrNumber) 1,
+						   "xmltable", XMLOID, -1, 0);
+	}
+
+	/* XXX OK to do this?  looks a bit out of place ... */
+	assign_record_type_typmod(typeInfo);
+
+	return typeInfo;
+}
+
+/*
  * ExecTypeSetColNames - set column names in a TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..c04febb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,63 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(namespaces);
+	COPY_NODE_FIELD(document_expr);
+	COPY_NODE_FIELD(row_expr);
+	COPY_NODE_FIELD(columns);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4600,6 +4657,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5104,6 +5167,9 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..238fc30 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2615,6 +2615,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2645,6 +2675,18 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(row_expr);
+	COMPARE_NODE_FIELD(document_expr);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2910,6 +2952,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3401,6 +3449,9 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 20e2dbd..592760f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -17,6 +17,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "executor/executor.h"
 #include "fmgr.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..6db3ccf 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +258,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +499,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate a blessed tupdesc from the column definitions, and
+				 * returns its typmod.
+				 */
+				TupleDesc	tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +747,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 +951,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1155,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1587,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2249,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->column_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3069,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_expr, te->row_expr, Node *);
+				MUTATE(newnode->document_expr, te->document_expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->columns, te->columns, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->column_expr, tec->column_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3708,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..a3ee9bd 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,48 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outTableExpr(StringInfo str, const TableExpr *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPR");
+
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_NODE_FIELD(document_expr);
+	WRITE_NODE_FIELD(row_expr);
+	WRITE_NODE_FIELD(columns);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3539,6 +3581,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3913,9 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..1aed810 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,44 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(namespaces);
+	READ_NODE_FIELD(document_expr);
+	READ_NODE_FIELD(row_expr);
+	READ_NODE_FIELD(columns);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(column_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2535,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9af29dd..5d5fd41 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -829,6 +829,12 @@ expression_returns_set_rows_walker(Node *node, double *count)
 			*count *= get_func_rows(expr->opfuncid);
 		}
 	}
+	if (IsA(node, TableExpr))
+	{
+		/* we have not any method how to estimate it, use default */
+		*count = 1000;
+		return false;
+	}
 
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
@@ -3598,6 +3604,33 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (tec->column_expr != NULL || tec->default_expr != NULL)
+				{
+					TableExprColumn *newtec = makeNode(TableExprColumn);
+
+					newtec->colname = tec->colname;
+					newtec->typid = tec->typid;
+					newtec->typmod = tec->typmod;
+					newtec->collation = tec->collation;
+					newtec->for_ordinality = tec->for_ordinality;
+					newtec->is_not_null = tec->is_not_null;
+					newtec->column_expr =
+						eval_const_expressions_mutator((Node *) tec->column_expr,
+													   context);
+					newtec->default_expr =
+						eval_const_expressions_mutator((Node *) tec->default_expr,
+													   context);
+					newtec->location = tec->location;
+
+					return (Node *) newtec;
+				}
+			}
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d6274b4..d3254cf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -545,6 +545,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	XmlTableExprColList XmlTableExprColOptions
+%type <node>	XmlTableExprCol
+%type <defelt>	XmlTableExprColOption
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -576,10 +582,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
-	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
-	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-	CROSS CSV CUBE CURRENT_P
+	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS
+	COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION
+	CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST
+	CREATE CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -649,8 +655,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
 
@@ -12588,6 +12594,174 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+XmlTableExprColList: XmlTableExprCol						{ $$ = list_make1($1); }
+			| XmlTableExprColList ',' XmlTableExprCol		{ $$ = lappend($1, $3); }
+		;
+
+XmlTableExprCol:
+			ColId Typename
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = false;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+			| ColId Typename XmlTableExprColOptions
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+					bool				nullability_seen = false;
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = false;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->column_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->column_expr = defel->arg;
+						}
+						else 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\"", rawc->colname),
+										 parser_errposition(defel->location)));
+							rawc->is_not_null = intVal(defel->arg);
+							nullability_seen = true;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+XmlTableExprColOptions: XmlTableExprColOption				{ $$ = list_make1($1); }
+			| XmlTableExprColOptions XmlTableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+XmlTableExprColOption:
+			IDENT b_expr
+				{ $$ = makeDefElem($1, $2, @1); }
+			| DEFAULT b_expr
+				{ $$ = makeDefElem("default", $2, @1); }
+			| NOT NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(true), @1); }
+			| NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(false), @1); }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			b_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT b_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
 		;
 
 /*
@@ -13696,6 +13870,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13997,10 +14172,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..06739f6 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1095,9 +1095,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1105,9 +1105,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1116,7 +1116,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1143,6 +1143,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 17d1cbf..42269a8 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2765,6 +2771,172 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a table expression.
+ *
+ * Transform the namespace clauses, the document-generating expression, the
+ * row-generating expression, the column-generating expressions, and the
+ * default value expressions.
+ */
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	const char *constructName;
+	Oid			exprType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	exprType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, te->location);
+
+	/* Transform the row-generating Xpath expression ... */
+	Assert(te->row_expr != NULL);
+	newte->row_expr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_expr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML itself */
+	Assert(te->document_expr != NULL);
+	newte->document_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, te->document_expr),
+												   exprType,
+												   constructName);
+
+	/* Columns, if any, also need to be transformed */
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		List	   *transformlist = NIL;
+		bool		for_ordinality = false;
+		char	  **colnames;
+		int			i = 0;
+
+		colnames = palloc(sizeof(char *) * list_length(te->columns));
+
+		/*
+		 * For each TableExprRawCol in the input TableExpr, generate one
+		 * TableExprColumn in the output TableExpr.
+		 */
+		foreach(col, te->columns)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc;
+			int			j;
+
+			/* Only one FOR ORDINALITY column is allowed; verify */
+			if (rawc->for_ordinality)
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+			}
+
+			newc = makeNode(TableExprColumn);
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				newc->typid = INT4OID;
+				newc->typmod = -1;
+			}
+			else
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &newc->typid, &newc->typmod);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->column_expr)
+				newc->column_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, rawc->column_expr),
+															TEXTOID,
+															constructName);
+			if (rawc->default_expr)
+				newc->default_expr = coerce_to_specific_type_typmod(pstate,
+							transformExprRecurse(pstate, rawc->default_expr),
+												   newc->typid, newc->typmod,
+															  constructName);
+
+
+			transformlist = lappend(transformlist, newc);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(colnames[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			colnames[i] = rawc->colname;
+
+			i++;
+		}
+
+		newte->columns = transformlist;
+		pfree(colnames);
+	}
+	else
+		newte->columns = NIL;
+
+	/* Namespaces, if any, also need to be transformed */
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("namespace name \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(nsnames);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
+/*
  * Transform a "row compare-op row" construct
  *
  * 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 d440dec..d59d83f 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,14 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+
+			/*
+			 * Make TableExpr act like a regular function. Only XMLTABLE expr
+			 * is supported in this moment.
+			 */
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..ef12407 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8306,6 +8307,105 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/*
+				 * Deparse TableExpr - the is only one TableExpr producer, the
+				 * function XMLTABLE.
+				 */
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_expr, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->document_expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->columns != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->column_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->column_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..b9e5f18 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 XmlTableInitializeBuilder(TableExprState *state);
+static void XmlTableSetContent(TableExprState *state, Datum value);
+static void XmlTableDeclareNamespace(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 =
+{
+	XmlTableInitializeBuilder,
+	XmlTableSetContent,
+	XmlTableDeclareNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyBuilder
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4109,585 @@ 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
+XmlTableInitializeBuilder(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 */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+
+	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 */
+}
+
+/*
+ * XmlTableDeclareNamespace.  Add a namespace declaration.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableDeclareNamespace(TableExprState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid TableExprState");
+
+	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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetRowFilter called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetRowFilter called with invalid TableExprState");
+
+	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
+ */
+static void
+XmlTableSetColumnFilter(TableExprState *state, char *path, int colnum)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	xmlChar    *xstr;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid TableExprState");
+
+	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
+	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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableFetchRow called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableFetchRow called with invalid TableExprState");
+
+	/*
+	 * 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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableGetValue called with invalid TableExprState");
+
+	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 (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = 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
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		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 = (XmlTableBuilderData *) state->opaque;
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableDestroyBuilder called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDestroyBuilder called with invalid TableExprState");
+
+	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 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c6d69bc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -272,6 +272,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
 extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..34f5f29
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "access/tupdesc.h"
+#include "fmgr.h"
+
+/*
+ * TableExprRoutine holds function pointers used for generating content of
+ * table-expression functions, such as XMLTABLE.
+ *
+ * CreateContext creates the table builder context.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed as
+ * created by the executor scaffold.  The memory context being passed is an
+ * IMO bogus thing that shall be fixed by Pavel.
+ *
+ * SetContent is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * DeclareNamespace 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.
+ *
+ * DestroyContext 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		(*InitializeBuilder) (TableExprState *state);
+	void		(*SetContent) (TableExprState *state, Datum value);
+	void		(*DeclareNamespace) (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/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8004d85..02e7978 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1011,6 +1011,31 @@ 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;			/* content builder private space */
+	TupleTableSlot *resultSlot; /* output tuple table slot */
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	FmgrInfo   *in_functions;	/* input function for each column */
+	Oid		   *typioparams;	/* typioparam for each column */
+	AttrNumber	for_ordinality_col;		/* FOR ORDINALITY column */
+	int			rownum;			/* row number to be output next */
+	ExprState  *doc_expr;		/* state for document expression */
+	int			ncolumns;		/* number of explicitly defined columns */
+	ExprState  *row_expr;		/* state for row-generating expression */
+	ExprState **col_expr;		/* state for column-generating expression */
+	ExprState **def_expr;		/* state for column default values */
+	bool	   *not_null;		/* nullability flag for each column */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b6e5151 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -454,6 +457,7 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..bf42692 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,23 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - 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 TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	TypeName   *typeName;		/* type of generated column */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..9e149e2 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,37 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - for table expressions, such as XMLTABLE.
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	List	   *namespaces;		/* list of namespaces */
+	Node	   *document_expr;	/* processed data */
+	Node	   *row_expr;		/* row filter expression */
+	List	   *columns;		/* columns definitions */
+	int			location;		/* token location, or -1 if unknown */
+} TableExpr;
+
+/*----------
+ * TableExprColumn - one column in a table expression.
+ *		Raw form of this is TableExprRawCol.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	Oid			typid;			/* type of generated column */
+	int32		typmod;			/* typmod of generated column */
+	Oid			collation;		/* column collation */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..de4b8b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -441,10 +442,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..4ed5297 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..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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 c680216..099c6ee 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
#92Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#91)
Re: patch: function xmltable

Pavel Stehule wrote:

I fixed two issues.

2. there was reverse setting in NOT NULL flag

Ah-hah, that was silly, thanks.

1. there are not columns data when there are not any explicit column - fixed

Hmm. Now that I see how this works, by having the GetValue "guess" what
is going on and have a special case for it, I actually don't like it
very much. It seems way too magical. I think we should do away with
the "if column is NULL" case in GetValue, and instead inject a column
during transformTableExpr if columns is NIL. This has implications on
ExecInitExpr too, which currently checks for an empty column list -- it
would no longer have to do so.

Maybe this means we need an additional method, which would request "the
expr that returns the whole row", so that transformExpr can work for
XmlTable (which I think would be something like "./") and the future
JsonTable stuff (I don't know how that one would work, but I assume it's
not necessarily the same thing).

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#93Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#92)
1 attachment(s)
Re: patch: function xmltable

2016-12-07 18:34 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

I fixed two issues.

2. there was reverse setting in NOT NULL flag

Ah-hah, that was silly, thanks.

1. there are not columns data when there are not any explicit column -

fixed

Hmm. Now that I see how this works, by having the GetValue "guess" what
is going on and have a special case for it, I actually don't like it
very much. It seems way too magical. I think we should do away with
the "if column is NULL" case in GetValue, and instead inject a column
during transformTableExpr if columns is NIL. This has implications on
ExecInitExpr too, which currently checks for an empty column list -- it
would no longer have to do so.

I prefer this way against second described. The implementation should be in
table builder routines, not in executor.

sending new update

Regards

Pavel

Show quoted text

Maybe this means we need an additional method, which would request "the
expr that returns the whole row", so that transformExpr can work for
XmlTable (which I think would be something like "./") and the future
JsonTable stuff (I don't know how that one would work, but I assume it's
not necessarily the same thing).

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-23.patchtext/x-patch; charset=US-ASCII; name=xmltable-23.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 43db430..d1c12a3 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 743e7d6..cdca823 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,13 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum value);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4508,255 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+
+	PG_TRY();
+	{
+		MemoryContext oldcxt;
+
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->opaque == NULL)
+		{
+			Datum		value;
+
+			/* Fill in table builder opaque area */
+			oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+			routine->InitializeBuilder(tstate);
+			MemoryContextSwitchTo(oldcxt);
+
+			/*
+			 * If evaluating the document expression returns NULL, the table
+			 * expression is empty and we return immediately.
+			 */
+			value = ExecEvalExpr(tstate->doc_expr, econtext, isNull, NULL);
+			if (*isNull)
+			{
+				*isDone = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any errors are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->opaque != NULL)
+		{
+			routine->DestroyBuilder(tstate);
+			ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		}
+
+		MemoryContextDelete(tstate->buildercxt);
+		tstate->buildercxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum value)
+{
+	const TableExprRoutine *routine;
+	MemoryContext oldcxt;
+	ListCell   *cell;
+	bool		isnull;
+	int			i;
+
+	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->SetContent(tstate, value);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Evaluate namespace specifications */
+	foreach(cell, tstate->namespaces)
+	{
+		Node	   *n = (Node *) lfirst(cell);
+		ExprState  *expr;
+		char	   *ns_name;
+		char	   *ns_uri;
+
+		if (IsA(n, NamedArgExpr))
+		{
+			NamedArgExpr *na = (NamedArgExpr *) n;
+
+			expr = (ExprState *) na->arg;
+			ns_name = na->name;
+		}
+		else
+		{
+			expr = (ExprState *) n;
+			ns_name = NULL;
+		}
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->DeclareNamespace(tstate, ns_name, ns_uri);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->row_expr, 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.
+	 */
+	for (i = 0; i < tstate->resultSlot->tts_tupleDescriptor->natts; i++)
+	{
+		char	   *col_filter;
+
+		if (tstate->col_expr[i] != NULL)
+		{
+			value = ExecEvalExpr(tstate->col_expr[i], econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("column filter expression must not be null"),
+						 errdetail("Filter for column \"%s\" is null.",
+				   NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname))));
+			col_filter = TextDatumGetCString(value);
+		}
+		else if (!tstate->use_auto_col)
+			col_filter = NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname);
+		else
+			col_filter = NULL;
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->SetColumnFilter(tstate, col_filter, i);
+		MemoryContextSwitchTo(oldcxt);
+	}
+}
+
+/*
+ * 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))
+	{
+		Datum	   *values = tstate->resultSlot->tts_values;
+		bool	   *nulls = tstate->resultSlot->tts_isnull;
+		int			i;
+		bool		isnull;
+		int			natts = tstate->resultSlot->tts_tupleDescriptor->natts;
+
+		ExecClearTuple(tstate->resultSlot);
+
+		for (i = 0; i < natts; i++)
+		{
+			if (i + 1 == tstate->for_ordinality_col)
+			{
+				values[i] = Int32GetDatum(tstate->rownum++);
+				nulls[i] = false;
+			}
+			else
+			{
+				MemoryContextSwitchTo(tstate->buildercxt);
+				values[i] = routine->GetValue(tstate, i, &isnull);
+				MemoryContextSwitchTo(oldcxt);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+		}
+
+		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
@@ -5262,6 +5519,105 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				TupleDesc	tupdesc;
+				ListCell   *col;
+				int			natts;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->opaque = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				/* XXX this assumes the anon record type has been registered */
+				tupdesc = lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+													exprTypmod((Node *) te));
+				natts = tupdesc->natts;
+				Assert(natts > 0);
+				tstate->resultSlot = MakeSingleTupleTableSlot(tupdesc);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * natts);
+				tstate->typioparams = palloc(sizeof(Oid) * natts);
+				tstate->use_auto_col = te->use_auto_col;
+
+				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]);
+				}
+
+				tstate->row_expr = ExecInitExpr((Expr *) te->row_expr, parent);
+				tstate->doc_expr = ExecInitExpr((Expr *) te->document_expr, parent);
+
+				Assert(natts == list_length(te->columns));
+
+				tstate->def_expr = palloc0(sizeof(ExprState *) * natts);
+				tstate->col_expr = palloc0(sizeof(ExprState *) * natts);
+				tstate->not_null = palloc0(sizeof(bool) * natts);
+				tstate->rownum = 1;
+
+				i = 0;
+				foreach(col, te->columns)
+				{
+					TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+					if (!tec->for_ordinality)
+					{
+						tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+														   parent);
+						tstate->col_expr[i] = ExecInitExpr((Expr *) tec->column_expr,
+														   parent);
+						tstate->not_null[i] = tec->is_not_null;
+					}
+					else
+						tstate->for_ordinality_col = i + 1;
+
+					i++;
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..9ed4028 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,38 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+	TupleDesc	typeInfo;
+	ListCell   *col;
+	int			i = 1;
+
+	typeInfo = CreateTemplateTupleDesc(list_length(te->columns), false);
+
+	foreach(col, te->columns)
+	{
+		TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+		TupleDescInitEntry(typeInfo,
+						   (AttrNumber) i,
+						   pstrdup(tec->colname),
+						   tec->typid,
+						   tec->typmod,
+						   0);
+		i++;
+	}
+
+	/* XXX OK to do this?  looks a bit out of place ... */
+	assign_record_type_typmod(typeInfo);
+
+	return typeInfo;
+}
+
+/*
  * ExecTypeSetColNames - set column names in a TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..b65def8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,64 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(namespaces);
+	COPY_NODE_FIELD(document_expr);
+	COPY_NODE_FIELD(row_expr);
+	COPY_NODE_FIELD(columns);
+	COPY_SCALAR_FIELD(use_auto_col);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4600,6 +4658,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5104,6 +5168,9 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..9adb8ac 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2615,6 +2615,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2645,6 +2675,19 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(row_expr);
+	COMPARE_NODE_FIELD(document_expr);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_SCALAR_FIELD(use_auto_col);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2910,6 +2953,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3401,6 +3450,9 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 20e2dbd..592760f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -17,6 +17,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "executor/executor.h"
 #include "fmgr.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..6db3ccf 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +258,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +499,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate a blessed tupdesc from the column definitions, and
+				 * returns its typmod.
+				 */
+				TupleDesc	tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +747,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 +951,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1155,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1552,6 +1587,9 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2211,6 +2249,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->column_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3007,6 +3069,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_expr, te->row_expr, Node *);
+				MUTATE(newnode->document_expr, te->document_expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->columns, te->columns, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->column_expr, tec->column_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3622,6 +3708,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..5414d76 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,49 @@ _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(namespaces);
+	WRITE_NODE_FIELD(document_expr);
+	WRITE_NODE_FIELD(row_expr);
+	WRITE_NODE_FIELD(columns);
+	WRITE_BOOL_FIELD(use_auto_col);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3539,6 +3582,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3865,6 +3914,9 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..94d39dc 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,45 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(namespaces);
+	READ_NODE_FIELD(document_expr);
+	READ_NODE_FIELD(row_expr);
+	READ_NODE_FIELD(columns);
+	READ_BOOL_FIELD(use_auto_col);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(column_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2536,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9af29dd..5d5fd41 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -829,6 +829,12 @@ expression_returns_set_rows_walker(Node *node, double *count)
 			*count *= get_func_rows(expr->opfuncid);
 		}
 	}
+	if (IsA(node, TableExpr))
+	{
+		/* we have not any method how to estimate it, use default */
+		*count = 1000;
+		return false;
+	}
 
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
@@ -3598,6 +3604,33 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (tec->column_expr != NULL || tec->default_expr != NULL)
+				{
+					TableExprColumn *newtec = makeNode(TableExprColumn);
+
+					newtec->colname = tec->colname;
+					newtec->typid = tec->typid;
+					newtec->typmod = tec->typmod;
+					newtec->collation = tec->collation;
+					newtec->for_ordinality = tec->for_ordinality;
+					newtec->is_not_null = tec->is_not_null;
+					newtec->column_expr =
+						eval_const_expressions_mutator((Node *) tec->column_expr,
+													   context);
+					newtec->default_expr =
+						eval_const_expressions_mutator((Node *) tec->default_expr,
+													   context);
+					newtec->location = tec->location;
+
+					return (Node *) newtec;
+				}
+			}
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d6274b4..d3254cf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -545,6 +545,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <list>	XmlTableExprColList XmlTableExprColOptions
+%type <node>	XmlTableExprCol
+%type <defelt>	XmlTableExprColOption
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -576,10 +582,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
-	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
-	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-	CROSS CSV CUBE CURRENT_P
+	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS
+	COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION
+	CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST
+	CREATE CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -649,8 +655,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
 
@@ -12588,6 +12594,174 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+XmlTableExprColList: XmlTableExprCol						{ $$ = list_make1($1); }
+			| XmlTableExprColList ',' XmlTableExprCol		{ $$ = lappend($1, $3); }
+		;
+
+XmlTableExprCol:
+			ColId Typename
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = false;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+			| ColId Typename XmlTableExprColOptions
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+					bool				nullability_seen = false;
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = false;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->column_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->column_expr = defel->arg;
+						}
+						else 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\"", rawc->colname),
+										 parser_errposition(defel->location)));
+							rawc->is_not_null = intVal(defel->arg);
+							nullability_seen = true;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+XmlTableExprColOptions: XmlTableExprColOption				{ $$ = list_make1($1); }
+			| XmlTableExprColOptions XmlTableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+XmlTableExprColOption:
+			IDENT b_expr
+				{ $$ = makeDefElem($1, $2, @1); }
+			| DEFAULT b_expr
+				{ $$ = makeDefElem("default", $2, @1); }
+			| NOT NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(true), @1); }
+			| NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(false), @1); }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			b_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT b_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
 		;
 
 /*
@@ -13696,6 +13870,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13997,10 +14172,12 @@ col_name_keyword:
 			| XMLELEMENT
 			| XMLEXISTS
 			| XMLFOREST
+			| XMLNAMESPACES
 			| XMLPARSE
 			| XMLPI
 			| XMLROOT
 			| XMLSERIALIZE
+			| XMLTABLE
 		;
 
 /* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d277fd6..06739f6 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1095,9 +1095,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1105,9 +1105,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1116,7 +1116,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1143,6 +1143,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 17d1cbf..e6dd1d3 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2765,6 +2771,190 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a table expression.
+ *
+ * Transform the namespace clauses, the document-generating expression, the
+ * row-generating expression, the column-generating expressions, and the
+ * default value expressions.
+ */
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	const char *constructName;
+	Oid			exprType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	exprType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, te->location);
+
+	/* Transform the row-generating Xpath expression ... */
+	Assert(te->row_expr != NULL);
+	newte->row_expr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_expr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML itself */
+	Assert(te->document_expr != NULL);
+	newte->document_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, te->document_expr),
+												   exprType,
+												   constructName);
+
+	/* Columns, if any, also need to be transformed */
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		List	   *transformlist = NIL;
+		bool		for_ordinality = false;
+		char	  **colnames;
+		int			i = 0;
+
+		colnames = palloc(sizeof(char *) * list_length(te->columns));
+
+		/*
+		 * For each TableExprRawCol in the input TableExpr, generate one
+		 * TableExprColumn in the output TableExpr.
+		 */
+		foreach(col, te->columns)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc;
+			int			j;
+
+			/* Only one FOR ORDINALITY column is allowed; verify */
+			if (rawc->for_ordinality)
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+			}
+
+			newc = makeNode(TableExprColumn);
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				newc->typid = INT4OID;
+				newc->typmod = -1;
+			}
+			else
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &newc->typid, &newc->typmod);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->column_expr)
+				newc->column_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, rawc->column_expr),
+															TEXTOID,
+															constructName);
+			if (rawc->default_expr)
+				newc->default_expr = coerce_to_specific_type_typmod(pstate,
+							transformExprRecurse(pstate, rawc->default_expr),
+												   newc->typid, newc->typmod,
+															  constructName);
+
+
+			transformlist = lappend(transformlist, newc);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(colnames[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			colnames[i] = rawc->colname;
+
+			i++;
+		}
+
+		newte->columns = transformlist;
+		newte->use_auto_col = false;
+		pfree(colnames);
+	}
+	else
+	{
+		TableExprColumn *newc;
+
+		/*
+		 * When there are not explicit column, define the implicit one.
+		 * The rules for implicit column can be different. Currently
+		 * only XMLTABLE is supported.
+		 */
+		newc = makeNode(TableExprColumn);
+		newc->colname = "xmltable";
+		newc->location = -1;
+		newc->typid = XMLOID;
+		newc->typmod = -1;
+		newc->location = -1;
+
+		newte->columns = list_make1(newc);
+		newte->use_auto_col = true;
+	}
+
+	/* Namespaces, if any, also need to be transformed */
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("namespace name \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(nsnames);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
+/*
  * Transform a "row compare-op row" construct
  *
  * 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 d440dec..d59d83f 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,14 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+
+			/*
+			 * Make TableExpr act like a regular function. Only XMLTABLE expr
+			 * is supported in this moment.
+			 */
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..ef12407 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -50,6 +50,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8306,6 +8307,105 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/*
+				 * Deparse TableExpr - the is only one TableExpr producer, the
+				 * function XMLTABLE.
+				 */
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_expr, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->document_expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->columns != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->column_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->column_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..8026176 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 XmlTableInitializeBuilder(TableExprState *state);
+static void XmlTableSetContent(TableExprState *state, Datum value);
+static void XmlTableDeclareNamespace(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 =
+{
+	XmlTableInitializeBuilder,
+	XmlTableSetContent,
+	XmlTableDeclareNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyBuilder
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4109,598 @@ 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
+XmlTableInitializeBuilder(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 */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+
+	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 */
+}
+
+/*
+ * XmlTableDeclareNamespace.  Add a namespace declaration.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableDeclareNamespace(TableExprState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid TableExprState");
+
+	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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetRowFilter called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetRowFilter called with invalid TableExprState");
+
+	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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid TableExprState");
+
+	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->use_auto_col);
+
+		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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableFetchRow called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableFetchRow called with invalid TableExprState");
+
+	/*
+	 * 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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableGetValue called with invalid TableExprState");
+
+	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 (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = 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
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		Assert(state->use_auto_col);
+
+		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 = (XmlTableBuilderData *) state->opaque;
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableDestroyBuilder called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDestroyBuilder called with invalid TableExprState");
+
+	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 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c6d69bc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -272,6 +272,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
 extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..34f5f29
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "access/tupdesc.h"
+#include "fmgr.h"
+
+/*
+ * TableExprRoutine holds function pointers used for generating content of
+ * table-expression functions, such as XMLTABLE.
+ *
+ * CreateContext creates the table builder context.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed as
+ * created by the executor scaffold.  The memory context being passed is an
+ * IMO bogus thing that shall be fixed by Pavel.
+ *
+ * SetContent is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * DeclareNamespace 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.
+ *
+ * DestroyContext 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		(*InitializeBuilder) (TableExprState *state);
+	void		(*SetContent) (TableExprState *state, Datum value);
+	void		(*DeclareNamespace) (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/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8004d85..ee0b7b1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1011,6 +1011,31 @@ 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;			/* content builder private space */
+	TupleTableSlot *resultSlot; /* output tuple table slot */
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	FmgrInfo   *in_functions;	/* input function for each column */
+	Oid		   *typioparams;	/* typioparam for each column */
+	bool		use_auto_col;	/* true, when column was not defined by user*/
+	AttrNumber	for_ordinality_col;		/* FOR ORDINALITY column */
+	int			rownum;			/* row number to be output next */
+	ExprState  *doc_expr;		/* state for document expression */
+	ExprState  *row_expr;		/* state for row-generating expression */
+	ExprState **col_expr;		/* state for column-generating expression */
+	ExprState **def_expr;		/* state for column default values */
+	bool	   *not_null;		/* nullability flag for each column */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b6e5151 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -454,6 +457,7 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..bf42692 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,23 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * TableExprRawCol - 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 TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	TypeName   *typeName;		/* type of generated column */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprRawCol;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 65510b0..d7da8aa 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,38 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - for table expressions, such as XMLTABLE.
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	List	   *namespaces;		/* list of namespaces */
+	Node	   *document_expr;	/* processed data */
+	Node	   *row_expr;		/* row filter expression */
+	List	   *columns;		/* columns definitions */
+	bool		use_auto_col;	/* true, when column was not defined by user */
+	int			location;		/* token location, or -1 if unknown */
+} TableExpr;
+
+/*----------
+ * TableExprColumn - one column in a table expression.
+ *		Raw form of this is TableExprRawCol.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	Oid			typid;			/* type of generated column */
+	int32		typmod;			/* typmod of generated column */
+	Oid			collation;		/* column collation */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..de4b8b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
 PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
 PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
+PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD)
 PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
@@ -441,10 +442,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..4ed5297 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..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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 c680216..099c6ee 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
#94Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#93)
Re: patch: function xmltable

Pavel Stehule wrote:

2016-12-07 18:34 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Hmm. Now that I see how this works, by having the GetValue "guess" what
is going on and have a special case for it, I actually don't like it
very much. It seems way too magical. I think we should do away with
the "if column is NULL" case in GetValue, and instead inject a column
during transformTableExpr if columns is NIL. This has implications on
ExecInitExpr too, which currently checks for an empty column list -- it
would no longer have to do so.

I prefer this way against second described. The implementation should be in
table builder routines, not in executor.

Well, given the way you have implemented it, I would prefer the original
too. But your v23 is not what I meant. Essentially what you do in v23
is to communicate the lack of COLUMNS clause in a different way --
previously it was "ncolumns = 0", now it's "is_auto_col=true". It's
still "magic". It's not an improvement.

What I want to happen is that there is no magic at all; it's up to
transformExpr to make sure that when COLUMNS is empty, one column
appears and it must not be a magic column that makes the xml.c code act
differently, but rather to xml.c it should appear that this is just a
normal column that happens to return the entire row. If I say "COLUMNS
foo PATH '/'" I should be able to obtain a similar behavior (except that
in the current code, if I ask for "COLUMNS foo XML PATH '/'" I don't get
XML at all but rather weird text where all tags have been stripped out,
which is very strange. I would expect the tags to be preserved if the
output type is XML. Maybe the tag-stripping behavior should occur if
the output type is some type of text.)

I still have to figure out how to fix the tupledesc thing. What we have
now is not good.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#95Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#94)
Re: patch: function xmltable

2016-12-07 20:50 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2016-12-07 18:34 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Hmm. Now that I see how this works, by having the GetValue "guess"

what

is going on and have a special case for it, I actually don't like it
very much. It seems way too magical. I think we should do away with
the "if column is NULL" case in GetValue, and instead inject a column
during transformTableExpr if columns is NIL. This has implications on
ExecInitExpr too, which currently checks for an empty column list -- it
would no longer have to do so.

I prefer this way against second described. The implementation should be

in

table builder routines, not in executor.

Well, given the way you have implemented it, I would prefer the original
too. But your v23 is not what I meant. Essentially what you do in v23
is to communicate the lack of COLUMNS clause in a different way --
previously it was "ncolumns = 0", now it's "is_auto_col=true". It's
still "magic". It's not an improvement.

is_auto_col is used primary for asserting. The table builder has
information for decision in parameter path, when path is NULL.

Hard to say, if this info should be assigned to column or to table. In both
locations has sense. But somewhere should be some flag.

What I want to happen is that there is no magic at all; it's up to
transformExpr to make sure that when COLUMNS is empty, one column
appears and it must not be a magic column that makes the xml.c code act
differently, but rather to xml.c it should appear that this is just a
normal column that happens to return the entire row. If I say "COLUMNS
foo PATH '/'" I should be able to obtain a similar behavior (except that
in the current code, if I ask for "COLUMNS foo XML PATH '/'" I don't get
XML at all but rather weird text where all tags have been stripped out,
which is very strange. I would expect the tags to be preserved if the
output type is XML. Maybe the tag-stripping behavior should occur if
the output type is some type of text.)

I am doing this. Just I using NULL for PATH.

I still have to figure out how to fix the tupledesc thing. What we have
now is not good.

cannot be moved to nodefunc?

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#96Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#93)
1 attachment(s)
Re: patch: function xmltable

2016-12-07 20:37 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2016-12-07 18:34 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

I fixed two issues.

2. there was reverse setting in NOT NULL flag

Ah-hah, that was silly, thanks.

1. there are not columns data when there are not any explicit column -

fixed

Hmm. Now that I see how this works, by having the GetValue "guess" what
is going on and have a special case for it, I actually don't like it
very much. It seems way too magical. I think we should do away with
the "if column is NULL" case in GetValue, and instead inject a column
during transformTableExpr if columns is NIL. This has implications on
ExecInitExpr too, which currently checks for an empty column list -- it
would no longer have to do so.

I prefer this way against second described. The implementation should be
in table builder routines, not in executor.

sending new update

new update - no functional changes, just unbreaking after last changes in
master.

Regards

Pavel

Show quoted text

Regards

Pavel

Maybe this means we need an additional method, which would request "the
expr that returns the whole row", so that transformExpr can work for
XmlTable (which I think would be something like "./") and the future
JsonTable stuff (I don't know how that one would work, but I assume it's
not necessarily the same thing).

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-24.patchtext/x-patch; charset=US-ASCII; name=xmltable-24.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5d2749d..11ebb71 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 743e7d6..cdca823 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,13 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum value);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4508,255 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+
+	PG_TRY();
+	{
+		MemoryContext oldcxt;
+
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->opaque == NULL)
+		{
+			Datum		value;
+
+			/* Fill in table builder opaque area */
+			oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+			routine->InitializeBuilder(tstate);
+			MemoryContextSwitchTo(oldcxt);
+
+			/*
+			 * If evaluating the document expression returns NULL, the table
+			 * expression is empty and we return immediately.
+			 */
+			value = ExecEvalExpr(tstate->doc_expr, econtext, isNull, NULL);
+			if (*isNull)
+			{
+				*isDone = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any errors are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->opaque != NULL)
+		{
+			routine->DestroyBuilder(tstate);
+			ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		}
+
+		MemoryContextDelete(tstate->buildercxt);
+		tstate->buildercxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum value)
+{
+	const TableExprRoutine *routine;
+	MemoryContext oldcxt;
+	ListCell   *cell;
+	bool		isnull;
+	int			i;
+
+	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->SetContent(tstate, value);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Evaluate namespace specifications */
+	foreach(cell, tstate->namespaces)
+	{
+		Node	   *n = (Node *) lfirst(cell);
+		ExprState  *expr;
+		char	   *ns_name;
+		char	   *ns_uri;
+
+		if (IsA(n, NamedArgExpr))
+		{
+			NamedArgExpr *na = (NamedArgExpr *) n;
+
+			expr = (ExprState *) na->arg;
+			ns_name = na->name;
+		}
+		else
+		{
+			expr = (ExprState *) n;
+			ns_name = NULL;
+		}
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->DeclareNamespace(tstate, ns_name, ns_uri);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->row_expr, 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.
+	 */
+	for (i = 0; i < tstate->resultSlot->tts_tupleDescriptor->natts; i++)
+	{
+		char	   *col_filter;
+
+		if (tstate->col_expr[i] != NULL)
+		{
+			value = ExecEvalExpr(tstate->col_expr[i], econtext, &isnull, NULL);
+			if (isnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("column filter expression must not be null"),
+						 errdetail("Filter for column \"%s\" is null.",
+				   NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname))));
+			col_filter = TextDatumGetCString(value);
+		}
+		else if (!tstate->use_auto_col)
+			col_filter = NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname);
+		else
+			col_filter = NULL;
+
+		MemoryContextSwitchTo(tstate->buildercxt);
+		routine->SetColumnFilter(tstate, col_filter, i);
+		MemoryContextSwitchTo(oldcxt);
+	}
+}
+
+/*
+ * 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))
+	{
+		Datum	   *values = tstate->resultSlot->tts_values;
+		bool	   *nulls = tstate->resultSlot->tts_isnull;
+		int			i;
+		bool		isnull;
+		int			natts = tstate->resultSlot->tts_tupleDescriptor->natts;
+
+		ExecClearTuple(tstate->resultSlot);
+
+		for (i = 0; i < natts; i++)
+		{
+			if (i + 1 == tstate->for_ordinality_col)
+			{
+				values[i] = Int32GetDatum(tstate->rownum++);
+				nulls[i] = false;
+			}
+			else
+			{
+				MemoryContextSwitchTo(tstate->buildercxt);
+				values[i] = routine->GetValue(tstate, i, &isnull);
+				MemoryContextSwitchTo(oldcxt);
+
+				if (isnull && tstate->def_expr[i] != NULL)
+					values[i] = ExecEvalExpr(tstate->def_expr[i], econtext, &isnull, NULL);
+
+				if (isnull && tstate->not_null[i])
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+									NameStr(tstate->resultSlot->tts_tupleDescriptor->attrs[i]->attname))));
+				nulls[i] = isnull;
+			}
+		}
+
+		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
@@ -5262,6 +5519,105 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				TupleDesc	tupdesc;
+				ListCell   *col;
+				int			natts;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->opaque = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				/* XXX this assumes the anon record type has been registered */
+				tupdesc = lookup_rowtype_tupdesc_copy(exprType((Node *) te),
+													exprTypmod((Node *) te));
+				natts = tupdesc->natts;
+				Assert(natts > 0);
+				tstate->resultSlot = MakeSingleTupleTableSlot(tupdesc);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * natts);
+				tstate->typioparams = palloc(sizeof(Oid) * natts);
+				tstate->use_auto_col = te->use_auto_col;
+
+				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]);
+				}
+
+				tstate->row_expr = ExecInitExpr((Expr *) te->row_expr, parent);
+				tstate->doc_expr = ExecInitExpr((Expr *) te->document_expr, parent);
+
+				Assert(natts == list_length(te->columns));
+
+				tstate->def_expr = palloc0(sizeof(ExprState *) * natts);
+				tstate->col_expr = palloc0(sizeof(ExprState *) * natts);
+				tstate->not_null = palloc0(sizeof(bool) * natts);
+				tstate->rownum = 1;
+
+				i = 0;
+				foreach(col, te->columns)
+				{
+					TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+					if (!tec->for_ordinality)
+					{
+						tstate->def_expr[i] = ExecInitExpr((Expr *) tec->default_expr,
+														   parent);
+						tstate->col_expr[i] = ExecInitExpr((Expr *) tec->column_expr,
+														   parent);
+						tstate->not_null[i] = tec->is_not_null;
+					}
+					else
+						tstate->for_ordinality_col = i + 1;
+
+					i++;
+				}
+
+				if (te->namespaces)
+				{
+					List	   *preparedlist = NIL;
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							preparedlist = lappend(preparedlist, nax);
+						}
+						else
+							preparedlist = lappend(preparedlist,
+										   ExecInitExpr((Expr *) n, parent));
+					}
+					tstate->namespaces = preparedlist;
+				}
+				else
+					tstate->namespaces = NIL;
+
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..9ed4028 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -975,6 +975,38 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
+ * Build a tuple descriptor for TableExpr using its declared column list, and
+ * assign it a record's typmod.
+ */
+TupleDesc
+ExecTypeFromTableExpr(const TableExpr *te)
+{
+	TupleDesc	typeInfo;
+	ListCell   *col;
+	int			i = 1;
+
+	typeInfo = CreateTemplateTupleDesc(list_length(te->columns), false);
+
+	foreach(col, te->columns)
+	{
+		TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+		TupleDescInitEntry(typeInfo,
+						   (AttrNumber) i,
+						   pstrdup(tec->colname),
+						   tec->typid,
+						   tec->typmod,
+						   0);
+		i++;
+	}
+
+	/* XXX OK to do this?  looks a bit out of place ... */
+	assign_record_type_typmod(typeInfo);
+
+	return typeInfo;
+}
+
+/*
  * ExecTypeSetColNames - set column names in a TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d973225..5918de1 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,64 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(namespaces);
+	COPY_NODE_FIELD(document_expr);
+	COPY_NODE_FIELD(row_expr);
+	COPY_NODE_FIELD(columns);
+	COPY_SCALAR_FIELD(use_auto_col);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprRawCol
+ */
+static TableExprRawCol *
+_copyTableExprRawCol(const TableExprRawCol *from)
+{
+	TableExprRawCol *newnode = makeNode(TableExprRawCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyTableExprColumn
+ */
+static TableExprColumn *
+_copyTableExprColumn(const TableExprColumn *from)
+{
+	TableExprColumn *newnode = makeNode(TableExprColumn);
+
+	COPY_STRING_FIELD(colname);
+	COPY_SCALAR_FIELD(typid);
+	COPY_SCALAR_FIELD(typmod);
+	COPY_SCALAR_FIELD(collation);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(column_expr);
+	COPY_NODE_FIELD(default_expr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -4666,6 +4724,12 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
+		case T_TableExprColumn:
+			retval = _copyTableExprColumn(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5185,6 +5249,9 @@ copyObject(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_TableExprRawCol:
+			retval = _copyTableExprRawCol(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index edc1797..882679b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2617,6 +2617,36 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 }
 
 static bool
+_equalTableExprRawCol(const TableExprRawCol *a, const TableExprRawCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalTableExprColumn(const TableExprColumn *a, const TableExprColumn *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_SCALAR_FIELD(typid);
+	COMPARE_SCALAR_FIELD(typmod);
+	COMPARE_SCALAR_FIELD(collation);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(column_expr);
+	COMPARE_NODE_FIELD(default_expr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 {
 	COMPARE_SCALAR_FIELD(xmloption);
@@ -2700,6 +2730,19 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(row_expr);
+	COMPARE_NODE_FIELD(document_expr);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_SCALAR_FIELD(use_auto_col);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2965,6 +3008,12 @@ equal(const void *a, const void *b)
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
+		case T_TableExprColumn:
+			retval = _equalTableExprColumn(a, b);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -3471,6 +3520,9 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_TableExprRawCol:
+			retval = _equalTableExprRawCol(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 20e2dbd..592760f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -17,6 +17,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "executor/executor.h"
 #include "fmgr.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 973fb15..84331a1 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/relation.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/typcache.h"
 
 
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -257,6 +258,12 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			type = RECORDOID;
+			break;
+		case T_TableExprColumn:
+			type = ((const TableExprColumn *) expr)->typid;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -492,6 +499,19 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_TableExpr:
+			{
+				/*
+				 * The result of table expression is pseudo-type record.
+				 * Generate a blessed tupdesc from the column definitions, and
+				 * returns its typmod.
+				 */
+				TupleDesc	tupdesc = ExecTypeFromTableExpr((const TableExpr *) expr);
+
+				return tupdesc->tdtypmod;
+			}
+		case T_TableExprColumn:
+			return ((const TableExprColumn *) expr)->typmod;
 		default:
 			break;
 	}
@@ -727,6 +747,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 +951,12 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite or XML or JSON */
+			break;
+		case T_TableExprColumn:
+			coll = ((const TableExprColumn *) expr)->collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -1127,6 +1155,13 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_CurrentOfExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite
+												 * or XML, .. */
+			break;
+		case T_TableExprColumn:
+			((TableExprColumn *) expr)->collation = collation;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			break;
@@ -1558,6 +1593,9 @@ exprLocation(const Node *expr)
 		case T_PartitionRangeDatum:
 			loc = ((const PartitionRangeDatum *) expr)->location;
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -2217,6 +2255,30 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (walker(tec->column_expr, context))
+					return true;
+				if (walker(tec->default_expr, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3013,6 +3075,30 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExpr  *newnode;
+
+				FLATCOPY(newnode, te, TableExpr);
+				MUTATE(newnode->row_expr, te->row_expr, Node *);
+				MUTATE(newnode->document_expr, te->document_expr, Node *);
+				MUTATE(newnode->namespaces, te->namespaces, List *);
+				MUTATE(newnode->columns, te->columns, List *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+				TableExprColumn *newnode;
+
+				FLATCOPY(newnode, tec, TableExprColumn);
+				MUTATE(newnode->column_expr, tec->column_expr, Node *);
+				MUTATE(newnode->default_expr, tec->default_expr, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3628,6 +3714,20 @@ raw_expression_tree_walker(Node *node,
 			break;
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				if (walker(te->row_expr, context))
+					return true;
+				if (walker(te->document_expr, context))
+					return true;
+				if (walker(te->columns, context))
+					return true;
+				if (walker(te->namespaces, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7258c03..24e5b28 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,49 @@ _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(namespaces);
+	WRITE_NODE_FIELD(document_expr);
+	WRITE_NODE_FIELD(row_expr);
+	WRITE_NODE_FIELD(columns);
+	WRITE_BOOL_FIELD(use_auto_col);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprColumn(StringInfo str, const TableExprColumn *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_OID_FIELD(typid);
+	WRITE_INT_FIELD(typmod);
+	WRITE_OID_FIELD(collation);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outTableExprRawCol(StringInfo str, const TableExprRawCol *node)
+{
+	WRITE_NODE_TYPE("TABLEEXPRCOLUMN");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(column_expr);
+	WRITE_NODE_FIELD(default_expr);
+	WRITE_LOCATION_FIELD(location);
+}
+
 /*****************************************************************************
  *
  *	Stuff from relation.h.
@@ -3584,6 +3627,12 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlExpr:
 				_outXmlExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
+			case T_TableExprColumn:
+				_outTableExprColumn(str, obj);
+				break;
 			case T_NullTest:
 				_outNullTest(str, obj);
 				break;
@@ -3922,6 +3971,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionRangeDatum:
 				_outPartitionRangeDatum(str, obj);
 				break;
+			case T_TableExprRawCol:
+				_outTableExprRawCol(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d608530..965a118 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2298,6 +2298,45 @@ _readPartitionRangeDatum(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(namespaces);
+	READ_NODE_FIELD(document_expr);
+	READ_NODE_FIELD(row_expr);
+	READ_NODE_FIELD(columns);
+	READ_BOOL_FIELD(use_auto_col);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
+ * _readTableExprColumn
+ */
+static TableExprColumn *
+_readTableExprColumnNode(void)
+{
+	READ_LOCALS(TableExprColumn);
+
+	READ_STRING_FIELD(colname);
+	READ_BOOL_FIELD(for_ordinality);
+	READ_OID_FIELD(typid);
+	READ_INT_FIELD(typmod);
+	READ_OID_FIELD(collation);
+	READ_BOOL_FIELD(is_not_null);
+	READ_NODE_FIELD(column_expr);
+	READ_NODE_FIELD(default_expr);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2533,6 +2572,10 @@ parseNodeString(void)
 		return_value = _readPartitionBoundSpec();
 	else if (MATCH("PARTRANGEDATUM", 14))
 		return_value = _readPartitionRangeDatum();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
+	else if (MATCH("TABLEEXPRCOLUMN", 15))
+		return_value = _readTableExprColumnNode();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9af29dd..5d5fd41 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -829,6 +829,12 @@ expression_returns_set_rows_walker(Node *node, double *count)
 			*count *= get_func_rows(expr->opfuncid);
 		}
 	}
+	if (IsA(node, TableExpr))
+	{
+		/* we have not any method how to estimate it, use default */
+		*count = 1000;
+		return false;
+	}
 
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
@@ -3598,6 +3604,33 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_TableExprColumn:
+			{
+				TableExprColumn *tec = (TableExprColumn *) node;
+
+				if (tec->column_expr != NULL || tec->default_expr != NULL)
+				{
+					TableExprColumn *newtec = makeNode(TableExprColumn);
+
+					newtec->colname = tec->colname;
+					newtec->typid = tec->typid;
+					newtec->typmod = tec->typmod;
+					newtec->collation = tec->collation;
+					newtec->for_ordinality = tec->for_ordinality;
+					newtec->is_not_null = tec->is_not_null;
+					newtec->column_expr =
+						eval_const_expressions_mutator((Node *) tec->column_expr,
+													   context);
+					newtec->default_expr =
+						eval_const_expressions_mutator((Node *) tec->default_expr,
+													   context);
+					newtec->location = tec->location;
+
+					return (Node *) newtec;
+				}
+			}
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2ed7b52..d8af197 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -560,6 +560,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partrange_datum>	PartitionRangeDatum
 %type <list>		range_datum_list
 
+%type <list>	XmlTableExprColList XmlTableExprColOptions
+%type <node>	XmlTableExprCol
+%type <defelt>	XmlTableExprColOption
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -591,10 +597,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 +671,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 +12361,132 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 		;
 
+XmlTableExprColList: XmlTableExprCol						{ $$ = list_make1($1); }
+			| XmlTableExprColList ',' XmlTableExprCol		{ $$ = lappend($1, $3); }
+		;
+
+XmlTableExprCol:
+			ColId Typename
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = false;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+			| ColId Typename XmlTableExprColOptions
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+					ListCell		   *option;
+					bool				nullability_seen = false;
+
+					rawc->colname = $1;
+					rawc->for_ordinality = false;
+					rawc->typeName = $2;
+					rawc->is_not_null = false;
+					rawc->column_expr = NULL;
+					rawc->default_expr = NULL;
+					rawc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (rawc->default_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->default_expr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (rawc->column_expr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							rawc->column_expr = defel->arg;
+						}
+						else 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\"", rawc->colname),
+										 parser_errposition(defel->location)));
+							rawc->is_not_null = intVal(defel->arg);
+							nullability_seen = true;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) rawc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					TableExprRawCol	   *rawc = makeNode(TableExprRawCol);
+
+					rawc->colname = $1;
+					rawc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					rawc->location = @1;
+
+					$$ = (Node *) rawc;
+				}
+		;
+
+XmlTableExprColOptions: XmlTableExprColOption				{ $$ = list_make1($1); }
+			| XmlTableExprColOptions XmlTableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+XmlTableExprColOption:
+			IDENT b_expr
+				{ $$ = makeDefElem($1, $2, @1); }
+			| DEFAULT b_expr
+				{ $$ = makeDefElem("default", $2, @1); }
+			| NOT NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(true), @1); }
+			| NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(false), @1); }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			b_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT b_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
+		;
+
 /*
  * Restricted expressions
  *
@@ -12922,6 +13054,48 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = NIL;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $3;
+					n->document_expr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = NIL;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExpr *n = makeNode(TableExpr);
+					n->row_expr = $8;
+					n->document_expr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -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 d277fd6..06739f6 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 8a2bdf0..ac97f44 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -122,6 +123,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformTableExpr(ParseState *pstate, TableExpr *te);
 static Node *make_row_comparison_op(ParseState *pstate, List *opname,
 					   List *largs, List *rargs, int location);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_TableExpr:
+			result = transformTableExpr(pstate, (TableExpr *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -2768,6 +2774,190 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
 }
 
 /*
+ * Transform a table expression.
+ *
+ * Transform the namespace clauses, the document-generating expression, the
+ * row-generating expression, the column-generating expressions, and the
+ * default value expressions.
+ */
+static Node *
+transformTableExpr(ParseState *pstate, TableExpr *te)
+{
+	TableExpr  *newte = makeNode(TableExpr);
+	const char *constructName;
+	Oid			exprType;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	exprType = XMLOID;
+
+	/* TableExpr is SRF, check target SRF usage */
+	check_srf_call_placement(pstate, te->location);
+
+	/* Transform the row-generating Xpath expression ... */
+	Assert(te->row_expr != NULL);
+	newte->row_expr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, te->row_expr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML itself */
+	Assert(te->document_expr != NULL);
+	newte->document_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, te->document_expr),
+												   exprType,
+												   constructName);
+
+	/* Columns, if any, also need to be transformed */
+	if (te->columns != NIL)
+	{
+		ListCell   *col;
+		List	   *transformlist = NIL;
+		bool		for_ordinality = false;
+		char	  **colnames;
+		int			i = 0;
+
+		colnames = palloc(sizeof(char *) * list_length(te->columns));
+
+		/*
+		 * For each TableExprRawCol in the input TableExpr, generate one
+		 * TableExprColumn in the output TableExpr.
+		 */
+		foreach(col, te->columns)
+		{
+			TableExprRawCol *rawc = (TableExprRawCol *) lfirst(col);
+			TableExprColumn *newc;
+			int			j;
+
+			/* Only one FOR ORDINALITY column is allowed; verify */
+			if (rawc->for_ordinality)
+			{
+				if (for_ordinality)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+				for_ordinality = true;
+			}
+
+			newc = makeNode(TableExprColumn);
+			newc->colname = pstrdup(rawc->colname);
+			newc->for_ordinality = rawc->for_ordinality;
+			newc->is_not_null = rawc->is_not_null;
+			newc->location = rawc->location;
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				newc->typid = INT4OID;
+				newc->typmod = -1;
+			}
+			else
+				typenameTypeIdAndMod(pstate, rawc->typeName,
+									 &newc->typid, &newc->typmod);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->column_expr)
+				newc->column_expr = coerce_to_specific_type(pstate,
+							 transformExprRecurse(pstate, rawc->column_expr),
+															TEXTOID,
+															constructName);
+			if (rawc->default_expr)
+				newc->default_expr = coerce_to_specific_type_typmod(pstate,
+							transformExprRecurse(pstate, rawc->default_expr),
+												   newc->typid, newc->typmod,
+															  constructName);
+
+
+			transformlist = lappend(transformlist, newc);
+
+			/* make sure column names are unique */
+			for (j = 0; j < i; j++)
+				if (strcmp(colnames[j], rawc->colname) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("column name \"%s\" is not unique",
+									rawc->colname),
+							 parser_errposition(pstate, rawc->location)));
+			colnames[i] = rawc->colname;
+
+			i++;
+		}
+
+		newte->columns = transformlist;
+		newte->use_auto_col = false;
+		pfree(colnames);
+	}
+	else
+	{
+		TableExprColumn *newc;
+
+		/*
+		 * When there are not explicit column, define the implicit one.
+		 * The rules for implicit column can be different. Currently
+		 * only XMLTABLE is supported.
+		 */
+		newc = makeNode(TableExprColumn);
+		newc->colname = "xmltable";
+		newc->location = -1;
+		newc->typid = XMLOID;
+		newc->typmod = -1;
+		newc->location = -1;
+
+		newte->columns = list_make1(newc);
+		newte->use_auto_col = true;
+	}
+
+	/* Namespaces, if any, also need to be transformed */
+	if (te->namespaces != NIL)
+	{
+		List	   *transformlist = NIL;
+		ListCell   *ns;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(te->namespaces));
+
+		foreach(ns, te->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("namespace name \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			transformlist = lappend(transformlist, n);
+		}
+		newte->namespaces = transformlist;
+		pfree(nsnames);
+	}
+	else
+		newte->namespaces = NIL;
+
+	newte->location = te->location;
+
+	return (Node *) newte;
+}
+
+/*
  * Transform a "row compare-op row" construct
  *
  * 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 d440dec..d59d83f 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,14 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_TableExpr:
+
+			/*
+			 * Make TableExpr act like a regular function. Only XMLTABLE expr
+			 * is supported in this moment.
+			 */
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 4e2ba19..02568f7 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -51,6 +51,7 @@
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -8547,6 +8548,105 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+
+				/*
+				 * Deparse TableExpr - the is only one TableExpr producer, the
+				 * function XMLTABLE.
+				 */
+
+				/* c_expr shoud be closed in brackets */
+				appendStringInfoString(buf, "XMLTABLE(");
+
+				if (te->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->row_expr, context, true);
+				appendStringInfoString(buf, ") PASSING (");
+				get_rule_expr((Node *) te->document_expr, context, true);
+				appendStringInfoChar(buf, ')');
+
+				if (te->columns != NIL)
+				{
+					ListCell   *col;
+					bool		first = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+
+					foreach(col, te->columns)
+					{
+						TableExprColumn *tec = (TableExprColumn *) lfirst(col);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						appendStringInfoString(buf, quote_identifier(tec->colname));
+						appendStringInfoChar(buf, ' ');
+
+						if (!tec->for_ordinality)
+						{
+							appendStringInfoString(buf,
+												   format_type_with_typemod(tec->typid, tec->typmod));
+
+							if (tec->default_expr != NULL)
+							{
+								appendStringInfoString(buf, " DEFAULT (");
+								get_rule_expr((Node *) tec->default_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->column_expr != NULL)
+							{
+								appendStringInfoString(buf, " PATH (");
+								get_rule_expr((Node *) tec->column_expr, context, true);
+								appendStringInfoChar(buf, ')');
+							}
+							if (tec->is_not_null)
+								appendStringInfoString(buf, " NOT NULL");
+						}
+						else
+						{
+							appendStringInfoString(buf, "FOR ORDINALITY");
+						}
+					}
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 057c3bf..8026176 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 XmlTableInitializeBuilder(TableExprState *state);
+static void XmlTableSetContent(TableExprState *state, Datum value);
+static void XmlTableDeclareNamespace(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 =
+{
+	XmlTableInitializeBuilder,
+	XmlTableSetContent,
+	XmlTableDeclareNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyBuilder
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -4065,3 +4109,598 @@ 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
+XmlTableInitializeBuilder(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 */
+}
+
+/*
+ * XmlTableSetContent
+ */
+static void
+XmlTableSetContent(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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+
+	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 */
+}
+
+/*
+ * XmlTableDeclareNamespace.  Add a namespace declaration.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableDeclareNamespace(TableExprState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDeclareNamespace called with invalid TableExprState");
+
+	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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetRowFilter called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetRowFilter called with invalid TableExprState");
+
+	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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableSetColumnFilter called with invalid TableExprState");
+
+	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->use_auto_col);
+
+		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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableFetchRow called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableFetchRow called with invalid TableExprState");
+
+	/*
+	 * 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;
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableSetContent called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableGetValue called with invalid TableExprState");
+
+	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 (xtCxt->xpathscomp[colnum] != NULL)
+	{
+		volatile xmlXPathObjectPtr column_xpathobj = 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
+	{
+		/*
+		 * xtCxt->xpathscomp[ncol] is NULL when COLUMNS is empty. Result is a
+		 * serialized current node.
+		 */
+		Assert(state->use_auto_col);
+
+		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 = (XmlTableBuilderData *) state->opaque;
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	/* Defend against someone passing us a bogus context struct */
+	if (!IsA(state, TableExprState))
+		elog(ERROR, "XmlTableDestroyBuilder called with invalid TableExprState");
+	xtCxt = (XmlTableBuilderData *) state->opaque;
+	if (xtCxt->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "XmlTableDestroyBuilder called with invalid TableExprState");
+
+	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 5d49fe5..979f3f8 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -241,6 +241,19 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, TableExpr))
+	{
+		Oid			typid = exprType(expr);
+		int32		typmod = exprTypmod(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+
+		if (resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, typmod);
+
+		return TYPEFUNC_COMPOSITE;
+	}
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3f649fa..11550a2 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -278,6 +278,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
+extern TupleDesc ExecTypeFromTableExpr(const TableExpr *te);
 extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..34f5f29
--- /dev/null
+++ b/src/include/executor/tableexpr.h
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "access/tupdesc.h"
+#include "fmgr.h"
+
+/*
+ * TableExprRoutine holds function pointers used for generating content of
+ * table-expression functions, such as XMLTABLE.
+ *
+ * CreateContext creates the table builder context.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed as
+ * created by the executor scaffold.  The memory context being passed is an
+ * IMO bogus thing that shall be fixed by Pavel.
+ *
+ * SetContent is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * DeclareNamespace 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.
+ *
+ * DestroyContext 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		(*InitializeBuilder) (TableExprState *state);
+	void		(*SetContent) (TableExprState *state, Datum value);
+	void		(*DeclareNamespace) (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/funcapi.h b/src/include/funcapi.h
index e73a824..e350316 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -184,7 +184,6 @@ extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
 								Datum proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
 
-
 /*----------
  *	Support to ease writing functions returning composite types
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 703604a..70dfbf8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1017,6 +1017,31 @@ 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;			/* content builder private space */
+	TupleTableSlot *resultSlot; /* output tuple table slot */
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	FmgrInfo   *in_functions;	/* input function for each column */
+	Oid		   *typioparams;	/* typioparam for each column */
+	bool		use_auto_col;	/* true, when column was not defined by user*/
+	AttrNumber	for_ordinality_col;		/* FOR ORDINALITY column */
+	int			rownum;			/* row number to be output next */
+	ExprState  *doc_expr;		/* state for document expression */
+	ExprState  *row_expr;		/* state for row-generating expression */
+	ExprState **col_expr;		/* state for column-generating expression */
+	ExprState **def_expr;		/* state for column default values */
+	bool	   *not_null;		/* nullability flag for each column */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c514d3f..5a4cdf5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,8 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
+	T_TableExprColumn,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -216,6 +218,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -459,6 +462,7 @@ typedef enum NodeTag
 	T_PartitionSpec,
 	T_PartitionBoundSpec,
 	T_PartitionRangeDatum,
+	T_TableExprRawCol,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fc532fb..dd30640 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,24 @@ typedef struct ParamRef
 } ParamRef;
 
 /*
+ * TableExprRawCol - 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 TableExprRawCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	TypeName   *typeName;		/* type of generated column */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprRawCol;
+
+/*
  * 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 65510b0..d7da8aa 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1467,4 +1467,38 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - for table expressions, such as XMLTABLE.
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	List	   *namespaces;		/* list of namespaces */
+	Node	   *document_expr;	/* processed data */
+	Node	   *row_expr;		/* row filter expression */
+	List	   *columns;		/* columns definitions */
+	bool		use_auto_col;	/* true, when column was not defined by user */
+	int			location;		/* token location, or -1 if unknown */
+} TableExpr;
+
+/*----------
+ * TableExprColumn - one column in a table expression.
+ *		Raw form of this is TableExprRawCol.
+ *----------
+ */
+typedef struct TableExprColumn
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	Oid			typid;			/* type of generated column */
+	int32		typmod;			/* typmod of generated column */
+	Oid			collation;		/* column collation */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *column_expr;	/* column filter expression */
+	Node	   *default_expr;	/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} TableExprColumn;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 581ff6e..3e26593 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 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..4ed5297 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..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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
#97Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#96)
1 attachment(s)
Re: patch: function xmltable

2016-12-18 16:27 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2016-12-07 20:37 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2016-12-07 18:34 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

I fixed two issues.

2. there was reverse setting in NOT NULL flag

Ah-hah, that was silly, thanks.

1. there are not columns data when there are not any explicit column -

fixed

Hmm. Now that I see how this works, by having the GetValue "guess" what
is going on and have a special case for it, I actually don't like it
very much. It seems way too magical. I think we should do away with
the "if column is NULL" case in GetValue, and instead inject a column
during transformTableExpr if columns is NIL. This has implications on
ExecInitExpr too, which currently checks for an empty column list -- it
would no longer have to do so.

I prefer this way against second described. The implementation should be
in table builder routines, not in executor.

sending new update

new update - no functional changes, just unbreaking after last changes in
master.

another update - lot of cleaning

Regards

Pavel

Show quoted text

Regards

Pavel

Regards

Pavel

Maybe this means we need an additional method, which would request "the
expr that returns the whole row", so that transformExpr can work for
XmlTable (which I think would be something like "./") and the future
JsonTable stuff (I don't know how that one would work, but I assume it's
not necessarily the same thing).

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-25.patchtext/x-patch; charset=US-ASCII; name=xmltable-25.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5d2749d..11ebb71 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 743e7d6..a275dbf 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,13 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum value);
 
 
 /* ----------------------------------------------------------------
@@ -4500,6 +4508,283 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+
+	PG_TRY();
+	{
+		MemoryContext oldcxt;
+
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->opaque == NULL)
+		{
+			Datum		value;
+
+			/* Fill in table builder opaque area */
+			oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+			routine->InitBuilder(tstate);
+			MemoryContextSwitchTo(oldcxt);
+
+			/*
+			 * 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 = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any errors are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->opaque != NULL)
+		{
+			routine->DestroyBuilder(tstate);
+			ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		}
+
+		MemoryContextDelete(tstate->buildercxt);
+		tstate->buildercxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum value)
+{
+	const TableExprRoutine *routine;
+	TupleDesc	tupdesc;
+	MemoryContext oldcxt;
+	ListCell   *cell;
+	bool		isnull;
+	int			colno;
+
+	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, value);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Evaluate namespace specifications */
+	foreach(cell, tstate->namespaces)
+	{
+		Node	   *n = (Node *) lfirst(cell);
+		ExprState  *expr;
+		char	   *ns_name;
+		char	   *ns_uri;
+
+		if (IsA(n, NamedArgExpr))
+		{
+			NamedArgExpr *na = (NamedArgExpr *) n;
+
+			expr = (ExprState *) na->arg;
+			ns_name = na->name;
+		}
+		else
+		{
+			expr = (ExprState *) n;
+			ns_name = NULL;
+		}
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		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(cell, tstate->colexprs)
+	{
+		char	   *colfilter;
+
+		if (tstate->evalcols && colno != tstate->ordinalitycol)
+		{
+			ExprState *colexpr = lfirst(cell);
+
+			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
@@ -5262,6 +5547,92 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				TupleDesc	tupdesc;
+				int			natts;
+				int			i;
+
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->opaque = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				tupdesc = BuildDescFromLists(te->colnames,
+														 te->coltypes,
+														 te->coltypmods,
+														 te->colcollations);
+				BlessTupleDesc(tupdesc);
+
+				natts = tupdesc->natts;
+				Assert(natts > 0 && natts == te->colcount);
+
+				tstate->resultSlot = MakeSingleTupleTableSlot(tupdesc);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * natts);
+				tstate->typioparams = palloc(sizeof(Oid) * natts);
+
+				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]);
+				}
+
+				tstate->rowexpr = ExecInitExpr((Expr *) te->rowexpr, parent);
+				tstate->docexpr = ExecInitExpr((Expr *) te->docexpr, parent);
+
+				tstate->evalcols = te->evalcols;
+				tstate->colexprs = (List *) ExecInitExpr((Expr *) te->colexprs, parent);
+				tstate->coldefexprs = (List *) ExecInitExpr((Expr *) te->coldefexprs, parent);
+				tstate->notnulls = te->notnulls;
+				tstate->ordinalitycol = te->ordinalitycol;
+				tstate->rownum = 1;
+
+				if (te->namespaces)
+				{
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							tstate->namespaces = lappend(tstate->namespaces, nax);
+						}
+						else
+						{
+							/*
+							 * Default namespace has not name. In this moment, we
+							 * doesn't support default namespaces in XMLTABLE function
+							 * due missing support by libxml2, but it is not reason
+							 * don't allow support generally (on executor level).
+							 */
+							tstate->namespaces = lappend(tstate->namespaces,
+										   ExecInitExpr((Expr *) n, parent));
+						}
+					}
+				}
+
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d973225..497c8d5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,68 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(namespaces);
+	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
  *
@@ -4666,6 +4728,9 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5185,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 edc1797..9b416cc 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,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(namespaces);
+	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 +3012,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 +3521,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 973fb15..fde7cf2 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->namespaces, 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->namespaces, te->namespaces, 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 7258c03..cd09f2e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,27 @@ _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(namespaces);
+	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 +3344,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 +3631,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 +3972,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 d608530..bae3cfb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2298,6 +2298,32 @@ _readPartitionRangeDatum(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(namespaces);
+	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 +2559,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 9af29dd..c4daaa8 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 2ed7b52..7c7eca1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -560,6 +560,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partrange_datum>	PartitionRangeDatum
 %type <list>		range_datum_list
 
+%type <list>	XmlTableExprColList XmlTableExprColOptions
+%type <node>	XmlTableExprCol
+%type <defelt>	XmlTableExprColOption
+%type <list>	XmlNamespaceList
+%type <node>	XmlNamespace
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -591,10 +597,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 +671,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 +12361,132 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 		;
 
+XmlTableExprColList: XmlTableExprCol						{ $$ = list_make1($1); }
+			| XmlTableExprColList ',' XmlTableExprCol		{ $$ = lappend($1, $3); }
+		;
+
+XmlTableExprCol:
+			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 XmlTableExprColOptions
+				{
+					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;
+				}
+		;
+
+XmlTableExprColOptions: XmlTableExprColOption				{ $$ = list_make1($1); }
+			| XmlTableExprColOptions XmlTableExprColOption	{ $$ = lappend($1, $2); }
+		;
+
+XmlTableExprColOption:
+			IDENT b_expr
+				{ $$ = makeDefElem($1, $2, @1); }
+			| DEFAULT b_expr
+				{ $$ = makeDefElem("default", $2, @1); }
+			| NOT NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(true), @1); }
+			| NULL_P
+				{ $$ = makeDefElem("notnull", (Node *) makeInteger(false), @1); }
+		;
+
+XmlNamespaceList: XmlNamespace							{ $$ = list_make1($1); }
+			| XmlNamespaceList ',' XmlNamespace			{ $$ = lappend($1, $3); }
+		;
+
+XmlNamespace:
+			b_expr AS ColLabel
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT b_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
+		;
+
 /*
  * Restricted expressions
  *
@@ -12922,6 +13054,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 XmlTableExprColList ')'
+				{
+					TableExprFunc *n = makeNode(TableExprFunc);
+					n->rowexpr = $3;
+					n->docexpr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' XmlNamespaceList ')' ','
+				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 '(' XmlNamespaceList ')' ','
+				c_expr xmlexists_argument COLUMNS XmlTableExprColList ')'
+				{
+					TableExprFunc *n = makeNode(TableExprFunc);
+					n->rowexpr = $8;
+					n->docexpr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -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 d277fd6..06739f6 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 8a2bdf0..1ac2574 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));
@@ -2768,6 +2774,198 @@ 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 the row-generating Xpath expression ... */
+	Assert(t->rowexpr != NULL);
+	newt->rowexpr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, t->rowexpr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML 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;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(t->namespaces));
+
+		foreach(ns, t->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("namespace name \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			newt->namespaces = lappend(newt->namespaces, n);
+		}
+		pfree(nsnames);
+	}
+
+	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 d440dec..519d23b 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 4e2ba19..b93c9de 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->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->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 057c3bf..2c67144 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,580 @@ 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.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableSetNamespace(TableExprState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	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 5d49fe5..63376cf 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 of TableExpr is determinable, although it is not
+		 * stored in system catalogue. 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 5c3b868..9626b0c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1020,6 +1020,31 @@ 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	   *namespaces;		/* list of prepared ResTarget fields */
+	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 c514d3f..6b09829 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,7 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -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 fc532fb..6d2a9cc 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 (TableExprRawCol) */
+	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 65510b0..7ef330b 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"
 
 
@@ -1467,4 +1468,27 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - for table expressions, such as XMLTABLE.
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	List	   *namespaces;		/* list of namespaces */
+	Node	   *docexpr;		/* processed data */
+	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;
+
 #endif   /* PRIMNODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 581ff6e..3e26593 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 66519e6..03502f8 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -58,6 +58,10 @@ extern Node *coerce_to_specific_type(ParseState *pstate, Node *node,
 						Oid targetTypeId,
 						const char *constructName);
 
+extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName);
+
 extern int parser_coercion_errposition(ParseState *pstate,
 							int coerce_location,
 							Node *input_expr);
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..4ed5297 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..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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
#98Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#97)
Re: patch: function xmltable

Pavel Stehule wrote:

another update - lot of cleaning

Ah, the tupledesc stuff in this one appears much more reasonable to me.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#99Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#97)
Re: patch: function xmltable

Pavel Stehule wrote:

another update - lot of cleaning

Thanks.

The more I look at this, the less I like using NameArgExpr for
namespaces. It looks all wrong to me, and it causes ugly code all over.
Maybe I just need to look at it a bit longer.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#100Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#99)
1 attachment(s)
Re: patch: function xmltable

Alvaro Herrera wrote:

The more I look at this, the less I like using NameArgExpr for
namespaces. It looks all wrong to me, and it causes ugly code all over.
Maybe I just need to look at it a bit longer.

I think it'd be cleaner to use ResTarget for the namespaces, like
xml_attribute_el does, and split the names from actual exprs in the same
way. So things like ExecInitExpr become simpler because you just
recurse to initialize the list, without having to examine each element
individually. tabexprInitialize can just do forboth().

The main reason I don't like abusing NamedArgExpr is that the whole
comment that explains it becomes a lie.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-26.patchtext/plain; charset=us-asciiDownload
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..f3952b9 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,13 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum value);
 
 
 /* ----------------------------------------------------------------
@@ -4513,6 +4521,283 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+
+	PG_TRY();
+	{
+		MemoryContext oldcxt;
+
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->opaque == NULL)
+		{
+			Datum		value;
+
+			/* Fill in table builder opaque area */
+			oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+			routine->InitBuilder(tstate);
+			MemoryContextSwitchTo(oldcxt);
+
+			/*
+			 * 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 = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any errors are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->opaque != NULL)
+		{
+			routine->DestroyBuilder(tstate);
+			ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		}
+
+		MemoryContextDelete(tstate->buildercxt);
+		tstate->buildercxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum value)
+{
+	const TableExprRoutine *routine;
+	TupleDesc	tupdesc;
+	MemoryContext oldcxt;
+	ListCell   *cell;
+	bool		isnull;
+	int			colno;
+
+	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, value);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Evaluate namespace specifications */
+	foreach(cell, tstate->namespaces)
+	{
+		Node	   *n = (Node *) lfirst(cell);
+		ExprState  *expr;
+		char	   *ns_name;
+		char	   *ns_uri;
+
+		if (IsA(n, NamedArgExpr))
+		{
+			NamedArgExpr *na = (NamedArgExpr *) n;
+
+			expr = (ExprState *) na->arg;
+			ns_name = na->name;
+		}
+		else
+		{
+			expr = (ExprState *) n;
+			ns_name = NULL;
+		}
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull, NULL);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		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(cell, tstate->colexprs)
+	{
+		char	   *colfilter;
+
+		if (tstate->evalcols && colno != tstate->ordinalitycol)
+		{
+			ExprState *colexpr = lfirst(cell);
+
+			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
@@ -5277,6 +5562,93 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				TupleDesc	tupdesc;
+				int			natts;
+				int			i;
+
+				tstate->xprstate.evalfunc =
+					(ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->opaque = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				tupdesc = BuildDescFromLists(te->colnames,
+											 te->coltypes,
+											 te->coltypmods,
+											 te->colcollations);
+				BlessTupleDesc(tupdesc);
+
+				natts = tupdesc->natts;
+				Assert(natts > 0 && natts == te->colcount);
+
+				tstate->resultSlot = MakeSingleTupleTableSlot(tupdesc);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * natts);
+				tstate->typioparams = palloc(sizeof(Oid) * natts);
+
+				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]);
+				}
+
+				tstate->rowexpr = ExecInitExpr((Expr *) te->rowexpr, parent);
+				tstate->docexpr = ExecInitExpr((Expr *) te->docexpr, parent);
+
+				tstate->evalcols = te->evalcols;
+				tstate->colexprs = (List *) ExecInitExpr((Expr *) te->colexprs, parent);
+				tstate->coldefexprs = (List *) ExecInitExpr((Expr *) te->coldefexprs, parent);
+				tstate->notnulls = te->notnulls;
+				tstate->ordinalitycol = te->ordinalitycol;
+				tstate->rownum = 1;
+
+				if (te->namespaces)
+				{
+					ListCell   *ns;
+
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+							NamedArgExpr *nax = makeNode(NamedArgExpr);
+
+							nax->name = na->name;
+							nax->arg = (Expr *) ExecInitExpr(na->arg, parent);
+							nax->location = na->location;
+							tstate->namespaces = lappend(tstate->namespaces, nax);
+						}
+						else
+						{
+							/*
+							 * Default namespace has not name. In this moment, we
+							 * doesn't support default namespaces in XMLTABLE function
+							 * due missing support by libxml2, but it is not reason
+							 * don't allow support generally (on executor level).
+							 */
+							tstate->namespaces = lappend(tstate->namespaces,
+										   ExecInitExpr((Expr *) n, parent));
+						}
+					}
+				}
+
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 930f2f1..6b016b5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1987,6 +1987,68 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyTableExpr
+ */
+static TableExpr *
+_copyTableExpr(const TableExpr *from)
+{
+	TableExpr  *newnode = makeNode(TableExpr);
+
+	COPY_NODE_FIELD(namespaces);
+	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 +4727,9 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5184,6 +5249,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..5f91595 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,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalTableExpr(const TableExpr *a, const TableExpr *b)
+{
+	COMPARE_NODE_FIELD(namespaces);
+	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 +3012,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 +3521,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..529e579 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->namespaces, 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->namespaces, te->namespaces, 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..8c6df65 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1574,6 +1574,27 @@ _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(namespaces);
+	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 +3344,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 +3631,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 +3972,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..affa461 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2298,6 +2298,32 @@ _readPartitionRangeDatum(void)
 }
 
 /*
+ * _readTableExprNode
+ */
+static TableExpr *
+_readTableExprNode(void)
+{
+	READ_LOCALS(TableExpr);
+
+	READ_NODE_FIELD(namespaces);
+	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 +2559,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..c90a5d0 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 <node>	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,131 @@ 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
+				{
+					NamedArgExpr *na = makeNode(NamedArgExpr);
+					na->name = $3;
+					na->arg = (Expr *) $1;
+					na->location = @1;
+					$$ = (Node *) na;
+				}
+			| DEFAULT b_expr
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
+		;
 
 /*
  * Aggregate decoration clauses
@@ -14031,6 +14204,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14333,10 +14507,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..c23e14f 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,198 @@ 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 the row-generating Xpath expression ... */
+	Assert(t->rowexpr != NULL);
+	newt->rowexpr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, t->rowexpr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML 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;
+		int			nnames = 0;
+		char	  **nsnames;
+
+		nsnames = palloc(sizeof(char *) * list_length(t->namespaces));
+
+		foreach(ns, t->namespaces)
+		{
+			Node	   *n = (Node *) lfirst(ns);
+			NamedArgExpr *na = (NamedArgExpr *) n;
+			int			i;
+
+			Assert(IsA(na, NamedArgExpr));
+
+			/* make sure namespace names are unique */
+			for (i = 0; i < nnames; i++)
+				if (strcmp(nsnames[i], na->name) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("namespace name \"%s\" is not unique",
+									na->name),
+							 parser_errposition(pstate, na->location)));
+			nsnames[nnames++] = na->name;
+
+			na->arg = (Expr *) coerce_to_specific_type(pstate,
+							  transformExprRecurse(pstate, (Node *) na->arg),
+													   TEXTOID,
+													   constructName);
+
+			newt->namespaces = lappend(newt->namespaces, n);
+		}
+		pfree(nsnames);
+	}
+
+	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..dcdbd4b 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->namespaces != NIL)
+				{
+					ListCell   *ns;
+					bool		first = true;
+
+					appendStringInfoString(buf, "XMLNAMESPACES(");
+					foreach(ns, te->namespaces)
+					{
+						Node	   *n = (Node *) lfirst(ns);
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						if (IsA(n, NamedArgExpr))
+						{
+							NamedArgExpr *na = (NamedArgExpr *) n;
+
+							get_rule_expr((Node *) na->arg, context, true);
+							appendStringInfo(buf, " AS %s", quote_identifier(na->name));
+						}
+						else
+						{
+							appendStringInfoString(buf, "DEFAULT ");
+							get_rule_expr(n, context, true);
+						}
+					}
+					appendStringInfoChar(buf, ')');
+				}
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) te->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..7689205 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,580 @@ 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.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableSetNamespace(TableExprState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	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/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ce13bf7..22121a8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1022,6 +1022,31 @@ 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	   *namespaces;		/* list of prepared ResTarget fields */
+	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..54dbb7d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,7 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -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..1a66159 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"
 
 
@@ -1460,4 +1461,27 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - for table expressions, such as XMLTABLE.
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	List	   *namespaces;		/* list of namespaces */
+	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;
+
 #endif   /* PRIMNODES_H */
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..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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
#101Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#100)
1 attachment(s)
Re: patch: function xmltable

Hi

2017-01-11 22:53 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Alvaro Herrera wrote:

The more I look at this, the less I like using NameArgExpr for
namespaces. It looks all wrong to me, and it causes ugly code all over.
Maybe I just need to look at it a bit longer.

I think it'd be cleaner to use ResTarget for the namespaces, like
xml_attribute_el does, and split the names from actual exprs in the same
way. So things like ExecInitExpr become simpler because you just
recurse to initialize the list, without having to examine each element
individually. tabexprInitialize can just do forboth().

The main reason I don't like abusing NamedArgExpr is that the whole
comment that explains it becomes a lie.

I used your proposed way based on Restarget

Updated patch attached.

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-27.patchtext/x-patch; charset=US-ASCII; name=xmltable-27.patchDownload
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..addb975 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,13 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isnull, ExprDoneCond *isDone);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull, ExprDoneCond *isDone);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum doc);
 
 
 /* ----------------------------------------------------------------
@@ -4513,6 +4521,271 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	return 0;					/* keep compiler quiet */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ * Evaluate a TableExpr node.  One tuple is returned per call; this function
+ * is to be called until no more rows are returned.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExpr(TableExprState *tstate,
+				  ExprContext *econtext,
+				  bool *isNull, ExprDoneCond *isDone)
+{
+	const TableExprRoutine *routine = tstate->routine;
+	Datum		result;
+
+	PG_TRY();
+	{
+		MemoryContext oldcxt;
+
+		/*
+		 * The first time around, create the table builder context and
+		 * initialize it with the document content.
+		 */
+		if (tstate->opaque == NULL)
+		{
+			Datum		value;
+
+			/* Fill in table builder opaque area */
+			oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+			routine->InitBuilder(tstate);
+			MemoryContextSwitchTo(oldcxt);
+
+			/*
+			 * 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 = ExprSingleResult;
+				return (Datum) 0;
+			}
+
+			/* otherwise, pass the document value to the table builder */
+			tabexprInitialize(tstate, econtext, value);
+		}
+
+		/* Fetch and return one row per call from the table builder */
+		result = tabexprFetchRow(tstate, econtext, isNull, isDone);
+	}
+	PG_CATCH();
+	{
+		/*
+		 * If any errors are raised by the table builder, clean up everything
+		 * before jumping ship.
+		 */
+		if (tstate->opaque != NULL)
+		{
+			routine->DestroyBuilder(tstate);
+			ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		}
+
+		MemoryContextDelete(tstate->buildercxt);
+		tstate->buildercxt = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * 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
@@ -5277,6 +5550,64 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* Don't fall through to the "common" code below */
 				return (ExprState *) outlist;
 			}
+		case T_TableExpr:
+			{
+				TableExpr  *te = (TableExpr *) node;
+				TableExprState *tstate = makeNode(TableExprState);
+				TupleDesc	tupdesc;
+				int			natts;
+				int			i;
+
+				tstate->xprstate.evalfunc =
+					(ExprStateEvalFunc) ExecEvalTableExpr;
+				tstate->opaque = NULL;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+
+				tupdesc = BuildDescFromLists(te->colnames,
+											 te->coltypes,
+											 te->coltypmods,
+											 te->colcollations);
+				BlessTupleDesc(tupdesc);
+
+				natts = tupdesc->natts;
+				Assert(natts > 0 && natts == te->colcount);
+
+				tstate->resultSlot = MakeSingleTupleTableSlot(tupdesc);
+				tstate->in_functions = palloc(sizeof(FmgrInfo) * natts);
+				tstate->typioparams = palloc(sizeof(Oid) * natts);
+
+				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]);
+				}
+
+				tstate->rowexpr = ExecInitExpr((Expr *) te->rowexpr, parent);
+				tstate->docexpr = ExecInitExpr((Expr *) te->docexpr, parent);
+
+				tstate->evalcols = te->evalcols;
+				tstate->colexprs = (List *) ExecInitExpr((Expr *) te->colexprs, parent);
+				tstate->coldefexprs = (List *) ExecInitExpr((Expr *) te->coldefexprs, parent);
+				tstate->notnulls = te->notnulls;
+				tstate->ordinalitycol = te->ordinalitycol;
+				tstate->rownum = 1;
+
+				tstate->ns_names = te->ns_names;
+				tstate->ns_uris = (List *) ExecInitExpr((Expr *) te->ns_uris, parent);
+
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(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..d9c519d 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,131 @@ 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
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("DEFAULT namespace is not supported"),
+							 parser_errposition(@1)));
+				}
+		;
 
 /*
  * Aggregate decoration clauses
@@ -14031,6 +14204,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14333,10 +14507,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..40eb531 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,218 @@ 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 the row-generating Xpath expression ... */
+	Assert(t->rowexpr != NULL);
+	newt->rowexpr = coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, t->rowexpr),
+											  TEXTOID,
+											  constructName);
+	/* ... and the XML 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;
+		int			nnames = 0;
+		char	  **nsnames;
+		List	   *ns_uris = NIL;
+		List	   *ns_names = NIL;
+		bool		use_default_namespace = false;
+
+		nsnames = palloc(sizeof(char *) * list_length(t->namespaces));
+
+		foreach(ns, t->namespaces)
+		{
+			ResTarget  *r = (ResTarget *) lfirst(ns);
+			char	   *name;
+
+			Assert(IsA(r, ResTarget));
+			if (r->name != NULL)
+			{
+				int			i;
+
+				/* make sure namespace names are unique */
+				name = r->name;
+				for (i = 0; i < nnames; i++)
+					if (strcmp(nsnames[i], name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("namespace name \"%s\" is not unique",
+										name),
+								 parser_errposition(pstate, r->location)));
+				nsnames[nnames++] = name;
+			}
+			else
+			{
+				if (use_default_namespace)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one default namespace is allowed"),
+								 parser_errposition(pstate, r->location)));
+				use_default_namespace = true;
+				name = NULL;
+			}
+
+			ns_names = lappend(ns_names, makeString(name));
+			ns_uris = lappend(ns_uris,
+						  coerce_to_specific_type(pstate,
+								  transformExprRecurse(pstate, r->val),
+														  TEXTOID,
+														  constructName));
+		}
+		pfree(nsnames);
+
+		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..3120e80 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);
+						}
+					}
+					appendStringInfoChar(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..7689205 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,580 @@ 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.  When name is NULL
+ * (the default namespace is being declared), store a fake namespace.
+ */
+static void
+XmlTableSetNamespace(TableExprState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	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..e434356 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1022,6 +1022,33 @@ 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 */
+	List	   *namespaces;		/* list of prepared ResTarget fields */
+	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..54dbb7d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -181,6 +181,7 @@ typedef enum NodeTag
 	T_FromExpr,
 	T_OnConflictExpr,
 	T_IntoClause,
+	T_TableExpr,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -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..5413d2c 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"
 
 
@@ -1460,4 +1461,28 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * TableExpr - for table expressions, such as XMLTABLE.
+ *----------
+ */
+typedef struct TableExpr
+{
+	NodeTag		type;
+	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;
+
 #endif   /* PRIMNODES_H */
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..1adc2ad 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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
#102Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#101)
1 attachment(s)
Re: patch: function xmltable

Pavel Stehule wrote:

I used your proposed way based on Restarget

Thanks. Some more tweaking to go yet before I consider this
committable, but it's much better now. Here's v28. I changed a few
things:

- make expression evaluation code more orthodox:
1. avoid PG_TRY, use a ExprContext shutdown callback instead
2. use a "Fast" evaluator, for calls past the first one
3. don't look up fmgrinfos until execution time
4. don't duplicate get_expr_result_type
- make parser accept DEFAULT namespace. Only xml implementation barfs.
(this means we lost the errposition pointer, but I don't really
care. We could fix it if we cared)
- clean up parse analysis code a little bit
- move decls/struct defs to better locations in source code
- remove leftover "namespaces" in TableExprState
- pgindent the whole mess.

I don't like the xml.c code and the "evalcols" flag. That's next on my
list to fix.

I don't think to_xmlstr() is necessary, considering xml_text2xmlChar.
We could just apply a cast of the source cstring to xmlChar.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-28.patchtext/plain; charset=us-asciiDownload
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..3120e80 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);
+						}
+					}
+					appendStringInfoChar(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/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..2abe7e4 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,415 @@ SELECT XMLPARSE(DOCUMENT '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4
  <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"><chapter>&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
+LINE 1: SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                                             ^
+SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>');
+ xmltable 
+----------
+ 10
+ 20
+(2 rows)
+
+-- used in prepare statements
+PREPARE pp AS
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+EXECUTE pp;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""1"">                                                +
+   <COUNTRY_ID>AU</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Australia</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""2"">                                                +
+   <COUNTRY_ID>CN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>China</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""3"">                                                +
+   <COUNTRY_ID>HK</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>HongKong</COUNTRY_NAME>                         +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+ ("<ROW id=""6"">                                                +
+   <COUNTRY_ID>SG</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Singapore</COUNTRY_NAME>                        +
+   <REGION_ID>3</REGION_ID><SIZE unit=""km"">791</SIZE>          +
+ </ROW>")
+(6 rows)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(1 row)
+
+SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata;
+                             xmltable                             
+------------------------------------------------------------------
+ ("<ROW id=""4"">                                                +
+   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID>                                      +
+ </ROW>")
+ ("<ROW id=""5"">                                                +
+   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+ </ROW>")
+(2 rows)
+
+SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s;
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ India        |         3
+ Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  1 | India        |         3
+  2 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+ id | COUNTRY_NAME | REGION_ID 
+----+--------------+-----------
+  4 | India        |         3
+  5 | Japan        |         3
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+ id 
+----
+  4
+  5
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+ id 
+----
+  1
+  2
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+ id | COUNTRY_NAME | REGION_ID |                             rawdata                              
+----+--------------+-----------+------------------------------------------------------------------
+  4 | India        |         3 | <ROW id="4">                                                    +
+    |              |           |   <COUNTRY_ID>IN</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>India</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID>                                      +
+    |              |           | </ROW>
+  5 | Japan        |         3 | <ROW id="5">                                                    +
+    |              |           |   <COUNTRY_ID>JP</COUNTRY_ID>                                   +
+    |              |           |   <COUNTRY_NAME>Japan</COUNTRY_NAME>                            +
+    |              |           |   <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>+
+    |              |           | </ROW>
+(2 rows)
+
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+ id | COUNTRY_NAME | REGION_ID |                                                           rawdata                                                           
+----+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------
+  4 | India        |         3 | <COUNTRY_ID>IN</COUNTRY_ID><COUNTRY_NAME>India</COUNTRY_NAME><REGION_ID>3</REGION_ID>
+  5 | Japan        |         3 | <COUNTRY_ID>JP</COUNTRY_ID><COUNTRY_NAME>Japan</COUNTRY_NAME><REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+(2 rows)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+      element      
+-------------------
+ a1aa2a   bbbbcccc
+(1 row)
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+ERROR:  more than one value returned by column XPath expression
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+            c            
+-------------------------
+ <hello> &"<>!<a>foo</a>
+ 2
+(2 rows)
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&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
#103Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#102)
1 attachment(s)
Re: patch: function xmltable

Hi

2017-01-13 21:32 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

I used your proposed way based on Restarget

Thanks. Some more tweaking to go yet before I consider this
committable, but it's much better now. Here's v28. I changed a few
things:

- make expression evaluation code more orthodox:
1. avoid PG_TRY, use a ExprContext shutdown callback instead
2. use a "Fast" evaluator, for calls past the first one
3. don't look up fmgrinfos until execution time
4. don't duplicate get_expr_result_type
- make parser accept DEFAULT namespace. Only xml implementation barfs.
(this means we lost the errposition pointer, but I don't really
care. We could fix it if we cared)
- clean up parse analysis code a little bit
- move decls/struct defs to better locations in source code
- remove leftover "namespaces" in TableExprState
- pgindent the whole mess.

I checked the changes and looks correct - although for some I had not
courage :) - like dynamic change of exprstate->evalfunc

I fixed test, and append forgotten header file

I don't like the xml.c code and the "evalcols" flag. That's next on my
list to fix.

You need some flag to specify if column paths are valid or not.

I don't think to_xmlstr() is necessary, considering xml_text2xmlChar.
We could just apply a cast of the source cstring to xmlChar.

is it safe? For one byte encodings?

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-29.patchtext/x-patch; charset=US-ASCII; name=xmltable-29.patchDownload
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..3120e80 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);
+						}
+					}
+					appendStringInfoChar(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
#104Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#103)
2 attachment(s)
Re: patch: function xmltable

2017-01-14 14:43 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

2017-01-13 21:32 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

I used your proposed way based on Restarget

Thanks. Some more tweaking to go yet before I consider this
committable, but it's much better now. Here's v28. I changed a few
things:

- make expression evaluation code more orthodox:
1. avoid PG_TRY, use a ExprContext shutdown callback instead
2. use a "Fast" evaluator, for calls past the first one
3. don't look up fmgrinfos until execution time
4. don't duplicate get_expr_result_type
- make parser accept DEFAULT namespace. Only xml implementation barfs.
(this means we lost the errposition pointer, but I don't really
care. We could fix it if we cared)
- clean up parse analysis code a little bit
- move decls/struct defs to better locations in source code
- remove leftover "namespaces" in TableExprState
- pgindent the whole mess.

I checked the changes and looks correct - although for some I had not
courage :) - like dynamic change of exprstate->evalfunc

I fixed test, and append forgotten header file

I don't like the xml.c code and the "evalcols" flag. That's next on my
list to fix.

You need some flag to specify if column paths are valid or not.

I don't think to_xmlstr() is necessary, considering xml_text2xmlChar.
We could just apply a cast of the source cstring to xmlChar.

is it safe? For one byte encodings?

Looks so this patch breaks regression tests

estoring database schemas in the new cluster
\"\ cdefghijklmnopqrstuvwxyz{|}~

regression
*failure*

Consult the last few lines of "pg_upgrade_dump_16387.log" for
the probable cause of the failure.
Failure, exiting
+ rm -rf /tmp/pg_upgrade_check-wSfzCh
Makefile:39: návod pro cíl „check“ selhal
make[2]: *** [check] Chyba 1
make[2]: Opouští se adresář „/home/pavel/src/postgresql/src/bin/pg_upgrade“

pg_restore: [archiver (db)] Error while PROCESSING TOC:
pg_restore: [archiver (db)] Error from TOC entry 496; 1259 47693 VIEW
xmltableview2 pavel
pg_restore: [archiver (db)] could not execute query: ERROR: syntax error
at or near "("
LINE 15: ...XMLTABLE(XMLNAMESPACES('http://x.y&#39;::&quot;text&quot; AS zz)('/zz:rows...

Fixed in attached patch

Show quoted text

Regards

Pavel

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-30.patchtext/x-patch; charset=US-ASCII; name=xmltable-30.patchDownload
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
diff.patchtext/x-patch; charset=US-ASCII; name=diff.patchDownload
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3120e80..4ca04a3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8587,7 +8587,7 @@ get_rule_expr(Node *node, deparse_context *context,
 							get_rule_expr(expr, context, true);
 						}
 					}
-					appendStringInfoChar(buf, ')');
+					appendStringInfoString(buf, "), ");
 				}
 
 				appendStringInfoChar(buf, '(');
#105Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#104)
Re: patch: function xmltable

I just realized that your new xml_xmlnodetostr is line-by-line identical
to the existing xml_xmlnodetoxmltype except for two or three lines.
I think that's wrong. I'm going to patch the existing function so that
they can share code.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#106Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#105)
Re: patch: function xmltable

Given
/messages/by-id/20170116210019.a3glfwspg5lnfrnm@alap3.anarazel.de
which is going to heavily change how the executor works in this area, I
am returning this patch to you again. I would like a few rather minor
changes:

1. to_xmlstr can be replaced with calls to xmlCharStrdup.
2. don't need xml_xmlnodetostr either -- just use xml_xmlnodetoxmltype
(which returns text*) and extract the cstring from the varlena. It's
a bit more wasteful in terms of cycles, but I don't think we care.
If we do care, change the function so that it returns cstring, and
have the callers that want text wrap it in cstring_to_text.
3. have a new perValueCxt memcxt in TableExprState, child of buildercxt,
and switch to it just before GetValue() (reset it just before
switching). Then, don't worry about leaks in GetValue. This way,
the text* conversions et al don't matter.

After that I think we're going to need to get this working on top of
Andres' changes. Which I'm afraid is going to be rather major surgery,
but I haven't looked.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#107Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#106)
Re: patch: function xmltable

In case this still matters, I think GetValue should look more or less
like this (untested):

/*
* Return the value for column number 'colnum' for the current row. If column
* -1 is requested, return representation of the whole row.
*
* This leaks memory, so be sure to reset often the context in which it's
* called.
*/
static Datum
XmlTableGetValue(TableExprState *state, int colnum, bool *isnull)
{
#ifdef USE_LIBXML
XmlTableBuilderData *xtCxt;
Datum result = (Datum) 0;
xmlNodePtr cur;
char *cstr = NULL;
volatile xmlXPathObjectPtr xpathobj;

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 (cur->type != XML_ELEMENT_NODE)
elog(ERROR, "unexpected xmlNode type");

/* Handle whole row case the easy way. */
if (colnum == -1)
{
text *txt;

txt = xml_xmlnodetoxmltype(cur, xtCxt->xmlerrcxt);
result = InputFunctionCall(&state->in_functions[0],
text_to_cstring(txt),
state->typioparams[0],
-1);
*isnull = false;

return result;
}

Assert(xtCxt->xpathscomp[colnum] != NULL);

xpathobj = NULL;
PG_TRY();
{
Form_pg_attribute attr;

attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum];

/* Set current node as entry point for XPath evaluation */
xmlXPathSetContextNode(cur, xtCxt->xpathcxt);

/* Evaluate column path */
xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
"could not create XPath object");

if (xpathobj->type == XPATH_NODESET)
{
int count;
Oid targettypid = attr->atttypid;

if (xpathobj->nodesetval != NULL)
count = xpathobj->nodesetval->nodeNr;

/*
* There are four possible cases, depending on the number of
* nodes returned by the XPath expression and the type of the
* target column: a) XPath returns no nodes. b) One node is
* returned, and column is of type XML. c) One node, column type
* other than XML. d) Multiple nodes are returned.
*/
if (xpathobj->nodesetval == NULL)
{
*isnull = true;
}
else if (count == 1 && targettypid == XMLOID)
{
textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
xtCxt->xmlerrcxt);
cstr = text_to_cstring(textstr);
}
else if (count == 1)
{
xmlChar *str;

str = xmlNodeListGetString(xtCxt->doc,
xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
1);
if (str)
{
PG_TRY();
{
cstr = pstrdup(str);
}
PG_CATCH();
{
xmlFree(str);
PG_RE_THROW();
}
PG_END_TRY();
xmlFree(str);
}
else
cstr = pstrdup("");
}
else
{
StringInfoData buf;
int i;

Assert(count > 1);

/*
* When evaluating the XPath expression returns multiple
* nodes, the result is the concatenation of them all.
* The target type must be XML.
*/
if (targettypid != XMLOID)
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("more than one value returned by column XPath expression")));

initStringInfo(&buf);
for (i = 0; i < count; i++)
/* worth freeing the text here? Naahh ... */
appendStringInfoText(&buf,
xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
xtCxt->xmlerrcxt));
cstr = buf.data;
}
}
else if (xpathobj->type == XPATH_STRING)
{
cstr = (char *) xpathobj->stringval;
*isnull = false;
}
else
elog(ERROR, "unexpected XPath object type %u", xpathobj->type);

/*
* By here, either cstr contains the result value, or the isnull flag
* has been set.
*/
Assert(cstr || *isnull);

if (!*isnull)
result = InputFunctionCall(&state->in_functions[colnum],
cstr,
state->typioparams[colnum],
attr->atttypmod);
}
PG_CATCH();
{
if (xpathobj != NULL)
xmlXPathFreeObject(xpathobj);
PG_RE_THROW();
}
PG_END_TRY();

if (xpathobj)
xmlXPathFreeObject(xpathobj);

return result;
#else
NO_XML_SUPPORT();
#endif /* not USE_LIBXML */
}

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#108Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#106)
Re: patch: function xmltable

2017-01-16 23:51 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Given
/messages/by-id/20170116210019.
a3glfwspg5lnfrnm@alap3.anarazel.de
which is going to heavily change how the executor works in this area, I
am returning this patch to you again. I would like a few rather minor
changes:

1. to_xmlstr can be replaced with calls to xmlCharStrdup.
2. don't need xml_xmlnodetostr either -- just use xml_xmlnodetoxmltype
(which returns text*) and extract the cstring from the varlena. It's
a bit more wasteful in terms of cycles, but I don't think we care.
If we do care, change the function so that it returns cstring, and
have the callers that want text wrap it in cstring_to_text.
3. have a new perValueCxt memcxt in TableExprState, child of buildercxt,
and switch to it just before GetValue() (reset it just before
switching). Then, don't worry about leaks in GetValue. This way,
the text* conversions et al don't matter.

After that I think we're going to need to get this working on top of
Andres' changes. Which I'm afraid is going to be rather major surgery,
but I haven't looked.

I'll try to clean xml part first, and then I can reflect the SRF changes. I
am not sure if I understand to all your proposed changes here, I have to
look there.

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#109Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#106)
1 attachment(s)
Re: patch: function xmltable

Hi

2017-01-16 23:51 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Given
/messages/by-id/20170116210019.a3glfws
pg5lnfrnm@alap3.anarazel.de
which is going to heavily change how the executor works in this area, I
am returning this patch to you again. I would like a few rather minor
changes:

1. to_xmlstr can be replaced with calls to xmlCharStrdup.

I checked this idea, and it doesn't look well - xmlCharStrdup created xml
string in own memory - and it should be explicitly released with xmlFree().
In this case is more practical using PostgreSQL memory context - because
this memory is released safely in exception. I can rename this function to
more conventional pg_xmlCharStrndup. This function can be used more time in
current code.

2. don't need xml_xmlnodetostr either -- just use xml_xmlnodetoxmltype
(which returns text*) and extract the cstring from the varlena. It's
a bit more wasteful in terms of cycles, but I don't think we care.
If we do care, change the function so that it returns cstring, and
have the callers that want text wrap it in cstring_to_text.

done - it is related to not too often use case, and possible slowdown is
minimal

3. have a new perValueCxt memcxt in TableExprState, child of buildercxt,
and switch to it just before GetValue() (reset it just before
switching). Then, don't worry about leaks in GetValue. This way,
the text* conversions et al don't matter.

done

After that I think we're going to need to get this working on top of
Andres' changes. Which I'm afraid is going to be rather major surgery,
but I haven't looked.

I am waiting on new commits in this area. This moment I have not idea what
will be broken.

attached updated patch with cleaned xml part

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-31.patchtext/x-patch; charset=US-ASCII; name=xmltable-31.patchDownload
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..102c447 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,311 @@ 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 - it is evaluated in short life context */
+				MemoryContextSwitchTo(tstate->perValueCxt);
+				values[colno] = routine->GetValue(tstate,
+												  tstate->evalcols ? colno : -1,
+												  &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);
+
+		/* reset one row life context */
+		MemoryContextReset(tstate->perValueCxt);
+	}
+	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 +5568,49 @@ 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->perValueCxt =
+					AllocSetContextCreate(tstate->buildercxt,
+										  "TableExpr per value 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 7107bbf..cca22ef 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1989,6 +1989,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
  *
@@ -4681,6 +4744,9 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5203,6 +5269,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 ec4bbfc..94e460b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2629,6 +2629,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);
@@ -2712,6 +2738,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
  */
@@ -2977,6 +3025,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
@@ -3486,6 +3537,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 cf0a605..caee000 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1576,6 +1576,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.
@@ -3328,6 +3350,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'
@@ -3589,6 +3637,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;
@@ -3927,6 +3978,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 e02dd94..ffd4f8a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2303,6 +2303,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
@@ -2538,6 +2565,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 59ccdf4..5efbb58 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 e61ba06..f483968 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -543,6 +543,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
@@ -600,10 +605,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
 
@@ -674,8 +679,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
 
@@ -12378,6 +12383,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 		;
 
+
 /*
  * Restricted expressions
  *
@@ -12945,6 +12951,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;
+				}
 		;
 
 /*
@@ -13020,6 +13068,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
@@ -14054,6 +14228,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14356,10 +14531,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 add3be6..6fc765a 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..daccd6c 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"
@@ -145,6 +146,7 @@ static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
 static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
 					   ArrayBuildState *astate,
 					   PgXmlErrorContext *xmlerrcxt);
+static xmlChar *pg_xmlCharStrndup(char *str, size_t len);
 #endif   /* USE_LIBXML */
 
 static StringInfo query_to_xml_internal(const char *query, char *tablename,
@@ -165,6 +167,49 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+/* XMLTABLE support */
+#ifdef USE_LIBXML
+/* random number to identify XmlTableContext */
+#define XMLTABLE_CONTEXT_MAGIC	46922182
+typedef struct XmlTableBuilderData
+{
+	int			magic;
+	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), \
@@ -1369,8 +1414,8 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
 			/*
 			 * Note, that here we try to apply DTD defaults
 			 * (XML_PARSE_DTDATTR) according to SQL/XML:2008 GR 10.16.7.d:
-			 * 'Default values defined by internal DTD are applied'. As for
-			 * external DTDs, we try to support them too, (see SQL/XML:2008 GR
+  			 * 'Default values defined by internal DTD are applied'. As for
+  			 * external DTDs, we try to support them too, (see SQL/XML:2008 GR
 			 * 10.16.7.e)
 			 */
 			doc = xmlCtxtReadDoc(ctxt, utf8string,
@@ -3811,13 +3856,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
 				(errcode(ERRCODE_DATA_EXCEPTION),
 				 errmsg("empty XPath expression")));
 
-	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
-	memcpy(string, datastr, len);
-	string[len] = '\0';
-
-	xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
-	memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
-	xpath_expr[xpath_len] = '\0';
+	string = pg_xmlCharStrndup(datastr, len);
+	xpath_expr = pg_xmlCharStrndup(VARDATA(xpath_expr_text), xpath_len);
 
 	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
 
@@ -4065,3 +4105,518 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Securization of cstring for usage in libxml2
+ */
+static xmlChar *
+pg_xmlCharStrndup(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+#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 = pg_xmlCharStrndup(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,
+						   pg_xmlCharStrndup(name, strlen(name)),
+						   pg_xmlCharStrndup(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowFilter
+ */
+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 = pg_xmlCharStrndup(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 = pg_xmlCharStrndup(path, strlen(path));
+
+		xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+		if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+						"invalid XPath expression");
+	}
+	else
+	{
+		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;
+}
+
+/*
+ * Return the value for column number 'colnum' for the current row.  If column
+ * -1 is requested, return representation of the whole row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
+ */
+static Datum
+XmlTableGetValue(TableExprState *state, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+	volatile xmlXPathObjectPtr xpathobj = NULL;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
+
+	Assert(xtCxt->xpathobj &&
+		   xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	*isnull = false;
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
+
+	if (colnum == -1)
+	{
+		/*
+		 * Use whole row, when user doesn't specify a column.
+		 * The target type must be XMLOID.
+		 */
+		Assert(state->resultSlot->tts_tupleDescriptor->attrs[0]->atttypid == XMLOID);
+
+		result = PointerGetDatum(xml_xmlnodetoxmltype(cur, xtCxt->xmlerrcxt));
+
+		return result;
+	}
+
+	Assert(xtCxt->xpathscomp[colnum] != NULL);
+
+	/* fast leaving */
+	if (cur->type != XML_ELEMENT_NODE)
+		elog(ERROR, "unexpected xmlNode type");
+
+	PG_TRY();
+	{
+		Form_pg_attribute attr;
+
+		attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum];
+
+		/* Set current node as entry point for XPath evaluation */
+		xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+		/* Evaluate column path */
+		xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+		if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		/*
+		 * There are four possible cases, depending on the number of
+		 * nodes returned by the XPath expression and the type of the
+		 * target column: a) XPath returns no nodes.  b) One node is
+		 * returned, and column is of type XML.  c) One node, column type
+		 * other than XML.  d) Multiple nodes are returned.
+		 */
+		if (xpathobj->type == XPATH_NODESET)
+		{
+			int			count = 0;
+			Oid			targettypid = attr->atttypid;
+
+			if (xpathobj->nodesetval != NULL)
+				count = xpathobj->nodesetval->nodeNr;
+
+			if (xpathobj->nodesetval == NULL || count == 0)
+			{
+				*isnull = true;
+			}
+			else if (count == 1 && targettypid == XMLOID)
+			{
+				text *textstr;
+
+				/* simple case, result is one value */
+				
+				
+				textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
+																	xtCxt->xmlerrcxt);
+				cstr = text_to_cstring(textstr);
+			}
+			else if (count == 1)
+			{
+				xmlChar    *str;
+
+				str = xmlNodeListGetString(xtCxt->doc,
+								   xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+								   1);
+
+				if (str != NULL)
+				{
+					PG_TRY();
+					{
+						cstr = pstrdup((char *) str);
+					}
+					PG_CATCH();
+					{
+						xmlFree(str);
+						PG_RE_THROW();
+					}
+					PG_END_TRY();
+					xmlFree(str);
+				}
+				else
+				{
+					/* Return empty string when tag is empty */
+					cstr = "";
+				}
+			}
+			else
+			{
+				StringInfoData str;
+				int			i;
+
+				Assert(count > 1);
+
+				/*
+				 * When evaluating the XPath expression returns multiple
+				 * nodes, the result is the concatenation of them all.
+				 * The target type must be XML.
+				 */
+				if (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++)
+				{
+					appendStringInfoText(&str,
+										   xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
+												  xtCxt->xmlerrcxt));
+				}
+				cstr = str.data;
+			}
+		}
+		else if (xpathobj->type == XPATH_STRING)
+		{
+			cstr = (char *) xpathobj->stringval;
+		}
+		else
+			elog(ERROR, "unexpected XPath object type %u", xpathobj->type);
+
+		/*
+		 * By here, either cstr contains the result value, or the isnull flag
+		 * has been set.
+		 */
+		Assert(cstr || *isnull);
+
+		if (!*isnull)
+			result = InputFunctionCall(&state->in_functions[colnum],
+													  cstr,
+													  state->typioparams[colnum],
+													  attr->atttypmod);
+	}
+	PG_CATCH();
+	{
+		if (xpathobj != NULL)
+			xmlXPathFreeObject(xpathobj);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xmlXPathFreeObject(xpathobj);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * 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/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ce13bf7..c0a8464 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1022,6 +1022,33 @@ 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 */
+	MemoryContext perValueCxt;	/* short life context for a value evaluation */
+	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 4c4319b..445e3df 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)
@@ -460,6 +462,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 edb5cd2..b2fe028 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -172,7 +172,6 @@ typedef struct Query
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } Query;
 
-
 /****************************************************************************
  *	Supporting data structures for Parse Trees
  *
@@ -238,6 +237,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 cc1fc39..a3db437 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;
 
@@ -76,4 +77,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
#110Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#109)
1 attachment(s)
Re: patch: function xmltable

Hi

New update - rebase after yesterday changes.

What you want to change?

Regards

Pavel

Attachments:

xmltable-32.patchtext/x-patch; charset=US-ASCII; name=xmltable-32.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index dc662e3..674986f 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 eed7e95..23b0b69 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"
@@ -185,6 +186,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);
 
 
 /* ----------------------------------------------------------------
@@ -4504,6 +4514,311 @@ 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 - it is evaluated in short life context */
+				MemoryContextSwitchTo(tstate->perValueCxt);
+				values[colno] = routine->GetValue(tstate,
+												  tstate->evalcols ? colno : -1,
+												  &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);
+
+		/* reset one row life context */
+		MemoryContextReset(tstate->perValueCxt);
+	}
+	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
@@ -5249,6 +5564,49 @@ 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->perValueCxt =
+					AllocSetContextCreate(tstate->buildercxt,
+										  "TableExpr per value 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 f871e9d..fd213ce 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2005,6 +2005,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
  *
@@ -4702,6 +4765,9 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5224,6 +5290,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 78ed3c7..f28321a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2630,6 +2630,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);
@@ -2713,6 +2739,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
  */
@@ -2978,6 +3026,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
@@ -3487,6 +3538,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 1560ac3..ddb654c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1584,6 +1584,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.
@@ -3352,6 +3374,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'
@@ -3616,6 +3664,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;
@@ -3957,6 +4008,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 dcfa6ee..b3ff987 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2316,6 +2316,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
@@ -2553,6 +2580,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 85ffa3a..aebc40f 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -819,6 +819,11 @@ expression_returns_set_rows(Node *clause)
 			return clamp_row_est(get_func_rows(expr->opfuncid));
 		}
 	}
+	if (IsA(clause, TableExpr))
+	{
+		/* What is adequate estimation 10, 100, 1000? */
+		return 100.0;
+	}
 	return 1.0;
 }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e61ba06..f483968 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -543,6 +543,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
@@ -600,10 +605,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
 
@@ -674,8 +679,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
 
@@ -12378,6 +12383,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 		;
 
+
 /*
  * Restricted expressions
  *
@@ -12945,6 +12951,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;
+				}
 		;
 
 /*
@@ -13020,6 +13068,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
@@ -14054,6 +14228,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14356,10 +14531,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 2a2ac32..2c3f3cd 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1096,9 +1096,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1106,9 +1106,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1117,7 +1117,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1144,6 +1144,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index add3be6..6fc765a 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..daccd6c 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"
@@ -145,6 +146,7 @@ static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
 static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
 					   ArrayBuildState *astate,
 					   PgXmlErrorContext *xmlerrcxt);
+static xmlChar *pg_xmlCharStrndup(char *str, size_t len);
 #endif   /* USE_LIBXML */
 
 static StringInfo query_to_xml_internal(const char *query, char *tablename,
@@ -165,6 +167,49 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+/* XMLTABLE support */
+#ifdef USE_LIBXML
+/* random number to identify XmlTableContext */
+#define XMLTABLE_CONTEXT_MAGIC	46922182
+typedef struct XmlTableBuilderData
+{
+	int			magic;
+	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), \
@@ -1369,8 +1414,8 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
 			/*
 			 * Note, that here we try to apply DTD defaults
 			 * (XML_PARSE_DTDATTR) according to SQL/XML:2008 GR 10.16.7.d:
-			 * 'Default values defined by internal DTD are applied'. As for
-			 * external DTDs, we try to support them too, (see SQL/XML:2008 GR
+  			 * 'Default values defined by internal DTD are applied'. As for
+  			 * external DTDs, we try to support them too, (see SQL/XML:2008 GR
 			 * 10.16.7.e)
 			 */
 			doc = xmlCtxtReadDoc(ctxt, utf8string,
@@ -3811,13 +3856,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
 				(errcode(ERRCODE_DATA_EXCEPTION),
 				 errmsg("empty XPath expression")));
 
-	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
-	memcpy(string, datastr, len);
-	string[len] = '\0';
-
-	xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
-	memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
-	xpath_expr[xpath_len] = '\0';
+	string = pg_xmlCharStrndup(datastr, len);
+	xpath_expr = pg_xmlCharStrndup(VARDATA(xpath_expr_text), xpath_len);
 
 	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
 
@@ -4065,3 +4105,518 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Securization of cstring for usage in libxml2
+ */
+static xmlChar *
+pg_xmlCharStrndup(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+#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 = pg_xmlCharStrndup(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,
+						   pg_xmlCharStrndup(name, strlen(name)),
+						   pg_xmlCharStrndup(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowFilter
+ */
+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 = pg_xmlCharStrndup(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 = pg_xmlCharStrndup(path, strlen(path));
+
+		xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+		if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+						"invalid XPath expression");
+	}
+	else
+	{
+		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;
+}
+
+/*
+ * Return the value for column number 'colnum' for the current row.  If column
+ * -1 is requested, return representation of the whole row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
+ */
+static Datum
+XmlTableGetValue(TableExprState *state, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+	volatile xmlXPathObjectPtr xpathobj = NULL;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
+
+	Assert(xtCxt->xpathobj &&
+		   xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	*isnull = false;
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
+
+	if (colnum == -1)
+	{
+		/*
+		 * Use whole row, when user doesn't specify a column.
+		 * The target type must be XMLOID.
+		 */
+		Assert(state->resultSlot->tts_tupleDescriptor->attrs[0]->atttypid == XMLOID);
+
+		result = PointerGetDatum(xml_xmlnodetoxmltype(cur, xtCxt->xmlerrcxt));
+
+		return result;
+	}
+
+	Assert(xtCxt->xpathscomp[colnum] != NULL);
+
+	/* fast leaving */
+	if (cur->type != XML_ELEMENT_NODE)
+		elog(ERROR, "unexpected xmlNode type");
+
+	PG_TRY();
+	{
+		Form_pg_attribute attr;
+
+		attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum];
+
+		/* Set current node as entry point for XPath evaluation */
+		xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+		/* Evaluate column path */
+		xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+		if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		/*
+		 * There are four possible cases, depending on the number of
+		 * nodes returned by the XPath expression and the type of the
+		 * target column: a) XPath returns no nodes.  b) One node is
+		 * returned, and column is of type XML.  c) One node, column type
+		 * other than XML.  d) Multiple nodes are returned.
+		 */
+		if (xpathobj->type == XPATH_NODESET)
+		{
+			int			count = 0;
+			Oid			targettypid = attr->atttypid;
+
+			if (xpathobj->nodesetval != NULL)
+				count = xpathobj->nodesetval->nodeNr;
+
+			if (xpathobj->nodesetval == NULL || count == 0)
+			{
+				*isnull = true;
+			}
+			else if (count == 1 && targettypid == XMLOID)
+			{
+				text *textstr;
+
+				/* simple case, result is one value */
+				
+				
+				textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
+																	xtCxt->xmlerrcxt);
+				cstr = text_to_cstring(textstr);
+			}
+			else if (count == 1)
+			{
+				xmlChar    *str;
+
+				str = xmlNodeListGetString(xtCxt->doc,
+								   xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+								   1);
+
+				if (str != NULL)
+				{
+					PG_TRY();
+					{
+						cstr = pstrdup((char *) str);
+					}
+					PG_CATCH();
+					{
+						xmlFree(str);
+						PG_RE_THROW();
+					}
+					PG_END_TRY();
+					xmlFree(str);
+				}
+				else
+				{
+					/* Return empty string when tag is empty */
+					cstr = "";
+				}
+			}
+			else
+			{
+				StringInfoData str;
+				int			i;
+
+				Assert(count > 1);
+
+				/*
+				 * When evaluating the XPath expression returns multiple
+				 * nodes, the result is the concatenation of them all.
+				 * The target type must be XML.
+				 */
+				if (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++)
+				{
+					appendStringInfoText(&str,
+										   xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
+												  xtCxt->xmlerrcxt));
+				}
+				cstr = str.data;
+			}
+		}
+		else if (xpathobj->type == XPATH_STRING)
+		{
+			cstr = (char *) xpathobj->stringval;
+		}
+		else
+			elog(ERROR, "unexpected XPath object type %u", xpathobj->type);
+
+		/*
+		 * By here, either cstr contains the result value, or the isnull flag
+		 * has been set.
+		 */
+		Assert(cstr || *isnull);
+
+		if (!*isnull)
+			result = InputFunctionCall(&state->in_functions[colnum],
+													  cstr,
+													  state->typioparams[colnum],
+													  attr->atttypmod);
+	}
+	PG_CATCH();
+	{
+		if (xpathobj != NULL)
+			xmlXPathFreeObject(xpathobj);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xmlXPathFreeObject(xpathobj);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * 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/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1da1e1f..79fe3a0 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1028,6 +1028,33 @@ 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 */
+	MemoryContext perValueCxt;	/* short life context for a value evaluation */
+	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 d659581..3c073f2 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -176,6 +176,7 @@ typedef enum NodeTag
 	T_CoerceToDomainValue,
 	T_SetToDefault,
 	T_CurrentOfExpr,
+	T_TableExpr,
 	T_InferenceElem,
 	T_TargetEntry,
 	T_RangeTblRef,
@@ -218,6 +219,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -463,6 +465,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 edb5cd2..b2fe028 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -172,7 +172,6 @@ typedef struct Query
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } Query;
 
-
 /****************************************************************************
  *	Supporting data structures for Parse Trees
  *
@@ -238,6 +237,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 cc1fc39..a3db437 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;
 
@@ -76,4 +77,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
#111Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#110)
Re: patch: function xmltable

Pavel Stehule wrote:

Hi

New update - rebase after yesterday changes.

What you want to change?

I think the problem might come from the still pending patch on that
series, which Andres posted in
/messages/by-id/20170118221154.aldebi7yyjvds5qa@alap3.anarazel.de
As far as I understand, minor details of that patch might change before
commit, but it is pretty much in definitive form.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#112Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#111)
Re: patch: function xmltable

2017-01-19 13:35 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

Hi

New update - rebase after yesterday changes.

What you want to change?

I think the problem might come from the still pending patch on that
series, which Andres posted in
/messages/by-id/20170118221154.
aldebi7yyjvds5qa@alap3.anarazel.de
As far as I understand, minor details of that patch might change before
commit, but it is pretty much in definitive form.

ok, we have to wait - please, check XML part if it is good for you

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#113Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#111)
1 attachment(s)
Re: patch: function xmltable

Hi

2017-01-19 13:35 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

Hi

New update - rebase after yesterday changes.

What you want to change?

I think the problem might come from the still pending patch on that
series, which Andres posted in
/messages/by-id/20170118221154.
aldebi7yyjvds5qa@alap3.anarazel.de
As far as I understand, minor details of that patch might change before
commit, but it is pretty much in definitive form.

new rebased update after these changes

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-33.patchtext/x-patch; charset=US-ASCII; name=xmltable-33.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1815c84..3b11bc4 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 19dd0b2..93f64d3 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"
@@ -185,6 +186,15 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull);
+static Datum ExecEvalTableExpr(TableExprState *tstate, ExprContext *econtext,
+				  bool *isnull);
+static Datum ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext,
+					  bool *isNull);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum doc);
+static void ShutdownTableExpr(Datum arg);
 
 
 /* ----------------------------------------------------------------
@@ -2033,7 +2043,15 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 		else
 		{
 			result = ExecEvalExpr(funcexpr, econtext, &fcinfo.isnull);
-			rsinfo.isDone = ExprSingleResult;
+
+			/*
+			 * Any other expressions except TableExpr produces
+			 * single result only.
+			 */
+			if (funcexpr && IsA(funcexpr, TableExprState))
+				rsinfo.isDone = ((TableExprState *) funcexpr)->isDone;
+			else
+				rsinfo.isDone = ExprSingleResult;
 		}
 
 		/* Which protocol does function want to use? */
@@ -4209,6 +4227,329 @@ 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(TableExprState *tstate, ExprContext *econtext,
+				  bool *isNull)
+{
+	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);
+	if (*isNull)
+	{
+		tstate->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 */
+	tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExprFast;
+
+	/* Fetch and return one row per call from the table builder */
+	result = tabexprFetchRow(tstate, econtext, isNull);
+
+	return result;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ *		Returns a Datum for a table expression (such as XMLTABLE).
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext,
+					  bool *isNull)
+{
+	TableExprState *tstate = (TableExprState *) exprstate;
+
+	return tabexprFetchRow(tstate, econtext, isNull);
+}
+
+/*
+ * 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);
+	}
+}
+
+/* ----------------------------------------------------------------
+ *		ExecMakeTableExprResultSet
+ * ----------------------------------------------------------------
+ */
+Datum
+ExecMakeTableExprResultSet(TableExprState *texpr,
+						  ExprContext *econtext,
+						  bool *isNull,
+						  ExprDoneCond *isDone)
+{
+	Datum	result;
+
+	result = ExecEvalExpr((ExprState *) texpr,
+							econtext,
+							isNull);
+	*isDone = texpr->isDone;
+
+	return result;
+}
+
+/*
+ * 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);
+		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);
+	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);
+				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)
+{
+	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 - it is evaluated in short life context */
+				MemoryContextSwitchTo(tstate->perValueCxt);
+				values[colno] = routine->GetValue(tstate,
+												  tstate->evalcols ? colno : -1,
+												  &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);
+				}
+
+				/* 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;
+		tstate->isDone = ExprMultipleResult;
+
+		result = ExecFetchSlotTupleDatum(tstate->resultSlot);
+
+		/* reset one row life context */
+		MemoryContextReset(tstate->perValueCxt);
+	}
+	else
+	{
+		/* no more rows */
+		routine->DestroyBuilder(tstate);
+
+		/* make sure all memory is released */
+		ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		MemoryContextReset(tstate->buildercxt);
+
+		*isNull = true;
+		tstate->isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	/* push back short life memory context */
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -4953,6 +5294,49 @@ 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 = (ExprStateEvalFunc) ExecEvalTableExpr;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+				tstate->perValueCxt =
+					AllocSetContextCreate(tstate->buildercxt,
+										  "TableExpr per value 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/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c
index eae0f1d..24f529b 100644
--- a/src/backend/executor/nodeProjectSet.c
+++ b/src/backend/executor/nodeProjectSet.c
@@ -166,6 +166,20 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 				node->pending_srf_tuples = true;
 			hassrf = true;
 		}
+		else if (IsA(gstate->arg, TableExprState))
+		{
+			/*
+			 * Evaluate TableExpr - possibly continuing previously started output.
+			 */
+			*result = ExecMakeTableExprResultSet((TableExprState *) gstate->arg,
+												econtext, isnull, isdone);
+
+			if (*isdone != ExprEndResult)
+				hasresult = true;
+			if (*isdone == ExprMultipleResult)
+				node->pending_srf_tuples = true;
+			hassrf = true;
+		}
 		else
 		{
 			/* Non-SRF tlist expression, just evaluate normally. */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 30d733e..b604e57 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2005,6 +2005,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
  *
@@ -4765,6 +4828,9 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5302,6 +5368,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 55c73b7..6280d0a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2688,6 +2688,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);
@@ -2771,6 +2797,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
  */
@@ -3036,6 +3084,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
@@ -3560,6 +3611,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..c381dcc 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 */
@@ -694,6 +698,9 @@ expression_returns_set_walker(Node *node, void *context)
 		/* else fall through to check args */
 	}
 
+	if (IsA(node, TableExpr))
+		return true;
+
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
 		return false;
@@ -929,6 +936,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 +1137,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 +1572,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 +2237,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 +3049,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 +3678,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 1560ac3..ddb654c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1584,6 +1584,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.
@@ -3352,6 +3374,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'
@@ -3616,6 +3664,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;
@@ -3957,6 +4008,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 dcfa6ee..b3ff987 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2316,6 +2316,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
@@ -2553,6 +2580,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/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4b5902f..9a756a4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5194,6 +5194,8 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
 			PathTarget *thistarget = (PathTarget *) lfirst(lc1);
 			bool		contains_srfs = (bool) lfirst_int(lc2);
 
+
+
 			/* If this level doesn't contain SRFs, do regular projection */
 			if (contains_srfs)
 				newpath = (Path *) create_set_projection_path(root,
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index d589dc2..17f9fdb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -819,6 +819,11 @@ expression_returns_set_rows(Node *clause)
 			return clamp_row_est(get_func_rows(expr->opfuncid));
 		}
 	}
+	if (IsA(clause, TableExpr))
+	{
+		/* What is adequate estimation 10, 100, 1000? */
+		return 100.0;
+	}
 	return 1.0;
 }
 
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index cca5db8..1abbcf1 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -877,6 +877,13 @@ split_pathtarget_at_srfs(PlannerInfo *root,
 				split_pathtarget_walker((Node *) ((OpExpr *) node)->args,
 										&context);
 			}
+			else if (IsA(node, TableExpr))
+			{
+				/* Same as above, but for table expressions like XMLTABLE */
+				target_contains_srfs = true;
+				split_pathtarget_walker((Node *) ((OpExpr *) node)->args,
+										&context);
+			}
 			else
 			{
 				/* Not a top-level SRF, so recursively examine expression */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a8e35fe..b4eeb68 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -549,6 +549,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
@@ -606,10 +611,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
 
@@ -680,8 +685,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
 
@@ -12623,6 +12628,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 		;
 
+
 /*
  * Restricted expressions
  *
@@ -13190,6 +13196,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;
+				}
 		;
 
 /*
@@ -13265,6 +13313,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
@@ -14299,6 +14473,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14604,10 +14779,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 2a2ac32..2c3f3cd 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1096,9 +1096,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1106,9 +1106,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1117,7 +1117,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1144,6 +1144,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index add3be6..6fc765a 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 745e009..e9889b9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8548,6 +8548,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 e8bce3b..de671c7 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"
@@ -145,6 +146,7 @@ static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
 static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
 					   ArrayBuildState *astate,
 					   PgXmlErrorContext *xmlerrcxt);
+static xmlChar *pg_xmlCharStrndup(char *str, size_t len);
 #endif   /* USE_LIBXML */
 
 static StringInfo query_to_xml_internal(const char *query, char *tablename,
@@ -165,6 +167,49 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+/* XMLTABLE support */
+#ifdef USE_LIBXML
+/* random number to identify XmlTableContext */
+#define XMLTABLE_CONTEXT_MAGIC	46922182
+typedef struct XmlTableBuilderData
+{
+	int			magic;
+	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), \
@@ -1369,8 +1414,8 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
 			/*
 			 * Note, that here we try to apply DTD defaults
 			 * (XML_PARSE_DTDATTR) according to SQL/XML:2008 GR 10.16.7.d:
-			 * 'Default values defined by internal DTD are applied'. As for
-			 * external DTDs, we try to support them too, (see SQL/XML:2008 GR
+  			 * 'Default values defined by internal DTD are applied'. As for
+  			 * external DTDs, we try to support them too, (see SQL/XML:2008 GR
 			 * 10.16.7.e)
 			 */
 			doc = xmlCtxtReadDoc(ctxt, utf8string,
@@ -3811,13 +3856,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
 				(errcode(ERRCODE_DATA_EXCEPTION),
 				 errmsg("empty XPath expression")));
 
-	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
-	memcpy(string, datastr, len);
-	string[len] = '\0';
-
-	xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
-	memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
-	xpath_expr[xpath_len] = '\0';
+	string = pg_xmlCharStrndup(datastr, len);
+	xpath_expr = pg_xmlCharStrndup(VARDATA(xpath_expr_text), xpath_len);
 
 	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
 
@@ -4065,3 +4105,518 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Securization of cstring for usage in libxml2
+ */
+static xmlChar *
+pg_xmlCharStrndup(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+#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 = pg_xmlCharStrndup(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,
+						   pg_xmlCharStrndup(name, strlen(name)),
+						   pg_xmlCharStrndup(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowFilter
+ */
+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 = pg_xmlCharStrndup(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 = pg_xmlCharStrndup(path, strlen(path));
+
+		xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+		if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+						"invalid XPath expression");
+	}
+	else
+	{
+		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;
+}
+
+/*
+ * Return the value for column number 'colnum' for the current row.  If column
+ * -1 is requested, return representation of the whole row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
+ */
+static Datum
+XmlTableGetValue(TableExprState *state, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+	volatile xmlXPathObjectPtr xpathobj = NULL;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
+
+	Assert(xtCxt->xpathobj &&
+		   xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	*isnull = false;
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
+
+	if (colnum == -1)
+	{
+		/*
+		 * Use whole row, when user doesn't specify a column.
+		 * The target type must be XMLOID.
+		 */
+		Assert(state->resultSlot->tts_tupleDescriptor->attrs[0]->atttypid == XMLOID);
+
+		result = PointerGetDatum(xml_xmlnodetoxmltype(cur, xtCxt->xmlerrcxt));
+
+		return result;
+	}
+
+	Assert(xtCxt->xpathscomp[colnum] != NULL);
+
+	/* fast leaving */
+	if (cur->type != XML_ELEMENT_NODE)
+		elog(ERROR, "unexpected xmlNode type");
+
+	PG_TRY();
+	{
+		Form_pg_attribute attr;
+
+		attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum];
+
+		/* Set current node as entry point for XPath evaluation */
+		xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+		/* Evaluate column path */
+		xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+		if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		/*
+		 * There are four possible cases, depending on the number of
+		 * nodes returned by the XPath expression and the type of the
+		 * target column: a) XPath returns no nodes.  b) One node is
+		 * returned, and column is of type XML.  c) One node, column type
+		 * other than XML.  d) Multiple nodes are returned.
+		 */
+		if (xpathobj->type == XPATH_NODESET)
+		{
+			int			count = 0;
+			Oid			targettypid = attr->atttypid;
+
+			if (xpathobj->nodesetval != NULL)
+				count = xpathobj->nodesetval->nodeNr;
+
+			if (xpathobj->nodesetval == NULL || count == 0)
+			{
+				*isnull = true;
+			}
+			else if (count == 1 && targettypid == XMLOID)
+			{
+				text *textstr;
+
+				/* simple case, result is one value */
+				
+				
+				textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
+																	xtCxt->xmlerrcxt);
+				cstr = text_to_cstring(textstr);
+			}
+			else if (count == 1)
+			{
+				xmlChar    *str;
+
+				str = xmlNodeListGetString(xtCxt->doc,
+								   xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+								   1);
+
+				if (str != NULL)
+				{
+					PG_TRY();
+					{
+						cstr = pstrdup((char *) str);
+					}
+					PG_CATCH();
+					{
+						xmlFree(str);
+						PG_RE_THROW();
+					}
+					PG_END_TRY();
+					xmlFree(str);
+				}
+				else
+				{
+					/* Return empty string when tag is empty */
+					cstr = "";
+				}
+			}
+			else
+			{
+				StringInfoData str;
+				int			i;
+
+				Assert(count > 1);
+
+				/*
+				 * When evaluating the XPath expression returns multiple
+				 * nodes, the result is the concatenation of them all.
+				 * The target type must be XML.
+				 */
+				if (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++)
+				{
+					appendStringInfoText(&str,
+										   xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
+												  xtCxt->xmlerrcxt));
+				}
+				cstr = str.data;
+			}
+		}
+		else if (xpathobj->type == XPATH_STRING)
+		{
+			cstr = (char *) xpathobj->stringval;
+		}
+		else
+			elog(ERROR, "unexpected XPath object type %u", xpathobj->type);
+
+		/*
+		 * By here, either cstr contains the result value, or the isnull flag
+		 * has been set.
+		 */
+		Assert(cstr || *isnull);
+
+		if (!*isnull)
+			result = InputFunctionCall(&state->in_functions[colnum],
+													  cstr,
+													  state->typioparams[colnum],
+													  attr->atttypmod);
+	}
+	PG_CATCH();
+	{
+		if (xpathobj != NULL)
+			xmlXPathFreeObject(xpathobj);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xmlXPathFreeObject(xpathobj);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * 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 c55da54..3ccee52 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -242,6 +242,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/executor.h b/src/include/executor/executor.h
index 02dbe7b..1727ed8 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -256,6 +256,10 @@ extern Datum ExecMakeFunctionResultSet(FuncExprState *fcache,
 						  ExprContext *econtext,
 						  bool *isNull,
 						  ExprDoneCond *isDone);
+extern Datum ExecMakeTableExprResultSet(TableExprState *texpr,
+						  ExprContext *econtext,
+						  bool *isNull,
+						  ExprDoneCond *isDone);
 extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
 						  bool *isNull);
 extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f9bcdd6..093e295 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1019,6 +1019,34 @@ 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 */
+	MemoryContext perValueCxt;	/* short life context for a value evaluation */
+	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 */
+	ExprDoneCond isDone;		/* status for ValuePerCall mode */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fa4932a..dee8caf 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -176,6 +176,7 @@ typedef enum NodeTag
 	T_CoerceToDomainValue,
 	T_SetToDefault,
 	T_CurrentOfExpr,
+	T_TableExpr,
 	T_InferenceElem,
 	T_TargetEntry,
 	T_RangeTblRef,
@@ -218,6 +219,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -468,6 +470,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 aad4699..8407b5e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -172,7 +172,6 @@ typedef struct Query
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } Query;
 
-
 /****************************************************************************
  *	Supporting data structures for Parse Trees
  *
@@ -238,6 +237,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 985d650..28c4dab 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)
@@ -446,10 +447,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
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 cc1fc39..a3db437 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;
 
@@ -76,4 +77,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..f786584 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,417 @@ 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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d702703..6557ccd 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,328 @@ 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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 rows)
+
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5..d5d06ca 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,417 @@ 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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 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
#114Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#113)
1 attachment(s)
Re: patch: function xmltable

2017-01-21 9:30 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

2017-01-19 13:35 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

Hi

New update - rebase after yesterday changes.

What you want to change?

I think the problem might come from the still pending patch on that
series, which Andres posted in
/messages/by-id/20170118221154.aldebi7
yyjvds5qa@alap3.anarazel.de
As far as I understand, minor details of that patch might change before
commit, but it is pretty much in definitive form.

new rebased update after these changes

fix white spaces

pavel

Show quoted text

Regards

Pavel

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-34.patchtext/x-patch; charset=US-ASCII; name=xmltable-34.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1815c84..3b11bc4 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 19dd0b2..93f64d3 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"
@@ -185,6 +186,15 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull);
+static Datum ExecEvalTableExpr(TableExprState *tstate, ExprContext *econtext,
+				  bool *isnull);
+static Datum ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext,
+					  bool *isNull);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum doc);
+static void ShutdownTableExpr(Datum arg);
 
 
 /* ----------------------------------------------------------------
@@ -2033,7 +2043,15 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 		else
 		{
 			result = ExecEvalExpr(funcexpr, econtext, &fcinfo.isnull);
-			rsinfo.isDone = ExprSingleResult;
+
+			/*
+			 * Any other expressions except TableExpr produces
+			 * single result only.
+			 */
+			if (funcexpr && IsA(funcexpr, TableExprState))
+				rsinfo.isDone = ((TableExprState *) funcexpr)->isDone;
+			else
+				rsinfo.isDone = ExprSingleResult;
 		}
 
 		/* Which protocol does function want to use? */
@@ -4209,6 +4227,329 @@ 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(TableExprState *tstate, ExprContext *econtext,
+				  bool *isNull)
+{
+	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);
+	if (*isNull)
+	{
+		tstate->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 */
+	tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExprFast;
+
+	/* Fetch and return one row per call from the table builder */
+	result = tabexprFetchRow(tstate, econtext, isNull);
+
+	return result;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ *		Returns a Datum for a table expression (such as XMLTABLE).
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext,
+					  bool *isNull)
+{
+	TableExprState *tstate = (TableExprState *) exprstate;
+
+	return tabexprFetchRow(tstate, econtext, isNull);
+}
+
+/*
+ * 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);
+	}
+}
+
+/* ----------------------------------------------------------------
+ *		ExecMakeTableExprResultSet
+ * ----------------------------------------------------------------
+ */
+Datum
+ExecMakeTableExprResultSet(TableExprState *texpr,
+						  ExprContext *econtext,
+						  bool *isNull,
+						  ExprDoneCond *isDone)
+{
+	Datum	result;
+
+	result = ExecEvalExpr((ExprState *) texpr,
+							econtext,
+							isNull);
+	*isDone = texpr->isDone;
+
+	return result;
+}
+
+/*
+ * 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);
+		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);
+	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);
+				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)
+{
+	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 - it is evaluated in short life context */
+				MemoryContextSwitchTo(tstate->perValueCxt);
+				values[colno] = routine->GetValue(tstate,
+												  tstate->evalcols ? colno : -1,
+												  &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);
+				}
+
+				/* 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;
+		tstate->isDone = ExprMultipleResult;
+
+		result = ExecFetchSlotTupleDatum(tstate->resultSlot);
+
+		/* reset one row life context */
+		MemoryContextReset(tstate->perValueCxt);
+	}
+	else
+	{
+		/* no more rows */
+		routine->DestroyBuilder(tstate);
+
+		/* make sure all memory is released */
+		ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		MemoryContextReset(tstate->buildercxt);
+
+		*isNull = true;
+		tstate->isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	/* push back short life memory context */
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -4953,6 +5294,49 @@ 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 = (ExprStateEvalFunc) ExecEvalTableExpr;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+				tstate->perValueCxt =
+					AllocSetContextCreate(tstate->buildercxt,
+										  "TableExpr per value 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/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c
index eae0f1d..24f529b 100644
--- a/src/backend/executor/nodeProjectSet.c
+++ b/src/backend/executor/nodeProjectSet.c
@@ -166,6 +166,20 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 				node->pending_srf_tuples = true;
 			hassrf = true;
 		}
+		else if (IsA(gstate->arg, TableExprState))
+		{
+			/*
+			 * Evaluate TableExpr - possibly continuing previously started output.
+			 */
+			*result = ExecMakeTableExprResultSet((TableExprState *) gstate->arg,
+												econtext, isnull, isdone);
+
+			if (*isdone != ExprEndResult)
+				hasresult = true;
+			if (*isdone == ExprMultipleResult)
+				node->pending_srf_tuples = true;
+			hassrf = true;
+		}
 		else
 		{
 			/* Non-SRF tlist expression, just evaluate normally. */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 30d733e..b604e57 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2005,6 +2005,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
  *
@@ -4765,6 +4828,9 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5302,6 +5368,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 55c73b7..6280d0a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2688,6 +2688,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);
@@ -2771,6 +2797,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
  */
@@ -3036,6 +3084,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
@@ -3560,6 +3611,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..c381dcc 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 */
@@ -694,6 +698,9 @@ expression_returns_set_walker(Node *node, void *context)
 		/* else fall through to check args */
 	}
 
+	if (IsA(node, TableExpr))
+		return true;
+
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
 		return false;
@@ -929,6 +936,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 +1137,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 +1572,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 +2237,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 +3049,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 +3678,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 1560ac3..ddb654c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1584,6 +1584,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.
@@ -3352,6 +3374,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'
@@ -3616,6 +3664,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;
@@ -3957,6 +4008,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 dcfa6ee..b3ff987 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2316,6 +2316,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
@@ -2553,6 +2580,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/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4b5902f..9a756a4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5194,6 +5194,8 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
 			PathTarget *thistarget = (PathTarget *) lfirst(lc1);
 			bool		contains_srfs = (bool) lfirst_int(lc2);
 
+
+
 			/* If this level doesn't contain SRFs, do regular projection */
 			if (contains_srfs)
 				newpath = (Path *) create_set_projection_path(root,
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index d589dc2..17f9fdb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -819,6 +819,11 @@ expression_returns_set_rows(Node *clause)
 			return clamp_row_est(get_func_rows(expr->opfuncid));
 		}
 	}
+	if (IsA(clause, TableExpr))
+	{
+		/* What is adequate estimation 10, 100, 1000? */
+		return 100.0;
+	}
 	return 1.0;
 }
 
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index cca5db8..1abbcf1 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -877,6 +877,13 @@ split_pathtarget_at_srfs(PlannerInfo *root,
 				split_pathtarget_walker((Node *) ((OpExpr *) node)->args,
 										&context);
 			}
+			else if (IsA(node, TableExpr))
+			{
+				/* Same as above, but for table expressions like XMLTABLE */
+				target_contains_srfs = true;
+				split_pathtarget_walker((Node *) ((OpExpr *) node)->args,
+										&context);
+			}
 			else
 			{
 				/* Not a top-level SRF, so recursively examine expression */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a8e35fe..b4eeb68 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -549,6 +549,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
@@ -606,10 +611,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
 
@@ -680,8 +685,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
 
@@ -12623,6 +12628,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 		;
 
+
 /*
  * Restricted expressions
  *
@@ -13190,6 +13196,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;
+				}
 		;
 
 /*
@@ -13265,6 +13313,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
@@ -14299,6 +14473,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14604,10 +14779,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 2a2ac32..2c3f3cd 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1096,9 +1096,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1106,9 +1106,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1117,7 +1117,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1144,6 +1144,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index add3be6..6fc765a 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 745e009..e9889b9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8548,6 +8548,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 e8bce3b..bcf6097 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"
@@ -145,6 +146,7 @@ static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
 static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
 					   ArrayBuildState *astate,
 					   PgXmlErrorContext *xmlerrcxt);
+static xmlChar *pg_xmlCharStrndup(char *str, size_t len);
 #endif   /* USE_LIBXML */
 
 static StringInfo query_to_xml_internal(const char *query, char *tablename,
@@ -165,6 +167,49 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+/* XMLTABLE support */
+#ifdef USE_LIBXML
+/* random number to identify XmlTableContext */
+#define XMLTABLE_CONTEXT_MAGIC	46922182
+typedef struct XmlTableBuilderData
+{
+	int			magic;
+	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), \
@@ -3811,13 +3856,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
 				(errcode(ERRCODE_DATA_EXCEPTION),
 				 errmsg("empty XPath expression")));
 
-	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
-	memcpy(string, datastr, len);
-	string[len] = '\0';
-
-	xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
-	memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
-	xpath_expr[xpath_len] = '\0';
+	string = pg_xmlCharStrndup(datastr, len);
+	xpath_expr = pg_xmlCharStrndup(VARDATA(xpath_expr_text), xpath_len);
 
 	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
 
@@ -4065,3 +4105,516 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Securization of cstring for usage in libxml2
+ */
+static xmlChar *
+pg_xmlCharStrndup(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+#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 = pg_xmlCharStrndup(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,
+						   pg_xmlCharStrndup(name, strlen(name)),
+						   pg_xmlCharStrndup(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowFilter
+ */
+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 = pg_xmlCharStrndup(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 = pg_xmlCharStrndup(path, strlen(path));
+
+		xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+		if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+						"invalid XPath expression");
+	}
+	else
+	{
+		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;
+}
+
+/*
+ * Return the value for column number 'colnum' for the current row.  If column
+ * -1 is requested, return representation of the whole row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
+ */
+static Datum
+XmlTableGetValue(TableExprState *state, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+	volatile xmlXPathObjectPtr xpathobj = NULL;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
+
+	Assert(xtCxt->xpathobj &&
+		   xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	*isnull = false;
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
+
+	if (colnum == -1)
+	{
+		/*
+		 * Use whole row, when user doesn't specify a column.
+		 * The target type must be XMLOID.
+		 */
+		Assert(state->resultSlot->tts_tupleDescriptor->attrs[0]->atttypid == XMLOID);
+
+		result = PointerGetDatum(xml_xmlnodetoxmltype(cur, xtCxt->xmlerrcxt));
+
+		return result;
+	}
+
+	Assert(xtCxt->xpathscomp[colnum] != NULL);
+
+	/* fast leaving */
+	if (cur->type != XML_ELEMENT_NODE)
+		elog(ERROR, "unexpected xmlNode type");
+
+	PG_TRY();
+	{
+		Form_pg_attribute attr;
+
+		attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum];
+
+		/* Set current node as entry point for XPath evaluation */
+		xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+		/* Evaluate column path */
+		xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+		if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		/*
+		 * There are four possible cases, depending on the number of
+		 * nodes returned by the XPath expression and the type of the
+		 * target column: a) XPath returns no nodes.  b) One node is
+		 * returned, and column is of type XML.  c) One node, column type
+		 * other than XML.  d) Multiple nodes are returned.
+		 */
+		if (xpathobj->type == XPATH_NODESET)
+		{
+			int			count = 0;
+			Oid			targettypid = attr->atttypid;
+
+			if (xpathobj->nodesetval != NULL)
+				count = xpathobj->nodesetval->nodeNr;
+
+			if (xpathobj->nodesetval == NULL || count == 0)
+			{
+				*isnull = true;
+			}
+			else if (count == 1 && targettypid == XMLOID)
+			{
+				text *textstr;
+
+				/* simple case, result is one value */
+				textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
+																	xtCxt->xmlerrcxt);
+				cstr = text_to_cstring(textstr);
+			}
+			else if (count == 1)
+			{
+				xmlChar    *str;
+
+				str = xmlNodeListGetString(xtCxt->doc,
+								   xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+								   1);
+
+				if (str != NULL)
+				{
+					PG_TRY();
+					{
+						cstr = pstrdup((char *) str);
+					}
+					PG_CATCH();
+					{
+						xmlFree(str);
+						PG_RE_THROW();
+					}
+					PG_END_TRY();
+					xmlFree(str);
+				}
+				else
+				{
+					/* Return empty string when tag is empty */
+					cstr = "";
+				}
+			}
+			else
+			{
+				StringInfoData str;
+				int			i;
+
+				Assert(count > 1);
+
+				/*
+				 * When evaluating the XPath expression returns multiple
+				 * nodes, the result is the concatenation of them all.
+				 * The target type must be XML.
+				 */
+				if (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++)
+				{
+					appendStringInfoText(&str,
+										   xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
+												  xtCxt->xmlerrcxt));
+				}
+				cstr = str.data;
+			}
+		}
+		else if (xpathobj->type == XPATH_STRING)
+		{
+			cstr = (char *) xpathobj->stringval;
+		}
+		else
+			elog(ERROR, "unexpected XPath object type %u", xpathobj->type);
+
+		/*
+		 * By here, either cstr contains the result value, or the isnull flag
+		 * has been set.
+		 */
+		Assert(cstr || *isnull);
+
+		if (!*isnull)
+			result = InputFunctionCall(&state->in_functions[colnum],
+													  cstr,
+													  state->typioparams[colnum],
+													  attr->atttypmod);
+	}
+	PG_CATCH();
+	{
+		if (xpathobj != NULL)
+			xmlXPathFreeObject(xpathobj);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xmlXPathFreeObject(xpathobj);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * 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 c55da54..3ccee52 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -242,6 +242,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/executor.h b/src/include/executor/executor.h
index 02dbe7b..1727ed8 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -256,6 +256,10 @@ extern Datum ExecMakeFunctionResultSet(FuncExprState *fcache,
 						  ExprContext *econtext,
 						  bool *isNull,
 						  ExprDoneCond *isDone);
+extern Datum ExecMakeTableExprResultSet(TableExprState *texpr,
+						  ExprContext *econtext,
+						  bool *isNull,
+						  ExprDoneCond *isDone);
 extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
 						  bool *isNull);
 extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f9bcdd6..093e295 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1019,6 +1019,34 @@ 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 */
+	MemoryContext perValueCxt;	/* short life context for a value evaluation */
+	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 */
+	ExprDoneCond isDone;		/* status for ValuePerCall mode */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fa4932a..dee8caf 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -176,6 +176,7 @@ typedef enum NodeTag
 	T_CoerceToDomainValue,
 	T_SetToDefault,
 	T_CurrentOfExpr,
+	T_TableExpr,
 	T_InferenceElem,
 	T_TargetEntry,
 	T_RangeTblRef,
@@ -218,6 +219,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -468,6 +470,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 aad4699..8407b5e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -172,7 +172,6 @@ typedef struct Query
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } Query;
 
-
 /****************************************************************************
  *	Supporting data structures for Parse Trees
  *
@@ -238,6 +237,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 985d650..28c4dab 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)
@@ -446,10 +447,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
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 cc1fc39..a3db437 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;
 
@@ -76,4 +77,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..f786584 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,417 @@ 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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d702703..6557ccd 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,328 @@ 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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 rows)
+
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5..d5d06ca 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,417 @@ 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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 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
#115Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#114)
1 attachment(s)
Re: patch: function xmltable

Hi

2017-01-21 10:31 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2017-01-21 9:30 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

2017-01-19 13:35 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

Hi

New update - rebase after yesterday changes.

What you want to change?

I think the problem might come from the still pending patch on that
series, which Andres posted in
/messages/by-id/20170118221154.aldebi7
yyjvds5qa@alap3.anarazel.de
As far as I understand, minor details of that patch might change before
commit, but it is pretty much in definitive form.

new rebased update after these changes

fix white spaces

few fixes:

* SELECT (xmltable(..)).* + regress tests
* compilation and regress tests without --with-libxml

Regards

Pavel

Show quoted text

pavel

Regards

Pavel

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-35.patchtext/x-patch; charset=US-ASCII; name=xmltable-35.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1815c84..3b11bc4 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 19dd0b2..93f64d3 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"
@@ -185,6 +186,15 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull);
+static Datum ExecEvalTableExpr(TableExprState *tstate, ExprContext *econtext,
+				  bool *isnull);
+static Datum ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext,
+					  bool *isNull);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum doc);
+static void ShutdownTableExpr(Datum arg);
 
 
 /* ----------------------------------------------------------------
@@ -2033,7 +2043,15 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 		else
 		{
 			result = ExecEvalExpr(funcexpr, econtext, &fcinfo.isnull);
-			rsinfo.isDone = ExprSingleResult;
+
+			/*
+			 * Any other expressions except TableExpr produces
+			 * single result only.
+			 */
+			if (funcexpr && IsA(funcexpr, TableExprState))
+				rsinfo.isDone = ((TableExprState *) funcexpr)->isDone;
+			else
+				rsinfo.isDone = ExprSingleResult;
 		}
 
 		/* Which protocol does function want to use? */
@@ -4209,6 +4227,329 @@ 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(TableExprState *tstate, ExprContext *econtext,
+				  bool *isNull)
+{
+	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);
+	if (*isNull)
+	{
+		tstate->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 */
+	tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExprFast;
+
+	/* Fetch and return one row per call from the table builder */
+	result = tabexprFetchRow(tstate, econtext, isNull);
+
+	return result;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ *		Returns a Datum for a table expression (such as XMLTABLE).
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext,
+					  bool *isNull)
+{
+	TableExprState *tstate = (TableExprState *) exprstate;
+
+	return tabexprFetchRow(tstate, econtext, isNull);
+}
+
+/*
+ * 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);
+	}
+}
+
+/* ----------------------------------------------------------------
+ *		ExecMakeTableExprResultSet
+ * ----------------------------------------------------------------
+ */
+Datum
+ExecMakeTableExprResultSet(TableExprState *texpr,
+						  ExprContext *econtext,
+						  bool *isNull,
+						  ExprDoneCond *isDone)
+{
+	Datum	result;
+
+	result = ExecEvalExpr((ExprState *) texpr,
+							econtext,
+							isNull);
+	*isDone = texpr->isDone;
+
+	return result;
+}
+
+/*
+ * 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);
+		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);
+	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);
+				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)
+{
+	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 - it is evaluated in short life context */
+				MemoryContextSwitchTo(tstate->perValueCxt);
+				values[colno] = routine->GetValue(tstate,
+												  tstate->evalcols ? colno : -1,
+												  &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);
+				}
+
+				/* 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;
+		tstate->isDone = ExprMultipleResult;
+
+		result = ExecFetchSlotTupleDatum(tstate->resultSlot);
+
+		/* reset one row life context */
+		MemoryContextReset(tstate->perValueCxt);
+	}
+	else
+	{
+		/* no more rows */
+		routine->DestroyBuilder(tstate);
+
+		/* make sure all memory is released */
+		ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		MemoryContextReset(tstate->buildercxt);
+
+		*isNull = true;
+		tstate->isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	/* push back short life memory context */
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -4953,6 +5294,49 @@ 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 = (ExprStateEvalFunc) ExecEvalTableExpr;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+				tstate->perValueCxt =
+					AllocSetContextCreate(tstate->buildercxt,
+										  "TableExpr per value 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/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c
index eae0f1d..24f529b 100644
--- a/src/backend/executor/nodeProjectSet.c
+++ b/src/backend/executor/nodeProjectSet.c
@@ -166,6 +166,20 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 				node->pending_srf_tuples = true;
 			hassrf = true;
 		}
+		else if (IsA(gstate->arg, TableExprState))
+		{
+			/*
+			 * Evaluate TableExpr - possibly continuing previously started output.
+			 */
+			*result = ExecMakeTableExprResultSet((TableExprState *) gstate->arg,
+												econtext, isnull, isdone);
+
+			if (*isdone != ExprEndResult)
+				hasresult = true;
+			if (*isdone == ExprMultipleResult)
+				node->pending_srf_tuples = true;
+			hassrf = true;
+		}
 		else
 		{
 			/* Non-SRF tlist expression, just evaluate normally. */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 30d733e..b604e57 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2005,6 +2005,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
  *
@@ -4765,6 +4828,9 @@ copyObject(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5302,6 +5368,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 55c73b7..6280d0a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2688,6 +2688,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);
@@ -2771,6 +2797,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
  */
@@ -3036,6 +3084,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
@@ -3560,6 +3611,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..c381dcc 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 */
@@ -694,6 +698,9 @@ expression_returns_set_walker(Node *node, void *context)
 		/* else fall through to check args */
 	}
 
+	if (IsA(node, TableExpr))
+		return true;
+
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
 		return false;
@@ -929,6 +936,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 +1137,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 +1572,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 +2237,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 +3049,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 +3678,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 1560ac3..ddb654c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1584,6 +1584,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.
@@ -3352,6 +3374,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'
@@ -3616,6 +3664,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;
@@ -3957,6 +4008,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 dcfa6ee..b3ff987 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2316,6 +2316,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
@@ -2553,6 +2580,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/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4b5902f..9a756a4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5194,6 +5194,8 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
 			PathTarget *thistarget = (PathTarget *) lfirst(lc1);
 			bool		contains_srfs = (bool) lfirst_int(lc2);
 
+
+
 			/* If this level doesn't contain SRFs, do regular projection */
 			if (contains_srfs)
 				newpath = (Path *) create_set_projection_path(root,
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index d589dc2..17f9fdb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -819,6 +819,11 @@ expression_returns_set_rows(Node *clause)
 			return clamp_row_est(get_func_rows(expr->opfuncid));
 		}
 	}
+	if (IsA(clause, TableExpr))
+	{
+		/* What is adequate estimation 10, 100, 1000? */
+		return 100.0;
+	}
 	return 1.0;
 }
 
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index cca5db8..dd6d6f8 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -877,6 +877,13 @@ split_pathtarget_at_srfs(PlannerInfo *root,
 				split_pathtarget_walker((Node *) ((OpExpr *) node)->args,
 										&context);
 			}
+			else if (IsA(node, TableExpr))
+			{
+				/* Same as above, but for table expressions like XMLTABLE */
+				target_contains_srfs = true;
+				split_pathtarget_walker((Node *) ((OpExpr *) node)->args,
+										&context);
+			}
 			else
 			{
 				/* Not a top-level SRF, so recursively examine expression */
@@ -937,7 +944,8 @@ split_pathtarget_walker(Node *node, split_pathtarget_context *context)
 	else if ((IsA(node, FuncExpr) &&
 			  ((FuncExpr *) node)->funcretset) ||
 			 (IsA(node, OpExpr) &&
-			  ((OpExpr *) node)->opretset))
+			  ((OpExpr *) node)->opretset) ||
+			 IsA(node, TableExpr))
 	{
 		/*
 		 * Pass SRFs down to the child plan level for evaluation, and mark
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a8e35fe..b4eeb68 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -549,6 +549,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
@@ -606,10 +611,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
 
@@ -680,8 +685,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
 
@@ -12623,6 +12628,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 		;
 
+
 /*
  * Restricted expressions
  *
@@ -13190,6 +13196,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;
+				}
 		;
 
 /*
@@ -13265,6 +13313,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
@@ -14299,6 +14473,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14604,10 +14779,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 2a2ac32..2c3f3cd 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1096,9 +1096,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1106,9 +1106,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1117,7 +1117,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1144,6 +1144,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index add3be6..6fc765a 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 745e009..e9889b9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8548,6 +8548,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 e8bce3b..bd153bf 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"
@@ -145,6 +146,7 @@ static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
 static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
 					   ArrayBuildState *astate,
 					   PgXmlErrorContext *xmlerrcxt);
+static xmlChar *pg_xmlCharStrndup(char *str, size_t len);
 #endif   /* USE_LIBXML */
 
 static StringInfo query_to_xml_internal(const char *query, char *tablename,
@@ -165,6 +167,49 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+/* XMLTABLE support */
+#ifdef USE_LIBXML
+/* random number to identify XmlTableContext */
+#define XMLTABLE_CONTEXT_MAGIC	46922182
+typedef struct XmlTableBuilderData
+{
+	int			magic;
+	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), \
@@ -3811,13 +3856,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
 				(errcode(ERRCODE_DATA_EXCEPTION),
 				 errmsg("empty XPath expression")));
 
-	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
-	memcpy(string, datastr, len);
-	string[len] = '\0';
-
-	xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
-	memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
-	xpath_expr[xpath_len] = '\0';
+	string = pg_xmlCharStrndup(datastr, len);
+	xpath_expr = pg_xmlCharStrndup(VARDATA(xpath_expr_text), xpath_len);
 
 	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
 
@@ -4065,3 +4105,516 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE function
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Securization of cstring for usage in libxml2
+ */
+static xmlChar *
+pg_xmlCharStrndup(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+
+/*
+ * 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;
+}
+#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 */
+}
+
+/*
+ * 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 = pg_xmlCharStrndup(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,
+						   pg_xmlCharStrndup(name, strlen(name)),
+						   pg_xmlCharStrndup(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowFilter
+ */
+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 = pg_xmlCharStrndup(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 = pg_xmlCharStrndup(path, strlen(path));
+
+		xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+		if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+						"invalid XPath expression");
+	}
+	else
+	{
+		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;
+}
+
+/*
+ * Return the value for column number 'colnum' for the current row.  If column
+ * -1 is requested, return representation of the whole row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
+ */
+static Datum
+XmlTableGetValue(TableExprState *state, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+	volatile xmlXPathObjectPtr xpathobj = NULL;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
+
+	Assert(xtCxt->xpathobj &&
+		   xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	*isnull = false;
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
+
+	if (colnum == -1)
+	{
+		/*
+		 * Use whole row, when user doesn't specify a column.
+		 * The target type must be XMLOID.
+		 */
+		Assert(state->resultSlot->tts_tupleDescriptor->attrs[0]->atttypid == XMLOID);
+
+		result = PointerGetDatum(xml_xmlnodetoxmltype(cur, xtCxt->xmlerrcxt));
+
+		return result;
+	}
+
+	Assert(xtCxt->xpathscomp[colnum] != NULL);
+
+	/* fast leaving */
+	if (cur->type != XML_ELEMENT_NODE)
+		elog(ERROR, "unexpected xmlNode type");
+
+	PG_TRY();
+	{
+		Form_pg_attribute attr;
+
+		attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum];
+
+		/* Set current node as entry point for XPath evaluation */
+		xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+		/* Evaluate column path */
+		xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+		if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		/*
+		 * There are four possible cases, depending on the number of
+		 * nodes returned by the XPath expression and the type of the
+		 * target column: a) XPath returns no nodes.  b) One node is
+		 * returned, and column is of type XML.  c) One node, column type
+		 * other than XML.  d) Multiple nodes are returned.
+		 */
+		if (xpathobj->type == XPATH_NODESET)
+		{
+			int			count = 0;
+			Oid			targettypid = attr->atttypid;
+
+			if (xpathobj->nodesetval != NULL)
+				count = xpathobj->nodesetval->nodeNr;
+
+			if (xpathobj->nodesetval == NULL || count == 0)
+			{
+				*isnull = true;
+			}
+			else if (count == 1 && targettypid == XMLOID)
+			{
+				text *textstr;
+
+				/* simple case, result is one value */
+				textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
+																	xtCxt->xmlerrcxt);
+				cstr = text_to_cstring(textstr);
+			}
+			else if (count == 1)
+			{
+				xmlChar    *str;
+
+				str = xmlNodeListGetString(xtCxt->doc,
+								   xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+								   1);
+
+				if (str != NULL)
+				{
+					PG_TRY();
+					{
+						cstr = pstrdup((char *) str);
+					}
+					PG_CATCH();
+					{
+						xmlFree(str);
+						PG_RE_THROW();
+					}
+					PG_END_TRY();
+					xmlFree(str);
+				}
+				else
+				{
+					/* Return empty string when tag is empty */
+					cstr = "";
+				}
+			}
+			else
+			{
+				StringInfoData str;
+				int			i;
+
+				Assert(count > 1);
+
+				/*
+				 * When evaluating the XPath expression returns multiple
+				 * nodes, the result is the concatenation of them all.
+				 * The target type must be XML.
+				 */
+				if (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++)
+				{
+					appendStringInfoText(&str,
+										   xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
+												  xtCxt->xmlerrcxt));
+				}
+				cstr = str.data;
+			}
+		}
+		else if (xpathobj->type == XPATH_STRING)
+		{
+			cstr = (char *) xpathobj->stringval;
+		}
+		else
+			elog(ERROR, "unexpected XPath object type %u", xpathobj->type);
+
+		/*
+		 * By here, either cstr contains the result value, or the isnull flag
+		 * has been set.
+		 */
+		Assert(cstr || *isnull);
+
+		if (!*isnull)
+			result = InputFunctionCall(&state->in_functions[colnum],
+													  cstr,
+													  state->typioparams[colnum],
+													  attr->atttypmod);
+	}
+	PG_CATCH();
+	{
+		if (xpathobj != NULL)
+			xmlXPathFreeObject(xpathobj);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xmlXPathFreeObject(xpathobj);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * 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 c55da54..3ccee52 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -242,6 +242,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/executor.h b/src/include/executor/executor.h
index 02dbe7b..1727ed8 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -256,6 +256,10 @@ extern Datum ExecMakeFunctionResultSet(FuncExprState *fcache,
 						  ExprContext *econtext,
 						  bool *isNull,
 						  ExprDoneCond *isDone);
+extern Datum ExecMakeTableExprResultSet(TableExprState *texpr,
+						  ExprContext *econtext,
+						  bool *isNull,
+						  ExprDoneCond *isDone);
 extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
 						  bool *isNull);
 extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f9bcdd6..093e295 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1019,6 +1019,34 @@ 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 */
+	MemoryContext perValueCxt;	/* short life context for a value evaluation */
+	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 */
+	ExprDoneCond isDone;		/* status for ValuePerCall mode */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fa4932a..dee8caf 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -176,6 +176,7 @@ typedef enum NodeTag
 	T_CoerceToDomainValue,
 	T_SetToDefault,
 	T_CurrentOfExpr,
+	T_TableExpr,
 	T_InferenceElem,
 	T_TargetEntry,
 	T_RangeTblRef,
@@ -218,6 +219,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_TableExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -468,6 +470,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 aad4699..8407b5e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -172,7 +172,6 @@ typedef struct Query
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } Query;
 
-
 /****************************************************************************
  *	Supporting data structures for Parse Trees
  *
@@ -238,6 +237,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 985d650..28c4dab 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)
@@ -446,10 +447,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
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 cc1fc39..a3db437 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;
 
@@ -76,4 +77,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..d471b47 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,435 @@ 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)
+
+SELECT (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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>')).*;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Result
+   Output: ((XMLTABLE(('/rows/row/a/text()'::text) PASSING ('<rows><row><a>10</a><a>20</a></row></rows>'::xml))))."xmltable"
+   ->  ProjectSet
+         Output: XMLTABLE(('/rows/row/a/text()'::text) PASSING ('<rows><row><a>10</a><a>20</a></row></rows>'::xml))
+         ->  Result
+(5 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d702703..73f1995 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,343 @@ 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:  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 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.
+SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>')).*;
+ERROR:  unsupported XML feature
+LINE 1: SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a...
+                                                      ^
+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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>')).*;
+ERROR:  unsupported XML feature
+LINE 2: SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a...
+                                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5..fa9f5ee 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,435 @@ 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)
+
+SELECT (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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>')).*;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Result
+   Output: ((XMLTABLE(('/rows/row/a/text()'::text) PASSING ('<rows><row><a>10</a><a>20</a></row></rows>'::xml))))."xmltable"
+   ->  ProjectSet
+         Output: XMLTABLE(('/rows/row/a/text()'::text) PASSING ('<rows><row><a>10</a><a>20</a></row></rows>'::xml))
+         ->  Result
+(5 rows)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30..7c4e3136 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,178 @@ 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>');
+
+SELECT (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;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>')).*;
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
#116Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#115)
1 attachment(s)
Re: patch: function xmltable

Pavel Stehule wrote:

* SELECT (xmltable(..)).* + regress tests
* compilation and regress tests without --with-libxml

Thanks. I just realized that this is doing more work than necessary --
I think it would be simpler to have tableexpr fill a tuplestore with the
results, instead of just expecting function execution to apply
ExecEvalExpr over and over to obtain the results. So evaluating a
tableexpr returns just the tuplestore, which function evaluation can
return as-is. That code doesn't use the value-per-call interface
anyway.

I also realized that the expr context callback is not called if there's
an error, which leaves us without shutting down libxml properly. I
added PG_TRY around the fetchrow calls, but I'm not sure that's correct
either, because there could be an error raised in other parts of the
code, after we've already emitted a few rows (for example out of
memory). I think the right way is to have PG_TRY around the execution
of the whole thing rather than just row at a time; and the tuplestore
mechanism helps us with that.

I think it would be good to have a more complex test case in regress --
let's say there is a table with some simple XML values, then we use
XMLFOREST (or maybe one of the table_to_xml functions) to generate a
large document, and then XMLTABLE uses that document as input document.

Please fix.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-36.patchtext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1815c84..3b11bc4 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 19dd0b2..adbe3db 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"
@@ -185,6 +186,15 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
 						 ExprContext *econtext,
 						 bool *isNull);
+static Datum ExecEvalTableExpr(TableExprState *tstate, ExprContext *econtext,
+				  bool *isnull);
+static Datum ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext,
+					  bool *isNull);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum doc);
+static void ShutdownTableExpr(Datum arg);
 
 
 /* ----------------------------------------------------------------
@@ -2033,7 +2043,15 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 		else
 		{
 			result = ExecEvalExpr(funcexpr, econtext, &fcinfo.isnull);
-			rsinfo.isDone = ExprSingleResult;
+
+			/*
+			 * Any other expressions except TableExpr produces single result
+			 * only.
+			 */
+			if (funcexpr && IsA(funcexpr, TableExprState))
+				rsinfo.isDone = ((TableExprState *) funcexpr)->isDone;
+			else
+				rsinfo.isDone = ExprSingleResult;
 		}
 
 		/* Which protocol does function want to use? */
@@ -4209,6 +4227,349 @@ 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(TableExprState *tstate, ExprContext *econtext,
+				  bool *isNull)
+{
+	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);
+
+	/*
+	 * 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);
+	if (*isNull)
+	{
+		tstate->isDone = ExprEndResult;
+		return (Datum) 0;
+	}
+
+	/* otherwise, pass the document value to the table builder */
+	tabexprInitialize(tstate, econtext, value);
+
+	/*
+	 * 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]);
+	}
+
+	/* skip all of the above on future executions of node */
+	tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExprFast;
+
+	/* Fetch and return one row per call from the table builder */
+	PG_TRY();
+	{
+		result = tabexprFetchRow(tstate, econtext, isNull);
+	}
+	PG_CATCH();
+	{
+		ShutdownTableExpr(PointerGetDatum(tstate));
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEvalTableExpr
+ *
+ *		Returns a Datum for a table expression (such as XMLTABLE).
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext,
+					  bool *isNull)
+{
+	TableExprState *tstate = (TableExprState *) exprstate;
+	Datum		result;
+
+	/* Fetch and return one row per call from the table builder */
+	PG_TRY();
+	{
+		result = tabexprFetchRow(tstate, econtext, isNull);
+	}
+	PG_CATCH();
+	{
+		ShutdownTableExpr(PointerGetDatum(tstate));
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
+/*
+ * callback function in case a TableExpr needs to be shut down before
+ * it has been run to completion.
+ */
+static void
+ShutdownTableExpr(Datum arg)
+{
+	TableExprState *tstate = (TableExprState *) arg;
+	const TableExprRoutine *routine = tstate->routine;
+
+	if (tstate->opaque != NULL)
+	{
+		routine->DestroyBuilder(tstate);
+		ExecDropSingleTupleTableSlot(tstate->resultSlot);
+		MemoryContextDelete(tstate->buildercxt);
+	}
+}
+
+/* ----------------------------------------------------------------
+ *		ExecMakeTableExprResultSet
+ * ----------------------------------------------------------------
+ */
+Datum
+ExecMakeTableExprResultSet(TableExprState *texpr,
+						  ExprContext *econtext,
+						  bool *isNull,
+						  ExprDoneCond *isDone)
+{
+	Datum	result;
+
+	result = ExecEvalExpr((ExprState *) texpr,
+							econtext,
+							isNull);
+	*isDone = texpr->isDone;
+
+	return result;
+}
+
+/*
+ * 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);
+		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);
+	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);
+				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; if one can be obtained,
+ * push the values for each column onto the output.
+ */
+static Datum
+tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull)
+{
+	const TableExprRoutine *routine = tstate->routine;
+	MemoryContext oldcxt;
+	bool		gotrow;
+	Datum		result;
+
+	/* Fetch a row */
+	oldcxt = MemoryContextSwitchTo(tstate->buildercxt);
+	gotrow = routine->FetchRow(tstate);
+	MemoryContextSwitchTo(oldcxt);
+
+	if (gotrow)
+	{
+		TupleDesc	tupdesc = tstate->resultSlot->tts_tupleDescriptor;
+		Datum	   *values = tstate->resultSlot->tts_values;
+		bool	   *nulls = tstate->resultSlot->tts_isnull;
+		ListCell   *cell = list_head(tstate->coldefexprs);
+		int			natts = tupdesc->natts;
+		int			colno = 0;
+		bool		isnull;
+
+		ExecClearTuple(tstate->resultSlot);
+
+		/*
+		 * Obtain the value of each column for this row, installing it into
+		 * the values/isnull arrays.
+		 */
+		for (colno = 0; colno < natts; colno++)
+		{
+			if (colno == tstate->ordinalitycol)
+			{
+				/* fast path when column is ordinality */
+				values[colno] = Int32GetDatum(tstate->rownum++);
+				nulls[colno] = false;
+			}
+			else
+			{
+				/* slow path: fetch value from builder */
+				MemoryContextSwitchTo(tstate->perValueCxt);
+				values[colno] = routine->GetValue(tstate,
+											   tstate->evalcols ? colno : -1,
+												  &isnull);
+				MemoryContextSwitchTo(oldcxt);
+
+				/* No value?  Evaluate and apply the default, if any */
+				if (isnull && cell != NULL)
+				{
+					ExprState  *coldefexpr = (ExprState *) lfirst(cell);
+
+					if (coldefexpr != NULL)
+						values[colno] = ExecEvalExpr(coldefexpr, econtext,
+													 &isnull);
+				}
+
+				/* Verify a possible NOT NULL constraint */
+				if (isnull && bms_is_member(colno,
+							((TableExpr *) tstate->xprstate.expr)->notnulls))
+					ereport(ERROR,
+							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+							 errmsg("null is not allowed in column \"%s\"",
+								  NameStr(tupdesc->attrs[colno]->attname))));
+
+				nulls[colno] = isnull;
+			}
+
+			/* advance list of default expressions */
+			if (cell != NULL)
+				cell = lnext(cell);
+		}
+
+		ExecStoreVirtualTuple(tstate->resultSlot);
+
+		*isNull = false;
+		tstate->isDone = ExprMultipleResult;
+
+		result = ExecFetchSlotTupleDatum(tstate->resultSlot);
+
+		/* reset one row life context */
+		MemoryContextReset(tstate->perValueCxt);
+	}
+	else
+	{
+		/* no more rows */
+		*isNull = true;
+		tstate->isDone = ExprEndResult;
+
+		result = (Datum) 0;
+	}
+
+	return result;
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -4953,6 +5314,53 @@ 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;
+				int			natts;
+
+				functypclass = get_expr_result_type((Node *) node, NULL, &tupdesc);
+				Assert(functypclass == TYPEFUNC_COMPOSITE);
+				Assert(tupdesc->natts == te->colcount);
+				natts = tupdesc->natts;
+
+				tstate = makeNode(TableExprState);
+				tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr;
+
+				/* Only XmlTableBuilder is supported currently */
+				tstate->routine = &XmlTableExprRoutine;
+				tstate->buildercxt =
+					AllocSetContextCreate(CurrentMemoryContext,
+										  "TableExpr builder context",
+										  ALLOCSET_DEFAULT_SIZES);
+				tstate->perValueCxt =
+					AllocSetContextCreate(tstate->buildercxt,
+										  "TableExpr per value 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) * natts);
+				tstate->typioparams = palloc(sizeof(Oid) * 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);
+
+				state = (ExprState *) tstate;
+			}
+			break;
 		case T_TargetEntry:
 			{
 				TargetEntry *tle = (TargetEntry *) node;
diff --git a/src/backend/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c
index eae0f1d..a2da042 100644
--- a/src/backend/executor/nodeProjectSet.c
+++ b/src/backend/executor/nodeProjectSet.c
@@ -166,6 +166,21 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 				node->pending_srf_tuples = true;
 			hassrf = true;
 		}
+		else if (IsA(gstate->arg, TableExprState))
+		{
+			/*
+			 * Evaluate TableExpr - possibly continuing previously started
+			 * output.
+			 */
+			*result = ExecMakeTableExprResultSet((TableExprState *) gstate->arg,
+												 econtext, isnull, isdone);
+
+			if (*isdone != ExprEndResult)
+				hasresult = true;
+			if (*isdone == ExprMultipleResult)
+				node->pending_srf_tuples = true;
+			hassrf = true;
+		}
 		else
 		{
 			/* Non-SRF tlist expression, just evaluate normally. */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 30d733e..d1c6f8b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1445,6 +1445,33 @@ _copyScalarArrayOpExpr(const ScalarArrayOpExpr *from)
 }
 
 /*
+ * _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;
+}
+
+/*
  * _copyBoolExpr
  */
 static BoolExpr *
@@ -2964,6 +2991,36 @@ _copyFuncWithArgs(const FuncWithArgs *from)
 	return newnode;
 }
 
+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;
+}
+
+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;
+}
+
 static AccessPriv *
 _copyAccessPriv(const AccessPriv *from)
 {
@@ -4666,6 +4723,9 @@ copyObject(const void *from)
 		case T_ScalarArrayOpExpr:
 			retval = _copyScalarArrayOpExpr(from);
 			break;
+		case T_TableExpr:
+			retval = _copyTableExpr(from);
+			break;
 		case T_BoolExpr:
 			retval = _copyBoolExpr(from);
 			break;
@@ -5275,6 +5335,12 @@ copyObject(const void *from)
 		case T_FuncWithArgs:
 			retval = _copyFuncWithArgs(from);
 			break;
+		case T_TableExprFunc:
+			retval = _copyTableExprFunc(from);
+			break;
+		case T_TableExprFuncCol:
+			retval = _copyTableExprFuncCol(from);
+			break;
 		case T_AccessPriv:
 			retval = _copyAccessPriv(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 55c73b7..3a6b17f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -388,6 +388,28 @@ _equalScalarArrayOpExpr(const ScalarArrayOpExpr *a, const ScalarArrayOpExpr *b)
 }
 
 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;
+}
+
+static bool
 _equalBoolExpr(const BoolExpr *a, const BoolExpr *b)
 {
 	COMPARE_SCALAR_FIELD(boolop);
@@ -2688,6 +2710,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);
@@ -2937,6 +2985,9 @@ equal(const void *a, const void *b)
 		case T_ScalarArrayOpExpr:
 			retval = _equalScalarArrayOpExpr(a, b);
 			break;
+		case T_TableExpr:
+			retval = _equalTableExpr(a, b);
+			break;
 		case T_BoolExpr:
 			retval = _equalBoolExpr(a, b);
 			break;
@@ -3533,6 +3584,12 @@ equal(const void *a, const void *b)
 		case T_FuncWithArgs:
 			retval = _equalFuncWithArgs(a, b);
 			break;
+		case T_TableExprFunc:
+			retval = _equalTableExprFunc(a, b);
+			break;
+		case T_TableExprFuncCol:
+			retval = _equalTableExprFuncCol(a, b);
+			break;
 		case T_AccessPriv:
 			retval = _equalAccessPriv(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c2f03b7..4355519 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -95,6 +95,10 @@ exprType(const Node *expr)
 		case T_ScalarArrayOpExpr:
 			type = BOOLOID;
 			break;
+		case T_TableExpr:
+			/* result is record OID always */
+			type = RECORDOID;
+			break;
 		case T_BoolExpr:
 			type = BOOLOID;
 			break;
@@ -694,6 +698,9 @@ expression_returns_set_walker(Node *node, void *context)
 		/* else fall through to check args */
 	}
 
+	if (IsA(node, TableExpr))
+		return true;
+
 	/* Avoid recursion for some cases that can't return a set */
 	if (IsA(node, Aggref))
 		return false;
@@ -793,6 +800,9 @@ exprCollation(const Node *expr)
 		case T_ScalarArrayOpExpr:
 			coll = InvalidOid;	/* result is always boolean */
 			break;
+		case T_TableExpr:
+			coll = InvalidOid;	/* result is composite, XML or JSON */
+			break;
 		case T_BoolExpr:
 			coll = InvalidOid;	/* result is always boolean */
 			break;
@@ -1035,6 +1045,10 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_ScalarArrayOpExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_TableExpr:
+			Assert(!OidIsValid(collation));		/* result is always composite,
+												 * XML or JSON */
+			break;
 		case T_BoolExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
@@ -1279,6 +1293,9 @@ exprLocation(const Node *expr)
 								  exprLocation((Node *) saopexpr->args));
 			}
 			break;
+		case T_TableExpr:
+			loc = ((const TableExpr *) expr)->location;
+			break;
 		case T_BoolExpr:
 			{
 				const BoolExpr *bexpr = (const BoolExpr *) expr;
@@ -1558,6 +1575,9 @@ exprLocation(const Node *expr)
 		case T_PartitionRangeDatum:
 			loc = ((const PartitionRangeDatum *) expr)->location;
 			break;
+		case T_TableExprFunc:
+			loc = ((const TableExprFunc *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -1981,6 +2001,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;
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
@@ -2598,6 +2634,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;
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
@@ -3628,6 +3678,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 1560ac3..3630c63 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1172,6 +1172,28 @@ _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
 }
 
 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);
+}
+
+static void
 _outBoolExpr(StringInfo str, const BoolExpr *node)
 {
 	char	   *opstr = NULL;
@@ -3352,6 +3374,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'
@@ -3553,6 +3601,9 @@ outNode(StringInfo str, const void *obj)
 			case T_ScalarArrayOpExpr:
 				_outScalarArrayOpExpr(str, obj);
 				break;
+			case T_TableExpr:
+				_outTableExpr(str, obj);
+				break;
 			case T_BoolExpr:
 				_outBoolExpr(str, obj);
 				break;
@@ -3957,6 +4008,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 dcfa6ee..745921b 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -744,6 +744,33 @@ _readScalarArrayOpExpr(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();
+}
+
+/*
  * _readBoolExpr
  */
 static BoolExpr *
@@ -2383,6 +2410,8 @@ parseNodeString(void)
 		return_value = _readNullIfExpr();
 	else if (MATCH("SCALARARRAYOPEXPR", 17))
 		return_value = _readScalarArrayOpExpr();
+	else if (MATCH("TABLEEXPR", 9))
+		return_value = _readTableExprNode();
 	else if (MATCH("BOOLEXPR", 8))
 		return_value = _readBoolExpr();
 	else if (MATCH("SUBLINK", 7))
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4b5902f..9a756a4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5194,6 +5194,8 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
 			PathTarget *thistarget = (PathTarget *) lfirst(lc1);
 			bool		contains_srfs = (bool) lfirst_int(lc2);
 
+
+
 			/* If this level doesn't contain SRFs, do regular projection */
 			if (contains_srfs)
 				newpath = (Path *) create_set_projection_path(root,
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index d589dc2..17f9fdb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -819,6 +819,11 @@ expression_returns_set_rows(Node *clause)
 			return clamp_row_est(get_func_rows(expr->opfuncid));
 		}
 	}
+	if (IsA(clause, TableExpr))
+	{
+		/* What is adequate estimation 10, 100, 1000? */
+		return 100.0;
+	}
 	return 1.0;
 }
 
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index cca5db8..dd6d6f8 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -877,6 +877,13 @@ split_pathtarget_at_srfs(PlannerInfo *root,
 				split_pathtarget_walker((Node *) ((OpExpr *) node)->args,
 										&context);
 			}
+			else if (IsA(node, TableExpr))
+			{
+				/* Same as above, but for table expressions like XMLTABLE */
+				target_contains_srfs = true;
+				split_pathtarget_walker((Node *) ((OpExpr *) node)->args,
+										&context);
+			}
 			else
 			{
 				/* Not a top-level SRF, so recursively examine expression */
@@ -937,7 +944,8 @@ split_pathtarget_walker(Node *node, split_pathtarget_context *context)
 	else if ((IsA(node, FuncExpr) &&
 			  ((FuncExpr *) node)->funcretset) ||
 			 (IsA(node, OpExpr) &&
-			  ((OpExpr *) node)->opretset))
+			  ((OpExpr *) node)->opretset) ||
+			 IsA(node, TableExpr))
 	{
 		/*
 		 * Pass SRFs down to the child plan level for evaluation, and mark
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a8e35fe..b4eeb68 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -549,6 +549,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
@@ -606,10 +611,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
 
@@ -680,8 +685,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
 
@@ -12623,6 +12628,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 		;
 
+
 /*
  * Restricted expressions
  *
@@ -13190,6 +13196,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;
+				}
 		;
 
 /*
@@ -13265,6 +13313,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
@@ -14299,6 +14473,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14604,10 +14779,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 2a2ac32..2c3f3cd 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1096,9 +1096,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1106,9 +1106,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1117,7 +1117,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1144,6 +1144,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index add3be6..6fc765a 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..7579245 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1868,6 +1868,14 @@ 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 f26175e..fe42f77 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8548,6 +8548,120 @@ 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;
+						bool		ordinality = te->ordinalitycol == colnum;
+						bool		notnull = bms_is_member(colnum, te->notnulls);
+
+						typid = lfirst_oid(l2);
+						l2 = lnext(l2);
+						typmod = lfirst_int(l3);
+						l3 = lnext(l3);
+						colexpr = (Node *) lfirst(l4);
+						l4 = lnext(l4);
+						coldefexpr = (Node *) lfirst(l5);
+						l5 = lnext(l5);
+
+						if (colnum > 0)
+							appendStringInfoString(buf, ", ");
+						colnum++;
+
+						appendStringInfo(buf, "%s %s", quote_identifier(colname),
+										 ordinality ? "FOR ORDINALITY" :
+									format_type_with_typemod(typid, typmod));
+						if (ordinality)
+							continue;
+
+						if (coldefexpr != NULL)
+						{
+							appendStringInfoString(buf, " DEFAULT (");
+							get_rule_expr((Node *) coldefexpr, context, true);
+							appendStringInfoChar(buf, ')');
+						}
+						if (colexpr != NULL)
+						{
+							appendStringInfoString(buf, " PATH (");
+							get_rule_expr((Node *) colexpr, context, true);
+							appendStringInfoChar(buf, ')');
+						}
+						if (notnull)
+							appendStringInfoString(buf, " NOT NULL");
+					}
+				}
+
+				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 e8bce3b..244416e 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"
@@ -145,6 +146,7 @@ static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
 static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
 					   ArrayBuildState *astate,
 					   PgXmlErrorContext *xmlerrcxt);
+static xmlChar *pg_xmlCharStrndup(char *str, size_t len);
 #endif   /* USE_LIBXML */
 
 static StringInfo query_to_xml_internal(const char *query, char *tablename,
@@ -165,6 +167,49 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+/* XMLTABLE support */
+#ifdef USE_LIBXML
+/* random number to identify XmlTableContext */
+#define XMLTABLE_CONTEXT_MAGIC	46922182
+typedef struct XmlTableBuilderData
+{
+	int			magic;
+	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), \
@@ -1113,6 +1158,19 @@ xml_pnstrdup(const xmlChar *str, size_t len)
 	return result;
 }
 
+/* Ditto, except input is char* */
+static xmlChar *
+pg_xmlCharStrndup(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+
 /*
  * str is the null-terminated input string.  Remaining arguments are
  * output arguments; each can be NULL if value is not wanted.
@@ -3811,13 +3869,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
 				(errcode(ERRCODE_DATA_EXCEPTION),
 				 errmsg("empty XPath expression")));
 
-	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
-	memcpy(string, datastr, len);
-	string[len] = '\0';
-
-	xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
-	memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
-	xpath_expr[xpath_len] = '\0';
+	string = pg_xmlCharStrndup(datastr, len);
+	xpath_expr = pg_xmlCharStrndup(VARDATA(xpath_expr_text), xpath_len);
 
 	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
 
@@ -4065,3 +4118,502 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Returns private data from executor state. Ensure validity by check with
+ * MAGIC number.
+ */
+static inline XmlTableBuilderData *
+GetXmlTableBuilderPrivateData(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;
+}
+#endif
+
+/*
+ * XmlTableInitBuilder
+ *		Fill in TableExprState 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 */
+}
+
+/*
+ * XmlTableSetDoc
+ *		Install the input document
+ */
+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 = pg_xmlCharStrndup(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,
+						   pg_xmlCharStrndup(name, strlen(name)),
+						   pg_xmlCharStrndup(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowFilter
+ *		Install the row-filter Xpath expression.
+ */
+static void
+XmlTableSetRowFilter(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 = pg_xmlCharStrndup(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
+ *		Install the column-filter Xpath expression, for the given column.
+ *
+ * The path can be NULL, when the only one result column is implicit. XXX fix
+ */
+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 = pg_xmlCharStrndup(path, strlen(path));
+
+		xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+		if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+						"invalid XPath expression");
+	}
+	else
+	{
+		Assert(!state->evalcols || colnum == state->ordinalitycol);
+
+		xtCxt->xpathscomp[colnum] = NULL;
+	}
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableFetchRow
+ *		Prepare the next "current" tuple for upcoming GetValue calls.
+ *		Returns FALSE if the row-filter expression returned no more rows.
+ */
+static bool
+XmlTableFetchRow(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;
+}
+
+/*
+ * XmlTableGetValue
+ *		Return the value for column number 'colnum' for the current row.  If
+ *		column -1 is requested, return representation of the whole row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
+ */
+static Datum
+XmlTableGetValue(TableExprState *state, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+	volatile xmlXPathObjectPtr xpathobj = NULL;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
+
+	Assert(xtCxt->xpathobj &&
+		   xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	*isnull = false;
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
+
+	if (colnum == -1)
+	{
+		/*
+		 * Use whole row, when user doesn't specify a column. The target type
+		 * must be XMLOID.
+		 */
+		Assert(state->resultSlot->tts_tupleDescriptor->attrs[0]->atttypid == XMLOID);
+
+		result = PointerGetDatum(xml_xmlnodetoxmltype(cur, xtCxt->xmlerrcxt));
+
+		return result;
+	}
+
+	Assert(xtCxt->xpathscomp[colnum] != NULL);
+
+	/* fast leaving */
+	if (cur->type != XML_ELEMENT_NODE)
+		elog(ERROR, "unexpected xmlNode type");
+
+	PG_TRY();
+	{
+		Form_pg_attribute attr;
+
+		attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum];
+
+		/* Set current node as entry point for XPath evaluation */
+		xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+		/* Evaluate column path */
+		xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+		if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		/*
+		 * There are four possible cases, depending on the number of nodes
+		 * returned by the XPath expression and the type of the target column:
+		 * a) XPath returns no nodes.  b) One node is returned, and column is
+		 * of type XML.  c) One node, column type other than XML.  d) Multiple
+		 * nodes are returned.
+		 */
+		if (xpathobj->type == XPATH_NODESET)
+		{
+			int			count = 0;
+			Oid			targettypid = attr->atttypid;
+
+			if (xpathobj->nodesetval != NULL)
+				count = xpathobj->nodesetval->nodeNr;
+
+			if (xpathobj->nodesetval == NULL || count == 0)
+			{
+				*isnull = true;
+			}
+			else if (count == 1 && targettypid == XMLOID)
+			{
+				text	   *textstr;
+
+				/* simple case, result is one value */
+				textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
+											   xtCxt->xmlerrcxt);
+				cstr = text_to_cstring(textstr);
+			}
+			else if (count == 1)
+			{
+				xmlChar    *str;
+
+				str = xmlNodeListGetString(xtCxt->doc,
+						   xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+										   1);
+
+				if (str != NULL)
+				{
+					PG_TRY();
+					{
+						cstr = pstrdup((char *) str);
+					}
+					PG_CATCH();
+					{
+						xmlFree(str);
+						PG_RE_THROW();
+					}
+					PG_END_TRY();
+					xmlFree(str);
+				}
+				else
+				{
+					/* Return empty string when tag is empty */
+					cstr = "";
+				}
+			}
+			else
+			{
+				StringInfoData str;
+				int			i;
+
+				Assert(count > 1);
+
+				/*
+				 * When evaluating the XPath expression returns multiple
+				 * nodes, the result is the concatenation of them all. The
+				 * target type must be XML.
+				 */
+				if (targettypid != XMLOID)
+					ereport(ERROR,
+							(errcode(ERRCODE_CARDINALITY_VIOLATION),
+							 errmsg("more than one value returned by column XPath expression")));
+
+				/* Concatenate serialized values */
+				initStringInfo(&str);
+				for (i = 0; i < count; i++)
+				{
+					appendStringInfoText(&str,
+					   xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
+											xtCxt->xmlerrcxt));
+				}
+				cstr = str.data;
+			}
+		}
+		else if (xpathobj->type == XPATH_STRING)
+		{
+			cstr = (char *) xpathobj->stringval;
+		}
+		else
+			elog(ERROR, "unexpected XPath object type %u", xpathobj->type);
+
+		/*
+		 * By here, either cstr contains the result value, or the isnull flag
+		 * has been set.
+		 */
+		Assert(cstr || *isnull);
+
+		if (!*isnull)
+			result = InputFunctionCall(&state->in_functions[colnum],
+									   cstr,
+									   state->typioparams[colnum],
+									   attr->atttypmod);
+	}
+	PG_CATCH();
+	{
+		if (xpathobj != NULL)
+			xmlXPathFreeObject(xpathobj);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xmlXPathFreeObject(xpathobj);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * 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 c55da54..069c60c2 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -242,6 +242,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/executor.h b/src/include/executor/executor.h
index 02dbe7b..1727ed8 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -256,6 +256,10 @@ extern Datum ExecMakeFunctionResultSet(FuncExprState *fcache,
 						  ExprContext *econtext,
 						  bool *isNull,
 						  ExprDoneCond *isDone);
+extern Datum ExecMakeTableExprResultSet(TableExprState *texpr,
+						  ExprContext *econtext,
+						  bool *isNull,
+						  ExprDoneCond *isDone);
 extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
 						  bool *isNull);
 extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h
new file mode 100644
index 0000000..a54d947
--- /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 f9bcdd6..e26889a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1019,6 +1019,33 @@ 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 */
+	MemoryContext perValueCxt;	/* short life context for a value evaluation */
+	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 expressions */
+	ExprDoneCond isDone;		/* status for ValuePerCall mode */
+} TableExprState;
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fa4932a..a0a2fb3 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -149,6 +149,7 @@ typedef enum NodeTag
 	T_DistinctExpr,
 	T_NullIfExpr,
 	T_ScalarArrayOpExpr,
+	T_TableExpr,
 	T_BoolExpr,
 	T_SubLink,
 	T_SubPlan,
@@ -199,6 +200,7 @@ typedef enum NodeTag
 	T_ArrayRefExprState,
 	T_FuncExprState,
 	T_ScalarArrayOpExprState,
+	T_TableExprState,
 	T_BoolExprState,
 	T_SubPlanState,
 	T_AlternativeSubPlanState,
@@ -451,6 +453,8 @@ typedef enum NodeTag
 	T_GroupingSet,
 	T_WindowClause,
 	T_FuncWithArgs,
+	T_TableExprFunc,
+	T_TableExprFuncCol,
 	T_AccessPriv,
 	T_CreateOpClassItem,
 	T_TableLikeClause,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07a8436..e447079 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -172,7 +172,6 @@ typedef struct Query
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
 } Query;
 
-
 /****************************************************************************
  *	Supporting data structures for Parse Trees
  *
@@ -238,6 +237,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..3acb3fb 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"
 
 
@@ -522,6 +523,29 @@ typedef struct ScalarArrayOpExpr
 } ScalarArrayOpExpr;
 
 /*
+ * TableExpr - expression node for a table expression, 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 expression */
+	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;		/* nullability flag for each output column */
+	int			ordinalitycol;	/* ordinality column, -1 if not used */
+	bool		evalcols;		/* true, when columns should be evaluated */
+	int			location;		/* token location, or -1 if unknown */
+} TableExpr;
+
+/*
  * BoolExpr - expression node for the basic Boolean operators AND, OR, NOT
  *
  * Notice the arguments are given as a List.  For NOT, of course the list
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 985d650..28c4dab 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)
@@ -446,10 +447,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
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 cc1fc39..a3db437 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;
 
@@ -76,4 +77,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..d471b47 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,435 @@ 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)
+
+SELECT (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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>')).*;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Result
+   Output: ((XMLTABLE(('/rows/row/a/text()'::text) PASSING ('<rows><row><a>10</a><a>20</a></row></rows>'::xml))))."xmltable"
+   ->  ProjectSet
+         Output: XMLTABLE(('/rows/row/a/text()'::text) PASSING ('<rows><row><a>10</a><a>20</a></row></rows>'::xml))
+         ->  Result
+(5 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d702703..73f1995 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,343 @@ 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:  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 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.
+SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>')).*;
+ERROR:  unsupported XML feature
+LINE 1: SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a...
+                                                      ^
+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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>')).*;
+ERROR:  unsupported XML feature
+LINE 2: SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a...
+                                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5..fa9f5ee 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,435 @@ 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)
+
+SELECT (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                                                                                                                                                                                                           
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ProjectSet
+   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))
+   ->  Seq Scan on public.xmldata
+         Output: data
+(4 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
+   ->  ProjectSet
+         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))
+         ->  Seq Scan on public.xmldata
+               Output: xmldata.data
+(6 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>')).*;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Result
+   Output: ((XMLTABLE(('/rows/row/a/text()'::text) PASSING ('<rows><row><a>10</a><a>20</a></row></rows>'::xml))))."xmltable"
+   ->  ProjectSet
+         Output: XMLTABLE(('/rows/row/a/text()'::text) PASSING ('<rows><row><a>10</a><a>20</a></row></rows>'::xml))
+         ->  Result
+(5 rows)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30..7c4e3136 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,178 @@ 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>');
+
+SELECT (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;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT (XMLTABLE('/rows/row/a/text()' PASSING '<rows><row><a>10</a><a>20</a></row></rows>')).*;
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
#117Andres Freund
andres@anarazel.de
In reply to: Alvaro Herrera (#116)
Re: patch: function xmltable

Hi,

On 2017-01-24 17:38:49 -0300, Alvaro Herrera wrote:

+static Datum ExecEvalTableExpr(TableExprState *tstate, ExprContext *econtext,
+				  bool *isnull);
+static Datum ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext,
+					  bool *isNull);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum doc);
+static void ShutdownTableExpr(Datum arg);

To me this (and a lot of the other code) hints quite strongly that
expression evalution is the wrong approach to implementing this. What
you're essentially doing is building a vulcano style scan node. Even if
we can this, we shouldn't double down on the bad decision to have these
magic expressions that return multiple rows. There's historical reason
for tSRFs, but we shouldn't add more weirdness like this.

Andres

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#118Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Andres Freund (#117)
Re: patch: function xmltable

Andres Freund wrote:

Hi,

On 2017-01-24 17:38:49 -0300, Alvaro Herrera wrote:

+static Datum ExecEvalTableExpr(TableExprState *tstate, ExprContext *econtext,
+				  bool *isnull);
+static Datum ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext,
+					  bool *isNull);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum doc);
+static void ShutdownTableExpr(Datum arg);

To me this (and a lot of the other code) hints quite strongly that
expression evalution is the wrong approach to implementing this. What
you're essentially doing is building a vulcano style scan node. Even if
we can this, we shouldn't double down on the bad decision to have these
magic expressions that return multiple rows. There's historical reason
for tSRFs, but we shouldn't add more weirdness like this.

Thanks for giving it a look. I have long thought that this patch would
be at odds with your overall executor work.

XMLTABLE is specified by the standard to return multiple rows ... but
then as far as my reading goes, it is only supposed to be supported in
the range table (FROM clause) not in the target list. I wonder if
this would end up better if we only tried to support it in RT. I asked
Pavel to implement it like that a few weeks ago, but ...

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#119Andres Freund
andres@anarazel.de
In reply to: Alvaro Herrera (#118)
Re: patch: function xmltable

On 2017-01-24 21:32:56 -0300, Alvaro Herrera wrote:

Andres Freund wrote:

Hi,

On 2017-01-24 17:38:49 -0300, Alvaro Herrera wrote:

+static Datum ExecEvalTableExpr(TableExprState *tstate, ExprContext *econtext,
+				  bool *isnull);
+static Datum ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext,
+					  bool *isNull);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext,
+				bool *isNull);
+static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext,
+				  Datum doc);
+static void ShutdownTableExpr(Datum arg);

To me this (and a lot of the other code) hints quite strongly that
expression evalution is the wrong approach to implementing this. What
you're essentially doing is building a vulcano style scan node. Even if
we can this, we shouldn't double down on the bad decision to have these
magic expressions that return multiple rows. There's historical reason
for tSRFs, but we shouldn't add more weirdness like this.

Thanks for giving it a look. I have long thought that this patch would
be at odds with your overall executor work.

Not fundamentally, but it makes it harder.

XMLTABLE is specified by the standard to return multiple rows ... but
then as far as my reading goes, it is only supposed to be supported in
the range table (FROM clause) not in the target list. I wonder if
this would end up better if we only tried to support it in RT. I asked
Pavel to implement it like that a few weeks ago, but ...

Right - it makes sense in the FROM list - but then it should be an
executor node, instead of some expression thingy.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#120Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#119)
Re: patch: function xmltable

Andres Freund <andres@anarazel.de> writes:

On 2017-01-24 21:32:56 -0300, Alvaro Herrera wrote:

XMLTABLE is specified by the standard to return multiple rows ... but
then as far as my reading goes, it is only supposed to be supported in
the range table (FROM clause) not in the target list. I wonder if
this would end up better if we only tried to support it in RT. I asked
Pavel to implement it like that a few weeks ago, but ...

Right - it makes sense in the FROM list - but then it should be an
executor node, instead of some expression thingy.

+1 --- we're out of the business of having simple expressions that
return rowsets.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#121Pavel Stehule
pavel.stehule@gmail.com
In reply to: Andres Freund (#119)
Re: patch: function xmltable

Hi

2017-01-25 1:35 GMT+01:00 Andres Freund <andres@anarazel.de>:

On 2017-01-24 21:32:56 -0300, Alvaro Herrera wrote:

Andres Freund wrote:

Hi,

On 2017-01-24 17:38:49 -0300, Alvaro Herrera wrote:

+static Datum ExecEvalTableExpr(TableExprState *tstate, ExprContext

*econtext,

+                           bool *isnull);
+static Datum ExecEvalTableExprFast(TableExprState *exprstate,

ExprContext *econtext,

+                                   bool *isNull);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext

*econtext,

+                         bool *isNull);
+static void tabexprInitialize(TableExprState *tstate, ExprContext

*econtext,

+                           Datum doc);
+static void ShutdownTableExpr(Datum arg);

To me this (and a lot of the other code) hints quite strongly that
expression evalution is the wrong approach to implementing this. What
you're essentially doing is building a vulcano style scan node. Even

if

we can this, we shouldn't double down on the bad decision to have these
magic expressions that return multiple rows. There's historical reason
for tSRFs, but we shouldn't add more weirdness like this.

Thanks for giving it a look. I have long thought that this patch would
be at odds with your overall executor work.

Not fundamentally, but it makes it harder.

If you plan to hold support SRFin target list, then nothing is different.
In last patch is executed under nodeProjectSet.

XMLTABLE is specified by the standard to return multiple rows ... but
then as far as my reading goes, it is only supposed to be supported in
the range table (FROM clause) not in the target list. I wonder if
this would end up better if we only tried to support it in RT. I asked
Pavel to implement it like that a few weeks ago, but ...

Right - it makes sense in the FROM list - but then it should be an
executor node, instead of some expression thingy.

The XMLTABLE function is from user perspective, from implementation
perspective a form of SRF function. I use own executor node, because fcinfo
is complex already and not too enough to hold all information about result
columns.

The implementation as RT doesn't reduce code - it is just moving to
different file.

I'll try to explain my motivation. Please, check it and correct me if I am
wrong. I don't keep on my implementation - just try to implement XMLTABLE
be consistent with another behave and be used all time without any
surprise.

1. Any function that produces a content can be used in target list. We
support SRF in target list and in FROM part. Why XMLTABLE should be a
exception?

2. In standard the XMLTABLE is placed only on FROM part - but standard
doesn't need to solve my question - there are not SRF functions allowed in
targetlist.

If there be a common decision so this inconsistency (in behave of this kind
of functions) is expected, required - then I have not a problem to remove
this support from XMLTABLE.

In this moment I don't see a technical reason for this step - with last
Andres changes the support of XMLTABLE in target list needs less than 40
lines and there is not any special path for XMLTABLE only. Andres write
support for SRF functions and SRF operator. TableExpr is third category.

Regards

Pavel

Show quoted text

Greetings,

Andres Freund

#122Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#120)
Re: patch: function xmltable

2017-01-25 5:45 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:

Andres Freund <andres@anarazel.de> writes:

On 2017-01-24 21:32:56 -0300, Alvaro Herrera wrote:

XMLTABLE is specified by the standard to return multiple rows ... but
then as far as my reading goes, it is only supposed to be supported in
the range table (FROM clause) not in the target list. I wonder if
this would end up better if we only tried to support it in RT. I asked
Pavel to implement it like that a few weeks ago, but ...

Right - it makes sense in the FROM list - but then it should be an
executor node, instead of some expression thingy.

+1 --- we're out of the business of having simple expressions that
return rowsets.

If we do decision so this kind of function will have different behave than
other SRF functions, then I remove support for this.

There are not technical reasons (maybe I don't see it) - last Andres
changes do well support for my code.

Regards

Pavel

Show quoted text

regards, tom lane

#123Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#116)
Re: patch: function xmltable

2017-01-24 21:38 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

* SELECT (xmltable(..)).* + regress tests
* compilation and regress tests without --with-libxml

Thanks. I just realized that this is doing more work than necessary --

?? I don't understand?

I think it would be simpler to have tableexpr fill a tuplestore with the
results, instead of just expecting function execution to apply
ExecEvalExpr over and over to obtain the results. So evaluating a
tableexpr returns just the tuplestore, which function evaluation can
return as-is. That code doesn't use the value-per-call interface
anyway.

ok

I also realized that the expr context callback is not called if there's
an error, which leaves us without shutting down libxml properly. I
added PG_TRY around the fetchrow calls, but I'm not sure that's correct
either, because there could be an error raised in other parts of the
code, after we've already emitted a few rows (for example out of
memory). I think the right way is to have PG_TRY around the execution
of the whole thing rather than just row at a time; and the tuplestore
mechanism helps us with that.

ok.

Show quoted text

I think it would be good to have a more complex test case in regress --
let's say there is a table with some simple XML values, then we use
XMLFOREST (or maybe one of the table_to_xml functions) to generate a
large document, and then XMLTABLE uses that document as input document.

Please fix.

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#124Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Tom Lane (#120)
Re: patch: function xmltable

Tom Lane wrote:

Andres Freund <andres@anarazel.de> writes:

On 2017-01-24 21:32:56 -0300, Alvaro Herrera wrote:

XMLTABLE is specified by the standard to return multiple rows ... but
then as far as my reading goes, it is only supposed to be supported in
the range table (FROM clause) not in the target list. I wonder if
this would end up better if we only tried to support it in RT. I asked
Pavel to implement it like that a few weeks ago, but ...

Right - it makes sense in the FROM list - but then it should be an
executor node, instead of some expression thingy.

+1 --- we're out of the business of having simple expressions that
return rowsets.

Well, that's it. I'm not committing this patch against two other
committers' opinion, plus I was already on the fence about the
implementation anyway. I think you should just go with the flow and
implement this by creating nodeTableexprscan.c. It's not even
difficult.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#125Andres Freund
andres@anarazel.de
In reply to: Pavel Stehule (#121)
Re: patch: function xmltable

Hi,

On 2017-01-25 05:45:24 +0100, Pavel Stehule wrote:

2017-01-25 1:35 GMT+01:00 Andres Freund <andres@anarazel.de>:

On 2017-01-24 21:32:56 -0300, Alvaro Herrera wrote:

Andres Freund wrote:

Hi,

On 2017-01-24 17:38:49 -0300, Alvaro Herrera wrote:

+static Datum ExecEvalTableExpr(TableExprState *tstate, ExprContext

*econtext,

+                           bool *isnull);
+static Datum ExecEvalTableExprFast(TableExprState *exprstate,

ExprContext *econtext,

+                                   bool *isNull);
+static Datum tabexprFetchRow(TableExprState *tstate, ExprContext

*econtext,

+                         bool *isNull);
+static void tabexprInitialize(TableExprState *tstate, ExprContext

*econtext,

+                           Datum doc);
+static void ShutdownTableExpr(Datum arg);

To me this (and a lot of the other code) hints quite strongly that
expression evalution is the wrong approach to implementing this. What
you're essentially doing is building a vulcano style scan node. Even

if

we can this, we shouldn't double down on the bad decision to have these
magic expressions that return multiple rows. There's historical reason
for tSRFs, but we shouldn't add more weirdness like this.

Thanks for giving it a look. I have long thought that this patch would
be at odds with your overall executor work.

Not fundamentally, but it makes it harder.

If you plan to hold support SRFin target list, then nothing is different.
In last patch is executed under nodeProjectSet.

It is, because we suddenly need to call different functions - and I'm
revamping most of execQual to have an opcode dispatch based execution
model (which then also can be JITed).

XMLTABLE is specified by the standard to return multiple rows ... but
then as far as my reading goes, it is only supposed to be supported in
the range table (FROM clause) not in the target list. I wonder if
this would end up better if we only tried to support it in RT. I asked
Pavel to implement it like that a few weeks ago, but ...

Right - it makes sense in the FROM list - but then it should be an
executor node, instead of some expression thingy.

The XMLTABLE function is from user perspective, from implementation
perspective a form of SRF function. I use own executor node, because fcinfo
is complex already and not too enough to hold all information about result
columns.

The implementation as RT doesn't reduce code - it is just moving to
different file.

You're introducing a wholly separate callback system (TableExprRoutine)
for the new functionality. And that stuff is excruciatingly close to
stuff that the normal executor already knows how to do.

I'll try to explain my motivation. Please, check it and correct me if I am
wrong. I don't keep on my implementation - just try to implement XMLTABLE
be consistent with another behave and be used all time without any
surprise.

1. Any function that produces a content can be used in target list. We
support SRF in target list and in FROM part. Why XMLTABLE should be a
exception?

targetlist SRFs were a big mistake. They cause a fair number of problems
code-wise. They permeated for a long while into bits of both planner and
executor, where they really shouldn't belong. Even after the recent
changes there's a fair amount of uglyness associated with them. We
can't remove tSRFs for backward compatibility reasons, but that's not
true for XMLTABLE

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#126Pavel Stehule
pavel.stehule@gmail.com
In reply to: Andres Freund (#125)
Re: patch: function xmltable

If you plan to hold support SRFin target list, then nothing is different.
In last patch is executed under nodeProjectSet.

It is, because we suddenly need to call different functions - and I'm
revamping most of execQual to have an opcode dispatch based execution
model (which then also can be JITed).

XMLTABLE is specified by the standard to return multiple rows ... but
then as far as my reading goes, it is only supposed to be supported

in

the range table (FROM clause) not in the target list. I wonder if
this would end up better if we only tried to support it in RT. I

asked

Pavel to implement it like that a few weeks ago, but ...

Right - it makes sense in the FROM list - but then it should be an
executor node, instead of some expression thingy.

The XMLTABLE function is from user perspective, from implementation
perspective a form of SRF function. I use own executor node, because

fcinfo

is complex already and not too enough to hold all information about

result

columns.

The implementation as RT doesn't reduce code - it is just moving to
different file.

You're introducing a wholly separate callback system (TableExprRoutine)
for the new functionality. And that stuff is excruciatingly close to
stuff that the normal executor already knows how to do.

These callbacks are related to isolation TableExpr infrastructure and
TableExpr implementation - This design is prepared for reusing for
JSON_TABLE function.

Any placing of TableExpr code should not impact this callback system (Or I
am absolutely out and executor is able do some work what is hidden to me).

I'll try to explain my motivation. Please, check it and correct me if I

am

wrong. I don't keep on my implementation - just try to implement XMLTABLE
be consistent with another behave and be used all time without any
surprise.

1. Any function that produces a content can be used in target list. We
support SRF in target list and in FROM part. Why XMLTABLE should be a
exception?

targetlist SRFs were a big mistake. They cause a fair number of problems
code-wise. They permeated for a long while into bits of both planner and
executor, where they really shouldn't belong. Even after the recent
changes there's a fair amount of uglyness associated with them. We
can't remove tSRFs for backward compatibility reasons, but that's not
true for XMLTABLE

ok

I afraid when I cannot to reuse a SRF infrastructure, I have to reimplement
it partially :( - mainly for usage in "ROWS FROM ()"

Greetings,

Show quoted text

Andres Freund

#127Andres Freund
andres@anarazel.de
In reply to: Pavel Stehule (#126)
Re: patch: function xmltable

Hi,

I'll try to explain my motivation. Please, check it and correct me if I

am

wrong. I don't keep on my implementation - just try to implement XMLTABLE
be consistent with another behave and be used all time without any
surprise.

1. Any function that produces a content can be used in target list. We
support SRF in target list and in FROM part. Why XMLTABLE should be a
exception?

targetlist SRFs were a big mistake. They cause a fair number of problems
code-wise. They permeated for a long while into bits of both planner and
executor, where they really shouldn't belong. Even after the recent
changes there's a fair amount of uglyness associated with them. We
can't remove tSRFs for backward compatibility reasons, but that's not
true for XMLTABLE

ok

I afraid when I cannot to reuse a SRF infrastructure, I have to reimplement
it partially :( - mainly for usage in "ROWS FROM ()"

Huh?

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#128Pavel Stehule
pavel.stehule@gmail.com
In reply to: Andres Freund (#127)
Re: patch: function xmltable

2017-01-25 22:40 GMT+01:00 Andres Freund <andres@anarazel.de>:

Hi,

I'll try to explain my motivation. Please, check it and correct me

if I

am

wrong. I don't keep on my implementation - just try to implement

XMLTABLE

be consistent with another behave and be used all time without any
surprise.

1. Any function that produces a content can be used in target list.

We

support SRF in target list and in FROM part. Why XMLTABLE should be a
exception?

targetlist SRFs were a big mistake. They cause a fair number of

problems

code-wise. They permeated for a long while into bits of both planner

and

executor, where they really shouldn't belong. Even after the recent
changes there's a fair amount of uglyness associated with them. We
can't remove tSRFs for backward compatibility reasons, but that's not
true for XMLTABLE

ok

I afraid when I cannot to reuse a SRF infrastructure, I have to

reimplement

it partially :( - mainly for usage in "ROWS FROM ()"

The TableExpr implementation is based on SRF now. You and Alvaro propose
independent implementation like generic executor node. I am sceptic so
FunctionScan supports reading from generic executor node.

Regards

Pavel

Show quoted text

Huh?

Greetings,

Andres Freund

#129Andres Freund
andres@anarazel.de
In reply to: Pavel Stehule (#128)
Re: patch: function xmltable

On 2017-01-25 22:51:37 +0100, Pavel Stehule wrote:

2017-01-25 22:40 GMT+01:00 Andres Freund <andres@anarazel.de>:

I afraid when I cannot to reuse a SRF infrastructure, I have to

reimplement

it partially :( - mainly for usage in "ROWS FROM ()"

The TableExpr implementation is based on SRF now. You and Alvaro propose
independent implementation like generic executor node. I am sceptic so
FunctionScan supports reading from generic executor node.

Why would it need to?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#130Pavel Stehule
pavel.stehule@gmail.com
In reply to: Andres Freund (#129)
Re: patch: function xmltable

2017-01-25 23:33 GMT+01:00 Andres Freund <andres@anarazel.de>:

On 2017-01-25 22:51:37 +0100, Pavel Stehule wrote:

2017-01-25 22:40 GMT+01:00 Andres Freund <andres@anarazel.de>:

I afraid when I cannot to reuse a SRF infrastructure, I have to

reimplement

it partially :( - mainly for usage in "ROWS FROM ()"

The TableExpr implementation is based on SRF now. You and Alvaro propose
independent implementation like generic executor node. I am sceptic so
FunctionScan supports reading from generic executor node.

Why would it need to?

Simply - due consistency with any other functions that can returns rows.

Maybe I don't understand to Alvaro proposal well - I have a XMLTABLE
function - TableExpr that looks like SRF function, has similar behave -
returns more rows, but should be significantly different implemented, and
should to have different limits - should not be used there and there ... It
is hard to see consistency there for me.

Regards

Pavel

#131Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#124)
Re: patch: function xmltable

2017-01-25 15:07 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Tom Lane wrote:

Andres Freund <andres@anarazel.de> writes:

On 2017-01-24 21:32:56 -0300, Alvaro Herrera wrote:

XMLTABLE is specified by the standard to return multiple rows ... but
then as far as my reading goes, it is only supposed to be supported in
the range table (FROM clause) not in the target list. I wonder if
this would end up better if we only tried to support it in RT. I

asked

Pavel to implement it like that a few weeks ago, but ...

Right - it makes sense in the FROM list - but then it should be an
executor node, instead of some expression thingy.

+1 --- we're out of the business of having simple expressions that
return rowsets.

Well, that's it. I'm not committing this patch against two other
committers' opinion, plus I was already on the fence about the
implementation anyway. I think you should just go with the flow and
implement this by creating nodeTableexprscan.c. It's not even
difficult.

I am playing with this and the patch looks about 15kB longer - just due
implementation basic scan functionality - and I didn't touch a planner.

I am not happy from this - still I have a feeling so I try to reimplement
reduced SRF.

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#132Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#124)
1 attachment(s)
Re: patch: function xmltable

Hi

2017-01-25 15:07 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Tom Lane wrote:

Andres Freund <andres@anarazel.de> writes:

On 2017-01-24 21:32:56 -0300, Alvaro Herrera wrote:

XMLTABLE is specified by the standard to return multiple rows ... but
then as far as my reading goes, it is only supposed to be supported in
the range table (FROM clause) not in the target list. I wonder if
this would end up better if we only tried to support it in RT. I

asked

Pavel to implement it like that a few weeks ago, but ...

Right - it makes sense in the FROM list - but then it should be an
executor node, instead of some expression thingy.

+1 --- we're out of the business of having simple expressions that
return rowsets.

Well, that's it. I'm not committing this patch against two other
committers' opinion, plus I was already on the fence about the
implementation anyway. I think you should just go with the flow and
implement this by creating nodeTableexprscan.c. It's not even
difficult.

I am sending new version - it is based on own executor scan node and
tuplestore.

Some now obsolete regress tests removed, some new added.

The executor code (memory context usage) should be cleaned little bit - but
other code should be good.

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-40.patch.gzapplication/x-gzip; name=xmltable-40.patch.gzDownload
����Xxmltable-40.patch�<kS#G���_Q�],b�B�5�h��1cc3����s%�$�hui�[����~�Y��~	
0{w�PwuUVV�3�Z#o<f����o�����7����<6��
J�y�H<�A���n�i4���`��Z[[{;;k�z��Z�Z-���7����n��v�XU_m3h�8:9:�dS�������_�e8�k5�� �ltf��c)�S��M������m�9�Q<	E��o�p����x�1���0n3o�~Q�=Du\��r(��&�]��:���{n��/O:M���������p��{��������/�<��4����t��If<�j>v)�F��q."&�l�c���L>���xUc�j�?�@��#�C|#�]������P���������)D3���������x�&p<���Z�-��x��HS���0pZj�������}{r�bF��7%���������uB�]u���=`������{Uww8YeK9������~�@�"/���]��b�����[?u�j,���m`9CH��L� �a�������	x5$�+/�Y&dF"b)'+��e�
��
;��R�M�O�D����D=����:
��c�x����IP�,�RC�3�����
�H�H�����P��`�,�Y_C9>M`���<T3���� bv��^3C�E]�
�F��441jh��XVu������OAx	*H��z�|*@�����BL��^����q���L,
�N�'��H9]&S.��a�f����p�j����~',j,�2V�k$I����`����R�J92���B99N�?��+G�����)HS>�Q�bW����?RK��e}��HM��{���3V��E0i����S�(�������*^C4jHg�@��2]�|HP�3��il|��)m6���@s#��)��������(���h���4��t���q������_�J� ���>�y#h�C(KmX3
������&�<�!����E��48Izy/�jw��0���&R��+Ff��Rx��tv�����H��*��2�@���*�/#��>Slw�r�����b���*x/)�
�d��{���3_�-�k���a(D�������z����l��xV6���[�D�*�5���{��M6�
bc(�� ��,v�n�|�~z�~�������A�Al����;������TV���WNV�r(��
��J\�=D�����&N��(ws��(j*\����A�,�rm&��3�,�S�	��{����c/vnI:n�TS�������(�8�	�
l?h��������v�������|���I	��u�t�^^�������-����OH�K��g�li&�/L�_M��+!����z`��	��j��1�vL� �|��T�A��	����3��IB�^�c/;�RMy:��)P�J��R���4�7B�G�}�p���`�$Ct�:j��Y0�_���N����H��"`}������1�dT3%����j9X�#�+i,��f2������@�R6{N��2W������E��`=��� ���5��`�� ��2�����c��v�F�o�������{	����������^j��h�K=�~�4�d/��V�Tu���N��o}���Ti����<�2��w�J��U�39~��h��������cX��L}���8>��Y���������574K����������t�0�M?��M�>�]��7��|����/
�������4�/�rzv�N�NN�g�J����)�� |N�Q-�QK�TG�9�36�����q)3E��G��q�������H0�q9<�����u[��r,�-Ew�7�����"k
��l���m���j����WdLZ�X�]�^��>�����4��L�����no�� _NzR���?����ow��
M����H���8<}�CH�^��"~��Q~���'�{	v�p8~����v����Q~�3|���������{����7�K����W������	�L8�@��P�8��#6]|�~�|���ib[
=�F�J�t�n8��/�1�R���-�0��G�E���Z�0c������GV��h��Z��l����wy���X��#��o#������t3����[���l|��6jK��P��JyMEI��4�I_	�(�s��jPB1�{�Z����@D����/����7(E�=C9��F�������\���6���'��]u�`�wo`���1�Z6 DFAz|N���K���>+|VxNO���u����S-��}�.�3Xo����+����j�\���:T��$�O�
��������@�@#�d�����F2����We�4��S
��������	_��"���@��
�YM�6`V<��<������#�`b��|H� ���qvoI���t�~�?F�v���E����wi��,;����z�q�-
h�B�=� B��t���
��}��-���<K�O���
\�Q�8�]TL���Xl`�D�y����#\��a��F�3Eb����������
9����[������e��>qcY�����U$�nl'~�dR�;R��?�j/�=A2J��@��^��&����p$��pW��$0�}��0��������>�w���L���\�$%��S�"r]�|�D�l�*-�I��H��;�q8�]����>)��g���\���=
Sjl�q��a���5�������mU���@�`jf������T)K(�q��O#W�6Jo�F�2�O��b�a��<�j`�5�H	 ������D�d���16UeWF��J
G�B�V��x0�!%m��MY+]�/W	+;�E����������)���{��n|�!�9W'U]`��\!��V��>���c=:�"���Mdc�4��Xih�u�SHm$gfG�e�i ��P����;6������X������;4���f5'C�F�����Na�4��������������#G�^kFNJDU/)���P�Hr�'�q0cJ�]���n|@�o1K8���{*y��xx��+���g(��^�gW�,@���7�zQ
����f~0��O����@�Q�a#+9������<
|��~���Ts�^ �`���!���5��l��?h_�|�Gq���IB>g��X��j�U��r�x�p,�,S�
v>����%�FtL @v�f��
2�To�� ]$J9B?8�`
D`*@�#�j�	�4$�
 ^�t�/+2&�LdH�@<�.�s�����j�Ic��Q���l�c���`i>x��S���
����D�k8f���N��������`����xO`� 1V��z:��QR�E�
!���h�)M������ k���9d�4�8���G��QE	�P�I������.�p2 y:pm�^�`�l�9�Vc�V����� �����L@;�+`���i�@�����y�X�D����PN@q�FE��V�L���nP�}�0w;z�.o��_�� ��z�����7��� �<@#c�t���Cw�N�����
��J�������)P���LM�����6*��Y�&�)(�R;����*}��9/J*����^BCP��YVxx2PD"�C6�L���
s3n����L���Hn���<������TM�V�cZ!b9���N�v)���Q��z�2�z�r�\��@X�����[��X����20��GS��B�t�R�Q'j)��P�J����;~'�#:$���!)4���-�<d����W��L�@@r�a7����k�z5��n2���z���9��t���uz�#0������YpjC���`d��0;�������c�2�+��1:u<��;�:
��)FA�����d����G��������qn�tV��Sw��F��zN�f@�\�0�������/��X�[f�BT�X.AP�&-q�6��-N*�H"����������oU�`���7vy����^l���8k��O��N�A�)g�!}"e`�C/@�M ���'S����"�P{)v{�o��	b-!�^�b����\6:I�� ���U8�90Ut���)[.@Lr��c�7Y��K:�md�b�k*�� ��#r��A�9JB��A�D�w�N[$yd]@v���#�4�l�_c���z�l��nh_GKn,vJ�2H�
�[S���{R$A��`�J3�	�J'#u��[��9'����d�)������IZ�x�P��������1AW0��ID���C��u
krl��58/��/����\f	� ��a��&�isj��$]��e&v�xQI�.K���H�]:��*VN�|V������L%'[������ �k��=���Y��D5c���@o�U6m�Z�V�Z������U�WX �Hg��2g��QX|cJ����*�H�*O�tcIF&
��Gk��n=�a�D�n�B������(�~�:	�������! �y��q6����8�#��zz'�B���1@�$H���EIy�1��T|�$�!ka���^"ey�e���(�
s��xXR�q��0�����i�S�1�~8����w������_�"��X�{]�K��l���:/��w���#I���,9��V�9z��������6�2�&���"�_�eg�������4��5.[�
v��RQ��?��9���u6���:��[��23����)=Se|�~��k�-���B�VS�����qW���k�8@�����WHm���M����(e��A!���J}����@Sc�����M#s���nX�O����������3��/>�5l�VS0���s/h����h����@l5���%D�h�~���Nx~�M�����������s�80o���W�A�.b��|
��/k�[/���f��� �5F��k���!B����!��N������������@^�����S�:�z�l�?dxKP�\�A{W|���8+^����z�[�Q,��^�9����>�g��^7]�YG������WZ-d�H�R)i�`T�l�����6R��S�4��o���N������z�:_��t���v?��N�U�����b*�Tm�_����>`b�iD����xcVQ���@F�;#������k2�h�@*��z�f��wm/�r��Klp�����kMu��Pf���:��Y�C;;��^��I�i���������]���ZZ�O !����������R�����b{�5Ia{������j�Z-G
/!�q_�$�J
kx"�B{5�O)�������*�kxNA������d����q��z��������ytM{��N�������W������h�V�cK���i�)u��A� #���V�1X,��q���b�h��7��v�;�C�{�|�=%���}��
���^C���:�����#}K���8n��?�sXv����P�R5�O���lf�!���W�V;L1����C�K�����	y3�|�2����p@�Y u�H��I�N
����	R!Fk �b( ;���:�P��+x6$���e�IA�0������Wb9�Yy�f.B��z�����F��Z|G�o��[����h�`|����& ��o�	x�
0�G^c�f�n�[��Y/��ay�DB�_��_�K�Z�E����:@/
4�"����G��O��e�`e���n@T��wz�&�Z���2�Nc�eO�H����I#]-4�z��"���J�k��h�{;H������G�w��3��)������H�?���gE����Nhr�vE��_���/����I
G����^���������#������N�_��J���K�l��
=���A�3i6<SA"ac�OL��|L�x5�V���r!�w���j��L���<��J����$X���X
�1�D���]����fS���Z���W���Y��iMM�$������O���Q0*'���U�����1�J
9�^�pm��8����sm���F���b���l��<�����!����j����)����mb�������H���_��)&`ss��J��w{�����,�~���
��W��E�g1��g�y��2���"	cg�����o3=��'��F�twuwuwuuUuU7�Q���d�*���ZG)E,e�������hB:9����K�B�O\�^��Iw��NY�4�����!n�^�E��`S�AAS!��W�$�j�(N�� d25����:>9h��6�
Z4"`��G��w��k($����6���M"e7���R�)k5=�gh�]{"�S?sk��&�3>��a��P0��hJ�Dy��$^O��^�E��7M�M�y�~T?��n4$q��($����;#�*��&��!��I���NM���X�7�/������h��iJ����7-%�Z!�qt��_�uh�����g��h5H��p�^fx��C��
_������x��������	��'����o�f��9�N��:��t�6���k>Y�������������Z4\�
SZ�d�
dN����s�;��bm/�(:������ym�;���bQp_'�H��x��D�Yl:� ��������M`����E��8�4v��A����F4LA6�x�M%RQN�s��`����O&������It�O4i����i[@4k!����s��
��I��FU=��Y���tYC��c��v���X��'$���d�W^�
�$*��Q���!���	�\�w��I'�C����gk��w+\(��W�frr� ���}7{��&5����
s��2�����E�S�`��,d�����%O�%
�����Xea�B���*���9�2r��u0�3��l{��[���J���N�Z�w2s@���L���2��[���MYR��v1���(n^�U'���Q�^]C��ud�h��<��53r_���'���8��n�����'��s_����%=���2���/=��s�9��>���-��s�\��lr�Gh^���HO"�k�3�~��]1�~8T,,��$A�6fwb�!��')k��b_=���&�?�����������&�mv�����j
R)-��t�3���/z8I>5�0��6)Kv��0��+U~;U���S��p>mV��������<���\������(�;����)��@��d���C;���L�����s��M`}�'|������%�1���5��g'����sk>9�I�[2��m#��Bmk��]j���p�w@C���M����1�v�$�n�2���,����<n�iN�q�L0<��.,s��/�\���J;�*	!�c���\�?y��<�(��M���. �d�g�����3Eo�-��x�����
��&"��j�Z.V�
/l���M��u��N�\�V�:;I�@�q��4x4�c����4�����4c��x&g��
��&�����SR��;���%w�����Q����U<;�atY�[p�`�z4	�����%�$w����z+2�:i�&h�Re����jm�&j�	����j������	L�sdzi��3}K�Z���i��>N`�*0��9Po�f*`k���������U���n�\�;��J�k�����6�6"�����9�t����/P6Z��b0�
��
g�����ESe�����y����+�Y����2z�:7��Y����n��@+�-�:����Ujd�U���.h/]5��kW�\mx��5a�_����Ws������)�w5��������~B���6�������S���=��>~Ru\�0�����N��7N��9RQ;��K���.E��w�]u.C�J@����PR��t55�}/�6A��=(���2��dD;�z�;4��]����&Y�	���r�[C��pg;l�;'X��	V6�as��b���������o��Q?B77�D�Z@���-4�#��:Bs5g~pq[&�G"���/Dfp��i�����y�~�"2��"���+Y�ID���-��>F,������`�M��L�U��J�6_���������!,��a='�W�0��<|�Z��H����D[�����Fh������j�)����Sq����/�Qop�L�F��I��999��485CP�����?�l�E�����4�����j�W��Zi�\f�LO|-���Fjlm�v����8��)[��U�&!�%��%�'��<��e�v�n�����bm�E�%��������$1��<��i�Y��E3��X�c��>�t�k�|����D���~Qy��B�<�d;��(m��XM�	%'���I.��6(�'��I��������9�o��0�&C6�j���@���m�)qm�\e���Q��&�G�������vi)�|,�r��.-%�.�����ZZ��=)-�a�)�������
��.�h�� G�����������*w�,��;�?��Y��������<�<3�����)�|L�eg��Z�)}�s��g)�*I�3�~u�[f���w�:��g*U:�@�����eU����J����]��L"7�����z���m����$�R��OJ��."�	'�:�k���"�Dm����n}~u�N��$~0299�5g��������nrw���0,��{�p�[�7�6��	`�a�2���k&�C�2\�8�g��A����YXS����	��u���(��9 ��e�'n�PZ������n�4������������s�'�������I��<9������(=���u��� R�kz��Z�	�Xa;7Cx:Y[�L@�����Fc��%��������R-�V�Ow<_�����*^�s���Z���2����Z�X�h�X� �����<���.b�*�n��*���a��4S+[2��H_?���%~����T����k��"�������I0dfK`[����i��	���^�/�:��"���T0��idN��`�E�>0��z��]�������:G@�m(�6��b��s�9����jYc6��Y��9��}��j\4�nz�d��!|r�j���Tf#�\
~
'���w�=�g�iH���������R�����+�#�!%����D��L�����u��`O-E�[X	�B�:t<Bo���,��g�&�+&I.��C��j��_����vU����Yx��~�|5��
:=��2U��$�0$l�v�i���[�f:�.086)�}~p�+b�#�5�r����6[���)K�������GXS����pu���r�AED�&J����Q��;s�r�_��H�{�NyOD
�x���I����]/����x�� ��xv&G���
��7tmO����"�
_��q�i�J���kE����u^�q���i������k���W*�[���#E�Q2�{�ODLZZ��CS�����[�8��)\`��F��xsY_�~���{��\r8��8���I���-(_x����oq|x��qP�=��C�����M�utP
��GKarn��3d�96�C&��e f�!��r�������wM1�$�<���}*R�b���������=@��3Q������/$�$:��D������v�G#R twa%�B3X�H��5�X��GO�r1p��?V��c|�s�a��i8�o���M�t�ad������m�RYG���4�"NS��L!�7��t.]T�4�����P�)��'dI�
'U�z:�>UPo`��x;����h����|���j�vx��r�������-Hsyc+/[�����?������Srh
or�]
H��)O�/B��!A*y�P!)B:�
�j�������b�X8�����3�<
	�d@�s��h
��:�)���P}��Ea\-�BD{K�&Z"��Ip���P��]=�nc
zd����A]�{������E��:(?�)������@\)��1�YJ3������5$�����A�)����{���	%9�����x	D�$&��FW����l�,({�CG�����a8�����H��y�s�_�'?�G*fG��R��@q��X��l�
(J������g�������?��t�sYhS�J���TAY,��r�=]�]�a�~��;$�]
(x��(.�PDo�u	pp���,z�����HS��.�	�lXC,Z
�o���m8)J��7����h�>�� ���D�Ah�13`��Ah�9����_�Q�q��M����w�vg�����"Mt���������|��A�Y[�_�tI�u��^;<�����[��W�Rj�%�����H/Ha40��|e-��Z9T#q�9�&���R�)�PMD������L[�<��3:!������mX���B)��;�(�1�/�;#�h��v�njJn��fr��w���?��L�h&��eL�V��2��o��Nb�n���Q	IXI�c[��m>�oU���:���%��<��`|v99P+��Vw���*�r"����/x�����F��#K[Io)V=1��J
`�+|{�R��4D���hS���(�S����l����<���o_��-4����[���<p��/���i�+y���j�)���s��->��i^Z:t^by8{��&���po�)�}�V�;��wvk�Z'w�����'r�vrK�����5>������F�a��������0c�a�����%��
��fC4��k?��9E{�?�������?����
n���:��u�7vT����,MDi�������$7����f�F��@��`v�T���
��&��6�3��fJ��?����;eQu�y0����[�)�{�����d���[����8�������C�:S'�j��&���k�L@�r�w�'S�~&>����o����S��(;��UjV�o��b]�� ��}@/t
/���L�N�,��%�L�>��3 �:N�}�O�0;�[���I���f������������y��$�qy�f��]�����W���U��������(�bF��!��?���2�4.��5��)^Uw��P�R���f�]����/m.���L�<���A�*qk�����:'v����UG��d�u���[�8����J��9E���g��uU�X�n/-�;���6����I�tY��a)�����kBZZg65�R�9uX��s�09�p�X�-E��#�1)T59�%���a�Y
��g��RD�/��X\�Y������3�PP��6�CO���J��������=R"�X;�w3x���v[�\E^�vc���6��f�dC������vg^!#h�0��,_���;�,�&N�xe�gU��+o)�-��&	�&�=�������q{0���H�DGmO
�E�v�F���tM�9�L��d�Cb�m��.x�N���.Qg8`U-!������K^O��>v�i�#l�n�,s2��$����AlI���&�[���bu��y��e�<�<A�����������^��9b^��%�;X��S�+cdZ��E�#M
�(��oOt*6=PR`��7C���utdHlK�*��F1��n��k.F���q����A�q�Y8w\�Jd��l�G ceT��*��p�T
wk;������\q����0����=�����7��\Yc�4:$'~��^	q;�[���[����*C��u�5�qy�
\W(e�*6�����K�c��,����P;h�i%���I������l����R�]�����sO)h��2Y���_���Y>l��z<&+���i�M��"���DM��1��l�
O����l��
J,W5�7o�O��tA����?�Z�|U?�e]�$]��^4/ ��I<��������5O��P~+5Q���d9:|y�m����OO���,-�zO����y��V����G��}��o�A��Q��R������%_��&�3���	�LK��X�y�h�F�R
�mO�R�i2Q���Le�V}�6X�z �h��US��}:���R�*���=���LP����6-d�{�I�����Z/\�d��������R���r�X!�h*����q��F��]�g1�'f!C�)������<��3���0]����{eX���	�lA��S���x���
<8��ub������a�DX�
yYnb~xr\t��������^����<�q�-����n�k���M,/?�����Q���I������OeY��R��c���������������jKlB����A�H��ox�-r�V��yPF&�3���Q}�Zh���������5O ,*4�ve�c�V���v�?�r0�����t����f��T��2oS>a���JY���d:+�%Z��������T)���*w���U��:�vq��uqmA/
�h#����g�a5�"8Z^^��\D���A�S��Yd����,��������el���h:<������������>���V:������~&+�L`�
|�����&(��Z8�Z|C���x����[C��P'9���)�I�I5[�/%<1z��'�$Pu�4-�+�4Z:�t�����&���+�
,�A4�E�>�R����ko�;S>[[�.���������+5H��Z`��L-1�9�����$������AdX���s����E�O��4�sv=S��L2#QK�#��zJ\�"=VnPa�����3
�@����Q���ww����t����r�K�P�����/��sb���N}"����k�}3�]��h��z�AOd�������4�NWd��p+_������W���\E�_%j���L�j�BR{�����"�a�J~�gf��j-&^�jP�yf�i���^L��[��2�v�+��.�\��w��n�����9=�9��5��vB]�[�:�i��,������Q'���um�V�>����M�{[w�Zw�x��oC�����k��y��������b��7`+����+����x"�
N���A���R��BW�n�������_��w���2�J��l�iY�����)��q$���]�����g�!]����X��|��pk����]*��n������C�Z}��4�v���?����� ��X��2���
�:Q������t�ztg"�����h6��D�/�p��+*
0Vd����b'�g�4����s��E?�{���J&�Y�����N��P%�1<_e�m�Ob��x�o;�S�9��	�E�h�!��e�K	���������X�����o��_+[o��Z��d�RM�B:����n��I�������7mH���\cP�P\�����rK��>W��r�����m���W�BD��9�sS-���v��f���W�	w:�R�Z��wrq�1@�I�����{��C���\��Cwx����y��jOz��"
Go��!�F�S6"����H����}\������V�����5����_7�e���[o\4����]}�A���{0����N����,:V�EKE[T��\�k�I���������y�G�&,���7���**���`v���v�"��_�2���m:���s�w��i/���!���~�����J�p��F2 ^��=m����
�WT��A7�[��}����l��s�[���,����{��4w���4����l�b�&~G$	b�5����jRB�����\�;�jV��q!m�d�
�������d�rwg{	p7�lw���! ��Dv��!����\@XGS ~WGQ�x)��N���������$���_&����;x4�:����y�=�_�.�����5�l�Ml���8�(���
���A9\3'�H�u�Y�)���8����S�H�Y�xE�/`������5Sd��=�t�P7�h;����������!�G��`g���{o�O��W����v�C��5W���
�A�/ �U�8���po�����5�UI���zP���L������.�E+��OtN���^�.�J��^e{ko�J����H�%�=j�������&���&��m�� �9'	]/|����@��1I��������,����s�����j���iqG�3�������1zB�&-�]�A��P���x��z�W��`P~@yh<�E�=c�$�A�?��Q�����&��	��b�����;���GZ�%�x�!7�-&�j���Y��L��)�Hj�}w5L��.4��D��{�hC:'(B������������~{�]�fv���E�[�=��&�>���~����c�|��A�k���3�[�Iu�5�3�cF�������h��[^wP�����a�_"F��R��N�p�3�)��de�mh��nE|>�gAc4s��2������F7�(��I�,G3���U��8��$�����S�(�n�>�0������Q?:j��y�~�T���~]��������i���~x��Z�����9�������9�n6��OO��Q�^��7aM7�^�S��:z��zN��\B>�z
|{�?^�lby�<�/���\`KO���� ��������3����a�7��D0 (��>� �O�vx���_�k����a�q�5 N�`#����`������pC=���%~�'z��.���[����������&�|�����cQ?�N���s�/�V����&�_�_��7�������j��8j�u����^A�w�|�m�8�?gM�{x�<?�7c��|��X�q��"@����g��Sh7�HK�_A9�[��8;����_8����fn8��kXP�������h��C|qvrB	��������0^Z�q�%w���Q�Az����hB�M���s�������
�(}>Sj��/Y� �H�����%�F*����!�E���5���%J�u�My�
���������'"*�����(Nw$��!��U�2��u�)bh��FU�F�uP�����6������7HZ$q���������J����o7Qy��V�Nze��W������|'0�����`����Z��>tB-b�3�u
�|�S��jaUq�I�(X][�?(n7��
����������������{�)��Nc������cN�����}j�|���`����&�P".������Q{~���vF}��0Q�GO�[���J�c��K�?N�!��
���������'A|'t�W\��S����n[)���-%.�K��r�~�����O�c�(1oK����^����l��Q�Pa���hm�r���g�HPOb|���n���.��|/x���7���\0<��Jcb����vg��mM����S��I���A�C����o
�����0�t�I�"��zgL��5s�z:�t��*t%��AdEB����-zY[��:�p�O_p���M��:)R=������E9�\7�J�L��Q4��_]����C ���XA��X:�'�H2���~W_�m�4��;��X�������C�������@k�e�1N>��g�.�Q�&�$�]�zm�p���������`"n�P1/h������^Y)e�p|��h0���z+��d�'2wp�����JE����h���9G�0��X����N��p�:�>s�~��:9;8<�^��	Y{�6���~)'�8!/\�U#�v�����/��x�$��oB��XA2
���f��\�Y��;u,������)0����j��SV���N�|6��M��K��p4/�I��Z������6\���I�qWO��;��#W1~CX�~���h����v��T���Uc�Wf8��j�B|�%�b�) �FK��Y1�-	�����|���yt��ld�6Vs��.l>��G�-�a"������$��An*[[����������9|T���I����2G}'U�"�|@Wi��p���������*[��*�jN��������7���8�j�<,M7?�wr�_�t�o(1#�$G�n�K��1�b���t@��!W'�����Y=�E�a�)V�!��4#!7�������������^7�Y�J��C,�U�b�g�r�?�Yf+0����rS>KoV|�o��8%i|�j_a��7M�6M/n,C[��l8>�����=�M7a����
�B����G���-�\��dJ������g&te��<���&�k�L�&.�����3��l�I1��1��	���"���7~l����j��'��x8���g��%F�z�/�"Q�n���<�Li�q��~X))��B��0�
6������}P��o'��v

Mz��hgh�Zzl\r�$<r�{�E�/�����,r���3hI�59[�b�cry�S29'�~��U�>�_��s�rVk�(����A�" �|����"4�E3�D�l���
������
����T
�]Qy)N�������U��������*��&��;�o�����]t��=
mut��}0rpF`��������,����a���J���8��|��?h��"�������="
�6
(���M��7�p�@����(x~v����RP�8`�]��_Br�Y���
x�p���4��+
�,-m�
7'wv��hVZ[VQL�M�m��X�kC��r���Ds��9a�~@	��'���f��5)mq�0��ZCI��9������K�p\�}��&Xph�^yh��mA���G�E�4p8��+uY�h���i��t�8��
}�)�*��X����{MD@�����-�0hZ7J�����|(nR^��Z��X��7���~`�7D$�7X�	�����:�"����E�0��E"xiD(��.h�F]�����R�{@!d�#�[�w�����
�0��^�a����"Z��(��:���i	���b4�!��
�"����2/0%ERZx/��@b*+"(��7���%i�AW�exs����	�*����nXK�9��[�k��?J�$
WP'��#��]D��a�Mz��v��V������%$�:|���m��'�B�3�72K(�S��0P��n����3���|B0H8GTq�J&�j�^I^�x��zW���l
`������{�His�h�W���1������Y�U8�`��[�,�[Y�K2=��`�i,U�bk[\�R�m<����[\�gL��������������
�J�B������x�����N��"�>��G��Q/VL��YM_�8�0o���kS������z}RR1 (R���WU]<�����VS9����4��Z�-�l9�\q���+�[K[��`�����9�f���SHo�tB��l���,��Y�F-�k_�7t���]G�t�m�a����9���/���j��.��$�d[�c�(cS���Ys{�	���O�-5m�wD�������q)����&wNz%UD�yJ��x�b�g����W�&���`��f/9:N�'�f���z4��Zk�p�.��x��?�S�������/Je*��~���H��N��
3A�I0/���(����/	����'�X`C�C��NOgR���}t�B��/{5���)g[�0k���7���p��a��i\��GT$�x[����(�73��`�CP+�jK�W�Y)M`!�l�E��mz�q�/���Q_y���\\Q��uq��X��6E��������]���%{5���w9Y��g����W�nU�[�<���������U��D�B�I�.u�ayNc���R�AE:���d�\�x��U�FN���za�w��N-;�Lk�����-��2�'8cb�R{�w����0H�!�. o�|�NN��T'g8��G�>V��V	������+��1~��_�3L)jS&NSjR9��D
@��s�j1�l)D�b�Vx�p02V���F2�i�����������:��0�{����9g�����R�	%s7������$
1v3��y|���Qw�?(�*�p�<����0Xa��$���7K�=�Rh��q�`��'�o���[�`*���U~v����J���I0L1��J0r�"��}�c�w��n�t'�t�a�M��{03a���A�E~�������a��h�*40�FbE�����(^�G�@����B�)�ij�>�XMBH����I�����WNw���-G9w=_`��c]���$�|F�+������	�W�\��6��������]%�c?c8��]-�0?�F���,����E{~���$��g�45������H��������v
���#alT}1��Y@������bH�M�cQ����Y�o�m��U^
���0�6�.�e0�:>���~�:`5iMH���R]����;#��bZ|F�Wt�a��8]������~�S��>���,�>�E�7slD$���T��v�VEo2�~���k#����Q������S�����d���1�+��p���r7�A��������L��D�LMsql�#��/���!� �A�����	�?KH�����������f�����%��e�sE� ��b���
n[?���o�'�bH�[ut�J�����wL�[�
 c��i�����8��F�8������$�kuB���i�H���H��D��t�;G��l���	��h@b�5��Q��������]tC#�� ���}�o$���_����;[�WE��z�q�Qq7����O�q�<k���~~~��
����������!�/8w3�L����NR��#��q��x�]j*��72QbP9���6�@��K�J�#$P�����a�S+5�������f�AA�	���i�P����wI&��9��R���5��<p���������c,g3��\'�CZ���1P�x���:��tJF2��J =�f$8���[��������s���.�#���������%,2����h�hG}���L�������C�b���lr	����Z�V)����~yw+'�-8s�m�$��T�d�>%���`�m�j����zH�B:��]W�$$F<\zM�6�T�'��Q���}��M,���
�B3al��7�V���M��p�Q8�,������u�|r���&u������+�(y���\(���o�W��|OM�;�����;�F���>h������5�p'��2�b����u��D�K`S#x�I�07�I����~m�
l�A,]4�����h�SyZ�O�/b�B6�>�`���Zl��ju-��`6v�F�F�bl���!�]��
����i�"�����o�i8��[�j�Y@;c�q�$��=0���ke�����!H�Yf���������d������H[kC�s����d�[���,��Jq
�;5R�X@c��)��-�c�&CF��5�djT�����W{A�S�S��vn��\��B��A�e���`���(#\����1���$�H��P7Dkd�gY��d�*�n��j8b��8+H#�'����0�"�����^$��V��h<@f8�� KDOB	]��Q�d�l?�s��{�::�����h�{|�f���5�}`�C�B����W"3�@I�;m]�}!y>��������)n9�iz�hQvj��<�,W��$�1����d������!�1��Bn�r<{��=Az;PG��"��,+��]�r�Wz������.z����4���Mw���e�qL�!}����W��s�������q�K�6������4`F��O(��R%-<'����l�Z�Q�������Zgo������ek��q$������Q����'�+
�4��nj
�n�YwucN{�����D�a��9@��\����TO3��l��|j�V7=����h���
��r��;<���f����z��MOfS�C
9m6N��>���r���$E7s���M��{L�S���v�p�J��d����$H����{�Im2�V,/�F���6d�f���Bq}�~-}�L���`��oa(����i�d��j��V�G���~��N_,����%5+���QD�`W����z���i�YF	��Iq�(2-P����g�|��r��P-��NM���O������W���j}��w����\W���e;VVd:��0�l�f�hUF����G����Tv��R��_���f8b�CI�#>�D�a��}{�m�n�$��P����e�de��V�b�Q~).���g������L�]�r���F
lAq/:����1�q� �a��g2��#y����8(=�t#?D�dL�D7����P����T�r&���r�n�N�w^\���w7�^�����w�����V*��%?x�6�y�)�{�EEE�I
��/*�<5tD���1���==m�VQGd����kh���Mwa�F���������ah8�%H�-���m����_XX<�,!{�I���V�3���,+��T��N�6K�;�F��b8�.��*p}#�J6����z������h_�Nm��J���G<�~uC�n)��u����o�
��|��4a# V)@b���R	���������=��e��-1"= ��x?]��7��
@���_4	�I���������g�2@�R~�Qh�u5N�a4���������iX|`�-�eDI�
���H�e0eb�a�;�*��m���d.�^X����*��6;�
6+|�S���	�m�f�j���i���8~}�V���x�{�\����*�=����� ����t���fQ�D��5���oSh�����
53;��mU�n�>����saMa���y0h�s��J��vXF~����ve�%8E���5'3��w���e�em5������$
���W������m�����W�
�aUI���m�^�����
�e���.I�m_uYh-���
�`co!Z�o�^��S^�T���G�����u��������]Fn:�5��T����B�6�&`�B,w�nz��;��Y�N_��x�dXSj��zg.�%��*�e�m�b��Y���*�mJn;DY&�3mF��0s9��D�Sh���L:�Db_<�����{���q^	t6�+i����ohL�\�S3���m�/���������������x��(A��&$��c��m�f�{�e���v�=�������q������@�d�p����fP��F��f���ta���50�#��w�����dM|G�|�f�����C����u*X�j1�=�Y���k�Xc�<%��X��+���n&�2��	���)�?"L��(�3����!�zw5��>Q�2���t�Z�T��*��0�A^�|���s�31�W���S��E���{=�&��KZ�t<�9��Iz�be�W�qp����������_6��0�VN�*[l����D8���>C�?N�Y�V$���O\��I���sz��j�DS����b2�:���<t�u�C)pB|�����L���:f�c��r���7�!���,|�l�w��#��a�`)��PW�'����k7�c���f���8��t���yD��;'����K����0������nqk?.�<?=lM�� �f�p
�l�{)b����n������r�������s�]����F�@4��Hk��8DS����?�>�128��X:��re�5�G�0�$L�#�0�^���E��^_�^�_6��v����^uy�+�&f�
M�pr���Y��'������XYa�G�2W��/
#4k�"8t-�fM>L���M�w4��3�pu�
:����^�$_�Q�o����!����?�S��Q������|TyG���7�(
.��k\�
��3�Az�bmf�4@�U�,P<}G�Gy%wS`�E7��YV�l�f$v�u`�G"�������o`A[s
cU}�h?�j�i��Q��SbS:}dd��5?�2���t����]���_���L�����V���'��z�c������w
��;����H�����I��Ip��Z�o�o���W��+Cv�7R������7��HC�z?��{��o ��Cp0��������p�>�%���z���GUdcW��
�����A�)�o(�t�&��$�9�����}Av��*��]��~�k���W]4���*6h@8p�o��44��0+V
����=�uu�=�!1�	O�(�������
�B��a��Z�^���g](Qs�v�fD/�������.�����!5w.���'��A���j�n4O��e�w�����n�f,}{��x�r������A���5�
�;����?�^���v�|W?Ctb�E�!RQ���N���AJr��:��v&�?_�P��Y
���0
�/�$]�6.�����:�~�}��2p�0��*p^�.WaL[7�p��F*�4S
�V����EX�1�gw��VV��S��q���v�HQ�y�L
D]�Ooq��KVx������4G�_��uQ��y/��P�E����5��x0����Ka+a����rs0C&S`������zl�I����
��u����]����|E�<`�eOY����0�H{[���o4n���&H.B�6��3?O���v����I����=}�P���rA���Cc;&7�J>���Xj~�|_z������%��!�w�����l�aV������z��>L��8�
����N��x�3k�##�VS��|��l��]��-�NcX���d6SJ��RE,ig?��$��R8������Q���Q�r,1�<:��C7/TLZQ��n�z,���)NQg,�2�v}���u��������}�X!�mU��9��R���{��_4����������'ah����2{�(��k9�����������]}��@#���H��mNt�Y�vY��`!���#�8/���i����j��&�@��^(�P���Z��;������X@d��;G�!��=����a��M�/�M�����a����4v�4�s�E����R0��"OY~p��E���j��k�������_���l7������o�s�L����{^E���,�E�;���"u\�-�@ -���s	C,�>(��8�N���}�L��N|5�E�c��Y o2��|���#v(?��C�C�toP�Lx:�@�@4����^�"u�.�g�����Y��\/eN��4T"�K�����9���O�����mI��������Z��L4#�w���C�v/�Y�����sh�m��$�n4]x���>�;���$����R�;����}���yg���f[���x^�#������1{����`��@m�.���T�qD���t���JJT}�b���LGN��}Fk�f�)�x$}J^�]�M���2���)q��Cf�n�=���_�8&��I����
�������{���gE��O�BH
�f#!3�	+������]S��d�[mV�1��	=)!�S��'g�D�JYE��r�6�(������R��P���6���B��~����9+�:#KW�@���������<J�J�_���2�~�vk��3)���G���e�X��������9K6�]�������NL��k-��B��M&�oZ��yL$N'h�J���+]������(���b{��#I�T�u)I����ySy���[���"�
Kp��� [<�jZ
Q����3y��Z����.�e����~m���?:�
�����W�2m0e`�S��!7K��a���]����c<��x4����i4��NBR���qx������V_
g���/�Q�-a��Z
?7����!�Z�`�����>�
���A
�@���%i��[p�*����
v��p�M�+��q� ����u�5�w���/���Q��X�L ����9lk�E������W%��IX�	-Lp� �����U�':8�����J�"i�����4�q� ��k4��C�A��y��o�"(=�U��8�<M������ ?�X1:|�r�m�u%E]��;eM���ZE/mBI���}Z{v|�U�V5C&�	e)�I
mT(�j�PX��@�z��B1��,��7o�!Q��3�a��y\���VxG"�������LX=e�;)���)��$�T������[���F7wd��	����X�i�Ny.����T�\P�L7iwv"]� ���������Y
���3�{V�����I4n_���Q'�@��]��"��N�e��{w0TJ��E�?&��FP��l�M�`yD�t��WSk��y�����p�w��e�{�r�qqKg�Q��+�������;#�W}�.|�?��or%H�U$\�����c`N��G�R���l����f`��c���i�����������7y��
*�)�z�V���Ry��I
�50�c���%$����Q<�&W-����;#9�-������S������rD>��&�x{*���]�G%9&���>�!�#�$r����IH�M��a��N��aq�+�sy�,�^�:E?
�|�x�cJwVpK�������\S��C��#
���+~T�9L�l���yL���V8�PM��w���Q�S��p����k���J�2X!P�k�UVnq��_�$go-x	�zSY Q��"U�M������&N-.Q_�$t�+��t�zb�y���Y\�\���x:l���g��0KK&"�.Y��4p��I�
W4q�EK��B�����YTR3�(Cc�P|0-IY^��K*��_��x �$�"h�J�:�o�e�PA�_�����4�����9���������lU���Q7o���{��`T�MS�)6�����a�WhQ.f�>��Dae)�pt����`K�	c�lua�n��*7��#��m���s��h��++h��s���m���4a��.���g����2#Pm��H/�U4��"h��Y���UP�=��@W��������|ZIe�4�^�_�k>$�F]_ml}wx�Qg}!�H�C���{s���?6������p��y=��Swo4�Y������������|(���j��oC^�!i�tp*6�Q1/�j-������+=K��
M��v:�{;~a�`���E�U�����f����������J1p[��m�yO���A8�]��$Q����9�HdWC/��
0��r��A���6i�tQ��� �d�2��K��l���!o�C���`���5�<���J
�*����}�V�f+T��y����~��k���U^c�l8�I3�����p��	�z>ugIq����K�
�Y������`���.#v
�+��s��8E����Ey����O1.�5nbbo95�=��/�|w��k���x��%��b6\^.���������L�x��1���-��Sf�c��Z�a��x�B�G��	�I����y+n����B\N�j����);3S�)P���/�'�2����(�
O����}��)�������>97���o7q����/����`���jn���e���%M�9��.�4��{����ZP������+�E�Aa��y� �[p����1i�^L���H[���.��T��h�6�L�N���y�g4h�f����W����n"(\��<��������7�v!AL#���+�`by�8�^\y���xi�Is�;�!�)EBK;RP\K�$�l����g���L��K6G��>1S��.=����11g,�i���������N'?�+�V���G[�v�����>���Z7y� �c.U�!;� P7)��G'z���y9�g��X�&���-���#Scj�
',��;��TV��r�i�e�)F��#���SPi]k�R���.�/g�q��@�������f%&a�j������:=��w���]�`����F�����W
�u�QH^����=�j�'H��x���P�7��:&��&B�&9=�O�PD�8	<��)v+�m�l�V����}��5 �8`]���8���8������w9�zzl�)��X�������ha���$�a�)i�Yg���N�M1�%3���o�(�	�������R
[��9���\���V���FG�	�k�����?vLc?6������V[���S�@����)����8��9������N*��c2�bx��`��O����l+6�t@,��_��Z+'#��`�Vq'S*��,8��3sT-,u�%�C��)�9���8�j��q"�M�KGe5�$��"�$���tPi�$.m�X"-g���Qw@5H�������,8A���:a�1lD2�� ����P���RyUD���8M�b+X�Y��;1gJ�]�o�#���Txd��� ���.�$6+{�
�]V�lI�?"7s|��5�X�����7/�q1�?�.��H-��8[��#����jjd�������{b�$�l���O��0�	�&��&�U|��N���)�*��v����!�^'�p���������[8�2Q�h9�=��I��a	�P���M�^��tf�,E���(����y��X������������W0���
�m�#~��@��<�OX��(�="T�-+���ec;s�Ym���xy�7s�P����h.�@��@(o��
��Ul�@;�a�8xc���Ch�������E-�fW�1��x�e����;���'{�j.�#0L���?@��X�I��6������������d��}�'�e�b9"����
'����"<
��
�c�*)�N/&V`W��
�%0��.'�l`�w���$��12!�sD����� ���>J��W1�;��4R.��R���!=���X\�$��"�FJ��`N��5��
���S��@p�F���M�HN4���I�6��G?	�}���U��.5�� ^�GnJ��te��-3_�L�d��
�$���,z�d,4s)h�BC��-������%�a� ��
i���*b�R>v/Z��Afm��������u��1n���^g��8oS�7��
E���;j?	��T���`��%a^�#"��FiN��M"wdE�1Y����c���0@a�.&���R)�i�������h���n��N4�����nXo���{j�}��v3����/Zu�T�_)��w�I�y���L�O�P���Dp��[[v8��4����4���W��6�q��[nW�0���K�-E�x���!�q��P��b��E���3(��n{o�Le�S���{���"m9����4���U�0�q"W������1����
��+�g���Ei��7tA2_(s��.�����1�E�q<����~�<�����L�u�	�&�����BZ��rC��*��������A����%Mun���� ��O6k�����"���'�8%��(�
�G���G����"��m�_0�h"��fq/���$Q�g���L�D�}|���/4%����F�X#����<a�6�=���w�Vw|%��so�@��R�Rd�~���xakQRU�����)N�����Y���0��
�W��9���0�����l�/�������EG_���:�w&qI�q�����>O5��	j�@�hr�Z��o��At=#����� 2_)��[m�p�IpFT��+3���x������=���E�@�a�C9�x���b;)$��*�1��lD]')7�
�	�g��/9�TV��J���l�x����������k������T*������*�%��
?A�Q\
q��+DI4�Z�Ch�+���J�jC��n�p�n�X���c�/�.���%��L<�>����ah��7m�N��eu�V�����\�+�A
t���^{�y-����W�����H)1��k<�`���IW2��j8�R����v+[s�B*wb�!v�c`�G%[��
�r����d\w�T!����*Nvk#���.�xN�?�XGT��m��OW�Cn������p�;��~e��[R���C�:�_	�"
�����t��-��QH���b�l�f@Yc�N���C���H����(M�KT��bu?����E3���f�m�����;�)Z������d�\�\��W�`����)U������(�������})&���.�����M(�����Z�$���'�1&�y3x��R�)l��K��/~�%yva��I��f�#�zK�+�@���#W��O�*��D7M���D�.�n��S�c�U��=�#�G��AQ�`���KG�8o5����I1h�5�4�Z��~N�#��r�K<�7���A��a�{bOS�9��{lv#�d1:OV��pg���|����lg��$-Od!����
�q���0WK1��ql�]�WS���,��'UH��?�8���I8,b@�����$�n�`���D�[��j]O�����T�~o�ev1wW���}1i���{�f�s9�xX�����t�6l]�`&I������h�<�/���D��
�;rL��OC3A[�$��*��j!�!�/��&�{�h�= +�Z/�����L��gjG���YB�A�p/��?B�8����h6�z�7>�vA.`AU���`�[������S�6����E�4c�0Yd���lUj�]`����r'C���SX�h���nQqw����"g��a�`J��C��i����)ap�c/>�I�k�B���a�5��DZMi=����i�F��������-n�� �C'�.��R6��v�Vu�D������)uAs$���o��[_�T������l�a�u�z�=l�|r�s��%���]��E���?9;@�m�9FHi������&~;j�_6U���X<�p�#T�z�������%�*�*������S���
�\���c_]���S]��W�s�wE����Kr������[�@Ut�R���v��2�Zw���`�]
�2?@z���qr��_��w:f(�B���CI�{}x���_���r��'�Qo]��RF[�%1��i���U'�.xZ?;�?���@
������������2�����16�6q9:����}��@�����i�)����C�<W�_���_'�M�y�Q��'�$���#D��]���vK�Z�����~(��2����$����e������}����A������X�����K��9�r���,&j{:��E�Q�x�Q��7g������M��5� ������s
d5s]o�hH��e��!)�L��+��-Tx�g�����D�	7)���'��w|�"<��g�9�Kf]������R����T�3�(ip��;+'��X1Y%���Q�����Q������M31K�b�`�����f��F�'�Uch1�y���p����dD��f}��m�����)��
��/d��@xW%���(��D-/.�~��!OL<+EM�n�����J��n��[I�lv���S}Z��P��+�I���
�	]�W��m����
E��K���������5�Q�:�Q{�^�m^_
���8���Ah����u����&v��e�rB��Q3?_xb����t7�������$�D)��r�C��dH3�(�_�V*0m�v;���09m���	������F���)�Dv����
$�>��#����u���9:l+��'���������� �~E?S8������nK�Rus�y�������77onnJQ{:�nD�pT�&����D�g��&��7�J�Yo��S���W��t������*�[����^| ���������%���?�|��*�.����g'��?[^��`��z�����W�g?���_=��~:��
>�_��B�i��{�y�|�QX�`����UnB�v��d���9U7��j��jk�j����j��F����V�V���y>�����NV��s�����=�E�OO��/�g��|0�5
����������d��_�i��l���w�b\��z4�}����������M|g7j�V�*."^w���jLdt���B��E��WX�D	6�Y
N�����/�4��g������J��f�T��&��r\/�ic����k�Bpz�1s�a��U���/�+r���V�O���#H=,H"�z#te-xW�l�(�Z!�P��jr%�*��M�,0kh�$������j1��~ZP����U�L&��B�<tJ���[
�O.�2�m@�q(	�� _`�����=���@�G�FmU�C������c��������'��Nlq���b5Pa�V��??�/��o4���c�����,4���������Yi���E��\O~_�d��T����L�=�|�Q���VS����oX�4�K���8�@���'9����nQ�����8�c�)
���d`��i.�z/B`;�~�_�"�^���>~�����C7���
[�V����7��~,���YW�������8�yAz�����O���TSc�Ig���zC���3tKq���9o� �AOV����{i/��T_I�D�I�b�oq%�����WT�d;)%��f��h����c$(kz[)�+qiw!X�:�E�X��S�mwP5���O�6���:�n���:/�*���vd�,�����1J]�I���$���b�� ��9�Z�`�7_���%]h��_�'���s(����@;���D���c
wE�� o�q�<�%���(�O:���U3sa��2Nv�|�<����y�������3=�&��y���������N�DS������k&6��^��M�I��b[��Sb'�O�0>��C�
��X:|��)���`
�8���kvS���&�>J�];y���zWz����_]K=z�n���c|�g�4���bM�^15�����p<
��]m��
i����6�^);��4�M���5���[U��0����BW�������C�dzq�������6�jh��I��>@G�UD6�$�Tq��!P��p��O��tyH�i����t^H*}�BR�}>������xu��w!�\&�`����:T��)��`Q�m���
�so#��	�+?�:�S��;�
�0��5}XA�A����s$N���l���F$�F�J�7�O�!��#7�����\6��d��V�g���v��"�C\Dzh���H�"��[�������D�+4�\��2m���N�\J��j��l���Q@3�������|�����^-���b�3l%���T��{�#�,1G'r����i�=p�����M���T��{���^o3pV�����	c���lf>M��'9N'���[�1�|j����/��&�,���o�l��Sa���E�gO�z��v�����O����]m?�;�h��Y�)�n�����w���{�t���.<O7��T��U�z$�����N����K34Zp�XM�����QV��R���'P�r��o�F�z���$�l�/���X^g��*������E�������gO�������M8F���V�>��i�Y?�P���O��.�T
T����\������n���-���Qt����}#�0�0��`6:k��l�b�w(E~���U{M��x�X���_����}�j�{=�f�t����������~�05�_!N�?.���avR._���?��j�!��+�O�}��n�~si�����4bB������B��b~��������<��y��y����ka��0�z��]x
~�n?��(��'����Y3���`��\�=d�j���sq��=
?��q����C��1���ek`�^u����{��������Y-H���&sx��A!��f%I3�~�94W�Y�(@o��B��Y<�s�R�8@��<(���7�Y�$�F5]��r0�����+�����Q�8�9h�i��p�������=>b�������=���
�V���VcY��es������,���y>t<Cs~0��uo�s��z-�,DH�g!B�>�^�d�����C?���G0�F��L�#�������E�JY������y@V-��rX�c-;��q��0���k�Z-(����j�M�``Ai�0Y��t��7���]@*��6j��9�b��>�v��XN`��uP��6��&Y�:CX�o�(�sR�?����.���,����B��`��z�|�	g�LUG�um�����������u��Ki��8F�V�5��E7��B��W��+��r����s��$�Eo�\�-�J���~y��	�7N�X����b���*�?N�����vXBi��=����02��#�q����]r n�q8
fo�`N�B���j�}:�B�������������Ct��������h:����(�&S��
���0J�������"��<2�~tx�*��<�m������{'<�Nx�����!m(�Jw�<�QZ�2�~�z���������{�Z���"�>���x>���������ra��#2]�@X�@�=m�g�,��m�B��`X�E��s�|B�����)_�o!T|pr�E��T�9U�e(�M�a���'�
���U����/����`Sq��
�����&���8�[3�����U9���g~�z���9K�?f���,0�,��|�K����Od��!���x���G�x`��m����Po�C��������i������w9)<����[�s_�t�S�r\�{�>�����8f*���Zs�����
�g6�r�e1�9�w�Zc!����Y7����YX}-���,��V_���j�������-2\�?�����m�������k�����E���#'^�
�g!*�>�^T�9��-���,���y��?�%���-������Y���C~����l�.{0�2=v���/�r��:����}5������~�V�����R'�u�v*�qy_�����]��W���=~l��?����nK�Rus�y�(W777��=L7�q8*E���^��@��&��7�J�Yo��S���W���}����@�%������������O���S9y7R�8�B*���m��?
S����#�29Y_xE_�g��lx����?_����#��{��{��~�1��5B�]��v���Y���9�?�����.Ho
���w�`���#�_D7XD7�=��g�,���Bq�`X�Et���zx��0��#
_8�WX��`>�-��#�\��v7T1���?���Q����NP�!�:�`�a\=Ie���%����B0�L��+��,�f|����xj$ARld�${s�������d��V�g��O���K\Dzh�����;y�,���5;�>Lv�����?��G�|n�����%�m�����W�#����[���#����H1K����u(#�lZg��5p1k���#�d��� ������,��yq�'��|���Or�N(���tc���$}��_�jM�Y2-X�����Z.9��hA�b5����[!�p�7�-��`0	g��Q�:��(=x}���	�wc�&4�F-N�MA�Ir���S�G�mBr���0A?7����!�
q�qAo�X���r�Jf�^����=FP�Q�\qZ���t���K��F��]�B�o?���~���0�Z<�Y�w-���]��k�@S���9=/��Y8�?�6�E��E��E���5����6~"$��!y�O/B��C�A����@�#��~#��`&�E��T����A��G������< ��Z[9������8h��hcU�5o���gwY5��M0��4}���i:N��
���. ��"�D��������o��l�M��u��P�.�Q���F�g��]<�YhuZ��V��hu��-�m,��x�
e >��s
 a�
��������h�_�z����V���(����Z�Tj����n���������`D��n#Z�G���7�E?���~oa��a�p~�|�l���&����V����)�{���GQiz�}S���(�_u�<��/8Jh�O���of���/8X0�za�-3�qpqP:�����z:��{T,'�hR������&�"0�"0�"0�����Bs,�g,�-�|��'�h��{>�������.n�?�?������s�9���_����2s��y[`����@��gr����o���{M�������������uO�g��8�z�nq�]��������\�����|f�U��@q�?����?�)���E��/.���.�.�����b��=����<��w1_}�b�z��z�eq(ZLr��SN����b����`w��$���ts|���(}?{aZ�3e���E,��[��v;,��+a��	=�Y@,���lhY^A��J�����.����=�o��^89�Ao������z6�&[#!��,R�zs��rH��
���Im����;�
�6���U�]k�����p�z���=��������2�\�!q:��q'�������w�|i4�����������V���wG-�<��cX�/�0���?]��U�)
#133Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#132)
Re: patch: function xmltable

Pavel Stehule wrote:

I am sending new version - it is based on own executor scan node and
tuplestore.

Some now obsolete regress tests removed, some new added.

The executor code (memory context usage) should be cleaned little bit - but
other code should be good.

I think you forgot nodeTableFuncscan.c.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#134Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#133)
1 attachment(s)
Re: patch: function xmltable

Hi

2017-01-30 20:38 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

I am sending new version - it is based on own executor scan node and
tuplestore.

Some now obsolete regress tests removed, some new added.

The executor code (memory context usage) should be cleaned little bit -

but

other code should be good.

I think you forgot nodeTableFuncscan.c.

true, I am sorry

attached

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-41.patch.gzapplication/x-gzip; name=xmltable-41.patch.gzDownload
#135Michael Paquier
michael.paquier@gmail.com
In reply to: Pavel Stehule (#134)
Re: patch: function xmltable

On Tue, Jan 31, 2017 at 5:18 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

true, I am sorry

Last status is a new patch and no reviews. On top of that this thread
is quite active. So moved to next CF. Pavel, please be careful about
the status of the patch on the CF app, it was set to "waiting on
author"...
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#136Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#116)
Re: patch: function xmltable

2017-01-24 21:38 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

* SELECT (xmltable(..)).* + regress tests
* compilation and regress tests without --with-libxml

Thanks. I just realized that this is doing more work than necessary --
I think it would be simpler to have tableexpr fill a tuplestore with the
results, instead of just expecting function execution to apply
ExecEvalExpr over and over to obtain the results. So evaluating a
tableexpr returns just the tuplestore, which function evaluation can
return as-is. That code doesn't use the value-per-call interface
anyway.

I also realized that the expr context callback is not called if there's
an error, which leaves us without shutting down libxml properly. I
added PG_TRY around the fetchrow calls, but I'm not sure that's correct
either, because there could be an error raised in other parts of the
code, after we've already emitted a few rows (for example out of
memory). I think the right way is to have PG_TRY around the execution
of the whole thing rather than just row at a time; and the tuplestore
mechanism helps us with that.

I think it would be good to have a more complex test case in regress --
let's say there is a table with some simple XML values, then we use
XMLFOREST (or maybe one of the table_to_xml functions) to generate a
large document, and then XMLTABLE uses that document as input document.

I have a 16K lines long real XML 6.MB. Probably we would not to append it
to regress tests.

It is really fast - original customer implementation 20min, nested our
xpath implementation 10 sec, PLPython xml reader 5 sec, xmltable 400ms

I have a plan to create tests based on pg_proc and CTE - if all works, then
the query must be empty

with x as (select proname, proowner, procost, pronargs,
array_to_string(proargnames,',') as proargnames,
array_to_string(proargtypes,',') as proargtypes from pg_proc), y as (select
xmlelement(name proc, xmlforest(proname, proowner, procost, pronargs,
proargnames, proargtypes)) as proc from x), z as (select xmltable.* from y,
lateral xmltable('/proc' passing proc columns proname name, proowner oid,
procost float, pronargs int, proargnames text, proargtypes text)) select *
from z except select * from x;

Show quoted text

Please fix.

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#137Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#136)
Re: patch: function xmltable

Pavel Stehule wrote:

2017-01-24 21:38 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

I think it would be good to have a more complex test case in regress --
let's say there is a table with some simple XML values, then we use
XMLFOREST (or maybe one of the table_to_xml functions) to generate a
large document, and then XMLTABLE uses that document as input document.

I have a 16K lines long real XML 6.MB. Probably we would not to append it
to regress tests.

It is really fast - original customer implementation 20min, nested our
xpath implementation 10 sec, PLPython xml reader 5 sec, xmltable 400ms

That's great numbers, kudos for the hard work here. That will make for
a nice headline in pg10 PR materials. But what I was getting at is that
I would like to exercise a bit more of the expression handling in
xmltable execution, to make sure it doesn't handle just string literals.

I have a plan to create tests based on pg_proc and CTE - if all works, then
the query must be empty

with x as (select proname, proowner, procost, pronargs,
array_to_string(proargnames,',') as proargnames,
array_to_string(proargtypes,',') as proargtypes from pg_proc), y as (select
xmlelement(name proc, xmlforest(proname, proowner, procost, pronargs,
proargnames, proargtypes)) as proc from x), z as (select xmltable.* from y,
lateral xmltable('/proc' passing proc columns proname name, proowner oid,
procost float, pronargs int, proargnames text, proargtypes text)) select *
from z except select * from x;

Nice one :-)

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#138Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#137)
Re: patch: function xmltable

2017-01-31 14:57 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2017-01-24 21:38 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

I think it would be good to have a more complex test case in regress --
let's say there is a table with some simple XML values, then we use
XMLFOREST (or maybe one of the table_to_xml functions) to generate a
large document, and then XMLTABLE uses that document as input document.

I have a 16K lines long real XML 6.MB. Probably we would not to append it
to regress tests.

It is really fast - original customer implementation 20min, nested our
xpath implementation 10 sec, PLPython xml reader 5 sec, xmltable 400ms

That's great numbers, kudos for the hard work here. That will make for
a nice headline in pg10 PR materials. But what I was getting at is that
I would like to exercise a bit more of the expression handling in
xmltable execution, to make sure it doesn't handle just string literals.

I'll try to write some more dynamic examples.

Show quoted text

I have a plan to create tests based on pg_proc and CTE - if all works,

then

the query must be empty

with x as (select proname, proowner, procost, pronargs,
array_to_string(proargnames,',') as proargnames,
array_to_string(proargtypes,',') as proargtypes from pg_proc), y as

(select

xmlelement(name proc, xmlforest(proname, proowner, procost, pronargs,
proargnames, proargtypes)) as proc from x), z as (select xmltable.* from

y,

lateral xmltable('/proc' passing proc columns proname name, proowner oid,
procost float, pronargs int, proargnames text, proargtypes text)) select

*

from z except select * from x;

Nice one :-)

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#139Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#137)
1 attachment(s)
Re: patch: function xmltable

Hi

2017-01-31 14:57 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2017-01-24 21:38 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

I think it would be good to have a more complex test case in regress --
let's say there is a table with some simple XML values, then we use
XMLFOREST (or maybe one of the table_to_xml functions) to generate a
large document, and then XMLTABLE uses that document as input document.

I have a 16K lines long real XML 6.MB. Probably we would not to append it
to regress tests.

It is really fast - original customer implementation 20min, nested our
xpath implementation 10 sec, PLPython xml reader 5 sec, xmltable 400ms

That's great numbers, kudos for the hard work here. That will make for
a nice headline in pg10 PR materials. But what I was getting at is that
I would like to exercise a bit more of the expression handling in
xmltable execution, to make sure it doesn't handle just string literals.

done

I have a plan to create tests based on pg_proc and CTE - if all works,

then

the query must be empty

with x as (select proname, proowner, procost, pronargs,
array_to_string(proargnames,',') as proargnames,
array_to_string(proargtypes,',') as proargtypes from pg_proc), y as

(select

xmlelement(name proc, xmlforest(proname, proowner, procost, pronargs,
proargnames, proargtypes)) as proc from x), z as (select xmltable.* from

y,

lateral xmltable('/proc' passing proc columns proname name, proowner oid,
procost float, pronargs int, proargnames text, proargtypes text)) select

*

from z except select * from x;

Nice one :-)

please see attached patch

* enhanced regress tests
* clean memory context work

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-42.patch.gzapplication/x-gzip; name=xmltable-42.patch.gzDownload
#140Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#139)
1 attachment(s)
Re: patch: function xmltable

Hi

please see attached patch

* enhanced regress tests
* clean memory context work

new update

fix a bug in string compare
fix some typo and obsolete comments

Regards

Pavel

Show quoted text

Regards

Pavel

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-43.patchtext/x-patch; charset=US-ASCII; name=xmltable-43.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d7738b18b7..4ceb8fe5d8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10325,50 +10325,53 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
   <sect2 id="functions-xml-processing">
    <title>Processing XML</title>
 
-   <indexterm>
-    <primary>XPath</primary>
-   </indexterm>
-
    <para>
     To process values of data type <type>xml</type>, PostgreSQL offers
-    the functions <function>xpath</function> and
+    the functions <function>xpath</function>,
     <function>xpath_exists</function>, which evaluate XPath 1.0
-    expressions.
+    expressions, and the <function>XMLTABLE</function> predicate.
    </para>
 
+   <sect3 id="functions-xml-processing-xpath">
+    <title><literal>xpath</literal></title>
+
+    <indexterm>
+     <primary>XPath</primary>
+    </indexterm>
+
 <synopsis>
 <function>xpath</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath</function> evaluates the XPath
-    expression <replaceable>xpath</replaceable> (a <type>text</> value)
-    against the XML value
-    <replaceable>xml</replaceable>.  It returns an array of XML values
-    corresponding to the node set produced by the XPath expression.
-    If the XPath expression returns a scalar value rather than a node set,
-    a single-element array is returned.
-   </para>
+    <para>
+     The function <function>xpath</function> evaluates the XPath
+     expression <replaceable>xpath</replaceable> (a <type>text</> value)
+     against the XML value
+     <replaceable>xml</replaceable>.  It returns an array of XML values
+     corresponding to the node set produced by the XPath expression.
+     If the XPath expression returns a scalar value rather than a node set,
+     a single-element array is returned.
+    </para>
 
-  <para>
-    The second argument must be a well formed XML document. In particular,
-    it must have a single root node element.
-  </para>
+    <para>
+     The second argument must be a well formed XML document. In particular,
+     it must have a single root node element.
+    </para>
 
-   <para>
-    The optional third argument of the function is an array of namespace
-    mappings.  This array should be a two-dimensional <type>text</> array with
-    the length of the second axis being equal to 2 (i.e., it should be an
-    array of arrays, each of which consists of exactly 2 elements).
-    The first element of each array entry is the namespace name (alias), the
-    second the namespace URI. It is not required that aliases provided in
-    this array be the same as those being used in the XML document itself (in
-    other words, both in the XML document and in the <function>xpath</function>
-    function context, aliases are <emphasis>local</>).
-   </para>
+    <para>
+     The optional third argument of the function is an array of namespace
+     mappings.  This array should be a two-dimensional <type>text</> array with
+     the length of the second axis being equal to 2 (i.e., it should be an
+     array of arrays, each of which consists of exactly 2 elements).
+     The first element of each array entry is the namespace name (alias), the
+     second the namespace URI. It is not required that aliases provided in
+     this array be the same as those being used in the XML document itself (in
+     other words, both in the XML document and in the <function>xpath</function>
+     function context, aliases are <emphasis>local</>).
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
              ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10378,10 +10381,10 @@ SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
 
-   <para>
-    To deal with default (anonymous) namespaces, do something like this:
+    <para>
+     To deal with default (anonymous) namespaces, do something like this:
 <screen><![CDATA[
 SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
              ARRAY[ARRAY['mydefns', 'http://example.com']]);
@@ -10391,27 +10394,31 @@ SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
+   </sect3>
 
-   <indexterm>
-    <primary>xpath_exists</primary>
-   </indexterm>
+   <sect3 id="functions-xml-processing-xpath-exists">
+    <title><literal>xpath_exists</literal></title>
+
+    <indexterm>
+     <primary>xpath_exists</primary>
+    </indexterm>
 
 <synopsis>
 <function>xpath_exists</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath_exists</function> is a specialized form
-    of the <function>xpath</function> function.  Instead of returning the
-    individual XML values that satisfy the XPath, this function returns a
-    Boolean indicating whether the query was satisfied or not.  This
-    function is equivalent to the standard <literal>XMLEXISTS</> predicate,
-    except that it also offers support for a namespace mapping argument.
-   </para>
+    <para>
+     The function <function>xpath_exists</function> is a specialized form
+     of the <function>xpath</function> function.  Instead of returning the
+     individual XML values that satisfy the XPath, this function returns a
+     Boolean indicating whether the query was satisfied or not.  This
+     function is equivalent to the standard <literal>XMLEXISTS</> predicate,
+     except that it also offers support for a namespace mapping argument.
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
                      ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10421,7 +10428,278 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
  t
 (1 row)
 ]]></screen>
+    </para>
+   </sect3>
+
+   <sect3 id="functions-xml-processing-xmltable">
+    <title><literal>xmltable</literal></title>
+
+    <indexterm>
+     <primary>xmltable</primary>
+    </indexterm>
+
+<synopsis>
+<function>xmltable</function>(
+    <optional>xmlnamespaces(<replaceable>namespace uri</replaceable> AS <replaceable>namespace name</replaceable><optional>, ...</optional>)</optional>
+    <replaceable>rowexpr</replaceable> PASSING <optional>BY REF</optional> <replaceable>xml</replaceable> <optional>BY REF</optional>
+    <optional>COLUMNS <replaceable>name</replaceable> <replaceable>type</replaceable>
+        <optional>PATH <replaceable>columnexpr</replaceable></optional>
+        <optional>DEFAULT <replaceable>expr</replaceable></optional>
+        <optional>NOT NULL | NULL</optional> <optional>, ...</optional></optional>
+)
+</synopsis>
+
+    <para>
+     The <function>xmltable</function> produces a table based on the
+     passed XML value, an xpath filter to extract rows, and an
+     optional set of column definitions.
+    </para>
+
+    <para>
+     For XML document:
+  <screen><![CDATA[
+<ROWS>
+  <ROW id="1">
+    <COUNTRY_ID>AU</COUNTRY_ID>
+    <COUNTRY_NAME>Australia</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="2">
+    <COUNTRY_ID>CN</COUNTRY_ID>
+    <COUNTRY_NAME>China</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="3">
+    <COUNTRY_ID>HK</COUNTRY_ID>
+    <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="4">
+    <COUNTRY_ID>IN</COUNTRY_ID>
+    <COUNTRY_NAME>India</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="5">
+    <COUNTRY_ID>JP</COUNTRY_ID>
+    <COUNTRY_NAME>Japan</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+  </ROW>
+  <ROW id="6">
+    <COUNTRY_ID>SG</COUNTRY_ID>
+    <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+  </ROW>
+</ROWS>
+]]></screen>
+
+     the following query produces the result:
+
+<screen><![CDATA[
+SELECT  xmltable.*
+  FROM (SELECT data FROM xmldata) x,
+        LATERAL xmltable('//ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  country_name text PATH 'COUNTRY_NAME',
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE[@unit = "km"]/text()',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+ id | country_name | country_id | region_id | size | unit | premier_name  
+----+--------------+------------+-----------+------+------+---------------
+  1 | Australia    | AU         |         3 |      |      | not specified
+  2 | China        | CN         |         3 |      |      | not specified
+  3 | HongKong     | HK         |         3 |      |      | not specified
+  4 | India        | IN         |         3 |      |      | not specified
+  5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 | Singapore    | SG         |         3 |  791 | km   | not specified
+]]></screen>
+    </para>
+
+    <para>
+     The optional <literal>xmlnamespaces</> clause is a comma-separated list of
+     namespaces of the form <replaceable>namespace-URI</> <literal>AS</>
+     <replaceable>namespace-name</>. It specifies the XML namespaces used in
+     the document and their aliases. The default namespace is not supported
+     yet.
+
+<screen><![CDATA[
+SELECT *
+  FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS ns)
+                '/ns:rows/ns:row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                COLUMNS a int PATH 'a');
+
+ a  
+----
+ 10
+]]></screen>
    </para>
+
+    <para>
+     The required <replaceable>rowexpr</> argument is an xpath
+     expression that is evaluated against the supplied XML document to
+     obtain an ordered sequence of XML nodes. This sequence is what
+     <function>xmltable</> transforms into output rows.
+    </para>
+
+    <note>
+     <para>
+      The SQL/XML standard specifies the the
+      <function>xmltable</function> construct should take an XQuery
+      expression as the first argument, but PostgreSQL currently only
+      supports XPath, which is a subset of XQuery.
+     </para>
+    </note>
+
+    <para>
+     The <literal>PASSING</> <replaceable>xml</> clause provides the
+     XML document to operate on.
+     The <literal>BY REF</literal> clauses have no effect in
+     PostgreSQL, but are allowed for SQL conformance and compatibility
+     with other implementations. Per the SQL/XML standard, the first
+     <literal>BY REF</literal> is required, the second is optional.
+     Passing <literal>BY VALUE</> is not supported. Multiple
+     comma-separated terms in the PASSING clause are not supported.
+     <literal>AS</> aliases are not supported. The argument must be
+     a well-formed XML document; fragments/forests are not accepted.
+    </para>
+
+    <para>
+     The optional <literal>COLUMNS</literal> clause specifies the list
+     of colums in the generated table. If the whole
+     <literal>COLUMNS</> list is omitted, then the result set is a
+     single column where each row is a field of <literal>xml</> type
+     containing the data matched by the <replaceable>rowexpr</>.
+     Otherwise each entry describes a single column. See the syntax
+     summary above for the format. The column name and type are
+     required; the path and not-null specifier are optional.
+ <screen><![CDATA[
+SELECT *
+  FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y' AS ns)
+                '/ns:rows/ns:row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>');
+
+           xmltable           
+------------------------------
+ <a xmlns="http://x.y">10</a>
+]]></screen>
+    </para>
+
+    <note>
+     <para>
+      Column names and types are identifiers so unlike normal
+      function arguments they may not be specified as bind parameters,
+      column references, or expressions. The path and default are
+      ordinary values.
+     </para>
+    </note>
+
+    <para>
+     The <literal>PATH</> for a column is an xpath expression that is
+     evaluated for each row, relative to the result of the
+     <replaceable>rowexpr</>, to find the value of the column.
+     If no <literal>PATH</> is given then the column name is used as an
+     implicit path.
+    </para>
+
+    <para>
+     The text body of the XML matched by the <literal>PATH</> expression is
+     used as the column value. Multiple <literal>text()</literal> nodes within
+     an element are concatenated in order. Any child elements, processing
+     instructions, and comments are ignored, but the text contents of child
+     elements are concatenated to the result:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--x-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+element
+-----------------
+ a1aa2a   bbbbcccc
+ (1 row)
+]]></programlisting>
+     Note that the whitespace-only <literal>text()</> node between two non-text
+     elements is preserved, and that leading whitespace on a <literal>text()</>
+     node is not flattened.
+    </para>
+    
+    <para>
+     A <literal>PATH</> expression that matches multiple top-level elements
+     results in an error:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>1</element><element>2</element></root>' COLUMNS element text);
+ERROR:  more than one value returned by column XPath expression
+]]></programlisting>
+     This applies to <literal>text()</> nodes too, so avoid using explicit text nodes
+     in your path expressions unless this behaviour is desired:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a</element></root>' COLUMNS element text PATH 'element/text()');
+ERROR:  more than one value returned by column XPath expression
+]]></programlisting>
+     If the <literal>PATH</> matches an empty tag the result is an empty string
+     (not <literal>NULL</>).  Any <literal>xsi:nil</> attributes are ignored.
+    </para>
+
+    <para>
+     The five predefined XML entities <literal>&apos;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/commands/explain.c b/src/backend/commands/explain.c
index c9e0a3e42d..328ebb6928 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -781,6 +781,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 		case T_TidScan:
 		case T_SubqueryScan:
 		case T_FunctionScan:
+		case T_TableFuncScan:
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_WorkTableScan:
@@ -926,6 +927,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_FunctionScan:
 			pname = sname = "Function Scan";
 			break;
+		case T_TableFuncScan:
+			pname = sname = "TableFunc Scan";
+			break;
 		case T_ValuesScan:
 			pname = sname = "Values Scan";
 			break;
@@ -1103,6 +1107,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_TidScan:
 		case T_SubqueryScan:
 		case T_FunctionScan:
+		case T_TableFuncScan:
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_WorkTableScan:
@@ -1416,6 +1421,20 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			break;
+		case T_TableFuncScan:
+			if (es->verbose)
+			{
+				TableFunc *tablefunc = ((TableFuncScan *) plan)->tablefunc;
+
+				show_expression((Node *) tablefunc,
+								"Table Function Call", planstate, ancestors,
+								es->verbose, es);
+			}
+			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+			if (plan->qual)
+				show_instrumentation_count("Rows Removed by Filter", 1,
+										   planstate, es);
+			break;
 		case T_TidScan:
 			{
 				/*
@@ -2593,6 +2612,11 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 				objecttag = "Function Name";
 			}
 			break;
+		case T_TableFuncScan:
+			Assert(rte->rtekind == RTE_TABLEFUNC);
+			objectname = "xmltable";
+			objecttag = "Table Function Name";
+			break;
 		case T_ValuesScan:
 			Assert(rte->rtekind == RTE_VALUES);
 			break;
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 2a2b7eb9bd..a9893c2b22 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -26,6 +26,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \
        nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
        nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
        nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
-       nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o
+       nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \
+       nodeTableFuncscan.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index d3802079f5..5d59f95a91 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -48,6 +48,7 @@
 #include "executor/nodeSort.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
+#include "executor/nodeTableFuncscan.h"
 #include "executor/nodeTidscan.h"
 #include "executor/nodeUnique.h"
 #include "executor/nodeValuesscan.h"
@@ -198,6 +199,10 @@ ExecReScan(PlanState *node)
 			ExecReScanFunctionScan((FunctionScanState *) node);
 			break;
 
+		case T_TableFuncScanState:
+			ExecReScanTableFuncScan((TableFuncScanState *) node);
+			break;
+
 		case T_ValuesScanState:
 			ExecReScanValuesScan((ValuesScanState *) node);
 			break;
@@ -564,6 +569,7 @@ ExecMaterializesOutput(NodeTag plantype)
 	{
 		case T_Material:
 		case T_FunctionScan:
+		case T_TableFuncScan:
 		case T_CteScan:
 		case T_WorkTableScan:
 		case T_Sort:
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 0dd95c6d17..651bbae381 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -110,6 +110,7 @@
 #include "executor/nodeSort.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
+#include "executor/nodeTableFuncscan.h"
 #include "executor/nodeTidscan.h"
 #include "executor/nodeUnique.h"
 #include "executor/nodeValuesscan.h"
@@ -239,6 +240,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 														estate, eflags);
 			break;
 
+		case T_TableFuncScan:
+			result = (PlanState *) ExecInitTableFuncScan((TableFuncScan *) node,
+														estate, eflags);
+			break;
+
 		case T_ValuesScan:
 			result = (PlanState *) ExecInitValuesScan((ValuesScan *) node,
 													  estate, eflags);
@@ -459,6 +465,10 @@ ExecProcNode(PlanState *node)
 			result = ExecFunctionScan((FunctionScanState *) node);
 			break;
 
+		case T_TableFuncScanState:
+			result = ExecTableFuncScan((TableFuncScanState *) node);
+			break;
+
 		case T_ValuesScanState:
 			result = ExecValuesScan((ValuesScanState *) node);
 			break;
@@ -715,6 +725,10 @@ ExecEndNode(PlanState *node)
 			ExecEndFunctionScan((FunctionScanState *) node);
 			break;
 
+		case T_TableFuncScanState:
+			ExecEndTableFuncScan((TableFuncScanState *) node);
+			break;
+
 		case T_ValuesScanState:
 			ExecEndValuesScan((ValuesScanState *) node);
 			break;
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
new file mode 100644
index 0000000000..2d83a0e2bd
--- /dev/null
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -0,0 +1,540 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeTableFuncscan.c
+ *	  Support routines for scanning RangeTableFunc (XMLTABLE like functions).
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/nodeTableFuncscan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * INTERFACE ROUTINES
+ *		ExecTableFuncscan		scans a function.
+ *		ExecFunctionNext		retrieve next tuple in sequential order.
+ *		ExecInitTableFuncscan	creates and initializes a TableFuncscan node.
+ *		ExecEndTableFuncscan		releases any storage allocated.
+ *		ExecReScanTableFuncscan	rescans the function
+ */
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "executor/nodeTableFuncscan.h"
+#include "executor/tablefunc.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/tuplestore.h"
+#include "utils/xml.h"
+
+static TupleTableSlot *TableFuncNext(TableFuncScanState *node);
+static bool TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot);
+
+static void FetchRows(TableFuncScanState *tstate, ExprContext *econtext);
+static void Initialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc);
+static bool FetchRow(TableFuncScanState *tstate, ExprContext *econtext);
+
+/* ----------------------------------------------------------------
+ *						Scan Support
+ * ----------------------------------------------------------------
+ */
+/* ----------------------------------------------------------------
+ *		TableFuncNext
+ *
+ *		This is a workhorse for ExecTableFuncscan
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+TableFuncNext(TableFuncScanState *node)
+{
+	TupleTableSlot *scanslot;
+
+	scanslot = node->ss.ss_ScanTupleSlot;
+
+	/*
+	 * If first time through, read all tuples from function and put them
+	 * in a tuplestore. Subsequent calls just fetch tuples from
+	 * tuplestore.
+	 */
+	if (node->tupstore == NULL)
+		FetchRows(node, node->ss.ps.ps_ExprContext);
+
+	/*
+	 * Get the next tuple from tuplestore.
+	 */
+	(void) tuplestore_gettupleslot(node->tupstore,
+								   true,
+								   false,
+								   scanslot);
+	return scanslot;
+}
+
+/*
+ * TableFuncRecheck -- access method routine to recheck a tuple in EvalPlanQual
+ */
+static bool
+TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot)
+{
+	/* nothing to check */
+	return true;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecTableFuncscan(node)
+ *
+ *		Scans the function sequentially and returns the next qualifying
+ *		tuple.
+ *		We call the ExecScan() routine and pass it the appropriate
+ *		access method functions.
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecTableFuncScan(TableFuncScanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) TableFuncNext,
+					(ExecScanRecheckMtd) TableFuncRecheck);
+}
+
+/* ----------------------------------------------------------------
+ *		ExecInitTableFuncscan
+ * ----------------------------------------------------------------
+ */
+TableFuncScanState *
+ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
+{
+	TableFuncScanState *scanstate;
+	TableFunc		   *tf = node->tablefunc;
+	TupleDesc			tupdesc;
+	int			i;
+
+	/* check for unsupported flags */
+	Assert(!(eflags & EXEC_FLAG_MARK));
+
+	/*
+	 * TableFuncscan should not have any children.
+	 */
+	Assert(outerPlan(node) == NULL);
+	Assert(innerPlan(node) == NULL);
+
+	/*
+	 * create new ScanState for node
+	 */
+	scanstate = makeNode(TableFuncScanState);
+	scanstate->ss.ps.plan = (Plan *) node;
+	scanstate->ss.ps.state = estate;
+
+	/*
+	 * Miscellaneous initialization
+	 *
+	 * create expression context for node
+	 */
+	ExecAssignExprContext(estate, &scanstate->ss.ps);
+
+	/*
+	 * initialize child expressions
+	 */
+	scanstate->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) node->scan.plan.targetlist,
+					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) node->scan.plan.qual,
+					 (PlanState *) scanstate);
+
+	/*
+	 * tuple table initialization
+	 */
+	ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
+	ExecInitScanTupleSlot(estate, &scanstate->ss);
+
+	/*
+	 * initialize source tuple type
+	 */
+	tupdesc = BuildDescFromLists(tf->colnames,
+								tf->coltypes,
+								tf->coltypmods,
+								tf->colcollations);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
+
+	/*
+	 * Initialize result tuple type and projection info.
+	 */
+	ExecAssignResultTypeFromTL(&scanstate->ss.ps);
+	ExecAssignScanProjectionInfo(&scanstate->ss);
+
+	scanstate->resultSlot = MakeSingleTupleTableSlot(tupdesc);
+
+	/* Only XmlTableBuilder is supported currently */
+	scanstate->routine = &XmlTableRoutine;
+
+	scanstate->buildercxt =
+		AllocSetContextCreate(CurrentMemoryContext,
+							  "TableFunc builder context",
+							  ALLOCSET_DEFAULT_SIZES);
+	scanstate->perValueCxt =
+		AllocSetContextCreate(scanstate->buildercxt,
+							  "TableFunc per value context",
+							  ALLOCSET_DEFAULT_SIZES);
+	scanstate->opaque = NULL;	/* initialized at runtime */
+
+	scanstate->ns_names = tf->ns_names;
+
+	scanstate->ns_uris = (List *)
+		ExecInitExpr((Expr *) tf->ns_uris,
+					 (PlanState *) scanstate);
+	scanstate->docexpr =
+		ExecInitExpr((Expr *) tf->docexpr,
+					 (PlanState *) scanstate);
+	scanstate->rowexpr =
+		ExecInitExpr((Expr *) tf->rowexpr,
+					 (PlanState *) scanstate);
+	scanstate->colexprs = (List *)
+		ExecInitExpr((Expr *) tf->colexprs,
+					 (PlanState *) scanstate);
+	scanstate->coldefexprs = (List *)
+		ExecInitExpr((Expr *) tf->coldefexprs,
+					 (PlanState *) scanstate);
+
+	/* these are allocated now and initialized later */
+	scanstate->notnulls = tf->notnulls;
+	scanstate->in_functions = palloc(sizeof(FmgrInfo) * tupdesc->natts);
+	scanstate->typioparams = palloc(sizeof(Oid) * tupdesc->natts);
+	scanstate->evalcols = tf->evalcols;
+	scanstate->ordinalitycol = tf->ordinalitycol;
+
+	/*
+	 * Fill in the necessary fmgr infos.
+	 */
+	for (i = 0; i < tupdesc->natts; i++)
+	{
+		Oid			in_funcid;
+
+		getTypeInputInfo(tupdesc->attrs[i]->atttypid,
+					 &in_funcid, &scanstate->typioparams[i]);
+		fmgr_info(in_funcid, &scanstate->in_functions[i]);
+	}
+
+	return scanstate;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEndTableFuncscan
+ *
+ *		frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndTableFuncScan(TableFuncScanState *node)
+{
+	/*
+	 * Free the exprcontext
+	 */
+	ExecFreeExprContext(&node->ss.ps);
+
+	/*
+	 * clean out the tuple table
+	 */
+	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+	ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+	/*
+	 * Release slots and tuplestore resources
+	 */
+	ExecDropSingleTupleTableSlot(node->resultSlot);
+	if (node->tupstore != NULL)
+		tuplestore_end(node->tupstore);
+	node->tupstore = NULL;
+
+	MemoryContextDelete(node->buildercxt);
+}
+
+/* ----------------------------------------------------------------
+ *		ExecReScanTableFuncscan
+ *
+ *		Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+void
+ExecReScanTableFuncScan(TableFuncScanState *node)
+{
+	Bitmapset  *chgparam = node->ss.ps.chgParam;
+
+	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+	ExecClearTuple(node->resultSlot);
+
+	ExecScanReScan(&node->ss);
+
+	/*
+	 * recompute when parameters are changed
+	 */
+	if (chgparam)
+	{
+		if (node->tupstore != NULL)
+		{
+			tuplestore_end(node->tupstore);
+			node->tupstore = NULL;
+		}
+	}
+
+	if (node->tupstore != NULL)
+		tuplestore_rescan(node->tupstore);
+}
+
+/* ----------------------------------------------------------------
+ *		FetchRows
+ *
+ *		Reads a rows from TableFunc produces
+ *
+ * ----------------------------------------------------------------
+ */
+static void
+FetchRows(TableFuncScanState *tstate, ExprContext *econtext)
+{
+	const TableFuncRoutine *routine = tstate->routine;
+	MemoryContext oldcxt;
+	Datum		value;
+	bool		isnull;
+
+	Assert(tstate->opaque == NULL);
+
+	/* build tuplestore for result. */
+	oldcxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+	tstate->tupstore = tuplestore_begin_heap(false, false, work_mem);
+
+	/* other work be done in builder context */
+	MemoryContextSwitchTo(tstate->buildercxt);
+
+	PG_TRY();
+	{
+		routine->InitBuilder(tstate);
+
+		/*
+		 * If evaluating the document expression returns NULL, the table
+		 * expression is empty and we return immediately.
+		 */
+		value = ExecEvalExpr(tstate->docexpr, econtext, &isnull);
+
+		if (!isnull)
+		{
+			/* otherwise, pass the document value to the table builder */
+			Initialize(tstate, econtext, value);
+
+			/* skip all of the above on future executions of node */
+			tstate->rownum = 1;
+
+			while (FetchRow(tstate, econtext))
+			{
+				tuplestore_puttupleslot(tstate->tupstore, tstate->resultSlot);
+
+				/* reset short life context often */
+				MemoryContextReset(tstate->perValueCxt);
+			}
+
+			tuplestore_donestoring(tstate->tupstore);
+			tuplestore_rescan(tstate->tupstore);
+		}
+	}
+	PG_CATCH();
+	{
+		if (tstate->opaque != NULL)
+			routine->DestroyBuilder(tstate);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	/* Builder is not necessary now */
+	if (tstate->opaque != NULL)
+	{
+		routine->DestroyBuilder(tstate);
+		tstate->opaque = NULL;
+	}
+
+	/* return back memory context */
+	MemoryContextSwitchTo(oldcxt);
+
+	/* builder context is not necessary now */
+	MemoryContextResetOnly(tstate->buildercxt);
+
+	return;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+Initialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc)
+{
+	const TableFuncRoutine *routine;
+	TupleDesc	tupdesc;
+	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
+	 */
+	routine->SetDoc(tstate, doc);
+
+	/* Evaluate namespace specifications */
+	forboth(lc1, tstate->ns_uris, lc2, tstate->ns_names)
+	{
+		ExprState  *expr = (ExprState *) lfirst(lc1);
+		char	   *ns_name = strVal(lfirst(lc2));
+		char	   *ns_uri;
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		routine->SetNamespace(tstate, ns_name, ns_uri);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
+	if (isnull)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("row filter expression must not be null")));
+
+	routine->SetRowFilter(tstate, TextDatumGetCString(value));
+
+	/*
+	 * Install the column filter expressions into the table builder context.
+	 * If an expression is given, use that; otherwise the column name itself
+	 * is the column filter.
+	 */
+	colno = 0;
+	tupdesc = tstate->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);
+				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;
+
+		routine->SetColumnFilter(tstate, colfilter, colno);
+
+		colno++;
+	}
+}
+
+/*
+ * Fetch one more row from a TableFunc table builder; if one can be obtained,
+ * push the values for each column onto the output.
+ */
+static bool
+FetchRow(TableFuncScanState *tstate, ExprContext *econtext)
+{
+	const TableFuncRoutine *routine = tstate->routine;
+	MemoryContext	oldcxt;
+	bool			gotrow;
+	TupleDesc	tupdesc;
+	Datum	   *values;
+	bool	   *nulls;
+	ListCell   *cell;
+	int			natts;
+	int			colno;
+	bool		isnull;
+
+	/* Fetch a row */
+	gotrow = routine->FetchRow(tstate);
+
+	/* returns fast when there are not rows */
+	if (!gotrow)
+		return false;
+
+	tupdesc = tstate->resultSlot->tts_tupleDescriptor;
+	values = tstate->resultSlot->tts_values;
+	nulls = tstate->resultSlot->tts_isnull;
+	cell = list_head(tstate->coldefexprs);
+	natts = tupdesc->natts;
+	colno = 0;
+
+	ExecClearTuple(tstate->resultSlot);
+
+	/*
+	 * Obtain the value of each column for this row, installing it into
+	 * the values/isnull arrays.
+	 */
+	for (colno = 0; colno < natts; colno++)
+	{
+		if (colno == tstate->ordinalitycol)
+		{
+			/* fast path when column is ordinality */
+			values[colno] = Int32GetDatum(tstate->rownum++);
+			nulls[colno] = false;
+		}
+		else
+		{
+			/* slow path: fetch value from builder */
+			oldcxt = MemoryContextSwitchTo(tstate->perValueCxt);
+			values[colno] = routine->GetValue(tstate,
+										   tstate->evalcols ? colno : -1,
+											  &isnull);
+			MemoryContextSwitchTo(oldcxt);
+
+			/* No value?  Evaluate and apply the default, if any */
+			if (isnull && cell != NULL)
+			{
+				ExprState  *coldefexpr = (ExprState *) lfirst(cell);
+
+				if (coldefexpr != NULL)
+					values[colno] = ExecEvalExpr(coldefexpr, econtext,
+												 &isnull);
+			}
+
+			/* Verify a possible NOT NULL constraint */
+			if (isnull && bms_is_member(colno, tstate->notnulls))
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("null is not allowed in column \"%s\"",
+							  NameStr(tupdesc->attrs[colno]->attname))));
+
+			nulls[colno] = isnull;
+		}
+
+		/* advance list of default expressions */
+		if (cell != NULL)
+			cell = lnext(cell);
+	}
+
+	ExecStoreVirtualTuple(tstate->resultSlot);
+
+	return true;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 05d8538717..15b31edaa0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -588,6 +588,27 @@ _copyFunctionScan(const FunctionScan *from)
 }
 
 /*
+ * _copyTableFuncScan
+ */
+static TableFuncScan *
+_copyTableFuncScan(const TableFuncScan *from)
+{
+	TableFuncScan *newnode = makeNode(TableFuncScan);
+
+	/*
+	 * copy node superclass fields
+	 */
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	/*
+	 * copy remainder of node
+	 */
+	COPY_NODE_FIELD(tablefunc);
+
+	return newnode;
+}
+
+/*
  * _copyValuesScan
  */
 static ValuesScan *
@@ -1139,6 +1160,33 @@ _copyRangeVar(const RangeVar *from)
 }
 
 /*
+ * _copyTableFunc
+ */
+static TableFunc *
+_copyTableFunc(const TableFunc *from)
+{
+	TableFunc  *newnode = makeNode(TableFunc);
+
+	COPY_NODE_FIELD(ns_names);
+	COPY_NODE_FIELD(ns_uris);
+	COPY_NODE_FIELD(docexpr);
+	COPY_NODE_FIELD(rowexpr);
+	COPY_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;
+}
+
+/*
  * _copyIntoClause
  */
 static IntoClause *
@@ -2169,6 +2217,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_NODE_FIELD(joinaliasvars);
 	COPY_NODE_FIELD(functions);
 	COPY_SCALAR_FIELD(funcordinality);
+	COPY_NODE_FIELD(tablefunc);
 	COPY_NODE_FIELD(values_lists);
 	COPY_STRING_FIELD(ctename);
 	COPY_SCALAR_FIELD(ctelevelsup);
@@ -2590,6 +2639,38 @@ _copyRangeTableSample(const RangeTableSample *from)
 	return newnode;
 }
 
+static RangeTableFunc *
+_copyRangeTableFunc(const RangeTableFunc *from)
+{
+	RangeTableFunc *newnode = makeNode(RangeTableFunc);
+
+	COPY_SCALAR_FIELD(lateral);
+	COPY_NODE_FIELD(docexpr);
+	COPY_NODE_FIELD(rowexpr);
+	COPY_NODE_FIELD(namespaces);
+	COPY_NODE_FIELD(columns);
+	COPY_NODE_FIELD(alias);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static RangeTableFuncCol *
+_copyRangeTableFuncCol(const RangeTableFuncCol *from)
+{
+	RangeTableFuncCol *newnode = makeNode(RangeTableFuncCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(colexpr);
+	COPY_NODE_FIELD(coldefexpr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 static TypeCast *
 _copyTypeCast(const TypeCast *from)
 {
@@ -4550,6 +4631,9 @@ copyObject(const void *from)
 		case T_FunctionScan:
 			retval = _copyFunctionScan(from);
 			break;
+		case T_TableFuncScan:
+			retval = _copyTableFuncScan(from);
+			break;
 		case T_ValuesScan:
 			retval = _copyValuesScan(from);
 			break;
@@ -4626,6 +4710,9 @@ copyObject(const void *from)
 		case T_RangeVar:
 			retval = _copyRangeVar(from);
 			break;
+		case T_TableFunc:
+			retval = _copyTableFunc(from);
+			break;
 		case T_IntoClause:
 			retval = _copyIntoClause(from);
 			break;
@@ -5220,6 +5307,12 @@ copyObject(const void *from)
 		case T_RangeTableSample:
 			retval = _copyRangeTableSample(from);
 			break;
+		case T_RangeTableFunc:
+			retval = _copyRangeTableFunc(from);
+			break;
+		case T_RangeTableFuncCol:
+			retval = _copyRangeTableFuncCol(from);
+			break;
 		case T_TypeName:
 			retval = _copyTypeName(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index d595cd7481..c6b103198d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -117,6 +117,28 @@ _equalRangeVar(const RangeVar *a, const RangeVar *b)
 }
 
 static bool
+_equalTableFunc(const TableFunc *a, const TableFunc *b)
+{
+	COMPARE_NODE_FIELD(ns_names);
+	COMPARE_NODE_FIELD(ns_uris);
+	COMPARE_NODE_FIELD(docexpr);
+	COMPARE_NODE_FIELD(rowexpr);
+	COMPARE_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;
+}
+
+static bool
 _equalIntoClause(const IntoClause *a, const IntoClause *b)
 {
 	COMPARE_NODE_FIELD(rel);
@@ -2430,6 +2452,36 @@ _equalRangeTableSample(const RangeTableSample *a, const RangeTableSample *b)
 }
 
 static bool
+_equalRangeTableFunc(const RangeTableFunc *a, const RangeTableFunc *b)
+{
+	COMPARE_SCALAR_FIELD(lateral);
+	COMPARE_NODE_FIELD(docexpr);
+	COMPARE_NODE_FIELD(rowexpr);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_NODE_FIELD(alias);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalRangeTableFuncCol(const RangeTableFuncCol *a, const RangeTableFuncCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(colexpr);
+	COMPARE_NODE_FIELD(coldefexpr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+
+static bool
 _equalIndexElem(const IndexElem *a, const IndexElem *b)
 {
 	COMPARE_STRING_FIELD(name);
@@ -2531,6 +2583,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(jointype);
 	COMPARE_NODE_FIELD(joinaliasvars);
 	COMPARE_NODE_FIELD(functions);
+	COMPARE_NODE_FIELD(tablefunc);
 	COMPARE_SCALAR_FIELD(funcordinality);
 	COMPARE_NODE_FIELD(values_lists);
 	COMPARE_STRING_FIELD(ctename);
@@ -2897,6 +2950,9 @@ equal(const void *a, const void *b)
 		case T_RangeVar:
 			retval = _equalRangeVar(a, b);
 			break;
+		case T_TableFunc:
+			retval = _equalTableFunc(a, b);
+			break;
 		case T_IntoClause:
 			retval = _equalIntoClause(a, b);
 			break;
@@ -3478,6 +3534,12 @@ equal(const void *a, const void *b)
 		case T_RangeTableSample:
 			retval = _equalRangeTableSample(a, b);
 			break;
+		case T_RangeTableFunc:
+			retval = _equalRangeTableFunc(a, b);
+			break;
+		case T_RangeTableFuncCol:
+			retval = _equalRangeTableFuncCol(a, b);
+			break;
 		case T_TypeName:
 			retval = _equalTypeName(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c2f03b73f4..3deb1a28d1 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1217,6 +1217,9 @@ exprLocation(const Node *expr)
 		case T_RangeVar:
 			loc = ((const RangeVar *) expr)->location;
 			break;
+		case T_TableFunc:
+			loc = ((const TableFunc *) expr)->location;
+			break;
 		case T_Var:
 			loc = ((const Var *) expr)->location;
 			break;
@@ -2217,6 +2220,22 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableFunc:
+			{
+				TableFunc  *tf = (TableFunc *) node;
+
+				if (walker(tf->ns_uris, context))
+					return true;
+				if (walker(tf->docexpr, context))
+					return true;
+				if (walker(tf->rowexpr, context))
+					return true;
+				if (walker(tf->colexprs, context))
+					return true;
+				if (walker(tf->coldefexprs, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -2324,6 +2343,10 @@ range_table_walker(List *rtable,
 				if (walker(rte->functions, context))
 					return true;
 				break;
+			case RTE_TABLEFUNC:
+				if (walker(rte->tablefunc, context))
+					return true;
+				break;
 			case RTE_VALUES:
 				if (walker(rte->values_lists, context))
 					return true;
@@ -3013,6 +3036,20 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableFunc:
+			{
+				TableFunc  *tf = (TableFunc *) node;
+				TableFunc  *newnode;
+
+				FLATCOPY(newnode, tf, TableFunc);
+				MUTATE(newnode->ns_uris, tf->ns_uris, List *);
+				MUTATE(newnode->docexpr, tf->docexpr, Node *);
+				MUTATE(newnode->rowexpr, tf->rowexpr, Node *);
+				MUTATE(newnode->colexprs, tf->colexprs, List *);
+				MUTATE(newnode->coldefexprs, tf->coldefexprs, List *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3130,6 +3167,9 @@ range_table_mutator(List *rtable,
 			case RTE_FUNCTION:
 				MUTATE(newrte->functions, rte->functions, List *);
 				break;
+			case RTE_TABLEFUNC:
+				MUTATE(newrte->tablefunc, rte->tablefunc, TableFunc *);
+				break;
 			case RTE_VALUES:
 				MUTATE(newrte->values_lists, rte->values_lists, List *);
 				break;
@@ -3555,6 +3595,32 @@ raw_expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_RangeTableFunc:
+			{
+				RangeTableFunc *rtf = (RangeTableFunc *) node;
+
+				if (walker(rtf->docexpr, context))
+					return true;
+				if (walker(rtf->rowexpr, context))
+					return true;
+				if (walker(rtf->namespaces, context))
+					return true;
+				if (walker(rtf->columns, context))
+					return true;
+				if (walker(rtf->alias, context))
+					return true;
+			}
+			break;
+		case T_RangeTableFuncCol:
+			{
+				RangeTableFuncCol *rtfc = (RangeTableFuncCol *) node;
+
+				if (walker(rtfc->colexpr, context))
+					return true;
+				if (walker(rtfc->coldefexpr, context))
+					return true;
+			}
+			break;
 		case T_TypeName:
 			{
 				TypeName   *tn = (TypeName *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b3802b4428..09d7af6f52 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -566,6 +566,16 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
 }
 
 static void
+_outTableFuncScan(StringInfo str, const TableFuncScan *node)
+{
+	WRITE_NODE_TYPE("TABLEFUNCSCAN");
+
+	_outScanInfo(str, (const Scan *) node);
+
+	WRITE_NODE_FIELD(tablefunc);
+}
+
+static void
 _outValuesScan(StringInfo str, const ValuesScan *node)
 {
 	WRITE_NODE_TYPE("VALUESSCAN");
@@ -956,6 +966,28 @@ _outRangeVar(StringInfo str, const RangeVar *node)
 }
 
 static void
+_outTableFunc(StringInfo str, const TableFunc *node)
+{
+	WRITE_NODE_TYPE("TABLEFUNC");
+
+	WRITE_NODE_FIELD(ns_names);
+	WRITE_NODE_FIELD(ns_uris);
+	WRITE_NODE_FIELD(docexpr);
+	WRITE_NODE_FIELD(rowexpr);
+	WRITE_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);
+}
+
+static void
 _outIntoClause(StringInfo str, const IntoClause *node)
 {
 	WRITE_NODE_TYPE("INTOCLAUSE");
@@ -2869,6 +2901,9 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_NODE_FIELD(functions);
 			WRITE_BOOL_FIELD(funcordinality);
 			break;
+		case RTE_TABLEFUNC:
+			WRITE_NODE_FIELD(tablefunc);
+			break;
 		case RTE_VALUES:
 			WRITE_NODE_FIELD(values_lists);
 			WRITE_NODE_FIELD(coltypes);
@@ -3192,6 +3227,34 @@ _outRangeTableSample(StringInfo str, const RangeTableSample *node)
 }
 
 static void
+_outRangeTableFunc(StringInfo str, const RangeTableFunc *node)
+{
+	WRITE_NODE_TYPE("RANGETABLEFUNC");
+
+	WRITE_BOOL_FIELD(lateral);
+	WRITE_NODE_FIELD(docexpr);
+	WRITE_NODE_FIELD(rowexpr);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_NODE_FIELD(columns);
+	WRITE_NODE_FIELD(alias);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outRangeTableFuncCol(StringInfo str, const RangeTableFuncCol *node)
+{
+	WRITE_NODE_TYPE("RANGETABLEFUNCCOL");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(colexpr);
+	WRITE_NODE_FIELD(coldefexpr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
 _outConstraint(StringInfo str, const Constraint *node)
 {
 	WRITE_NODE_TYPE("CONSTRAINT");
@@ -3440,6 +3503,9 @@ outNode(StringInfo str, const void *obj)
 			case T_FunctionScan:
 				_outFunctionScan(str, obj);
 				break;
+			case T_TableFuncScan:
+				_outTableFuncScan(str, obj);
+				break;
 			case T_ValuesScan:
 				_outValuesScan(str, obj);
 				break;
@@ -3512,6 +3578,9 @@ outNode(StringInfo str, const void *obj)
 			case T_RangeVar:
 				_outRangeVar(str, obj);
 				break;
+			case T_TableFunc:
+				_outTableFunc(str, obj);
+				break;
 			case T_IntoClause:
 				_outIntoClause(str, obj);
 				break;
@@ -3922,6 +3991,12 @@ outNode(StringInfo str, const void *obj)
 			case T_RangeTableSample:
 				_outRangeTableSample(str, obj);
 				break;
+			case T_RangeTableFunc:
+				_outRangeTableFunc(str, obj);
+				break;
+			case T_RangeTableFuncCol:
+				_outRangeTableFuncCol(str, obj);
+				break;
 			case T_Constraint:
 				_outConstraint(str, obj);
 				break;
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index 926f226f34..dfb8bfa803 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -279,6 +279,10 @@ print_rt(const List *rtable)
 				printf("%d\t%s\t[rangefunction]",
 					   i, rte->eref->aliasname);
 				break;
+			case RTE_TABLEFUNC:
+				printf("%d\t%s\t[table function]",
+					   i, rte->eref->aliasname);
+				break;
 			case RTE_VALUES:
 				printf("%d\t%s\t[values list]",
 					   i, rte->eref->aliasname);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d2f69fe70b..f87d12945a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -460,6 +460,33 @@ _readRangeVar(void)
 	READ_DONE();
 }
 
+/*
+ * _readTableFunc
+ */
+static TableFunc *
+_readTableFunc(void)
+{
+	READ_LOCALS(TableFunc);
+
+	READ_NODE_FIELD(ns_names);
+	READ_NODE_FIELD(ns_uris);
+	READ_NODE_FIELD(docexpr);
+	READ_NODE_FIELD(rowexpr);
+	READ_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();
+}
+
 static IntoClause *
 _readIntoClause(void)
 {
@@ -1315,6 +1342,9 @@ _readRangeTblEntry(void)
 			READ_NODE_FIELD(functions);
 			READ_BOOL_FIELD(funcordinality);
 			break;
+		case RTE_TABLEFUNC:
+			READ_NODE_FIELD(tablefunc);
+			break;
 		case RTE_VALUES:
 			READ_NODE_FIELD(values_lists);
 			READ_NODE_FIELD(coltypes);
@@ -1801,6 +1831,21 @@ _readValuesScan(void)
 }
 
 /*
+ * _readTableFuncScan
+ */
+static TableFuncScan *
+_readTableFuncScan(void)
+{
+	READ_LOCALS(TableFuncScan);
+
+	ReadCommonScan(&local_node->scan);
+
+	READ_NODE_FIELD(tablefunc);
+
+	READ_DONE();
+}
+
+/*
  * _readCteScan
  */
 static CteScan *
@@ -2358,6 +2403,8 @@ parseNodeString(void)
 		return_value = _readRangeVar();
 	else if (MATCH("INTOCLAUSE", 10))
 		return_value = _readIntoClause();
+	else if (MATCH("TABLEFUNC", 9))
+		return_value = _readTableFunc();
 	else if (MATCH("VAR", 3))
 		return_value = _readVar();
 	else if (MATCH("CONST", 5))
@@ -2500,6 +2547,8 @@ parseNodeString(void)
 		return_value = _readFunctionScan();
 	else if (MATCH("VALUESSCAN", 10))
 		return_value = _readValuesScan();
+	else if (MATCH("TABLEFUNCSCAN", 13))
+		return_value = _readTableFuncScan();
 	else if (MATCH("CTESCAN", 7))
 		return_value = _readCteScan();
 	else if (MATCH("WORKTABLESCAN", 13))
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index eeacf815e3..c7d4d6c160 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -106,6 +106,8 @@ static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
 					  RangeTblEntry *rte);
 static void set_values_pathlist(PlannerInfo *root, RelOptInfo *rel,
 					RangeTblEntry *rte);
+static void set_tablefunc_pathlist(PlannerInfo *root, RelOptInfo *rel,
+					  RangeTblEntry *rte);
 static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				 RangeTblEntry *rte);
 static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
@@ -365,6 +367,9 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 			case RTE_FUNCTION:
 				set_function_size_estimates(root, rel);
 				break;
+			case RTE_TABLEFUNC:
+				set_tablefunc_size_estimates(root, rel);
+				break;
 			case RTE_VALUES:
 				set_values_size_estimates(root, rel);
 				break;
@@ -437,6 +442,10 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				/* RangeFunction */
 				set_function_pathlist(root, rel, rte);
 				break;
+			case RTE_TABLEFUNC:
+				/* Table Function */
+				set_tablefunc_pathlist(root, rel, rte);
+				break;
 			case RTE_VALUES:
 				/* Values list */
 				set_values_pathlist(root, rel, rte);
@@ -599,6 +608,12 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 				return;
 			break;
 
+		case RTE_TABLEFUNC:
+			/* Check for parallel-restricted functions. */
+			if (!is_parallel_safe(root, (Node *) rte->tablefunc))
+				return;
+			break;
+
 		case RTE_VALUES:
 			/* Check for parallel-restricted functions. */
 			if (!is_parallel_safe(root, (Node *) rte->values_lists))
@@ -1930,6 +1945,28 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 }
 
 /*
+ * set_tablefunc_pathlist
+ *		Build the (single) access path for a table func RTE
+ */
+static void
+set_tablefunc_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
+{
+	Relids		required_outer;
+	List	   *pathkeys = NIL;
+
+	/*
+	 * We don't support pushing join clauses into the quals of a function
+	 * scan, but it could still have required parameterization due to LATERAL
+	 * refs in the function expression.
+	 */
+	required_outer = rel->lateral_relids;
+
+	/* Generate appropriate path */
+	add_path(rel, create_tablefuncscan_path(root, rel,
+										   pathkeys, required_outer));
+}
+
+/*
  * set_cte_pathlist
  *		Build the (single) access path for a non-self-reference CTE RTE
  *
@@ -3029,6 +3066,9 @@ print_path(PlannerInfo *root, Path *path, int indent)
 				case T_FunctionScan:
 					ptype = "FunctionScan";
 					break;
+				case T_TableFuncScan:
+					ptype = "TableFuncScan";
+					break;
 				case T_ValuesScan:
 					ptype = "ValuesScan";
 					break;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index d01630f8db..665464c408 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1279,6 +1279,62 @@ cost_functionscan(Path *path, PlannerInfo *root,
 }
 
 /*
+ * cost_tablefuncscan
+ *	  Determines and returns the cost of scanning a table function.
+ *
+ * 'baserel' is the relation to be scanned
+ * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
+ */
+void
+cost_tablefuncscan(Path *path, PlannerInfo *root,
+				RelOptInfo *baserel, ParamPathInfo *param_info)
+{
+	Cost		startup_cost = 0;
+	Cost		run_cost = 0;
+	QualCost	qpqual_cost;
+	Cost		cpu_per_tuple;
+	RangeTblEntry *rte;
+	QualCost	exprcost;
+
+	/* Should only be applied to base relations that are functions */
+	Assert(baserel->relid > 0);
+	rte = planner_rt_fetch(baserel->relid, root);
+	Assert(rte->rtekind == RTE_TABLEFUNC);
+
+	/* Mark the path with the correct row estimate */
+	if (param_info)
+		path->rows = param_info->ppi_rows;
+	else
+		path->rows = baserel->rows;
+
+	/*
+	 * Estimate costs of executing the table func expression(s).
+	 *
+	 * XXX in principle we ought to charge tuplestore spill costs if the
+	 * number of rows is large.  However, given how phony our rowcount
+	 * estimates for functions tend to be, there's not a lot of point in that
+	 * refinement right now.
+	 */
+	cost_qual_eval_node(&exprcost, (Node *) rte->tablefunc, root);
+
+	startup_cost += exprcost.startup + exprcost.per_tuple;
+
+	/* Add scanning CPU costs */
+	get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
+
+	startup_cost += qpqual_cost.startup;
+	cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
+	run_cost += cpu_per_tuple * baserel->tuples;
+
+	/* tlist eval costs are paid per output row, not per tuple scanned */
+	startup_cost += path->pathtarget->cost.startup;
+	run_cost += path->pathtarget->cost.per_tuple * path->rows;
+
+	path->startup_cost = startup_cost;
+	path->total_cost = startup_cost + run_cost;
+}
+
+/*
  * cost_valuesscan
  *	  Determines and returns the cost of scanning a VALUES RTE.
  *
@@ -4430,6 +4486,31 @@ set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel)
 }
 
 /*
+ * set_function_size_estimates
+ *		Set the size estimates for a base relation that is a function call.
+ *
+ * The rel's targetlist and restrictinfo list must have been constructed
+ * already.
+ *
+ * We set the same fields as set_tablefunc_size_estimates.
+ */
+void
+set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+
+	/* Should only be applied to base relations that are functions */
+	Assert(rel->relid > 0);
+	rte = planner_rt_fetch(rel->relid, root);
+	Assert(rte->rtekind == RTE_TABLEFUNC);
+
+	rel->tuples = 100;
+
+	/* Now estimate number of output rows, etc */
+	set_baserel_size_estimates(root, rel);
+}
+
+/*
  * set_values_size_estimates
  *		Set the size estimates for a base relation that is a values list.
  *
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 997bdcff2e..a363634ca6 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -134,6 +134,8 @@ static FunctionScan *create_functionscan_plan(PlannerInfo *root, Path *best_path
 						 List *tlist, List *scan_clauses);
 static ValuesScan *create_valuesscan_plan(PlannerInfo *root, Path *best_path,
 					   List *tlist, List *scan_clauses);
+static TableFuncScan *create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
+					 List *tlist, List *scan_clauses);
 static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
 					List *tlist, List *scan_clauses);
 static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
@@ -190,6 +192,8 @@ static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
 				  Index scanrelid, List *functions, bool funcordinality);
 static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
 				Index scanrelid, List *values_lists);
+static TableFuncScan *make_tablefuncscan(List *qptlist, List *qpqual,
+				  Index scanrelid, TableFunc *tablefunc);
 static CteScan *make_ctescan(List *qptlist, List *qpqual,
 			 Index scanrelid, int ctePlanId, int cteParam);
 static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
@@ -355,6 +359,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
 		case T_TidScan:
 		case T_SubqueryScan:
 		case T_FunctionScan:
+		case T_TableFuncScan:
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_WorkTableScan:
@@ -636,6 +641,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
 													 scan_clauses);
 			break;
 
+		case T_TableFuncScan:
+			plan = (Plan *) create_tablefuncscan_plan(root,
+												   best_path,
+												   tlist,
+												   scan_clauses);
+			break;
+
 		case T_ValuesScan:
 			plan = (Plan *) create_valuesscan_plan(root,
 												   best_path,
@@ -756,6 +768,7 @@ use_physical_tlist(PlannerInfo *root, Path *path, int flags)
 		rel->rtekind != RTE_SUBQUERY &&
 		rel->rtekind != RTE_FUNCTION &&
 		rel->rtekind != RTE_VALUES &&
+		rel->rtekind != RTE_TABLEFUNC &&
 		rel->rtekind != RTE_CTE)
 		return false;
 
@@ -3018,6 +3031,49 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
 }
 
 /*
+ * create_tablefuncscan_plan
+ *	 Returns a tablefuncscan plan for the base relation scanned by 'best_path'
+ *	 with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+static TableFuncScan *
+create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
+						 List *tlist, List *scan_clauses)
+{
+	TableFuncScan *scan_plan;
+	Index		scan_relid = best_path->parent->relid;
+	RangeTblEntry *rte;
+	TableFunc	  *tablefunc;
+
+	/* it should be a function base rel... */
+	Assert(scan_relid > 0);
+	rte = planner_rt_fetch(scan_relid, root);
+	Assert(rte->rtekind == RTE_TABLEFUNC);
+	tablefunc = rte->tablefunc;
+
+	/* Sort clauses into best execution order */
+	scan_clauses = order_qual_clauses(root, scan_clauses);
+
+	/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+	scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+	/* Replace any outer-relation variables with nestloop params */
+	if (best_path->param_info)
+	{
+		scan_clauses = (List *)
+			replace_nestloop_params(root, (Node *) scan_clauses);
+		/* The function expressions could contain nestloop params, too */
+		tablefunc = (TableFunc *) replace_nestloop_params(root, (Node *) tablefunc);
+	}
+
+	scan_plan = make_tablefuncscan(tlist, scan_clauses, scan_relid,
+								  tablefunc);
+
+	copy_generic_path_info(&scan_plan->scan.plan, best_path);
+
+	return scan_plan;
+}
+
+/*
  * create_valuesscan_plan
  *	 Returns a valuesscan plan for the base relation scanned by 'best_path'
  *	 with restriction clauses 'scan_clauses' and targetlist 'tlist'.
@@ -4915,6 +4971,25 @@ make_functionscan(List *qptlist,
 	return node;
 }
 
+static TableFuncScan *
+make_tablefuncscan(List *qptlist,
+				  List *qpqual,
+				  Index scanrelid,
+				  TableFunc *tablefunc)
+{
+	TableFuncScan *node = makeNode(TableFuncScan);
+	Plan	   *plan = &node->scan.plan;
+
+	plan->targetlist = qptlist;
+	plan->qual = qpqual;
+	plan->lefttree = NULL;
+	plan->righttree = NULL;
+	node->scan.scanrelid = scanrelid;
+	node->tablefunc = tablefunc;
+
+	return node;
+}
+
 static ValuesScan *
 make_valuesscan(List *qptlist,
 				List *qpqual,
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index c170e9614f..e736f86d14 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -337,6 +337,8 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex)
 		vars = pull_vars_of_level((Node *) rte->functions, 0);
 	else if (rte->rtekind == RTE_VALUES)
 		vars = pull_vars_of_level((Node *) rte->values_lists, 0);
+	else if (rte->rtekind == RTE_TABLEFUNC)
+		vars = pull_vars_of_level((Node *) rte->tablefunc, 0);
 	else
 	{
 		Assert(false);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3d33d46971..3988f9a7b4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -69,17 +69,19 @@ create_upper_paths_hook_type create_upper_paths_hook = NULL;
 
 
 /* Expression kind codes for preprocess_expression */
-#define EXPRKIND_QUAL			0
-#define EXPRKIND_TARGET			1
-#define EXPRKIND_RTFUNC			2
-#define EXPRKIND_RTFUNC_LATERAL 3
-#define EXPRKIND_VALUES			4
-#define EXPRKIND_VALUES_LATERAL 5
-#define EXPRKIND_LIMIT			6
-#define EXPRKIND_APPINFO		7
-#define EXPRKIND_PHV			8
-#define EXPRKIND_TABLESAMPLE	9
-#define EXPRKIND_ARBITER_ELEM	10
+#define EXPRKIND_QUAL				0
+#define EXPRKIND_TARGET				1
+#define EXPRKIND_RTFUNC				2
+#define EXPRKIND_RTFUNC_LATERAL 	3
+#define EXPRKIND_VALUES				4
+#define EXPRKIND_VALUES_LATERAL 	5
+#define EXPRKIND_LIMIT				6
+#define EXPRKIND_APPINFO			7
+#define EXPRKIND_PHV				8
+#define EXPRKIND_TABLESAMPLE		9
+#define EXPRKIND_ARBITER_ELEM		10
+#define EXPRKIND_TABLEFUNC			11
+#define EXPRKIND_TABLEFUNC_LATERAL	12
 
 /* Passthrough data for standard_qp_callback */
 typedef struct
@@ -685,7 +687,15 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		{
 			/* Preprocess the function expression(s) fully */
 			kind = rte->lateral ? EXPRKIND_RTFUNC_LATERAL : EXPRKIND_RTFUNC;
-			rte->functions = (List *) preprocess_expression(root, (Node *) rte->functions, kind);
+			rte->functions = (List *)
+				preprocess_expression(root, (Node *) rte->functions, kind);
+		}
+		else if (rte->rtekind == RTE_TABLEFUNC)
+		{
+			/* Preprocess the function expression(s) fully */
+			kind = rte->lateral ? EXPRKIND_TABLEFUNC_LATERAL : EXPRKIND_TABLEFUNC;
+			rte->tablefunc = (TableFunc *)
+				preprocess_expression(root, (Node *) rte->tablefunc, kind);
 		}
 		else if (rte->rtekind == RTE_VALUES)
 		{
@@ -844,7 +854,8 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
 	if (root->hasJoinRTEs &&
 		!(kind == EXPRKIND_RTFUNC ||
 		  kind == EXPRKIND_VALUES ||
-		  kind == EXPRKIND_TABLESAMPLE))
+		  kind == EXPRKIND_TABLESAMPLE ||
+		  kind == EXPRKIND_TABLEFUNC))
 		expr = flatten_join_alias_vars(root, expr);
 
 	/*
@@ -5167,6 +5178,8 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
 			PathTarget *thistarget = (PathTarget *) lfirst(lc1);
 			bool		contains_srfs = (bool) lfirst_int(lc2);
 
+
+
 			/* If this level doesn't contain SRFs, do regular projection */
 			if (contains_srfs)
 				newpath = (Path *) create_set_projection_path(root,
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index be267b9da7..5dc1bb1fae 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -395,6 +395,7 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	newrte->subquery = NULL;
 	newrte->joinaliasvars = NIL;
 	newrte->functions = NIL;
+	newrte->tablefunc = NULL;
 	newrte->values_lists = NIL;
 	newrte->coltypes = NIL;
 	newrte->coltypmods = NIL;
@@ -555,6 +556,19 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 					fix_scan_list(root, splan->functions, rtoffset);
 			}
 			break;
+		case T_TableFuncScan:
+			{
+				TableFuncScan *splan = (TableFuncScan *) plan;
+
+				splan->scan.scanrelid += rtoffset;
+				splan->scan.plan.targetlist =
+					fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+				splan->scan.plan.qual =
+					fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+				splan->tablefunc = (TableFunc *)
+					fix_scan_expr(root, (Node *) splan->tablefunc, rtoffset);
+			}
+			break;
 		case T_ValuesScan:
 			{
 				ValuesScan *splan = (ValuesScan *) plan;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 7954c445dd..e8c7b431d0 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2422,6 +2422,12 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			}
 			break;
 
+		case T_TableFuncScan:
+			finalize_primnode((Node *) ((TableFuncScan *) plan)->tablefunc,
+							  &context);
+			context.paramids = bms_add_members(context.paramids, scan_params);
+			break;
+
 		case T_ValuesScan:
 			finalize_primnode((Node *) ((ValuesScan *) plan)->values_lists,
 							  &context);
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 6911177b68..81074ec94a 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1118,6 +1118,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 				case RTE_SUBQUERY:
 				case RTE_FUNCTION:
 				case RTE_VALUES:
+				case RTE_TABLEFUNC:
 					child_rte->lateral = true;
 					break;
 				case RTE_JOIN:
@@ -1965,6 +1966,11 @@ replace_vars_in_jointree(Node *jtnode,
 							pullup_replace_vars((Node *) rte->functions,
 												context);
 						break;
+					case RTE_TABLEFUNC:
+						rte->tablefunc = (TableFunc *)
+							pullup_replace_vars((Node *) rte->tablefunc,
+												context);
+						break;
 					case RTE_VALUES:
 						rte->values_lists = (List *)
 							pullup_replace_vars((Node *) rte->values_lists,
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 324829690d..90719aab11 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1751,6 +1751,32 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
 }
 
 /*
+ * create_tablefuncscan_path
+ *	  Creates a path corresponding to a sequential scan of a table function,
+ *	  returning the pathnode.
+ */
+Path *
+create_tablefuncscan_path(PlannerInfo *root, RelOptInfo *rel,
+						 List *pathkeys, Relids required_outer)
+{
+	Path	   *pathnode = makeNode(Path);
+
+	pathnode->pathtype = T_TableFuncScan;
+	pathnode->parent = rel;
+	pathnode->pathtarget = rel->reltarget;
+	pathnode->param_info = get_baserel_parampathinfo(root, rel,
+													 required_outer);
+	pathnode->parallel_aware = false;
+	pathnode->parallel_safe = rel->consider_parallel;
+	pathnode->parallel_workers = 0;
+	pathnode->pathkeys = NIL;	/* result is always unordered */
+
+	cost_tablefuncscan(pathnode, root, rel, pathnode->param_info);
+
+	return pathnode;
+}
+
+/*
  * create_valuesscan_path
  *	  Creates a path corresponding to a scan of a VALUES list,
  *	  returning the pathnode.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 4ed27054a1..7f65de4882 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1381,8 +1381,9 @@ relation_excluded_by_constraints(PlannerInfo *root,
  * dropped cols.
  *
  * We also support building a "physical" tlist for subqueries, functions,
- * values lists, and CTEs, since the same optimization can occur in
- * SubqueryScan, FunctionScan, ValuesScan, CteScan, and WorkTableScan nodes.
+ * values lists, table exprssionsand CTEs, since the same optimization can
+ * occur in SubqueryScan, FunctionScan, ValuesScan, CteScan, TableFunc
+ * and WorkTableScan nodes.
  */
 List *
 build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
@@ -1454,6 +1455,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
 			break;
 
 		case RTE_FUNCTION:
+		case RTE_TABLEFUNC:
 		case RTE_VALUES:
 		case RTE_CTE:
 			/* Not all of these can have dropped cols, but share code anyway */
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index adc1db94f4..ebf9a80213 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -151,6 +151,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
 		case RTE_VALUES:
+		case RTE_TABLEFUNC:
 		case RTE_CTE:
 
 			/*
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f7659bb6b..2ce15b9c6d 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2776,6 +2776,15 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 										LCS_asString(lc->strength)),
 							 parser_errposition(pstate, thisrel->location)));
 							break;
+						case RTE_TABLEFUNC:
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							/*------
+							  translator: %s is a SQL row locking clause such as FOR UPDATE */
+								 errmsg("%s cannot be applied to a table function",
+										LCS_asString(lc->strength)),
+							 parser_errposition(pstate, thisrel->location)));
+							break;
 						case RTE_VALUES:
 							ereport(ERROR,
 									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 07cc81ee76..943ff0b794 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -462,7 +462,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
 %type <node>	def_arg columnElem where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
-				columnref in_expr having_clause func_table array_expr
+				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality
@@ -549,6 +549,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
@@ -606,10 +611,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
 
@@ -680,8 +685,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
 
@@ -11295,6 +11300,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->coldeflist = lsecond($3);
 					$$ = (Node *) n;
 				}
+			| xmltable opt_alias_clause
+				{
+					RangeTableFunc *n = (RangeTableFunc *) $1;
+					n->alias = $2;
+					$$ = (Node *) n;
+				}
+			| LATERAL_P xmltable opt_alias_clause
+				{
+					RangeTableFunc *n = (RangeTableFunc *) $2;
+					n->lateral = true;
+					n->alias = $3;
+					$$ = (Node *) n;
+				}
 			| select_with_parens opt_alias_clause
 				{
 					RangeSubselect *n = makeNode(RangeSubselect);
@@ -11734,6 +11752,181 @@ TableFuncElement:	ColId Typename opt_collate_clause
 				}
 		;
 
+/*
+ * XMLTABLE
+ */
+xmltable:
+			XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					RangeTableFunc *n = makeNode(RangeTableFunc);
+					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 ')'
+				{
+					RangeTableFunc *n = makeNode(RangeTableFunc);
+					n->rowexpr = $3;
+					n->docexpr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' xml_namespace_list ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					RangeTableFunc *n = makeNode(RangeTableFunc);
+					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 ')'
+				{
+					RangeTableFunc *n = makeNode(RangeTableFunc);
+					n->rowexpr = $8;
+					n->docexpr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+xmltable_column_list: xmltable_column_el					{ $$ = list_make1($1); }
+			| xmltable_column_list ',' xmltable_column_el	{ $$ = lappend($1, $3); }
+		;
+
+xmltable_column_el:
+			ColId Typename
+				{
+					RangeTableFuncCol	   *fc = makeNode(RangeTableFuncCol);
+
+					fc->colname = $1;
+					fc->for_ordinality = false;
+					fc->typeName = $2;
+					fc->is_not_null = false;
+					fc->colexpr = NULL;
+					fc->coldefexpr = NULL;
+					fc->location = @1;
+
+					$$ = (Node *) fc;
+				}
+			| ColId Typename xmltable_column_opt_list
+				{
+					RangeTableFuncCol	   *fc = makeNode(RangeTableFuncCol);
+					ListCell		   *option;
+					bool				nullability_seen = false;
+
+					fc->colname = $1;
+					fc->typeName = $2;
+					fc->for_ordinality = false;
+					fc->is_not_null = false;
+					fc->colexpr = NULL;
+					fc->coldefexpr = NULL;
+					fc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (fc->coldefexpr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value is allowed"),
+										 parser_errposition(defel->location)));
+							fc->coldefexpr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (fc->colexpr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							fc->colexpr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "is_not_null") == 0)
+						{
+							if (nullability_seen)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("conflicting or redundant NULL / NOT NULL declarations for column \"%s\"", fc->colname),
+										 parser_errposition(defel->location)));
+							fc->is_not_null = intVal(defel->arg);
+							nullability_seen = true;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) fc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					RangeTableFuncCol	   *fc = makeNode(RangeTableFuncCol);
+
+					fc->colname = $1;
+					fc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					fc->location = @1;
+
+					$$ = (Node *) fc;
+				}
+		;
+
+xmltable_column_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("is_not_null", (Node *) makeInteger(true), @1); }
+			| NULL_P
+				{ $$ = makeDefElem("is_not_null", (Node *) makeInteger(false), @1); }
+		;
+
+xml_namespace_list: xml_namespace_el						{ $$ = list_make1($1); }
+			| xml_namespace_list ',' xml_namespace_el		{ $$ = lappend($1, $3); }
+		;
+
+xml_namespace_el:
+			b_expr AS ColLabel
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = $3;
+					$$->indirection = NIL;
+					$$->val = $1;
+					$$->location = @1;
+				}
+			| DEFAULT b_expr
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = NULL;
+					$$->indirection = NIL;
+					$$->val = $2;
+					$$->location = @1;
+				}
+		;
+
 /*****************************************************************************
  *
  *	Type syntax
@@ -14327,6 +14520,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14632,10 +14826,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_clause.c b/src/backend/parser/parse_clause.c
index 69f4736438..ef568c614d 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -22,6 +22,7 @@
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_constraint_fn.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
@@ -65,6 +66,8 @@ static RangeTblEntry *transformRangeSubselect(ParseState *pstate,
 						RangeSubselect *r);
 static RangeTblEntry *transformRangeFunction(ParseState *pstate,
 					   RangeFunction *r);
+static RangeTblEntry *transformRangeTableFunc(ParseState *pstate,
+					   RangeTableFunc * t);
 static TableSampleClause *transformRangeTableSample(ParseState *pstate,
 						  RangeTableSample *rts);
 static Node *transformFromClauseItem(ParseState *pstate, Node *n,
@@ -795,6 +798,250 @@ transformRangeTableSample(ParseState *pstate, RangeTableSample *rts)
 	return tablesample;
 }
 
+/*
+ * Transform a table function - RangeTableFunc is raw form of TableFunc.
+ *
+ * Transform the namespace clauses, the document-generating expression, the
+ * row-generating expression, the column-generating expressions, and the
+ * default value expressions.
+ */
+static RangeTblEntry *
+transformRangeTableFunc(ParseState *pstate, RangeTableFunc * rtf)
+{
+	TableFunc  *tf = makeNode(TableFunc);
+	const char *constructName;
+	Oid			docType;
+	RangeTblEntry *rte;
+	bool		is_lateral;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	docType = XMLOID;
+
+	/*
+	 * We make lateral_only names of this level visible, whether or not the
+	 * RangeTableFunbc is explicitly marked LATERAL.  This is needed for SQL
+	 * spec compliance and seems useful on convenience grounds for all
+	 * functions in FROM.
+	 *
+	 * (LATERAL can't nest within a single pstate level, so we don't need
+	 * save/restore logic here.)
+	 */
+	Assert(!pstate->p_lateral_active);
+	pstate->p_lateral_active = true;
+
+	/* Transform and apply typecast to the row-generating expression ... */
+	Assert(rtf->rowexpr != NULL);
+	tf->rowexpr = coerce_to_specific_type(pstate,
+				transformExpr(pstate, rtf->rowexpr, EXPR_KIND_FROM_FUNCTION),
+											TEXTOID,
+											constructName);
+	assign_expr_collations(pstate, tf->rowexpr);
+
+	/* ... and to the document itself */
+	Assert(rtf->docexpr != NULL);
+	tf->docexpr = coerce_to_specific_type(pstate,
+				transformExpr(pstate, rtf->docexpr, EXPR_KIND_FROM_FUNCTION),
+											docType,
+											constructName);
+	assign_expr_collations(pstate, tf->docexpr);
+
+	/* undef ordinality column number */
+	tf->ordinalitycol = -1;
+
+	/* Columns, if any, also need to be transformed */
+	if (rtf->columns != NIL)
+	{
+		ListCell   *col;
+		char	  **names;
+		int			i = 0;
+
+		tf->evalcols = true;
+
+		tf->colcount = list_length(rtf->columns);
+		names = palloc(sizeof(char *) * tf->colcount);
+
+		foreach(col, rtf->columns)
+		{
+			RangeTableFuncCol *rawc = (RangeTableFuncCol *) lfirst(col);
+			Oid			typid;
+			int32		typmod;
+			Node	   *colexpr;
+			Node	   *coldefexpr;
+			int			j;
+
+			tf->colnames = lappend(tf->colnames,
+									 makeString(pstrdup(rawc->colname)));
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				if (tf->ordinalitycol != -1)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+
+				typid = INT4OID;
+				typmod = -1;
+				tf->ordinalitycol = 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);
+			}
+
+			tf->coltypes = lappend_oid(tf->coltypes, typid);
+			tf->coltypmods = lappend_int(tf->coltypmods, typmod);
+			tf->colcollations = lappend_oid(tf->colcollations,
+			 type_is_collatable(typid) ? DEFAULT_COLLATION_OID : InvalidOid);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->colexpr)
+			{
+				colexpr = coerce_to_specific_type(pstate,
+							  transformExpr(pstate, rawc->colexpr,
+													  EXPR_KIND_FROM_FUNCTION),
+											  TEXTOID,
+											  constructName);
+				assign_expr_collations(pstate, colexpr);
+		  }
+			else
+				colexpr = NULL;
+
+			if (rawc->coldefexpr)
+			{
+				coldefexpr = coerce_to_specific_type_typmod(pstate,
+							  transformExpr(pstate, rawc->coldefexpr,
+													  EXPR_KIND_FROM_FUNCTION),
+														typid, typmod,
+														constructName);
+				assign_expr_collations(pstate, coldefexpr);
+			}
+			else
+				coldefexpr = NULL;
+
+			tf->colexprs = lappend(tf->colexprs, colexpr);
+			tf->coldefexprs = lappend(tf->coldefexprs, coldefexpr);
+
+			if (rawc->is_not_null)
+				tf->notnulls = bms_add_member(tf->notnulls, 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++;
+		}
+		tf->evalcols = true;
+		pfree(names);
+	}
+	else
+	{
+		tf->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.
+		 */
+		tf->colcount = 1;
+		tf->colnames = list_make1(makeString(pstrdup("xmltable")));
+		tf->coltypes = list_make1_oid(XMLOID);
+		tf->coltypmods = list_make1_int(-1);
+		tf->colcollations = list_make1_oid(InvalidOid);
+	}
+
+	/* Namespaces, if any, also need to be transformed */
+	if (rtf->namespaces != NIL)
+	{
+		ListCell   *ns;
+		ListCell   *lc2;
+		List	   *ns_uris = NIL;
+		List	   *ns_names = NIL;
+		bool		default_ns_seen = false;
+
+		foreach(ns, rtf->namespaces)
+		{
+			ResTarget  *r = (ResTarget *) lfirst(ns);
+			Node	   *ns_uri;
+
+			Assert(IsA(r, ResTarget));
+			ns_uri = transformExpr(pstate, r->val, EXPR_KIND_FROM_FUNCTION);
+			ns_uri = coerce_to_specific_type(pstate, ns_uri,
+													 TEXTOID, constructName);
+			assign_expr_collations(pstate, ns_uri);
+			ns_uris = lappend(ns_uris, ns_uri);
+
+			/* Verify consistency of name list: no dupes, only one DEFAULT */
+			if (r->name != NULL)
+			{
+				foreach(lc2, ns_names)
+				{
+					char	   *name = strVal(lfirst(lc2));
+
+					if (name == NULL)
+						continue;
+					if (strcmp(name, r->name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("namespace name \"%s\" is not unique",
+										name),
+								 parser_errposition(pstate, r->location)));
+				}
+			}
+			else
+			{
+				if (default_ns_seen)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one default namespace is allowed"),
+							 parser_errposition(pstate, r->location)));
+				default_ns_seen = true;
+			}
+
+			/* Note the string may be NULL */
+			ns_names = lappend(ns_names, makeString(r->name));
+		}
+
+		tf->ns_uris = ns_uris;
+		tf->ns_names = ns_names;
+	}
+
+	tf->location = rtf->location;
+
+	pstate->p_lateral_active = false;
+
+	/*
+	 * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
+	 * there are any lateral cross-references in it.
+	 */
+	is_lateral = rtf->lateral || contain_vars_of_level((Node *) tf, 0);
+
+	rte = addRangeTableEntryForTableFunc(pstate,
+										 tf, rtf->alias, is_lateral, true);
+
+	return rte;
+}
+
 
 /*
  * transformFromClauseItem -
@@ -918,6 +1165,24 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return (Node *) rtr;
 	}
+	else if (IsA(n, RangeTableFunc))
+	{
+		/* table function is like a plain relation */
+		RangeTblRef *rtr;
+		RangeTblEntry *rte;
+		int			rtindex;
+
+		rte = transformRangeTableFunc(pstate, (RangeTableFunc *) n);
+		/* assume new rte is at end */
+		rtindex = list_length(pstate->p_rtable);
+		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
+		*top_rte = rte;
+		*top_rti = rtindex;
+		*namespace = list_make1(makeDefaultNSItem(rte));
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 2a2ac32157..2c3f3cd9ce 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1096,9 +1096,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1106,9 +1106,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1117,7 +1117,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1144,6 +1144,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 4b73272417..db100be336 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"
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index e693c316e3..e619074220 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1629,6 +1629,68 @@ addRangeTableEntryForFunction(ParseState *pstate,
 }
 
 /*
+ * Add an entry for a table function to the pstate's range table (p_rtable).
+ *
+ * This is much like addRangeTableEntry() except that it makes a values RTE.
+ */
+RangeTblEntry *
+addRangeTableEntryForTableFunc(ParseState *pstate,
+							TableFunc *tf,
+							Alias *alias,
+							bool lateral,
+							bool inFromCl)
+{
+	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	char	   *refname = alias ? alias->aliasname : pstrdup("xmltable");
+	Alias	   *eref;
+	int			numaliases;
+
+	Assert(pstate != NULL);
+
+	rte->rtekind = RTE_TABLEFUNC;
+	rte->relid = InvalidOid;
+	rte->subquery = NULL;
+	rte->tablefunc = tf;
+	rte->coltypes = tf->coltypes;
+	rte->coltypmods = tf->coltypmods;
+	rte->colcollations = tf->colcollations;
+	rte->alias = alias;
+
+	eref = alias ? copyObject(alias) : makeAlias(refname, NIL);
+	numaliases = list_length(eref->colnames);
+
+	/* fill in any unspecified alias columns */
+	if (numaliases < list_length(tf->colnames))
+		eref->colnames = list_concat(eref->colnames,
+									 list_copy_tail(tf->colnames, numaliases));
+
+	rte->eref = eref;
+
+	/*
+	 * Set flags and access permissions.
+	 *
+	 * Subqueries are never checked for access rights.
+	 */
+	rte->lateral = lateral;
+	rte->inh = false;			/* never true for values RTEs */
+	rte->inFromCl = inFromCl;
+
+	rte->requiredPerms = 0;
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+
+	/*
+	 * Add completed RTE to pstate's range table list, but not to join list
+	 * nor namespace --- caller must do that if appropriate.
+	 */
+	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+
+	return rte;
+}
+
+/*
  * Add an entry for a VALUES list to the pstate's range table (p_rtable).
  *
  * This is much like addRangeTableEntry() except that it makes a values RTE.
@@ -2228,6 +2290,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 			}
 			break;
 		case RTE_VALUES:
+		case RTE_TABLEFUNC:
 		case RTE_CTE:
 			{
 				/* Values or CTE RTE */
@@ -2639,6 +2702,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
 				*varcollid = exprCollation(aliasvar);
 			}
 			break;
+		case RTE_TABLEFUNC:
 		case RTE_VALUES:
 		case RTE_CTE:
 			{
@@ -2685,9 +2749,13 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 			}
 			break;
 		case RTE_SUBQUERY:
+		case RTE_TABLEFUNC:
 		case RTE_VALUES:
 		case RTE_CTE:
-			/* Subselect, Values, CTE RTEs never have dropped columns */
+			/*
+			 * Subselect, Table Functions, Values, CTE RTEs never have dropped
+			 * columns
+			 */
 			result = false;
 			break;
 		case RTE_JOIN:
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2576e31239..3b84140a9b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -396,6 +396,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
 			break;
 		case RTE_FUNCTION:
 		case RTE_VALUES:
+		case RTE_TABLEFUNC:
 			/* not a simple relation, leave it unmarked */
 			break;
 		case RTE_CTE:
@@ -1557,6 +1558,12 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
 			 * its result columns as RECORD, which is not allowed.
 			 */
 			break;
+		case RTE_TABLEFUNC:
+
+			/*
+			 * Table function cannot have columns with RECORD type.
+			 */
+			break;
 		case RTE_CTE:
 			/* CTE reference: examine subquery's output expr */
 			if (!rte->self_reference)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d3e44fb135..d069e8a547 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -433,6 +433,10 @@ rewriteRuleAction(Query *parsetree,
 					sub_action->hasSubLinks =
 						checkExprHasSubLink((Node *) rte->functions);
 					break;
+				case RTE_TABLEFUNC:
+					sub_action->hasSubLinks =
+						checkExprHasSubLink((Node *) rte->tablefunc);
+					break;
 				case RTE_VALUES:
 					sub_action->hasSubLinks =
 						checkExprHasSubLink((Node *) rte->values_lists);
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f355954b53..9ac3df76e1 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -428,6 +428,8 @@ static void get_const_expr(Const *constval, deparse_context *context,
 static void get_const_collation(Const *constval, deparse_context *context);
 static void simple_quote_literal(StringInfo buf, const char *val);
 static void get_sublink_expr(SubLink *sublink, deparse_context *context);
+static void get_tablefunc(TableFunc *tf, deparse_context *context,
+			  bool showimplicit);
 static void get_from_clause(Query *query, const char *prefix,
 				deparse_context *context);
 static void get_from_clause_item(Node *jtnode, Query *query,
@@ -6709,6 +6711,7 @@ get_name_for_var_field(Var *var, int fieldno,
 			/* else fall through to inspect the expression */
 			break;
 		case RTE_FUNCTION:
+		case RTE_TABLEFUNC:
 
 			/*
 			 * We couldn't get here unless a function is declared with one of
@@ -8548,6 +8551,10 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableFunc:
+			get_tablefunc((TableFunc *) node, context, showimplicit);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
@@ -9286,6 +9293,124 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
 
 
 /* ----------
+ * get_tablefunc			- Parse back a table function
+ * ----------
+ */
+static void
+get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
+{
+	StringInfo	buf = context->buf;
+
+	/*
+	 * Deparse TableFunc - now only one TableFunc producer, the
+	 * function XMLTABLE.
+	 */
+
+	/* c_expr shoud be closed in brackets */
+	appendStringInfoString(buf, "XMLTABLE(");
+
+	if (tf->ns_uris != NIL)
+	{
+		ListCell   *lc1,
+				   *lc2;
+		bool		first = true;
+
+		appendStringInfoString(buf, "XMLNAMESPACES (");
+		forboth(lc1, tf->ns_uris, lc2, tf->ns_names)
+		{
+			Node	   *expr = (Node *) lfirst(lc1);
+			char	   *name = strVal(lfirst(lc2));
+
+			if (!first)
+				appendStringInfoString(buf, ", ");
+			else
+				first = false;
+
+			if (name != NULL)
+			{
+				get_rule_expr(expr, context, showimplicit);
+				appendStringInfo(buf, " AS %s", name);
+			}
+			else
+			{
+				appendStringInfoString(buf, "DEFAULT ");
+				get_rule_expr(expr, context, showimplicit);
+			}
+		}
+		appendStringInfoString(buf, "), ");
+	}
+
+	appendStringInfoChar(buf, '(');
+	get_rule_expr((Node *) tf->rowexpr, context, showimplicit);
+	appendStringInfoString(buf, ") PASSING (");
+	get_rule_expr((Node *) tf->docexpr, context, showimplicit);
+	appendStringInfoChar(buf, ')');
+
+	if (tf->evalcols)
+	{
+		ListCell   *l1;
+		ListCell   *l2;
+		ListCell   *l3;
+		ListCell   *l4;
+		ListCell   *l5;
+		int			colnum = 0;
+
+		l2 = list_head(tf->coltypes);
+		l3 = list_head(tf->coltypmods);
+		l4 = list_head(tf->colexprs);
+		l5 = list_head(tf->coldefexprs);
+
+		appendStringInfoString(buf, " COLUMNS ");
+		foreach(l1, tf->colnames)
+		{
+			char	   *colname = strVal(lfirst(l1));
+			Oid			typid;
+			int32		typmod;
+			Node	   *colexpr;
+			Node	   *coldefexpr;
+			bool		ordinality = tf->ordinalitycol == colnum;
+			bool		notnull = bms_is_member(colnum, tf->notnulls);
+
+			typid = lfirst_oid(l2);
+			l2 = lnext(l2);
+			typmod = lfirst_int(l3);
+			l3 = lnext(l3);
+			colexpr = (Node *) lfirst(l4);
+			l4 = lnext(l4);
+			coldefexpr = (Node *) lfirst(l5);
+			l5 = lnext(l5);
+
+			if (colnum > 0)
+				appendStringInfoString(buf, ", ");
+			colnum++;
+
+			appendStringInfo(buf, "%s %s", quote_identifier(colname),
+							 ordinality ? "FOR ORDINALITY" :
+						format_type_with_typemod(typid, typmod));
+			if (ordinality)
+				continue;
+
+			if (coldefexpr != NULL)
+			{
+				appendStringInfoString(buf, " DEFAULT (");
+				get_rule_expr((Node *) coldefexpr, context, showimplicit);
+				appendStringInfoChar(buf, ')');
+			}
+			if (colexpr != NULL)
+			{
+				appendStringInfoString(buf, " PATH (");
+				get_rule_expr((Node *) colexpr, context, showimplicit);
+				appendStringInfoChar(buf, ')');
+			}
+			if (notnull)
+				appendStringInfoString(buf, " NOT NULL");
+		}
+	}
+
+	appendStringInfoChar(buf, ')');
+}
+
+/* ----------
  * get_from_clause			- Parse back a FROM clause
  *
  * "prefix" is the keyword that denotes the start of the list of FROM
@@ -9518,6 +9643,9 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 				if (rte->funcordinality)
 					appendStringInfoString(buf, " WITH ORDINALITY");
 				break;
+			case RTE_TABLEFUNC:
+				get_tablefunc(rte->tablefunc, context, true);
+				break;
 			case RTE_VALUES:
 				/* Values list RTE */
 				appendStringInfoChar(buf, '(');
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index e8bce3b806..85785f4411 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/tablefunc.h"
 #include "fmgr.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
@@ -145,6 +146,7 @@ static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
 static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
 					   ArrayBuildState *astate,
 					   PgXmlErrorContext *xmlerrcxt);
+static xmlChar *pg_xmlCharStrndup(char *str, size_t len);
 #endif   /* USE_LIBXML */
 
 static StringInfo query_to_xml_internal(const char *query, char *tablename,
@@ -165,6 +167,49 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+/* XMLTABLE support */
+#ifdef USE_LIBXML
+/* random number to identify XmlTableContext */
+#define XMLTABLE_CONTEXT_MAGIC	46922182
+typedef struct XmlTableBuilderData
+{
+	int			magic;
+	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(TableFuncScanState *state);
+static void XmlTableSetDoc(TableFuncScanState *state, Datum value);
+static void XmlTableSetNamespace(TableFuncScanState *state, char *name,
+					 char *uri);
+static void XmlTableSetRowFilter(TableFuncScanState *state, char *path);
+static void XmlTableSetColumnFilter(TableFuncScanState *state, char *path,
+						int colnum);
+static bool XmlTableFetchRow(TableFuncScanState *state);
+static Datum XmlTableGetValue(TableFuncScanState *state, int colnum,
+				 bool *isnull);
+static void XmlTableDestroyBuilder(TableFuncScanState *state);
+
+const TableFuncRoutine XmlTableRoutine =
+{
+	XmlTableInitBuilder,
+	XmlTableSetDoc,
+	XmlTableSetNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyBuilder
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -1113,6 +1158,19 @@ xml_pnstrdup(const xmlChar *str, size_t len)
 	return result;
 }
 
+/* Ditto, except input is char* */
+static xmlChar *
+pg_xmlCharStrndup(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+
 /*
  * str is the null-terminated input string.  Remaining arguments are
  * output arguments; each can be NULL if value is not wanted.
@@ -3811,13 +3869,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
 				(errcode(ERRCODE_DATA_EXCEPTION),
 				 errmsg("empty XPath expression")));
 
-	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
-	memcpy(string, datastr, len);
-	string[len] = '\0';
-
-	xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
-	memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
-	xpath_expr[xpath_len] = '\0';
+	string = pg_xmlCharStrndup(datastr, len);
+	xpath_expr = pg_xmlCharStrndup(VARDATA(xpath_expr_text), xpath_len);
 
 	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
 
@@ -4065,3 +4118,502 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Returns private data from executor state. Ensure validity by check with
+ * MAGIC number.
+ */
+static inline XmlTableBuilderData *
+GetXmlTableBuilderPrivateData(TableFuncScanState *state, const char *fname)
+{
+	XmlTableBuilderData *result;
+
+	if (!IsA(state, TableFuncScanState))
+		elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+	result = (XmlTableBuilderData *) state->opaque;
+	if (result->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+
+	return result;
+}
+#endif
+
+/*
+ * XmlTableInitBuilder
+ *		Fill in TableFuncScanState for XmlTable builder. Initialize XML parser.
+ */
+static void
+XmlTableInitBuilder(TableFuncScanState *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 */
+}
+
+/*
+ * XmlTableSetDoc
+ *		Install the input document
+ */
+static void
+XmlTableSetDoc(TableFuncScanState *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 = pg_xmlCharStrndup(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(TableFuncScanState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	if (name == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("DEFAULT namespace is not supported")));
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetNamespace");
+
+	if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+						   pg_xmlCharStrndup(name, strlen(name)),
+						   pg_xmlCharStrndup(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowFilter
+ *		Install the row-filter Xpath expression.
+ */
+static void
+XmlTableSetRowFilter(TableFuncScanState *state, char *path)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	xmlChar    *xstr;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetRowFilter");
+
+	if (*path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("row path filter must not be empty string")));
+
+	xstr = pg_xmlCharStrndup(path, strlen(path));
+
+	xtCxt->xpathcomp = xmlXPathCompile(xstr);
+	if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetColumnFilter
+ *		Install the column-filter Xpath expression, for the given column.
+ *
+ * The path can be NULL, when the only one result column is implicit. XXX fix
+ */
+static void
+XmlTableSetColumnFilter(TableFuncScanState *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 = pg_xmlCharStrndup(path, strlen(path));
+
+		xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+		if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+						"invalid XPath expression");
+	}
+	else
+	{
+		Assert(!state->evalcols || colnum == state->ordinalitycol);
+
+		xtCxt->xpathscomp[colnum] = NULL;
+	}
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableFetchRow
+ *		Prepare the next "current" tuple for upcoming GetValue calls.
+ *		Returns FALSE if the row-filter expression returned no more rows.
+ */
+static bool
+XmlTableFetchRow(TableFuncScanState *state)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableFetchRow");
+
+	/*
+	 * XmlTable returns table - set of composite values. The error context, is
+	 * used for producement more values, between two calls, there can be
+	 * created and used another libxml2 error context. It is libxml2 global
+	 * value, so it should be refreshed any time before any libxml2 usage,
+	 * that is finished by returning some value.
+	 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		xtCxt->row_count = 0;
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->row_count++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+}
+
+/*
+ * XmlTableGetValue
+ *		Return the value for column number 'colnum' for the current row.  If
+ *		column -1 is requested, return representation of the whole row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
+ */
+static Datum
+XmlTableGetValue(TableFuncScanState *state, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+	volatile xmlXPathObjectPtr xpathobj = NULL;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
+
+	Assert(xtCxt->xpathobj &&
+		   xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	*isnull = false;
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
+
+	if (colnum == -1)
+	{
+		/*
+		 * Use whole row, when user doesn't specify a column. The target type
+		 * must be XMLOID.
+		 */
+		Assert(state->resultSlot->tts_tupleDescriptor->attrs[0]->atttypid == XMLOID);
+
+		result = PointerGetDatum(xml_xmlnodetoxmltype(cur, xtCxt->xmlerrcxt));
+
+		return result;
+	}
+
+	Assert(xtCxt->xpathscomp[colnum] != NULL);
+
+	/* fast leaving */
+	if (cur->type != XML_ELEMENT_NODE)
+		elog(ERROR, "unexpected xmlNode type");
+
+	PG_TRY();
+	{
+		Form_pg_attribute attr;
+
+		attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum];
+
+		/* Set current node as entry point for XPath evaluation */
+		xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+		/* Evaluate column path */
+		xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+		if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		/*
+		 * There are four possible cases, depending on the number of nodes
+		 * returned by the XPath expression and the type of the target column:
+		 * a) XPath returns no nodes.  b) One node is returned, and column is
+		 * of type XML.  c) One node, column type other than XML.  d) Multiple
+		 * nodes are returned.
+		 */
+		if (xpathobj->type == XPATH_NODESET)
+		{
+			int			count = 0;
+			Oid			targettypid = attr->atttypid;
+
+			if (xpathobj->nodesetval != NULL)
+				count = xpathobj->nodesetval->nodeNr;
+
+			if (xpathobj->nodesetval == NULL || count == 0)
+			{
+				*isnull = true;
+			}
+			else if (count == 1 && targettypid == XMLOID)
+			{
+				text	   *textstr;
+
+				/* simple case, result is one value */
+				textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
+											   xtCxt->xmlerrcxt);
+				cstr = text_to_cstring(textstr);
+			}
+			else if (count == 1)
+			{
+				xmlChar    *str;
+
+				str = xmlNodeListGetString(xtCxt->doc,
+						   xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+										   1);
+
+				if (str != NULL)
+				{
+					PG_TRY();
+					{
+						cstr = pstrdup((char *) str);
+					}
+					PG_CATCH();
+					{
+						xmlFree(str);
+						PG_RE_THROW();
+					}
+					PG_END_TRY();
+					xmlFree(str);
+				}
+				else
+				{
+					/* Return empty string when tag is empty */
+					cstr = "";
+				}
+			}
+			else
+			{
+				StringInfoData str;
+				int			i;
+
+				Assert(count > 1);
+
+				/*
+				 * When evaluating the XPath expression returns multiple
+				 * nodes, the result is the concatenation of them all. The
+				 * target type must be XML.
+				 */
+				if (targettypid != XMLOID)
+					ereport(ERROR,
+							(errcode(ERRCODE_CARDINALITY_VIOLATION),
+							 errmsg("more than one value returned by column XPath expression")));
+
+				/* Concatenate serialized values */
+				initStringInfo(&str);
+				for (i = 0; i < count; i++)
+				{
+					appendStringInfoText(&str,
+					   xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
+											xtCxt->xmlerrcxt));
+				}
+				cstr = str.data;
+			}
+		}
+		else if (xpathobj->type == XPATH_STRING)
+		{
+			cstr = (char *) xpathobj->stringval;
+		}
+		else
+			elog(ERROR, "unexpected XPath object type %u", xpathobj->type);
+
+		/*
+		 * By here, either cstr contains the result value, or the isnull flag
+		 * has been set.
+		 */
+		Assert(cstr || *isnull);
+
+		if (!*isnull)
+			result = InputFunctionCall(&state->in_functions[colnum],
+									   cstr,
+									   state->typioparams[colnum],
+									   attr->atttypmod);
+	}
+	PG_CATCH();
+	{
+		if (xpathobj != NULL)
+			xmlXPathFreeObject(xpathobj);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xmlXPathFreeObject(xpathobj);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * Release all libxml2 memory and related resources
+ */
+static void
+XmlTableDestroyBuilder(TableFuncScanState *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/include/executor/nodeTableFuncscan.h b/src/include/executor/nodeTableFuncscan.h
new file mode 100644
index 0000000000..529c929993
--- /dev/null
+++ b/src/include/executor/nodeTableFuncscan.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeTableFuncscan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeTableFuncscan.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODETABLEFUNCSCAN_H
+#define NODETABLEFUNCSCAN_H
+
+#include "nodes/execnodes.h"
+
+extern TableFuncScanState *ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecTableFuncScan(TableFuncScanState *node);
+extern void ExecEndTableFuncScan(TableFuncScanState *node);
+extern void ExecReScanTableFuncScan(TableFuncScanState *node);
+
+#endif   /* NODETABLEFUNCSCAN_H */
diff --git a/src/include/executor/tablefunc.h b/src/include/executor/tablefunc.h
new file mode 100644
index 0000000000..cacfeabe11
--- /dev/null
+++ b/src/include/executor/tablefunc.h
@@ -0,0 +1,65 @@
+/*-------------------------------------------------------------------------
+ *
+ * tablefunc.h
+ *				interface for TableFunc builder
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/tablefunc.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TABLEFUNC_H
+#define TABLEFUNC_H
+
+#include "nodes/execnodes.h"
+
+/*
+ * TableFuncRoutine holds function pointers used for generating content of
+ * table-producer functions, such as XMLTABLE.
+ *
+ * InitBuilder initialize table builder private objects.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed
+ * from executor state.
+ *
+ * SetDoc is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * SetNamespace is called to pass namespace declarations from the table
+ * expression.  This function may be NULL if namespaces are not supported by
+ * the table builder.  Namespaces must be given before setting the row and
+ * column filters.  If the name is given as NULL, the entry shall be for the
+ * default namespace.
+ *
+ * SetRowFilter is called do define the row-generating filter, which shall be
+ * used to extract each row from the input document.
+ *
+ * SetColumnFilter is called once for each column, to define the column-
+ * generating filter for the given column.
+ *
+ * FetchRow shall be called repeatedly until it returns that no more rows are
+ * found in the document.  On each invocation it shall set state in the table
+ * builder context such that each subsequent GetValue call returns the 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 TableFuncRoutine
+{
+	void		(*InitBuilder) (TableFuncScanState *state);
+	void		(*SetDoc) (TableFuncScanState *state, Datum value);
+	void		(*SetNamespace) (TableFuncScanState *state, char *name,
+											 char *uri);
+	void		(*SetRowFilter) (TableFuncScanState *state, char *path);
+	void		(*SetColumnFilter) (TableFuncScanState *state, char *path,
+												int colnum);
+	bool		(*FetchRow) (TableFuncScanState *state);
+	Datum		(*GetValue) (TableFuncScanState *state, int colnum, bool *isnull);
+	void		(*DestroyBuilder) (TableFuncScanState *state);
+} TableFuncRoutine;
+
+#endif   /* TABLEFUNC_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9f41babf35..7461b8d8d0 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1582,6 +1582,35 @@ typedef struct ValuesScanState
 } ValuesScanState;
 
 /* ----------------
+ *		TableFuncScanState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableFuncScanState
+{
+	ScanState	ss;				/* its first field is NodeTag */
+	ExprState  *docexpr;		/* state for document expression */
+	ExprState  *rowexpr;		/* state for row-generating expression */
+	List	   *colexprs;		/* state for column-generating expression */
+	List	   *coldefexprs;	/* state for column default expressions */
+	List	   *ns_names;		/* list of str nodes with namespace names */
+	List	   *ns_uris;		/* list of states of namespace uri exprs */
+	Bitmapset  *notnulls;		/* nullability flag for each output column */
+	bool		evalcols;		/* true, when column was not defined by user */
+	int			ordinalitycol;	/* number of ordinality column or -1 */
+	void	   *opaque;			/* table builder private space */
+	const struct TableFuncRoutine *routine;		/* table builder methods */
+	FmgrInfo   *in_functions;	/* input function for each column */
+	Oid		   *typioparams;	/* typioparam for each column */
+	int			rownum;			/* row number to be output next */
+	MemoryContext buildercxt;	/* memory context used by builder */
+	MemoryContext perValueCxt;	/* short life context for a value evaluation */
+	Tuplestorestate *tupstore;	/* output tuple store */
+	TupleTableSlot *resultSlot; /* output tuple table slot */
+} TableFuncScanState;
+
+/* ----------------
  *	 CteScanState information
  *
  *		CteScan nodes are used to scan a CommonTableExpr query.
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 95dd8baadd..14ee98b78b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -61,6 +61,7 @@ typedef enum NodeTag
 	T_SubqueryScan,
 	T_FunctionScan,
 	T_ValuesScan,
+	T_TableFuncScan,
 	T_CteScan,
 	T_WorkTableScan,
 	T_ForeignScan,
@@ -109,6 +110,7 @@ typedef enum NodeTag
 	T_TidScanState,
 	T_SubqueryScanState,
 	T_FunctionScanState,
+	T_TableFuncScanState,
 	T_ValuesScanState,
 	T_CteScanState,
 	T_WorkTableScanState,
@@ -135,6 +137,7 @@ typedef enum NodeTag
 	 */
 	T_Alias,
 	T_RangeVar,
+	T_TableFunc,
 	T_Expr,
 	T_Var,
 	T_Const,
@@ -438,6 +441,8 @@ typedef enum NodeTag
 	T_RangeSubselect,
 	T_RangeFunction,
 	T_RangeTableSample,
+	T_RangeTableFunc,
+	T_RangeTableFuncCol,
 	T_TypeName,
 	T_ColumnDef,
 	T_IndexElem,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5afc3ebea0..e7f4205c00 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -575,6 +575,40 @@ typedef struct RangeTableSample
 } RangeTableSample;
 
 /*
+ * RangeTableFunc - a raw form of pseudo functions like XMLTABLE
+ */
+typedef struct RangeTableFunc
+{
+	NodeTag		type;
+	bool		lateral;		/* does it have LATERAL prefix? */
+	Node	   *docexpr;
+	Node	   *rowexpr;
+	List	   *namespaces;		/* list of namespaces */
+	List	   *columns;		/* list of columns (TableFuncCol) */
+	Alias	   *alias;			/* table alias & optional column aliases */
+	int			location;		/* token location, or -1 if unknown */
+} RangeTableFunc;
+
+/*
+ * RangeTableFuncCol - one column in a RangeTableFunc 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 RangeTableFuncCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	TypeName   *typeName;		/* type of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *colexpr;		/* column filter expression */
+	Node	   *coldefexpr;		/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} RangeTableFuncCol;
+
+/*
  * ColumnDef - column definition (used in various creates)
  *
  * If the column has a default value, we may have the value expression
@@ -871,6 +905,7 @@ typedef enum RTEKind
 	RTE_SUBQUERY,				/* subquery in FROM */
 	RTE_JOIN,					/* join */
 	RTE_FUNCTION,				/* function in FROM */
+	RTE_TABLEFUNC,				/* TableFunc(.., column list) */
 	RTE_VALUES,					/* VALUES (<exprlist>), (<exprlist>), ... */
 	RTE_CTE						/* common table expr (WITH list element) */
 } RTEKind;
@@ -932,6 +967,11 @@ typedef struct RangeTblEntry
 	bool		funcordinality; /* is this called WITH ORDINALITY? */
 
 	/*
+	 * Fields valid for a TableFunc RTE (else NIL/zero)
+	 */
+	TableFunc	   *tablefunc;
+
+	/*
 	 * Fields valid for a values RTE (else NIL):
 	 */
 	List	   *values_lists;	/* list of expression lists */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index f72f7a8978..c6bcc14411 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -496,6 +496,16 @@ typedef struct ValuesScan
 } ValuesScan;
 
 /* ----------------
+ *		TableFunc scan node
+ * ----------------
+ */
+typedef struct TableFuncScan
+{
+	Scan		scan;
+	TableFunc	   *tablefunc;		/* table function node */
+} TableFuncScan;
+
+/* ----------------
  *		CteScan node
  * ----------------
  */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 235bc75096..e7e568ae42 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"
 
 
@@ -73,6 +74,29 @@ typedef struct RangeVar
 } RangeVar;
 
 /*
+ * TableFunc - node for a table function, such as XMLTABLE.
+ */
+typedef struct TableFunc
+{
+	NodeTag		type;
+	List	   *ns_uris;		/* list of namespace uri */
+	List	   *ns_names;		/* list of namespace names */
+	Node	   *docexpr;		/* input document expression */
+	Node	   *rowexpr;		/* row filter expression */
+	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;		/* nullability flag for each output column */
+	int			ordinalitycol;	/* ordinality column, -1 if not used */
+	bool		evalcols;		/* true, when columns should be evaluated */
+	int			location;		/* token location, or -1 if unknown */
+} TableFunc;
+
+/*
  * IntoClause - target information for SELECT INTO, CREATE TABLE AS, and
  * CREATE MATERIALIZED VIEW
  *
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 72200fa531..d02aba1559 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -89,8 +89,12 @@ extern void cost_subqueryscan(SubqueryScanPath *path, PlannerInfo *root,
 				  RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_functionscan(Path *path, PlannerInfo *root,
 				  RelOptInfo *baserel, ParamPathInfo *param_info);
+extern void cost_tableexprscan(Path *path, PlannerInfo *root,
+				  RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_valuesscan(Path *path, PlannerInfo *root,
 				RelOptInfo *baserel, ParamPathInfo *param_info);
+extern void cost_tablefuncscan(Path *path, PlannerInfo *root,
+				RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_ctescan(Path *path, PlannerInfo *root,
 			 RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_recursive_union(Path *runion, Path *nrterm, Path *rterm);
@@ -181,6 +185,7 @@ extern void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel,
 					   double cte_rows);
+extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
 extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 53cad247dc..4588680f4f 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -82,8 +82,12 @@ extern SubqueryScanPath *create_subqueryscan_path(PlannerInfo *root,
 						 List *pathkeys, Relids required_outer);
 extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
 						 List *pathkeys, Relids required_outer);
+extern Path *create_tablexprscan_path(PlannerInfo *root, RelOptInfo *rel,
+						 List *pathkeys, Relids required_outer);
 extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel,
 					   Relids required_outer);
+extern Path *create_tablefuncscan_path(PlannerInfo *root, RelOptInfo *rel,
+						 List *pathkeys, Relids required_outer);
 extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel,
 					Relids required_outer);
 extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 985d6505ec..28c4dab258 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)
@@ -446,10 +447,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index b50992cfd9..3eed81966d 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/parser/parse_relation.h b/src/include/parser/parse_relation.h
index cfb2e5b88c..dcb6194b1a 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -91,6 +91,11 @@ extern RangeTblEntry *addRangeTableEntryForValues(ParseState *pstate,
 							Alias *alias,
 							bool lateral,
 							bool inFromCl);
+extern RangeTblEntry *addRangeTableEntryForTableFunc(ParseState *pstate,
+							TableFunc *tf,
+							Alias *alias,
+							bool lateral,
+							bool inFromCl);
 extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,
 						  List *colnames,
 						  JoinType jointype,
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index cc1fc390e8..e570b71c04 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/tablefunc.h"
 
 typedef struct varlena xmltype;
 
@@ -76,4 +77,6 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern const TableFuncRoutine XmlTableRoutine;
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119f1e..84ccd33a10 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,524 @@ 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)
+
+-- XMLTABLE with columns
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+CREATE VIEW xmltableview1 AS SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+    "xmltable"._id,
+    "xmltable".country_name,
+    "xmltable".country_id,
+    "xmltable".region_id,
+    "xmltable".size,
+    "xmltable".unit,
+    "xmltable".premier_name
+   FROM ( SELECT xmldata.data
+           FROM xmldata) x,
+    LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+             QUERY PLAN             
+------------------------------------
+ Nested Loop
+   ->  Seq Scan on xmldata
+   ->  TableFunc 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ a  
+----
+ 10
+(1 row)
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+SELECT * FROM xmltableview2;
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  DEFAULT namespace is not supported
+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.* 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ Japan        |         3
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+                                                                                    QUERY PLAN                                                                                    
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+         Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+         Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia      | AU         |         3 |      |      | not specified
+  2 |   2 | China          | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong       | HK         |         3 |      |      | not specified
+  4 |   4 | India          | IN         |         3 |      |      | not specified
+  5 |   5 | Japan          | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore      | SG         |         3 |  791 | km   | not specified
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+ 20 |   1 | Egypt          | EG         |         1 |      |      | not specified
+ 21 |   2 | Sudan          | SD         |         1 |      |      | not specified
+(11 rows)
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+         Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ERROR:  null is not allowed in column "size"
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a  
+----
+ 11
+ 12
+ 13
+ 14
+(4 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d7027030c3..556c64b73a 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,491 @@ 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.
+-- XMLTABLE with columns
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+CREATE VIEW xmltableview1 AS SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+    "xmltable"._id,
+    "xmltable".country_name,
+    "xmltable".country_id,
+    "xmltable".region_id,
+    "xmltable".size,
+    "xmltable".unit,
+    "xmltable".premier_name
+   FROM ( SELECT xmldata.data
+           FROM xmldata) x,
+    LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+             QUERY PLAN             
+------------------------------------
+ Nested Loop
+   ->  Seq Scan on xmldata
+   ->  TableFunc 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltableview2;
+ERROR:  relation "xmltableview2" does not exist
+LINE 1: SELECT * FROM xmltableview2;
+                      ^
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+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.* 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+(0 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+                                                                                    QUERY PLAN                                                                                    
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+         Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+         Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmldata VALUES('<ROWS>
+                                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmldata VALUES('<ROWS>
+                                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+         Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a 
+---
+(0 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a 
+---
+(0 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a 
+---
+(0 rows)
+
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5daf..fe7a25da87 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,524 @@ 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)
+
+-- XMLTABLE with columns
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+CREATE VIEW xmltableview1 AS SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+    "xmltable"._id,
+    "xmltable".country_name,
+    "xmltable".country_id,
+    "xmltable".region_id,
+    "xmltable".size,
+    "xmltable".unit,
+    "xmltable".premier_name
+   FROM ( SELECT xmldata.data
+           FROM xmldata) x,
+    LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+             QUERY PLAN             
+------------------------------------
+ Nested Loop
+   ->  Seq Scan on xmldata
+   ->  TableFunc 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ a  
+----
+ 10
+(1 row)
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+SELECT * FROM xmltableview2;
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  DEFAULT namespace is not supported
+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.* 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ Japan        |         3
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+                                                                                    QUERY PLAN                                                                                    
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+         Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+         Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia      | AU         |         3 |      |      | not specified
+  2 |   2 | China          | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong       | HK         |         3 |      |      | not specified
+  4 |   4 | India          | IN         |         3 |      |      | not specified
+  5 |   5 | Japan          | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore      | SG         |         3 |  791 | km   | not specified
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+ 20 |   1 | Egypt          | EG         |         1 |      |      | not specified
+ 21 |   2 | Sudan          | SD         |         1 |      |      | not specified
+(11 rows)
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+         Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ERROR:  null is not allowed in column "size"
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a  
+----
+ 11
+ 12
+ 13
+ 14
+(4 rows)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30067..af635b945c 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,296 @@ 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>');
+
+-- 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.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+
+CREATE TABLE xmltest2(x xml, _path text);
+
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9f876ae264..990b2f7285 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1,3 +1,15 @@
+XmlTableBuilderData
+TableFuncRoutine
+XmlTableContext
+TableFuncBuilder
+TableFuncState
+TableFuncRawCol
+TableFunc
+TableFuncColumn
+SQLValueFunction
+max_parallel_hazard_context
+TriggerTransition
+SQLValueFunctionOp
 ABITVEC
 ACCESS_ALLOWED_ACE
 ACL_SIZE_INFORMATION
#141Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#140)
1 attachment(s)
Re: patch: function xmltable

Hi

2017-02-16 6:38 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

please see attached patch

* enhanced regress tests
* clean memory context work

new update

fix a bug in string compare
fix some typo and obsolete comments

Regards

some minor but interesting fix.

I found so some xml values imported via recv function can have
inconsistency between header encoding and used encoding. Internally the
header encoding is removed - see xml_out function.

So now, when I have to prepare data for libxml2, I don't do direct cast,
but I use xml_out_internal instead. Maybe this technique should be used
elsewhere? Same issue I see on xpath function.

Solved issue is not too often probably - the some different encoding than
utf8 should be used in XML document and XML document should be loaded with
recv function.

Regards

Pavel

Show quoted text

Pavel

Regards

Pavel

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-44.patchtext/x-patch; charset=US-ASCII; name=xmltable-44.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d7738b18b7..4ceb8fe5d8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10325,50 +10325,53 @@ SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuf
   <sect2 id="functions-xml-processing">
    <title>Processing XML</title>
 
-   <indexterm>
-    <primary>XPath</primary>
-   </indexterm>
-
    <para>
     To process values of data type <type>xml</type>, PostgreSQL offers
-    the functions <function>xpath</function> and
+    the functions <function>xpath</function>,
     <function>xpath_exists</function>, which evaluate XPath 1.0
-    expressions.
+    expressions, and the <function>XMLTABLE</function> predicate.
    </para>
 
+   <sect3 id="functions-xml-processing-xpath">
+    <title><literal>xpath</literal></title>
+
+    <indexterm>
+     <primary>XPath</primary>
+    </indexterm>
+
 <synopsis>
 <function>xpath</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath</function> evaluates the XPath
-    expression <replaceable>xpath</replaceable> (a <type>text</> value)
-    against the XML value
-    <replaceable>xml</replaceable>.  It returns an array of XML values
-    corresponding to the node set produced by the XPath expression.
-    If the XPath expression returns a scalar value rather than a node set,
-    a single-element array is returned.
-   </para>
+    <para>
+     The function <function>xpath</function> evaluates the XPath
+     expression <replaceable>xpath</replaceable> (a <type>text</> value)
+     against the XML value
+     <replaceable>xml</replaceable>.  It returns an array of XML values
+     corresponding to the node set produced by the XPath expression.
+     If the XPath expression returns a scalar value rather than a node set,
+     a single-element array is returned.
+    </para>
 
-  <para>
-    The second argument must be a well formed XML document. In particular,
-    it must have a single root node element.
-  </para>
+    <para>
+     The second argument must be a well formed XML document. In particular,
+     it must have a single root node element.
+    </para>
 
-   <para>
-    The optional third argument of the function is an array of namespace
-    mappings.  This array should be a two-dimensional <type>text</> array with
-    the length of the second axis being equal to 2 (i.e., it should be an
-    array of arrays, each of which consists of exactly 2 elements).
-    The first element of each array entry is the namespace name (alias), the
-    second the namespace URI. It is not required that aliases provided in
-    this array be the same as those being used in the XML document itself (in
-    other words, both in the XML document and in the <function>xpath</function>
-    function context, aliases are <emphasis>local</>).
-   </para>
+    <para>
+     The optional third argument of the function is an array of namespace
+     mappings.  This array should be a two-dimensional <type>text</> array with
+     the length of the second axis being equal to 2 (i.e., it should be an
+     array of arrays, each of which consists of exactly 2 elements).
+     The first element of each array entry is the namespace name (alias), the
+     second the namespace URI. It is not required that aliases provided in
+     this array be the same as those being used in the XML document itself (in
+     other words, both in the XML document and in the <function>xpath</function>
+     function context, aliases are <emphasis>local</>).
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
              ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10378,10 +10381,10 @@ SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
 
-   <para>
-    To deal with default (anonymous) namespaces, do something like this:
+    <para>
+     To deal with default (anonymous) namespaces, do something like this:
 <screen><![CDATA[
 SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
              ARRAY[ARRAY['mydefns', 'http://example.com']]);
@@ -10391,27 +10394,31 @@ SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a
  {test}
 (1 row)
 ]]></screen>
-   </para>
+    </para>
+   </sect3>
 
-   <indexterm>
-    <primary>xpath_exists</primary>
-   </indexterm>
+   <sect3 id="functions-xml-processing-xpath-exists">
+    <title><literal>xpath_exists</literal></title>
+
+    <indexterm>
+     <primary>xpath_exists</primary>
+    </indexterm>
 
 <synopsis>
 <function>xpath_exists</function>(<replaceable>xpath</replaceable>, <replaceable>xml</replaceable> <optional>, <replaceable>nsarray</replaceable></optional>)
 </synopsis>
 
-   <para>
-    The function <function>xpath_exists</function> is a specialized form
-    of the <function>xpath</function> function.  Instead of returning the
-    individual XML values that satisfy the XPath, this function returns a
-    Boolean indicating whether the query was satisfied or not.  This
-    function is equivalent to the standard <literal>XMLEXISTS</> predicate,
-    except that it also offers support for a namespace mapping argument.
-   </para>
+    <para>
+     The function <function>xpath_exists</function> is a specialized form
+     of the <function>xpath</function> function.  Instead of returning the
+     individual XML values that satisfy the XPath, this function returns a
+     Boolean indicating whether the query was satisfied or not.  This
+     function is equivalent to the standard <literal>XMLEXISTS</> predicate,
+     except that it also offers support for a namespace mapping argument.
+    </para>
 
-   <para>
-    Example:
+    <para>
+     Example:
 <screen><![CDATA[
 SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
                      ARRAY[ARRAY['my', 'http://example.com']]);
@@ -10421,7 +10428,278 @@ SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</m
  t
 (1 row)
 ]]></screen>
+    </para>
+   </sect3>
+
+   <sect3 id="functions-xml-processing-xmltable">
+    <title><literal>xmltable</literal></title>
+
+    <indexterm>
+     <primary>xmltable</primary>
+    </indexterm>
+
+<synopsis>
+<function>xmltable</function>(
+    <optional>xmlnamespaces(<replaceable>namespace uri</replaceable> AS <replaceable>namespace name</replaceable><optional>, ...</optional>)</optional>
+    <replaceable>rowexpr</replaceable> PASSING <optional>BY REF</optional> <replaceable>xml</replaceable> <optional>BY REF</optional>
+    <optional>COLUMNS <replaceable>name</replaceable> <replaceable>type</replaceable>
+        <optional>PATH <replaceable>columnexpr</replaceable></optional>
+        <optional>DEFAULT <replaceable>expr</replaceable></optional>
+        <optional>NOT NULL | NULL</optional> <optional>, ...</optional></optional>
+)
+</synopsis>
+
+    <para>
+     The <function>xmltable</function> produces a table based on the
+     passed XML value, an xpath filter to extract rows, and an
+     optional set of column definitions.
+    </para>
+
+    <para>
+     For XML document:
+  <screen><![CDATA[
+<ROWS>
+  <ROW id="1">
+    <COUNTRY_ID>AU</COUNTRY_ID>
+    <COUNTRY_NAME>Australia</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="2">
+    <COUNTRY_ID>CN</COUNTRY_ID>
+    <COUNTRY_NAME>China</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="3">
+    <COUNTRY_ID>HK</COUNTRY_ID>
+    <COUNTRY_NAME>HongKong</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="4">
+    <COUNTRY_ID>IN</COUNTRY_ID>
+    <COUNTRY_NAME>India</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID>
+  </ROW>
+  <ROW id="5">
+    <COUNTRY_ID>JP</COUNTRY_ID>
+    <COUNTRY_NAME>Japan</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
+  </ROW>
+  <ROW id="6">
+    <COUNTRY_ID>SG</COUNTRY_ID>
+    <COUNTRY_NAME>Singapore</COUNTRY_NAME>
+    <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE>
+  </ROW>
+</ROWS>
+]]></screen>
+
+     the following query produces the result:
+
+<screen><![CDATA[
+SELECT  xmltable.*
+  FROM (SELECT data FROM xmldata) x,
+        LATERAL xmltable('//ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  country_name text PATH 'COUNTRY_NAME',
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE[@unit = "km"]/text()',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+ id | country_name | country_id | region_id | size | unit | premier_name  
+----+--------------+------------+-----------+------+------+---------------
+  1 | Australia    | AU         |         3 |      |      | not specified
+  2 | China        | CN         |         3 |      |      | not specified
+  3 | HongKong     | HK         |         3 |      |      | not specified
+  4 | India        | IN         |         3 |      |      | not specified
+  5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 | Singapore    | SG         |         3 |  791 | km   | not specified
+]]></screen>
+    </para>
+
+    <para>
+     The optional <literal>xmlnamespaces</> clause is a comma-separated list of
+     namespaces of the form <replaceable>namespace-URI</> <literal>AS</>
+     <replaceable>namespace-name</>. It specifies the XML namespaces used in
+     the document and their aliases. The default namespace is not supported
+     yet.
+
+<screen><![CDATA[
+SELECT *
+  FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS ns)
+                '/ns:rows/ns:row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                COLUMNS a int PATH 'a');
+
+ a  
+----
+ 10
+]]></screen>
    </para>
+
+    <para>
+     The required <replaceable>rowexpr</> argument is an xpath
+     expression that is evaluated against the supplied XML document to
+     obtain an ordered sequence of XML nodes. This sequence is what
+     <function>xmltable</> transforms into output rows.
+    </para>
+
+    <note>
+     <para>
+      The SQL/XML standard specifies the the
+      <function>xmltable</function> construct should take an XQuery
+      expression as the first argument, but PostgreSQL currently only
+      supports XPath, which is a subset of XQuery.
+     </para>
+    </note>
+
+    <para>
+     The <literal>PASSING</> <replaceable>xml</> clause provides the
+     XML document to operate on.
+     The <literal>BY REF</literal> clauses have no effect in
+     PostgreSQL, but are allowed for SQL conformance and compatibility
+     with other implementations. Per the SQL/XML standard, the first
+     <literal>BY REF</literal> is required, the second is optional.
+     Passing <literal>BY VALUE</> is not supported. Multiple
+     comma-separated terms in the PASSING clause are not supported.
+     <literal>AS</> aliases are not supported. The argument must be
+     a well-formed XML document; fragments/forests are not accepted.
+    </para>
+
+    <para>
+     The optional <literal>COLUMNS</literal> clause specifies the list
+     of colums in the generated table. If the whole
+     <literal>COLUMNS</> list is omitted, then the result set is a
+     single column where each row is a field of <literal>xml</> type
+     containing the data matched by the <replaceable>rowexpr</>.
+     Otherwise each entry describes a single column. See the syntax
+     summary above for the format. The column name and type are
+     required; the path and not-null specifier are optional.
+ <screen><![CDATA[
+SELECT *
+  FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y' AS ns)
+                '/ns:rows/ns:row' PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>');
+
+           xmltable           
+------------------------------
+ <a xmlns="http://x.y">10</a>
+]]></screen>
+    </para>
+
+    <note>
+     <para>
+      Column names and types are identifiers so unlike normal
+      function arguments they may not be specified as bind parameters,
+      column references, or expressions. The path and default are
+      ordinary values.
+     </para>
+    </note>
+
+    <para>
+     The <literal>PATH</> for a column is an xpath expression that is
+     evaluated for each row, relative to the result of the
+     <replaceable>rowexpr</>, to find the value of the column.
+     If no <literal>PATH</> is given then the column name is used as an
+     implicit path.
+    </para>
+
+    <para>
+     The text body of the XML matched by the <literal>PATH</> expression is
+     used as the column value. Multiple <literal>text()</literal> nodes within
+     an element are concatenated in order. Any child elements, processing
+     instructions, and comments are ignored, but the text contents of child
+     elements are concatenated to the result:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--x-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+element
+-----------------
+ a1aa2a   bbbbcccc
+ (1 row)
+]]></programlisting>
+     Note that the whitespace-only <literal>text()</> node between two non-text
+     elements is preserved, and that leading whitespace on a <literal>text()</>
+     node is not flattened.
+    </para>
+    
+    <para>
+     A <literal>PATH</> expression that matches multiple top-level elements
+     results in an error:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>1</element><element>2</element></root>' COLUMNS element text);
+ERROR:  more than one value returned by column XPath expression
+]]></programlisting>
+     This applies to <literal>text()</> nodes too, so avoid using explicit text nodes
+     in your path expressions unless this behaviour is desired:
+     <programlisting><![CDATA[
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a</element></root>' COLUMNS element text PATH 'element/text()');
+ERROR:  more than one value returned by column XPath expression
+]]></programlisting>
+     If the <literal>PATH</> matches an empty tag the result is an empty string
+     (not <literal>NULL</>).  Any <literal>xsi:nil</> attributes are ignored.
+    </para>
+
+    <para>
+     The five predefined XML entities <literal>&apos;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/commands/explain.c b/src/backend/commands/explain.c
index c9e0a3e42d..328ebb6928 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -781,6 +781,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 		case T_TidScan:
 		case T_SubqueryScan:
 		case T_FunctionScan:
+		case T_TableFuncScan:
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_WorkTableScan:
@@ -926,6 +927,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_FunctionScan:
 			pname = sname = "Function Scan";
 			break;
+		case T_TableFuncScan:
+			pname = sname = "TableFunc Scan";
+			break;
 		case T_ValuesScan:
 			pname = sname = "Values Scan";
 			break;
@@ -1103,6 +1107,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_TidScan:
 		case T_SubqueryScan:
 		case T_FunctionScan:
+		case T_TableFuncScan:
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_WorkTableScan:
@@ -1416,6 +1421,20 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			break;
+		case T_TableFuncScan:
+			if (es->verbose)
+			{
+				TableFunc *tablefunc = ((TableFuncScan *) plan)->tablefunc;
+
+				show_expression((Node *) tablefunc,
+								"Table Function Call", planstate, ancestors,
+								es->verbose, es);
+			}
+			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+			if (plan->qual)
+				show_instrumentation_count("Rows Removed by Filter", 1,
+										   planstate, es);
+			break;
 		case T_TidScan:
 			{
 				/*
@@ -2593,6 +2612,11 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 				objecttag = "Function Name";
 			}
 			break;
+		case T_TableFuncScan:
+			Assert(rte->rtekind == RTE_TABLEFUNC);
+			objectname = "xmltable";
+			objecttag = "Table Function Name";
+			break;
 		case T_ValuesScan:
 			Assert(rte->rtekind == RTE_VALUES);
 			break;
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 2a2b7eb9bd..a9893c2b22 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -26,6 +26,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \
        nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
        nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
        nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
-       nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o
+       nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \
+       nodeTableFuncscan.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index d3802079f5..5d59f95a91 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -48,6 +48,7 @@
 #include "executor/nodeSort.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
+#include "executor/nodeTableFuncscan.h"
 #include "executor/nodeTidscan.h"
 #include "executor/nodeUnique.h"
 #include "executor/nodeValuesscan.h"
@@ -198,6 +199,10 @@ ExecReScan(PlanState *node)
 			ExecReScanFunctionScan((FunctionScanState *) node);
 			break;
 
+		case T_TableFuncScanState:
+			ExecReScanTableFuncScan((TableFuncScanState *) node);
+			break;
+
 		case T_ValuesScanState:
 			ExecReScanValuesScan((ValuesScanState *) node);
 			break;
@@ -564,6 +569,7 @@ ExecMaterializesOutput(NodeTag plantype)
 	{
 		case T_Material:
 		case T_FunctionScan:
+		case T_TableFuncScan:
 		case T_CteScan:
 		case T_WorkTableScan:
 		case T_Sort:
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 0dd95c6d17..651bbae381 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -110,6 +110,7 @@
 #include "executor/nodeSort.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
+#include "executor/nodeTableFuncscan.h"
 #include "executor/nodeTidscan.h"
 #include "executor/nodeUnique.h"
 #include "executor/nodeValuesscan.h"
@@ -239,6 +240,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 														estate, eflags);
 			break;
 
+		case T_TableFuncScan:
+			result = (PlanState *) ExecInitTableFuncScan((TableFuncScan *) node,
+														estate, eflags);
+			break;
+
 		case T_ValuesScan:
 			result = (PlanState *) ExecInitValuesScan((ValuesScan *) node,
 													  estate, eflags);
@@ -459,6 +465,10 @@ ExecProcNode(PlanState *node)
 			result = ExecFunctionScan((FunctionScanState *) node);
 			break;
 
+		case T_TableFuncScanState:
+			result = ExecTableFuncScan((TableFuncScanState *) node);
+			break;
+
 		case T_ValuesScanState:
 			result = ExecValuesScan((ValuesScanState *) node);
 			break;
@@ -715,6 +725,10 @@ ExecEndNode(PlanState *node)
 			ExecEndFunctionScan((FunctionScanState *) node);
 			break;
 
+		case T_TableFuncScanState:
+			ExecEndTableFuncScan((TableFuncScanState *) node);
+			break;
+
 		case T_ValuesScanState:
 			ExecEndValuesScan((ValuesScanState *) node);
 			break;
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
new file mode 100644
index 0000000000..2d83a0e2bd
--- /dev/null
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -0,0 +1,540 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeTableFuncscan.c
+ *	  Support routines for scanning RangeTableFunc (XMLTABLE like functions).
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/nodeTableFuncscan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * INTERFACE ROUTINES
+ *		ExecTableFuncscan		scans a function.
+ *		ExecFunctionNext		retrieve next tuple in sequential order.
+ *		ExecInitTableFuncscan	creates and initializes a TableFuncscan node.
+ *		ExecEndTableFuncscan		releases any storage allocated.
+ *		ExecReScanTableFuncscan	rescans the function
+ */
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "executor/nodeTableFuncscan.h"
+#include "executor/tablefunc.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/tuplestore.h"
+#include "utils/xml.h"
+
+static TupleTableSlot *TableFuncNext(TableFuncScanState *node);
+static bool TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot);
+
+static void FetchRows(TableFuncScanState *tstate, ExprContext *econtext);
+static void Initialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc);
+static bool FetchRow(TableFuncScanState *tstate, ExprContext *econtext);
+
+/* ----------------------------------------------------------------
+ *						Scan Support
+ * ----------------------------------------------------------------
+ */
+/* ----------------------------------------------------------------
+ *		TableFuncNext
+ *
+ *		This is a workhorse for ExecTableFuncscan
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+TableFuncNext(TableFuncScanState *node)
+{
+	TupleTableSlot *scanslot;
+
+	scanslot = node->ss.ss_ScanTupleSlot;
+
+	/*
+	 * If first time through, read all tuples from function and put them
+	 * in a tuplestore. Subsequent calls just fetch tuples from
+	 * tuplestore.
+	 */
+	if (node->tupstore == NULL)
+		FetchRows(node, node->ss.ps.ps_ExprContext);
+
+	/*
+	 * Get the next tuple from tuplestore.
+	 */
+	(void) tuplestore_gettupleslot(node->tupstore,
+								   true,
+								   false,
+								   scanslot);
+	return scanslot;
+}
+
+/*
+ * TableFuncRecheck -- access method routine to recheck a tuple in EvalPlanQual
+ */
+static bool
+TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot)
+{
+	/* nothing to check */
+	return true;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecTableFuncscan(node)
+ *
+ *		Scans the function sequentially and returns the next qualifying
+ *		tuple.
+ *		We call the ExecScan() routine and pass it the appropriate
+ *		access method functions.
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecTableFuncScan(TableFuncScanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) TableFuncNext,
+					(ExecScanRecheckMtd) TableFuncRecheck);
+}
+
+/* ----------------------------------------------------------------
+ *		ExecInitTableFuncscan
+ * ----------------------------------------------------------------
+ */
+TableFuncScanState *
+ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
+{
+	TableFuncScanState *scanstate;
+	TableFunc		   *tf = node->tablefunc;
+	TupleDesc			tupdesc;
+	int			i;
+
+	/* check for unsupported flags */
+	Assert(!(eflags & EXEC_FLAG_MARK));
+
+	/*
+	 * TableFuncscan should not have any children.
+	 */
+	Assert(outerPlan(node) == NULL);
+	Assert(innerPlan(node) == NULL);
+
+	/*
+	 * create new ScanState for node
+	 */
+	scanstate = makeNode(TableFuncScanState);
+	scanstate->ss.ps.plan = (Plan *) node;
+	scanstate->ss.ps.state = estate;
+
+	/*
+	 * Miscellaneous initialization
+	 *
+	 * create expression context for node
+	 */
+	ExecAssignExprContext(estate, &scanstate->ss.ps);
+
+	/*
+	 * initialize child expressions
+	 */
+	scanstate->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) node->scan.plan.targetlist,
+					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) node->scan.plan.qual,
+					 (PlanState *) scanstate);
+
+	/*
+	 * tuple table initialization
+	 */
+	ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
+	ExecInitScanTupleSlot(estate, &scanstate->ss);
+
+	/*
+	 * initialize source tuple type
+	 */
+	tupdesc = BuildDescFromLists(tf->colnames,
+								tf->coltypes,
+								tf->coltypmods,
+								tf->colcollations);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
+
+	/*
+	 * Initialize result tuple type and projection info.
+	 */
+	ExecAssignResultTypeFromTL(&scanstate->ss.ps);
+	ExecAssignScanProjectionInfo(&scanstate->ss);
+
+	scanstate->resultSlot = MakeSingleTupleTableSlot(tupdesc);
+
+	/* Only XmlTableBuilder is supported currently */
+	scanstate->routine = &XmlTableRoutine;
+
+	scanstate->buildercxt =
+		AllocSetContextCreate(CurrentMemoryContext,
+							  "TableFunc builder context",
+							  ALLOCSET_DEFAULT_SIZES);
+	scanstate->perValueCxt =
+		AllocSetContextCreate(scanstate->buildercxt,
+							  "TableFunc per value context",
+							  ALLOCSET_DEFAULT_SIZES);
+	scanstate->opaque = NULL;	/* initialized at runtime */
+
+	scanstate->ns_names = tf->ns_names;
+
+	scanstate->ns_uris = (List *)
+		ExecInitExpr((Expr *) tf->ns_uris,
+					 (PlanState *) scanstate);
+	scanstate->docexpr =
+		ExecInitExpr((Expr *) tf->docexpr,
+					 (PlanState *) scanstate);
+	scanstate->rowexpr =
+		ExecInitExpr((Expr *) tf->rowexpr,
+					 (PlanState *) scanstate);
+	scanstate->colexprs = (List *)
+		ExecInitExpr((Expr *) tf->colexprs,
+					 (PlanState *) scanstate);
+	scanstate->coldefexprs = (List *)
+		ExecInitExpr((Expr *) tf->coldefexprs,
+					 (PlanState *) scanstate);
+
+	/* these are allocated now and initialized later */
+	scanstate->notnulls = tf->notnulls;
+	scanstate->in_functions = palloc(sizeof(FmgrInfo) * tupdesc->natts);
+	scanstate->typioparams = palloc(sizeof(Oid) * tupdesc->natts);
+	scanstate->evalcols = tf->evalcols;
+	scanstate->ordinalitycol = tf->ordinalitycol;
+
+	/*
+	 * Fill in the necessary fmgr infos.
+	 */
+	for (i = 0; i < tupdesc->natts; i++)
+	{
+		Oid			in_funcid;
+
+		getTypeInputInfo(tupdesc->attrs[i]->atttypid,
+					 &in_funcid, &scanstate->typioparams[i]);
+		fmgr_info(in_funcid, &scanstate->in_functions[i]);
+	}
+
+	return scanstate;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEndTableFuncscan
+ *
+ *		frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndTableFuncScan(TableFuncScanState *node)
+{
+	/*
+	 * Free the exprcontext
+	 */
+	ExecFreeExprContext(&node->ss.ps);
+
+	/*
+	 * clean out the tuple table
+	 */
+	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+	ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+	/*
+	 * Release slots and tuplestore resources
+	 */
+	ExecDropSingleTupleTableSlot(node->resultSlot);
+	if (node->tupstore != NULL)
+		tuplestore_end(node->tupstore);
+	node->tupstore = NULL;
+
+	MemoryContextDelete(node->buildercxt);
+}
+
+/* ----------------------------------------------------------------
+ *		ExecReScanTableFuncscan
+ *
+ *		Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+void
+ExecReScanTableFuncScan(TableFuncScanState *node)
+{
+	Bitmapset  *chgparam = node->ss.ps.chgParam;
+
+	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+	ExecClearTuple(node->resultSlot);
+
+	ExecScanReScan(&node->ss);
+
+	/*
+	 * recompute when parameters are changed
+	 */
+	if (chgparam)
+	{
+		if (node->tupstore != NULL)
+		{
+			tuplestore_end(node->tupstore);
+			node->tupstore = NULL;
+		}
+	}
+
+	if (node->tupstore != NULL)
+		tuplestore_rescan(node->tupstore);
+}
+
+/* ----------------------------------------------------------------
+ *		FetchRows
+ *
+ *		Reads a rows from TableFunc produces
+ *
+ * ----------------------------------------------------------------
+ */
+static void
+FetchRows(TableFuncScanState *tstate, ExprContext *econtext)
+{
+	const TableFuncRoutine *routine = tstate->routine;
+	MemoryContext oldcxt;
+	Datum		value;
+	bool		isnull;
+
+	Assert(tstate->opaque == NULL);
+
+	/* build tuplestore for result. */
+	oldcxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+	tstate->tupstore = tuplestore_begin_heap(false, false, work_mem);
+
+	/* other work be done in builder context */
+	MemoryContextSwitchTo(tstate->buildercxt);
+
+	PG_TRY();
+	{
+		routine->InitBuilder(tstate);
+
+		/*
+		 * If evaluating the document expression returns NULL, the table
+		 * expression is empty and we return immediately.
+		 */
+		value = ExecEvalExpr(tstate->docexpr, econtext, &isnull);
+
+		if (!isnull)
+		{
+			/* otherwise, pass the document value to the table builder */
+			Initialize(tstate, econtext, value);
+
+			/* skip all of the above on future executions of node */
+			tstate->rownum = 1;
+
+			while (FetchRow(tstate, econtext))
+			{
+				tuplestore_puttupleslot(tstate->tupstore, tstate->resultSlot);
+
+				/* reset short life context often */
+				MemoryContextReset(tstate->perValueCxt);
+			}
+
+			tuplestore_donestoring(tstate->tupstore);
+			tuplestore_rescan(tstate->tupstore);
+		}
+	}
+	PG_CATCH();
+	{
+		if (tstate->opaque != NULL)
+			routine->DestroyBuilder(tstate);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	/* Builder is not necessary now */
+	if (tstate->opaque != NULL)
+	{
+		routine->DestroyBuilder(tstate);
+		tstate->opaque = NULL;
+	}
+
+	/* return back memory context */
+	MemoryContextSwitchTo(oldcxt);
+
+	/* builder context is not necessary now */
+	MemoryContextResetOnly(tstate->buildercxt);
+
+	return;
+}
+
+/*
+ * Fill in namespace declarations, the row filter, and column filters in a
+ * table expression builder context.
+ */
+static void
+Initialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc)
+{
+	const TableFuncRoutine *routine;
+	TupleDesc	tupdesc;
+	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
+	 */
+	routine->SetDoc(tstate, doc);
+
+	/* Evaluate namespace specifications */
+	forboth(lc1, tstate->ns_uris, lc2, tstate->ns_names)
+	{
+		ExprState  *expr = (ExprState *) lfirst(lc1);
+		char	   *ns_name = strVal(lfirst(lc2));
+		char	   *ns_uri;
+
+		value = ExecEvalExpr((ExprState *) expr, econtext, &isnull);
+		if (isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("namespace URI must not be null")));
+		ns_uri = TextDatumGetCString(value);
+
+		routine->SetNamespace(tstate, ns_name, ns_uri);
+	}
+
+	/* Install the row filter expression into the table builder context */
+	value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
+	if (isnull)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("row filter expression must not be null")));
+
+	routine->SetRowFilter(tstate, TextDatumGetCString(value));
+
+	/*
+	 * Install the column filter expressions into the table builder context.
+	 * If an expression is given, use that; otherwise the column name itself
+	 * is the column filter.
+	 */
+	colno = 0;
+	tupdesc = tstate->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);
+				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;
+
+		routine->SetColumnFilter(tstate, colfilter, colno);
+
+		colno++;
+	}
+}
+
+/*
+ * Fetch one more row from a TableFunc table builder; if one can be obtained,
+ * push the values for each column onto the output.
+ */
+static bool
+FetchRow(TableFuncScanState *tstate, ExprContext *econtext)
+{
+	const TableFuncRoutine *routine = tstate->routine;
+	MemoryContext	oldcxt;
+	bool			gotrow;
+	TupleDesc	tupdesc;
+	Datum	   *values;
+	bool	   *nulls;
+	ListCell   *cell;
+	int			natts;
+	int			colno;
+	bool		isnull;
+
+	/* Fetch a row */
+	gotrow = routine->FetchRow(tstate);
+
+	/* returns fast when there are not rows */
+	if (!gotrow)
+		return false;
+
+	tupdesc = tstate->resultSlot->tts_tupleDescriptor;
+	values = tstate->resultSlot->tts_values;
+	nulls = tstate->resultSlot->tts_isnull;
+	cell = list_head(tstate->coldefexprs);
+	natts = tupdesc->natts;
+	colno = 0;
+
+	ExecClearTuple(tstate->resultSlot);
+
+	/*
+	 * Obtain the value of each column for this row, installing it into
+	 * the values/isnull arrays.
+	 */
+	for (colno = 0; colno < natts; colno++)
+	{
+		if (colno == tstate->ordinalitycol)
+		{
+			/* fast path when column is ordinality */
+			values[colno] = Int32GetDatum(tstate->rownum++);
+			nulls[colno] = false;
+		}
+		else
+		{
+			/* slow path: fetch value from builder */
+			oldcxt = MemoryContextSwitchTo(tstate->perValueCxt);
+			values[colno] = routine->GetValue(tstate,
+										   tstate->evalcols ? colno : -1,
+											  &isnull);
+			MemoryContextSwitchTo(oldcxt);
+
+			/* No value?  Evaluate and apply the default, if any */
+			if (isnull && cell != NULL)
+			{
+				ExprState  *coldefexpr = (ExprState *) lfirst(cell);
+
+				if (coldefexpr != NULL)
+					values[colno] = ExecEvalExpr(coldefexpr, econtext,
+												 &isnull);
+			}
+
+			/* Verify a possible NOT NULL constraint */
+			if (isnull && bms_is_member(colno, tstate->notnulls))
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("null is not allowed in column \"%s\"",
+							  NameStr(tupdesc->attrs[colno]->attname))));
+
+			nulls[colno] = isnull;
+		}
+
+		/* advance list of default expressions */
+		if (cell != NULL)
+			cell = lnext(cell);
+	}
+
+	ExecStoreVirtualTuple(tstate->resultSlot);
+
+	return true;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 05d8538717..15b31edaa0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -588,6 +588,27 @@ _copyFunctionScan(const FunctionScan *from)
 }
 
 /*
+ * _copyTableFuncScan
+ */
+static TableFuncScan *
+_copyTableFuncScan(const TableFuncScan *from)
+{
+	TableFuncScan *newnode = makeNode(TableFuncScan);
+
+	/*
+	 * copy node superclass fields
+	 */
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	/*
+	 * copy remainder of node
+	 */
+	COPY_NODE_FIELD(tablefunc);
+
+	return newnode;
+}
+
+/*
  * _copyValuesScan
  */
 static ValuesScan *
@@ -1139,6 +1160,33 @@ _copyRangeVar(const RangeVar *from)
 }
 
 /*
+ * _copyTableFunc
+ */
+static TableFunc *
+_copyTableFunc(const TableFunc *from)
+{
+	TableFunc  *newnode = makeNode(TableFunc);
+
+	COPY_NODE_FIELD(ns_names);
+	COPY_NODE_FIELD(ns_uris);
+	COPY_NODE_FIELD(docexpr);
+	COPY_NODE_FIELD(rowexpr);
+	COPY_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;
+}
+
+/*
  * _copyIntoClause
  */
 static IntoClause *
@@ -2169,6 +2217,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_NODE_FIELD(joinaliasvars);
 	COPY_NODE_FIELD(functions);
 	COPY_SCALAR_FIELD(funcordinality);
+	COPY_NODE_FIELD(tablefunc);
 	COPY_NODE_FIELD(values_lists);
 	COPY_STRING_FIELD(ctename);
 	COPY_SCALAR_FIELD(ctelevelsup);
@@ -2590,6 +2639,38 @@ _copyRangeTableSample(const RangeTableSample *from)
 	return newnode;
 }
 
+static RangeTableFunc *
+_copyRangeTableFunc(const RangeTableFunc *from)
+{
+	RangeTableFunc *newnode = makeNode(RangeTableFunc);
+
+	COPY_SCALAR_FIELD(lateral);
+	COPY_NODE_FIELD(docexpr);
+	COPY_NODE_FIELD(rowexpr);
+	COPY_NODE_FIELD(namespaces);
+	COPY_NODE_FIELD(columns);
+	COPY_NODE_FIELD(alias);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static RangeTableFuncCol *
+_copyRangeTableFuncCol(const RangeTableFuncCol *from)
+{
+	RangeTableFuncCol *newnode = makeNode(RangeTableFuncCol);
+
+	COPY_STRING_FIELD(colname);
+	COPY_NODE_FIELD(typeName);
+	COPY_SCALAR_FIELD(for_ordinality);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_NODE_FIELD(colexpr);
+	COPY_NODE_FIELD(coldefexpr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 static TypeCast *
 _copyTypeCast(const TypeCast *from)
 {
@@ -4550,6 +4631,9 @@ copyObject(const void *from)
 		case T_FunctionScan:
 			retval = _copyFunctionScan(from);
 			break;
+		case T_TableFuncScan:
+			retval = _copyTableFuncScan(from);
+			break;
 		case T_ValuesScan:
 			retval = _copyValuesScan(from);
 			break;
@@ -4626,6 +4710,9 @@ copyObject(const void *from)
 		case T_RangeVar:
 			retval = _copyRangeVar(from);
 			break;
+		case T_TableFunc:
+			retval = _copyTableFunc(from);
+			break;
 		case T_IntoClause:
 			retval = _copyIntoClause(from);
 			break;
@@ -5220,6 +5307,12 @@ copyObject(const void *from)
 		case T_RangeTableSample:
 			retval = _copyRangeTableSample(from);
 			break;
+		case T_RangeTableFunc:
+			retval = _copyRangeTableFunc(from);
+			break;
+		case T_RangeTableFuncCol:
+			retval = _copyRangeTableFuncCol(from);
+			break;
 		case T_TypeName:
 			retval = _copyTypeName(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index d595cd7481..c6b103198d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -117,6 +117,28 @@ _equalRangeVar(const RangeVar *a, const RangeVar *b)
 }
 
 static bool
+_equalTableFunc(const TableFunc *a, const TableFunc *b)
+{
+	COMPARE_NODE_FIELD(ns_names);
+	COMPARE_NODE_FIELD(ns_uris);
+	COMPARE_NODE_FIELD(docexpr);
+	COMPARE_NODE_FIELD(rowexpr);
+	COMPARE_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;
+}
+
+static bool
 _equalIntoClause(const IntoClause *a, const IntoClause *b)
 {
 	COMPARE_NODE_FIELD(rel);
@@ -2430,6 +2452,36 @@ _equalRangeTableSample(const RangeTableSample *a, const RangeTableSample *b)
 }
 
 static bool
+_equalRangeTableFunc(const RangeTableFunc *a, const RangeTableFunc *b)
+{
+	COMPARE_SCALAR_FIELD(lateral);
+	COMPARE_NODE_FIELD(docexpr);
+	COMPARE_NODE_FIELD(rowexpr);
+	COMPARE_NODE_FIELD(namespaces);
+	COMPARE_NODE_FIELD(columns);
+	COMPARE_NODE_FIELD(alias);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalRangeTableFuncCol(const RangeTableFuncCol *a, const RangeTableFuncCol *b)
+{
+	COMPARE_STRING_FIELD(colname);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(for_ordinality);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_NODE_FIELD(colexpr);
+	COMPARE_NODE_FIELD(coldefexpr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+
+static bool
 _equalIndexElem(const IndexElem *a, const IndexElem *b)
 {
 	COMPARE_STRING_FIELD(name);
@@ -2531,6 +2583,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(jointype);
 	COMPARE_NODE_FIELD(joinaliasvars);
 	COMPARE_NODE_FIELD(functions);
+	COMPARE_NODE_FIELD(tablefunc);
 	COMPARE_SCALAR_FIELD(funcordinality);
 	COMPARE_NODE_FIELD(values_lists);
 	COMPARE_STRING_FIELD(ctename);
@@ -2897,6 +2950,9 @@ equal(const void *a, const void *b)
 		case T_RangeVar:
 			retval = _equalRangeVar(a, b);
 			break;
+		case T_TableFunc:
+			retval = _equalTableFunc(a, b);
+			break;
 		case T_IntoClause:
 			retval = _equalIntoClause(a, b);
 			break;
@@ -3478,6 +3534,12 @@ equal(const void *a, const void *b)
 		case T_RangeTableSample:
 			retval = _equalRangeTableSample(a, b);
 			break;
+		case T_RangeTableFunc:
+			retval = _equalRangeTableFunc(a, b);
+			break;
+		case T_RangeTableFuncCol:
+			retval = _equalRangeTableFuncCol(a, b);
+			break;
 		case T_TypeName:
 			retval = _equalTypeName(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c2f03b73f4..3deb1a28d1 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1217,6 +1217,9 @@ exprLocation(const Node *expr)
 		case T_RangeVar:
 			loc = ((const RangeVar *) expr)->location;
 			break;
+		case T_TableFunc:
+			loc = ((const TableFunc *) expr)->location;
+			break;
 		case T_Var:
 			loc = ((const Var *) expr)->location;
 			break;
@@ -2217,6 +2220,22 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_TableFunc:
+			{
+				TableFunc  *tf = (TableFunc *) node;
+
+				if (walker(tf->ns_uris, context))
+					return true;
+				if (walker(tf->docexpr, context))
+					return true;
+				if (walker(tf->rowexpr, context))
+					return true;
+				if (walker(tf->colexprs, context))
+					return true;
+				if (walker(tf->coldefexprs, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -2324,6 +2343,10 @@ range_table_walker(List *rtable,
 				if (walker(rte->functions, context))
 					return true;
 				break;
+			case RTE_TABLEFUNC:
+				if (walker(rte->tablefunc, context))
+					return true;
+				break;
 			case RTE_VALUES:
 				if (walker(rte->values_lists, context))
 					return true;
@@ -3013,6 +3036,20 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_TableFunc:
+			{
+				TableFunc  *tf = (TableFunc *) node;
+				TableFunc  *newnode;
+
+				FLATCOPY(newnode, tf, TableFunc);
+				MUTATE(newnode->ns_uris, tf->ns_uris, List *);
+				MUTATE(newnode->docexpr, tf->docexpr, Node *);
+				MUTATE(newnode->rowexpr, tf->rowexpr, Node *);
+				MUTATE(newnode->colexprs, tf->colexprs, List *);
+				MUTATE(newnode->coldefexprs, tf->coldefexprs, List *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3130,6 +3167,9 @@ range_table_mutator(List *rtable,
 			case RTE_FUNCTION:
 				MUTATE(newrte->functions, rte->functions, List *);
 				break;
+			case RTE_TABLEFUNC:
+				MUTATE(newrte->tablefunc, rte->tablefunc, TableFunc *);
+				break;
 			case RTE_VALUES:
 				MUTATE(newrte->values_lists, rte->values_lists, List *);
 				break;
@@ -3555,6 +3595,32 @@ raw_expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_RangeTableFunc:
+			{
+				RangeTableFunc *rtf = (RangeTableFunc *) node;
+
+				if (walker(rtf->docexpr, context))
+					return true;
+				if (walker(rtf->rowexpr, context))
+					return true;
+				if (walker(rtf->namespaces, context))
+					return true;
+				if (walker(rtf->columns, context))
+					return true;
+				if (walker(rtf->alias, context))
+					return true;
+			}
+			break;
+		case T_RangeTableFuncCol:
+			{
+				RangeTableFuncCol *rtfc = (RangeTableFuncCol *) node;
+
+				if (walker(rtfc->colexpr, context))
+					return true;
+				if (walker(rtfc->coldefexpr, context))
+					return true;
+			}
+			break;
 		case T_TypeName:
 			{
 				TypeName   *tn = (TypeName *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b3802b4428..09d7af6f52 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -566,6 +566,16 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
 }
 
 static void
+_outTableFuncScan(StringInfo str, const TableFuncScan *node)
+{
+	WRITE_NODE_TYPE("TABLEFUNCSCAN");
+
+	_outScanInfo(str, (const Scan *) node);
+
+	WRITE_NODE_FIELD(tablefunc);
+}
+
+static void
 _outValuesScan(StringInfo str, const ValuesScan *node)
 {
 	WRITE_NODE_TYPE("VALUESSCAN");
@@ -956,6 +966,28 @@ _outRangeVar(StringInfo str, const RangeVar *node)
 }
 
 static void
+_outTableFunc(StringInfo str, const TableFunc *node)
+{
+	WRITE_NODE_TYPE("TABLEFUNC");
+
+	WRITE_NODE_FIELD(ns_names);
+	WRITE_NODE_FIELD(ns_uris);
+	WRITE_NODE_FIELD(docexpr);
+	WRITE_NODE_FIELD(rowexpr);
+	WRITE_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);
+}
+
+static void
 _outIntoClause(StringInfo str, const IntoClause *node)
 {
 	WRITE_NODE_TYPE("INTOCLAUSE");
@@ -2869,6 +2901,9 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_NODE_FIELD(functions);
 			WRITE_BOOL_FIELD(funcordinality);
 			break;
+		case RTE_TABLEFUNC:
+			WRITE_NODE_FIELD(tablefunc);
+			break;
 		case RTE_VALUES:
 			WRITE_NODE_FIELD(values_lists);
 			WRITE_NODE_FIELD(coltypes);
@@ -3192,6 +3227,34 @@ _outRangeTableSample(StringInfo str, const RangeTableSample *node)
 }
 
 static void
+_outRangeTableFunc(StringInfo str, const RangeTableFunc *node)
+{
+	WRITE_NODE_TYPE("RANGETABLEFUNC");
+
+	WRITE_BOOL_FIELD(lateral);
+	WRITE_NODE_FIELD(docexpr);
+	WRITE_NODE_FIELD(rowexpr);
+	WRITE_NODE_FIELD(namespaces);
+	WRITE_NODE_FIELD(columns);
+	WRITE_NODE_FIELD(alias);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outRangeTableFuncCol(StringInfo str, const RangeTableFuncCol *node)
+{
+	WRITE_NODE_TYPE("RANGETABLEFUNCCOL");
+
+	WRITE_STRING_FIELD(colname);
+	WRITE_NODE_FIELD(typeName);
+	WRITE_BOOL_FIELD(for_ordinality);
+	WRITE_BOOL_FIELD(is_not_null);
+	WRITE_NODE_FIELD(colexpr);
+	WRITE_NODE_FIELD(coldefexpr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
 _outConstraint(StringInfo str, const Constraint *node)
 {
 	WRITE_NODE_TYPE("CONSTRAINT");
@@ -3440,6 +3503,9 @@ outNode(StringInfo str, const void *obj)
 			case T_FunctionScan:
 				_outFunctionScan(str, obj);
 				break;
+			case T_TableFuncScan:
+				_outTableFuncScan(str, obj);
+				break;
 			case T_ValuesScan:
 				_outValuesScan(str, obj);
 				break;
@@ -3512,6 +3578,9 @@ outNode(StringInfo str, const void *obj)
 			case T_RangeVar:
 				_outRangeVar(str, obj);
 				break;
+			case T_TableFunc:
+				_outTableFunc(str, obj);
+				break;
 			case T_IntoClause:
 				_outIntoClause(str, obj);
 				break;
@@ -3922,6 +3991,12 @@ outNode(StringInfo str, const void *obj)
 			case T_RangeTableSample:
 				_outRangeTableSample(str, obj);
 				break;
+			case T_RangeTableFunc:
+				_outRangeTableFunc(str, obj);
+				break;
+			case T_RangeTableFuncCol:
+				_outRangeTableFuncCol(str, obj);
+				break;
 			case T_Constraint:
 				_outConstraint(str, obj);
 				break;
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index 926f226f34..dfb8bfa803 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -279,6 +279,10 @@ print_rt(const List *rtable)
 				printf("%d\t%s\t[rangefunction]",
 					   i, rte->eref->aliasname);
 				break;
+			case RTE_TABLEFUNC:
+				printf("%d\t%s\t[table function]",
+					   i, rte->eref->aliasname);
+				break;
 			case RTE_VALUES:
 				printf("%d\t%s\t[values list]",
 					   i, rte->eref->aliasname);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d2f69fe70b..f87d12945a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -460,6 +460,33 @@ _readRangeVar(void)
 	READ_DONE();
 }
 
+/*
+ * _readTableFunc
+ */
+static TableFunc *
+_readTableFunc(void)
+{
+	READ_LOCALS(TableFunc);
+
+	READ_NODE_FIELD(ns_names);
+	READ_NODE_FIELD(ns_uris);
+	READ_NODE_FIELD(docexpr);
+	READ_NODE_FIELD(rowexpr);
+	READ_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();
+}
+
 static IntoClause *
 _readIntoClause(void)
 {
@@ -1315,6 +1342,9 @@ _readRangeTblEntry(void)
 			READ_NODE_FIELD(functions);
 			READ_BOOL_FIELD(funcordinality);
 			break;
+		case RTE_TABLEFUNC:
+			READ_NODE_FIELD(tablefunc);
+			break;
 		case RTE_VALUES:
 			READ_NODE_FIELD(values_lists);
 			READ_NODE_FIELD(coltypes);
@@ -1801,6 +1831,21 @@ _readValuesScan(void)
 }
 
 /*
+ * _readTableFuncScan
+ */
+static TableFuncScan *
+_readTableFuncScan(void)
+{
+	READ_LOCALS(TableFuncScan);
+
+	ReadCommonScan(&local_node->scan);
+
+	READ_NODE_FIELD(tablefunc);
+
+	READ_DONE();
+}
+
+/*
  * _readCteScan
  */
 static CteScan *
@@ -2358,6 +2403,8 @@ parseNodeString(void)
 		return_value = _readRangeVar();
 	else if (MATCH("INTOCLAUSE", 10))
 		return_value = _readIntoClause();
+	else if (MATCH("TABLEFUNC", 9))
+		return_value = _readTableFunc();
 	else if (MATCH("VAR", 3))
 		return_value = _readVar();
 	else if (MATCH("CONST", 5))
@@ -2500,6 +2547,8 @@ parseNodeString(void)
 		return_value = _readFunctionScan();
 	else if (MATCH("VALUESSCAN", 10))
 		return_value = _readValuesScan();
+	else if (MATCH("TABLEFUNCSCAN", 13))
+		return_value = _readTableFuncScan();
 	else if (MATCH("CTESCAN", 7))
 		return_value = _readCteScan();
 	else if (MATCH("WORKTABLESCAN", 13))
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index eeacf815e3..c7d4d6c160 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -106,6 +106,8 @@ static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
 					  RangeTblEntry *rte);
 static void set_values_pathlist(PlannerInfo *root, RelOptInfo *rel,
 					RangeTblEntry *rte);
+static void set_tablefunc_pathlist(PlannerInfo *root, RelOptInfo *rel,
+					  RangeTblEntry *rte);
 static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				 RangeTblEntry *rte);
 static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
@@ -365,6 +367,9 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 			case RTE_FUNCTION:
 				set_function_size_estimates(root, rel);
 				break;
+			case RTE_TABLEFUNC:
+				set_tablefunc_size_estimates(root, rel);
+				break;
 			case RTE_VALUES:
 				set_values_size_estimates(root, rel);
 				break;
@@ -437,6 +442,10 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				/* RangeFunction */
 				set_function_pathlist(root, rel, rte);
 				break;
+			case RTE_TABLEFUNC:
+				/* Table Function */
+				set_tablefunc_pathlist(root, rel, rte);
+				break;
 			case RTE_VALUES:
 				/* Values list */
 				set_values_pathlist(root, rel, rte);
@@ -599,6 +608,12 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 				return;
 			break;
 
+		case RTE_TABLEFUNC:
+			/* Check for parallel-restricted functions. */
+			if (!is_parallel_safe(root, (Node *) rte->tablefunc))
+				return;
+			break;
+
 		case RTE_VALUES:
 			/* Check for parallel-restricted functions. */
 			if (!is_parallel_safe(root, (Node *) rte->values_lists))
@@ -1930,6 +1945,28 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 }
 
 /*
+ * set_tablefunc_pathlist
+ *		Build the (single) access path for a table func RTE
+ */
+static void
+set_tablefunc_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
+{
+	Relids		required_outer;
+	List	   *pathkeys = NIL;
+
+	/*
+	 * We don't support pushing join clauses into the quals of a function
+	 * scan, but it could still have required parameterization due to LATERAL
+	 * refs in the function expression.
+	 */
+	required_outer = rel->lateral_relids;
+
+	/* Generate appropriate path */
+	add_path(rel, create_tablefuncscan_path(root, rel,
+										   pathkeys, required_outer));
+}
+
+/*
  * set_cte_pathlist
  *		Build the (single) access path for a non-self-reference CTE RTE
  *
@@ -3029,6 +3066,9 @@ print_path(PlannerInfo *root, Path *path, int indent)
 				case T_FunctionScan:
 					ptype = "FunctionScan";
 					break;
+				case T_TableFuncScan:
+					ptype = "TableFuncScan";
+					break;
 				case T_ValuesScan:
 					ptype = "ValuesScan";
 					break;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index d01630f8db..665464c408 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1279,6 +1279,62 @@ cost_functionscan(Path *path, PlannerInfo *root,
 }
 
 /*
+ * cost_tablefuncscan
+ *	  Determines and returns the cost of scanning a table function.
+ *
+ * 'baserel' is the relation to be scanned
+ * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
+ */
+void
+cost_tablefuncscan(Path *path, PlannerInfo *root,
+				RelOptInfo *baserel, ParamPathInfo *param_info)
+{
+	Cost		startup_cost = 0;
+	Cost		run_cost = 0;
+	QualCost	qpqual_cost;
+	Cost		cpu_per_tuple;
+	RangeTblEntry *rte;
+	QualCost	exprcost;
+
+	/* Should only be applied to base relations that are functions */
+	Assert(baserel->relid > 0);
+	rte = planner_rt_fetch(baserel->relid, root);
+	Assert(rte->rtekind == RTE_TABLEFUNC);
+
+	/* Mark the path with the correct row estimate */
+	if (param_info)
+		path->rows = param_info->ppi_rows;
+	else
+		path->rows = baserel->rows;
+
+	/*
+	 * Estimate costs of executing the table func expression(s).
+	 *
+	 * XXX in principle we ought to charge tuplestore spill costs if the
+	 * number of rows is large.  However, given how phony our rowcount
+	 * estimates for functions tend to be, there's not a lot of point in that
+	 * refinement right now.
+	 */
+	cost_qual_eval_node(&exprcost, (Node *) rte->tablefunc, root);
+
+	startup_cost += exprcost.startup + exprcost.per_tuple;
+
+	/* Add scanning CPU costs */
+	get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
+
+	startup_cost += qpqual_cost.startup;
+	cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
+	run_cost += cpu_per_tuple * baserel->tuples;
+
+	/* tlist eval costs are paid per output row, not per tuple scanned */
+	startup_cost += path->pathtarget->cost.startup;
+	run_cost += path->pathtarget->cost.per_tuple * path->rows;
+
+	path->startup_cost = startup_cost;
+	path->total_cost = startup_cost + run_cost;
+}
+
+/*
  * cost_valuesscan
  *	  Determines and returns the cost of scanning a VALUES RTE.
  *
@@ -4430,6 +4486,31 @@ set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel)
 }
 
 /*
+ * set_function_size_estimates
+ *		Set the size estimates for a base relation that is a function call.
+ *
+ * The rel's targetlist and restrictinfo list must have been constructed
+ * already.
+ *
+ * We set the same fields as set_tablefunc_size_estimates.
+ */
+void
+set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+
+	/* Should only be applied to base relations that are functions */
+	Assert(rel->relid > 0);
+	rte = planner_rt_fetch(rel->relid, root);
+	Assert(rte->rtekind == RTE_TABLEFUNC);
+
+	rel->tuples = 100;
+
+	/* Now estimate number of output rows, etc */
+	set_baserel_size_estimates(root, rel);
+}
+
+/*
  * set_values_size_estimates
  *		Set the size estimates for a base relation that is a values list.
  *
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 997bdcff2e..a363634ca6 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -134,6 +134,8 @@ static FunctionScan *create_functionscan_plan(PlannerInfo *root, Path *best_path
 						 List *tlist, List *scan_clauses);
 static ValuesScan *create_valuesscan_plan(PlannerInfo *root, Path *best_path,
 					   List *tlist, List *scan_clauses);
+static TableFuncScan *create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
+					 List *tlist, List *scan_clauses);
 static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
 					List *tlist, List *scan_clauses);
 static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
@@ -190,6 +192,8 @@ static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
 				  Index scanrelid, List *functions, bool funcordinality);
 static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
 				Index scanrelid, List *values_lists);
+static TableFuncScan *make_tablefuncscan(List *qptlist, List *qpqual,
+				  Index scanrelid, TableFunc *tablefunc);
 static CteScan *make_ctescan(List *qptlist, List *qpqual,
 			 Index scanrelid, int ctePlanId, int cteParam);
 static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
@@ -355,6 +359,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
 		case T_TidScan:
 		case T_SubqueryScan:
 		case T_FunctionScan:
+		case T_TableFuncScan:
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_WorkTableScan:
@@ -636,6 +641,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
 													 scan_clauses);
 			break;
 
+		case T_TableFuncScan:
+			plan = (Plan *) create_tablefuncscan_plan(root,
+												   best_path,
+												   tlist,
+												   scan_clauses);
+			break;
+
 		case T_ValuesScan:
 			plan = (Plan *) create_valuesscan_plan(root,
 												   best_path,
@@ -756,6 +768,7 @@ use_physical_tlist(PlannerInfo *root, Path *path, int flags)
 		rel->rtekind != RTE_SUBQUERY &&
 		rel->rtekind != RTE_FUNCTION &&
 		rel->rtekind != RTE_VALUES &&
+		rel->rtekind != RTE_TABLEFUNC &&
 		rel->rtekind != RTE_CTE)
 		return false;
 
@@ -3018,6 +3031,49 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
 }
 
 /*
+ * create_tablefuncscan_plan
+ *	 Returns a tablefuncscan plan for the base relation scanned by 'best_path'
+ *	 with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+static TableFuncScan *
+create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
+						 List *tlist, List *scan_clauses)
+{
+	TableFuncScan *scan_plan;
+	Index		scan_relid = best_path->parent->relid;
+	RangeTblEntry *rte;
+	TableFunc	  *tablefunc;
+
+	/* it should be a function base rel... */
+	Assert(scan_relid > 0);
+	rte = planner_rt_fetch(scan_relid, root);
+	Assert(rte->rtekind == RTE_TABLEFUNC);
+	tablefunc = rte->tablefunc;
+
+	/* Sort clauses into best execution order */
+	scan_clauses = order_qual_clauses(root, scan_clauses);
+
+	/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+	scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+	/* Replace any outer-relation variables with nestloop params */
+	if (best_path->param_info)
+	{
+		scan_clauses = (List *)
+			replace_nestloop_params(root, (Node *) scan_clauses);
+		/* The function expressions could contain nestloop params, too */
+		tablefunc = (TableFunc *) replace_nestloop_params(root, (Node *) tablefunc);
+	}
+
+	scan_plan = make_tablefuncscan(tlist, scan_clauses, scan_relid,
+								  tablefunc);
+
+	copy_generic_path_info(&scan_plan->scan.plan, best_path);
+
+	return scan_plan;
+}
+
+/*
  * create_valuesscan_plan
  *	 Returns a valuesscan plan for the base relation scanned by 'best_path'
  *	 with restriction clauses 'scan_clauses' and targetlist 'tlist'.
@@ -4915,6 +4971,25 @@ make_functionscan(List *qptlist,
 	return node;
 }
 
+static TableFuncScan *
+make_tablefuncscan(List *qptlist,
+				  List *qpqual,
+				  Index scanrelid,
+				  TableFunc *tablefunc)
+{
+	TableFuncScan *node = makeNode(TableFuncScan);
+	Plan	   *plan = &node->scan.plan;
+
+	plan->targetlist = qptlist;
+	plan->qual = qpqual;
+	plan->lefttree = NULL;
+	plan->righttree = NULL;
+	node->scan.scanrelid = scanrelid;
+	node->tablefunc = tablefunc;
+
+	return node;
+}
+
 static ValuesScan *
 make_valuesscan(List *qptlist,
 				List *qpqual,
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index c170e9614f..e736f86d14 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -337,6 +337,8 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex)
 		vars = pull_vars_of_level((Node *) rte->functions, 0);
 	else if (rte->rtekind == RTE_VALUES)
 		vars = pull_vars_of_level((Node *) rte->values_lists, 0);
+	else if (rte->rtekind == RTE_TABLEFUNC)
+		vars = pull_vars_of_level((Node *) rte->tablefunc, 0);
 	else
 	{
 		Assert(false);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3d33d46971..3988f9a7b4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -69,17 +69,19 @@ create_upper_paths_hook_type create_upper_paths_hook = NULL;
 
 
 /* Expression kind codes for preprocess_expression */
-#define EXPRKIND_QUAL			0
-#define EXPRKIND_TARGET			1
-#define EXPRKIND_RTFUNC			2
-#define EXPRKIND_RTFUNC_LATERAL 3
-#define EXPRKIND_VALUES			4
-#define EXPRKIND_VALUES_LATERAL 5
-#define EXPRKIND_LIMIT			6
-#define EXPRKIND_APPINFO		7
-#define EXPRKIND_PHV			8
-#define EXPRKIND_TABLESAMPLE	9
-#define EXPRKIND_ARBITER_ELEM	10
+#define EXPRKIND_QUAL				0
+#define EXPRKIND_TARGET				1
+#define EXPRKIND_RTFUNC				2
+#define EXPRKIND_RTFUNC_LATERAL 	3
+#define EXPRKIND_VALUES				4
+#define EXPRKIND_VALUES_LATERAL 	5
+#define EXPRKIND_LIMIT				6
+#define EXPRKIND_APPINFO			7
+#define EXPRKIND_PHV				8
+#define EXPRKIND_TABLESAMPLE		9
+#define EXPRKIND_ARBITER_ELEM		10
+#define EXPRKIND_TABLEFUNC			11
+#define EXPRKIND_TABLEFUNC_LATERAL	12
 
 /* Passthrough data for standard_qp_callback */
 typedef struct
@@ -685,7 +687,15 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		{
 			/* Preprocess the function expression(s) fully */
 			kind = rte->lateral ? EXPRKIND_RTFUNC_LATERAL : EXPRKIND_RTFUNC;
-			rte->functions = (List *) preprocess_expression(root, (Node *) rte->functions, kind);
+			rte->functions = (List *)
+				preprocess_expression(root, (Node *) rte->functions, kind);
+		}
+		else if (rte->rtekind == RTE_TABLEFUNC)
+		{
+			/* Preprocess the function expression(s) fully */
+			kind = rte->lateral ? EXPRKIND_TABLEFUNC_LATERAL : EXPRKIND_TABLEFUNC;
+			rte->tablefunc = (TableFunc *)
+				preprocess_expression(root, (Node *) rte->tablefunc, kind);
 		}
 		else if (rte->rtekind == RTE_VALUES)
 		{
@@ -844,7 +854,8 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
 	if (root->hasJoinRTEs &&
 		!(kind == EXPRKIND_RTFUNC ||
 		  kind == EXPRKIND_VALUES ||
-		  kind == EXPRKIND_TABLESAMPLE))
+		  kind == EXPRKIND_TABLESAMPLE ||
+		  kind == EXPRKIND_TABLEFUNC))
 		expr = flatten_join_alias_vars(root, expr);
 
 	/*
@@ -5167,6 +5178,8 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
 			PathTarget *thistarget = (PathTarget *) lfirst(lc1);
 			bool		contains_srfs = (bool) lfirst_int(lc2);
 
+
+
 			/* If this level doesn't contain SRFs, do regular projection */
 			if (contains_srfs)
 				newpath = (Path *) create_set_projection_path(root,
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index be267b9da7..5dc1bb1fae 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -395,6 +395,7 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	newrte->subquery = NULL;
 	newrte->joinaliasvars = NIL;
 	newrte->functions = NIL;
+	newrte->tablefunc = NULL;
 	newrte->values_lists = NIL;
 	newrte->coltypes = NIL;
 	newrte->coltypmods = NIL;
@@ -555,6 +556,19 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 					fix_scan_list(root, splan->functions, rtoffset);
 			}
 			break;
+		case T_TableFuncScan:
+			{
+				TableFuncScan *splan = (TableFuncScan *) plan;
+
+				splan->scan.scanrelid += rtoffset;
+				splan->scan.plan.targetlist =
+					fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
+				splan->scan.plan.qual =
+					fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+				splan->tablefunc = (TableFunc *)
+					fix_scan_expr(root, (Node *) splan->tablefunc, rtoffset);
+			}
+			break;
 		case T_ValuesScan:
 			{
 				ValuesScan *splan = (ValuesScan *) plan;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 7954c445dd..e8c7b431d0 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2422,6 +2422,12 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			}
 			break;
 
+		case T_TableFuncScan:
+			finalize_primnode((Node *) ((TableFuncScan *) plan)->tablefunc,
+							  &context);
+			context.paramids = bms_add_members(context.paramids, scan_params);
+			break;
+
 		case T_ValuesScan:
 			finalize_primnode((Node *) ((ValuesScan *) plan)->values_lists,
 							  &context);
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 6911177b68..81074ec94a 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1118,6 +1118,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 				case RTE_SUBQUERY:
 				case RTE_FUNCTION:
 				case RTE_VALUES:
+				case RTE_TABLEFUNC:
 					child_rte->lateral = true;
 					break;
 				case RTE_JOIN:
@@ -1965,6 +1966,11 @@ replace_vars_in_jointree(Node *jtnode,
 							pullup_replace_vars((Node *) rte->functions,
 												context);
 						break;
+					case RTE_TABLEFUNC:
+						rte->tablefunc = (TableFunc *)
+							pullup_replace_vars((Node *) rte->tablefunc,
+												context);
+						break;
 					case RTE_VALUES:
 						rte->values_lists = (List *)
 							pullup_replace_vars((Node *) rte->values_lists,
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 324829690d..90719aab11 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1751,6 +1751,32 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
 }
 
 /*
+ * create_tablefuncscan_path
+ *	  Creates a path corresponding to a sequential scan of a table function,
+ *	  returning the pathnode.
+ */
+Path *
+create_tablefuncscan_path(PlannerInfo *root, RelOptInfo *rel,
+						 List *pathkeys, Relids required_outer)
+{
+	Path	   *pathnode = makeNode(Path);
+
+	pathnode->pathtype = T_TableFuncScan;
+	pathnode->parent = rel;
+	pathnode->pathtarget = rel->reltarget;
+	pathnode->param_info = get_baserel_parampathinfo(root, rel,
+													 required_outer);
+	pathnode->parallel_aware = false;
+	pathnode->parallel_safe = rel->consider_parallel;
+	pathnode->parallel_workers = 0;
+	pathnode->pathkeys = NIL;	/* result is always unordered */
+
+	cost_tablefuncscan(pathnode, root, rel, pathnode->param_info);
+
+	return pathnode;
+}
+
+/*
  * create_valuesscan_path
  *	  Creates a path corresponding to a scan of a VALUES list,
  *	  returning the pathnode.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 4ed27054a1..7f65de4882 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1381,8 +1381,9 @@ relation_excluded_by_constraints(PlannerInfo *root,
  * dropped cols.
  *
  * We also support building a "physical" tlist for subqueries, functions,
- * values lists, and CTEs, since the same optimization can occur in
- * SubqueryScan, FunctionScan, ValuesScan, CteScan, and WorkTableScan nodes.
+ * values lists, table exprssionsand CTEs, since the same optimization can
+ * occur in SubqueryScan, FunctionScan, ValuesScan, CteScan, TableFunc
+ * and WorkTableScan nodes.
  */
 List *
 build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
@@ -1454,6 +1455,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
 			break;
 
 		case RTE_FUNCTION:
+		case RTE_TABLEFUNC:
 		case RTE_VALUES:
 		case RTE_CTE:
 			/* Not all of these can have dropped cols, but share code anyway */
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index adc1db94f4..ebf9a80213 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -151,6 +151,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
 		case RTE_VALUES:
+		case RTE_TABLEFUNC:
 		case RTE_CTE:
 
 			/*
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f7659bb6b..2ce15b9c6d 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2776,6 +2776,15 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 										LCS_asString(lc->strength)),
 							 parser_errposition(pstate, thisrel->location)));
 							break;
+						case RTE_TABLEFUNC:
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							/*------
+							  translator: %s is a SQL row locking clause such as FOR UPDATE */
+								 errmsg("%s cannot be applied to a table function",
+										LCS_asString(lc->strength)),
+							 parser_errposition(pstate, thisrel->location)));
+							break;
 						case RTE_VALUES:
 							ereport(ERROR,
 									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 07cc81ee76..943ff0b794 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -462,7 +462,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
 %type <node>	def_arg columnElem where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
-				columnref in_expr having_clause func_table array_expr
+				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality
@@ -549,6 +549,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
@@ -606,10 +611,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
 
@@ -680,8 +685,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
 
@@ -11295,6 +11300,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->coldeflist = lsecond($3);
 					$$ = (Node *) n;
 				}
+			| xmltable opt_alias_clause
+				{
+					RangeTableFunc *n = (RangeTableFunc *) $1;
+					n->alias = $2;
+					$$ = (Node *) n;
+				}
+			| LATERAL_P xmltable opt_alias_clause
+				{
+					RangeTableFunc *n = (RangeTableFunc *) $2;
+					n->lateral = true;
+					n->alias = $3;
+					$$ = (Node *) n;
+				}
 			| select_with_parens opt_alias_clause
 				{
 					RangeSubselect *n = makeNode(RangeSubselect);
@@ -11734,6 +11752,181 @@ TableFuncElement:	ColId Typename opt_collate_clause
 				}
 		;
 
+/*
+ * XMLTABLE
+ */
+xmltable:
+			XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					RangeTableFunc *n = makeNode(RangeTableFunc);
+					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 ')'
+				{
+					RangeTableFunc *n = makeNode(RangeTableFunc);
+					n->rowexpr = $3;
+					n->docexpr = $4;
+					n->columns = $6;
+					n->namespaces = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' XMLNAMESPACES '(' xml_namespace_list ')' ','
+				c_expr xmlexists_argument ')'
+				{
+					RangeTableFunc *n = makeNode(RangeTableFunc);
+					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 ')'
+				{
+					RangeTableFunc *n = makeNode(RangeTableFunc);
+					n->rowexpr = $8;
+					n->docexpr = $9;
+					n->columns = $11;
+					n->namespaces = $5;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+		;
+
+xmltable_column_list: xmltable_column_el					{ $$ = list_make1($1); }
+			| xmltable_column_list ',' xmltable_column_el	{ $$ = lappend($1, $3); }
+		;
+
+xmltable_column_el:
+			ColId Typename
+				{
+					RangeTableFuncCol	   *fc = makeNode(RangeTableFuncCol);
+
+					fc->colname = $1;
+					fc->for_ordinality = false;
+					fc->typeName = $2;
+					fc->is_not_null = false;
+					fc->colexpr = NULL;
+					fc->coldefexpr = NULL;
+					fc->location = @1;
+
+					$$ = (Node *) fc;
+				}
+			| ColId Typename xmltable_column_opt_list
+				{
+					RangeTableFuncCol	   *fc = makeNode(RangeTableFuncCol);
+					ListCell		   *option;
+					bool				nullability_seen = false;
+
+					fc->colname = $1;
+					fc->typeName = $2;
+					fc->for_ordinality = false;
+					fc->is_not_null = false;
+					fc->colexpr = NULL;
+					fc->coldefexpr = NULL;
+					fc->location = @1;
+
+					foreach(option, $3)
+					{
+						DefElem   *defel = (DefElem *) lfirst(option);
+
+						if (strcmp(defel->defname, "default") == 0)
+						{
+							if (fc->coldefexpr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one DEFAULT value is allowed"),
+										 parser_errposition(defel->location)));
+							fc->coldefexpr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "path") == 0)
+						{
+							if (fc->colexpr != NULL)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("only one PATH value per column is allowed"),
+										 parser_errposition(defel->location)));
+							fc->colexpr = defel->arg;
+						}
+						else if (strcmp(defel->defname, "is_not_null") == 0)
+						{
+							if (nullability_seen)
+								ereport(ERROR,
+										(errcode(ERRCODE_SYNTAX_ERROR),
+										 errmsg("conflicting or redundant NULL / NOT NULL declarations for column \"%s\"", fc->colname),
+										 parser_errposition(defel->location)));
+							fc->is_not_null = intVal(defel->arg);
+							nullability_seen = true;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("unrecognized column option \"%s\"",
+											defel->defname),
+									 parser_errposition(defel->location)));
+						}
+					}
+					$$ = (Node *) fc;
+				}
+			| ColId FOR ORDINALITY
+				{
+					RangeTableFuncCol	   *fc = makeNode(RangeTableFuncCol);
+
+					fc->colname = $1;
+					fc->for_ordinality = true;
+					/* other fields are ignored, initialized by makeNode */
+					fc->location = @1;
+
+					$$ = (Node *) fc;
+				}
+		;
+
+xmltable_column_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("is_not_null", (Node *) makeInteger(true), @1); }
+			| NULL_P
+				{ $$ = makeDefElem("is_not_null", (Node *) makeInteger(false), @1); }
+		;
+
+xml_namespace_list: xml_namespace_el						{ $$ = list_make1($1); }
+			| xml_namespace_list ',' xml_namespace_el		{ $$ = lappend($1, $3); }
+		;
+
+xml_namespace_el:
+			b_expr AS ColLabel
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = $3;
+					$$->indirection = NIL;
+					$$->val = $1;
+					$$->location = @1;
+				}
+			| DEFAULT b_expr
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = NULL;
+					$$->indirection = NIL;
+					$$->val = $2;
+					$$->location = @1;
+				}
+		;
+
 /*****************************************************************************
  *
  *	Type syntax
@@ -14327,6 +14520,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -14632,10 +14826,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_clause.c b/src/backend/parser/parse_clause.c
index 69f4736438..ef568c614d 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -22,6 +22,7 @@
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_constraint_fn.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
@@ -65,6 +66,8 @@ static RangeTblEntry *transformRangeSubselect(ParseState *pstate,
 						RangeSubselect *r);
 static RangeTblEntry *transformRangeFunction(ParseState *pstate,
 					   RangeFunction *r);
+static RangeTblEntry *transformRangeTableFunc(ParseState *pstate,
+					   RangeTableFunc * t);
 static TableSampleClause *transformRangeTableSample(ParseState *pstate,
 						  RangeTableSample *rts);
 static Node *transformFromClauseItem(ParseState *pstate, Node *n,
@@ -795,6 +798,250 @@ transformRangeTableSample(ParseState *pstate, RangeTableSample *rts)
 	return tablesample;
 }
 
+/*
+ * Transform a table function - RangeTableFunc is raw form of TableFunc.
+ *
+ * Transform the namespace clauses, the document-generating expression, the
+ * row-generating expression, the column-generating expressions, and the
+ * default value expressions.
+ */
+static RangeTblEntry *
+transformRangeTableFunc(ParseState *pstate, RangeTableFunc * rtf)
+{
+	TableFunc  *tf = makeNode(TableFunc);
+	const char *constructName;
+	Oid			docType;
+	RangeTblEntry *rte;
+	bool		is_lateral;
+
+	/* Currently only XMLTABLE is supported */
+	constructName = "XMLTABLE";
+	docType = XMLOID;
+
+	/*
+	 * We make lateral_only names of this level visible, whether or not the
+	 * RangeTableFunbc is explicitly marked LATERAL.  This is needed for SQL
+	 * spec compliance and seems useful on convenience grounds for all
+	 * functions in FROM.
+	 *
+	 * (LATERAL can't nest within a single pstate level, so we don't need
+	 * save/restore logic here.)
+	 */
+	Assert(!pstate->p_lateral_active);
+	pstate->p_lateral_active = true;
+
+	/* Transform and apply typecast to the row-generating expression ... */
+	Assert(rtf->rowexpr != NULL);
+	tf->rowexpr = coerce_to_specific_type(pstate,
+				transformExpr(pstate, rtf->rowexpr, EXPR_KIND_FROM_FUNCTION),
+											TEXTOID,
+											constructName);
+	assign_expr_collations(pstate, tf->rowexpr);
+
+	/* ... and to the document itself */
+	Assert(rtf->docexpr != NULL);
+	tf->docexpr = coerce_to_specific_type(pstate,
+				transformExpr(pstate, rtf->docexpr, EXPR_KIND_FROM_FUNCTION),
+											docType,
+											constructName);
+	assign_expr_collations(pstate, tf->docexpr);
+
+	/* undef ordinality column number */
+	tf->ordinalitycol = -1;
+
+	/* Columns, if any, also need to be transformed */
+	if (rtf->columns != NIL)
+	{
+		ListCell   *col;
+		char	  **names;
+		int			i = 0;
+
+		tf->evalcols = true;
+
+		tf->colcount = list_length(rtf->columns);
+		names = palloc(sizeof(char *) * tf->colcount);
+
+		foreach(col, rtf->columns)
+		{
+			RangeTableFuncCol *rawc = (RangeTableFuncCol *) lfirst(col);
+			Oid			typid;
+			int32		typmod;
+			Node	   *colexpr;
+			Node	   *coldefexpr;
+			int			j;
+
+			tf->colnames = lappend(tf->colnames,
+									 makeString(pstrdup(rawc->colname)));
+
+			/*
+			 * Determine the type and typmod for the new column. FOR
+			 * ORDINALITY columns are INTEGER per spec; the others are
+			 * user-specified.
+			 */
+			if (rawc->for_ordinality)
+			{
+				if (tf->ordinalitycol != -1)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("only one FOR ORDINALITY column is allowed"),
+							 parser_errposition(pstate, rawc->location)));
+
+				typid = INT4OID;
+				typmod = -1;
+				tf->ordinalitycol = 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);
+			}
+
+			tf->coltypes = lappend_oid(tf->coltypes, typid);
+			tf->coltypmods = lappend_int(tf->coltypmods, typmod);
+			tf->colcollations = lappend_oid(tf->colcollations,
+			 type_is_collatable(typid) ? DEFAULT_COLLATION_OID : InvalidOid);
+
+			/* Transform the PATH and DEFAULT expressions */
+			if (rawc->colexpr)
+			{
+				colexpr = coerce_to_specific_type(pstate,
+							  transformExpr(pstate, rawc->colexpr,
+													  EXPR_KIND_FROM_FUNCTION),
+											  TEXTOID,
+											  constructName);
+				assign_expr_collations(pstate, colexpr);
+		  }
+			else
+				colexpr = NULL;
+
+			if (rawc->coldefexpr)
+			{
+				coldefexpr = coerce_to_specific_type_typmod(pstate,
+							  transformExpr(pstate, rawc->coldefexpr,
+													  EXPR_KIND_FROM_FUNCTION),
+														typid, typmod,
+														constructName);
+				assign_expr_collations(pstate, coldefexpr);
+			}
+			else
+				coldefexpr = NULL;
+
+			tf->colexprs = lappend(tf->colexprs, colexpr);
+			tf->coldefexprs = lappend(tf->coldefexprs, coldefexpr);
+
+			if (rawc->is_not_null)
+				tf->notnulls = bms_add_member(tf->notnulls, 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++;
+		}
+		tf->evalcols = true;
+		pfree(names);
+	}
+	else
+	{
+		tf->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.
+		 */
+		tf->colcount = 1;
+		tf->colnames = list_make1(makeString(pstrdup("xmltable")));
+		tf->coltypes = list_make1_oid(XMLOID);
+		tf->coltypmods = list_make1_int(-1);
+		tf->colcollations = list_make1_oid(InvalidOid);
+	}
+
+	/* Namespaces, if any, also need to be transformed */
+	if (rtf->namespaces != NIL)
+	{
+		ListCell   *ns;
+		ListCell   *lc2;
+		List	   *ns_uris = NIL;
+		List	   *ns_names = NIL;
+		bool		default_ns_seen = false;
+
+		foreach(ns, rtf->namespaces)
+		{
+			ResTarget  *r = (ResTarget *) lfirst(ns);
+			Node	   *ns_uri;
+
+			Assert(IsA(r, ResTarget));
+			ns_uri = transformExpr(pstate, r->val, EXPR_KIND_FROM_FUNCTION);
+			ns_uri = coerce_to_specific_type(pstate, ns_uri,
+													 TEXTOID, constructName);
+			assign_expr_collations(pstate, ns_uri);
+			ns_uris = lappend(ns_uris, ns_uri);
+
+			/* Verify consistency of name list: no dupes, only one DEFAULT */
+			if (r->name != NULL)
+			{
+				foreach(lc2, ns_names)
+				{
+					char	   *name = strVal(lfirst(lc2));
+
+					if (name == NULL)
+						continue;
+					if (strcmp(name, r->name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("namespace name \"%s\" is not unique",
+										name),
+								 parser_errposition(pstate, r->location)));
+				}
+			}
+			else
+			{
+				if (default_ns_seen)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("only one default namespace is allowed"),
+							 parser_errposition(pstate, r->location)));
+				default_ns_seen = true;
+			}
+
+			/* Note the string may be NULL */
+			ns_names = lappend(ns_names, makeString(r->name));
+		}
+
+		tf->ns_uris = ns_uris;
+		tf->ns_names = ns_names;
+	}
+
+	tf->location = rtf->location;
+
+	pstate->p_lateral_active = false;
+
+	/*
+	 * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
+	 * there are any lateral cross-references in it.
+	 */
+	is_lateral = rtf->lateral || contain_vars_of_level((Node *) tf, 0);
+
+	rte = addRangeTableEntryForTableFunc(pstate,
+										 tf, rtf->alias, is_lateral, true);
+
+	return rte;
+}
+
 
 /*
  * transformFromClauseItem -
@@ -918,6 +1165,24 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return (Node *) rtr;
 	}
+	else if (IsA(n, RangeTableFunc))
+	{
+		/* table function is like a plain relation */
+		RangeTblRef *rtr;
+		RangeTblEntry *rte;
+		int			rtindex;
+
+		rte = transformRangeTableFunc(pstate, (RangeTableFunc *) n);
+		/* assume new rte is at end */
+		rtindex = list_length(pstate->p_rtable);
+		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
+		*top_rte = rte;
+		*top_rti = rtindex;
+		*namespace = list_make1(makeDefaultNSItem(rte));
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 2a2ac32157..2c3f3cd9ce 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1096,9 +1096,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
 }
 
 /*
- * coerce_to_specific_type()
- *		Coerce an argument of a construct that requires a specific data type.
- *		Also check that input is not a set.
+ * coerce_to_specific_type_typmod()
+ *		Coerce an argument of a construct that requires a specific data type,
+ *		with a specific typmod.  Also check that input is not a set.
  *
  * Returns the possibly-transformed node tree.
  *
@@ -1106,9 +1106,9 @@ coerce_to_boolean(ParseState *pstate, Node *node,
  * processing is wanted.
  */
 Node *
-coerce_to_specific_type(ParseState *pstate, Node *node,
-						Oid targetTypeId,
-						const char *constructName)
+coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
+							   Oid targetTypeId, int32 targetTypmod,
+							   const char *constructName)
 {
 	Oid			inputTypeId = exprType(node);
 
@@ -1117,7 +1117,7 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 		Node	   *newnode;
 
 		newnode = coerce_to_target_type(pstate, node, inputTypeId,
-										targetTypeId, -1,
+										targetTypeId, targetTypmod,
 										COERCION_ASSIGNMENT,
 										COERCE_IMPLICIT_CAST,
 										-1);
@@ -1144,6 +1144,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
 	return node;
 }
 
+/*
+ * coerce_to_specific_type()
+ *		Coerce an argument of a construct that requires a specific data type.
+ *		Also check that input is not a set.
+ *
+ * Returns the possibly-transformed node tree.
+ *
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ */
+Node *
+coerce_to_specific_type(ParseState *pstate, Node *node,
+						Oid targetTypeId,
+						const char *constructName)
+{
+	return coerce_to_specific_type_typmod(pstate, node,
+										  targetTypeId, -1,
+										  constructName);
+}
 
 /*
  * parser_coercion_errposition - report coercion error location, if possible
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 4b73272417..db100be336 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"
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index e693c316e3..e619074220 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1629,6 +1629,68 @@ addRangeTableEntryForFunction(ParseState *pstate,
 }
 
 /*
+ * Add an entry for a table function to the pstate's range table (p_rtable).
+ *
+ * This is much like addRangeTableEntry() except that it makes a values RTE.
+ */
+RangeTblEntry *
+addRangeTableEntryForTableFunc(ParseState *pstate,
+							TableFunc *tf,
+							Alias *alias,
+							bool lateral,
+							bool inFromCl)
+{
+	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	char	   *refname = alias ? alias->aliasname : pstrdup("xmltable");
+	Alias	   *eref;
+	int			numaliases;
+
+	Assert(pstate != NULL);
+
+	rte->rtekind = RTE_TABLEFUNC;
+	rte->relid = InvalidOid;
+	rte->subquery = NULL;
+	rte->tablefunc = tf;
+	rte->coltypes = tf->coltypes;
+	rte->coltypmods = tf->coltypmods;
+	rte->colcollations = tf->colcollations;
+	rte->alias = alias;
+
+	eref = alias ? copyObject(alias) : makeAlias(refname, NIL);
+	numaliases = list_length(eref->colnames);
+
+	/* fill in any unspecified alias columns */
+	if (numaliases < list_length(tf->colnames))
+		eref->colnames = list_concat(eref->colnames,
+									 list_copy_tail(tf->colnames, numaliases));
+
+	rte->eref = eref;
+
+	/*
+	 * Set flags and access permissions.
+	 *
+	 * Subqueries are never checked for access rights.
+	 */
+	rte->lateral = lateral;
+	rte->inh = false;			/* never true for values RTEs */
+	rte->inFromCl = inFromCl;
+
+	rte->requiredPerms = 0;
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->insertedCols = NULL;
+	rte->updatedCols = NULL;
+
+	/*
+	 * Add completed RTE to pstate's range table list, but not to join list
+	 * nor namespace --- caller must do that if appropriate.
+	 */
+	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+
+	return rte;
+}
+
+/*
  * Add an entry for a VALUES list to the pstate's range table (p_rtable).
  *
  * This is much like addRangeTableEntry() except that it makes a values RTE.
@@ -2228,6 +2290,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 			}
 			break;
 		case RTE_VALUES:
+		case RTE_TABLEFUNC:
 		case RTE_CTE:
 			{
 				/* Values or CTE RTE */
@@ -2639,6 +2702,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
 				*varcollid = exprCollation(aliasvar);
 			}
 			break;
+		case RTE_TABLEFUNC:
 		case RTE_VALUES:
 		case RTE_CTE:
 			{
@@ -2685,9 +2749,13 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 			}
 			break;
 		case RTE_SUBQUERY:
+		case RTE_TABLEFUNC:
 		case RTE_VALUES:
 		case RTE_CTE:
-			/* Subselect, Values, CTE RTEs never have dropped columns */
+			/*
+			 * Subselect, Table Functions, Values, CTE RTEs never have dropped
+			 * columns
+			 */
 			result = false;
 			break;
 		case RTE_JOIN:
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2576e31239..3b84140a9b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -396,6 +396,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
 			break;
 		case RTE_FUNCTION:
 		case RTE_VALUES:
+		case RTE_TABLEFUNC:
 			/* not a simple relation, leave it unmarked */
 			break;
 		case RTE_CTE:
@@ -1557,6 +1558,12 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
 			 * its result columns as RECORD, which is not allowed.
 			 */
 			break;
+		case RTE_TABLEFUNC:
+
+			/*
+			 * Table function cannot have columns with RECORD type.
+			 */
+			break;
 		case RTE_CTE:
 			/* CTE reference: examine subquery's output expr */
 			if (!rte->self_reference)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d3e44fb135..d069e8a547 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -433,6 +433,10 @@ rewriteRuleAction(Query *parsetree,
 					sub_action->hasSubLinks =
 						checkExprHasSubLink((Node *) rte->functions);
 					break;
+				case RTE_TABLEFUNC:
+					sub_action->hasSubLinks =
+						checkExprHasSubLink((Node *) rte->tablefunc);
+					break;
 				case RTE_VALUES:
 					sub_action->hasSubLinks =
 						checkExprHasSubLink((Node *) rte->values_lists);
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 894f026a41..0a1498faef 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -379,6 +379,9 @@ float4out(PG_FUNCTION_ARGS)
 				if (ndig < 1)
 					ndig = 1;
 
+				if (ndig > 50)
+					ndig = 50;
+
 				snprintf(ascii, MAXFLOATWIDTH + 1, "%.*g", ndig, num);
 			}
 	}
@@ -615,6 +618,9 @@ float8out_internal(double num)
 				if (ndig < 1)
 					ndig = 1;
 
+				if (ndig > 50)
+					ndig = 50;
+
 				snprintf(ascii, MAXDOUBLEWIDTH + 1, "%.*g", ndig, num);
 			}
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f355954b53..9ac3df76e1 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -428,6 +428,8 @@ static void get_const_expr(Const *constval, deparse_context *context,
 static void get_const_collation(Const *constval, deparse_context *context);
 static void simple_quote_literal(StringInfo buf, const char *val);
 static void get_sublink_expr(SubLink *sublink, deparse_context *context);
+static void get_tablefunc(TableFunc *tf, deparse_context *context,
+			  bool showimplicit);
 static void get_from_clause(Query *query, const char *prefix,
 				deparse_context *context);
 static void get_from_clause_item(Node *jtnode, Query *query,
@@ -6709,6 +6711,7 @@ get_name_for_var_field(Var *var, int fieldno,
 			/* else fall through to inspect the expression */
 			break;
 		case RTE_FUNCTION:
+		case RTE_TABLEFUNC:
 
 			/*
 			 * We couldn't get here unless a function is declared with one of
@@ -8548,6 +8551,10 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_TableFunc:
+			get_tablefunc((TableFunc *) node, context, showimplicit);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
@@ -9286,6 +9293,124 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
 
 
 /* ----------
+ * get_tablefunc			- Parse back a table function
+ * ----------
+ */
+static void
+get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
+{
+	StringInfo	buf = context->buf;
+
+	/*
+	 * Deparse TableFunc - now only one TableFunc producer, the
+	 * function XMLTABLE.
+	 */
+
+	/* c_expr shoud be closed in brackets */
+	appendStringInfoString(buf, "XMLTABLE(");
+
+	if (tf->ns_uris != NIL)
+	{
+		ListCell   *lc1,
+				   *lc2;
+		bool		first = true;
+
+		appendStringInfoString(buf, "XMLNAMESPACES (");
+		forboth(lc1, tf->ns_uris, lc2, tf->ns_names)
+		{
+			Node	   *expr = (Node *) lfirst(lc1);
+			char	   *name = strVal(lfirst(lc2));
+
+			if (!first)
+				appendStringInfoString(buf, ", ");
+			else
+				first = false;
+
+			if (name != NULL)
+			{
+				get_rule_expr(expr, context, showimplicit);
+				appendStringInfo(buf, " AS %s", name);
+			}
+			else
+			{
+				appendStringInfoString(buf, "DEFAULT ");
+				get_rule_expr(expr, context, showimplicit);
+			}
+		}
+		appendStringInfoString(buf, "), ");
+	}
+
+	appendStringInfoChar(buf, '(');
+	get_rule_expr((Node *) tf->rowexpr, context, showimplicit);
+	appendStringInfoString(buf, ") PASSING (");
+	get_rule_expr((Node *) tf->docexpr, context, showimplicit);
+	appendStringInfoChar(buf, ')');
+
+	if (tf->evalcols)
+	{
+		ListCell   *l1;
+		ListCell   *l2;
+		ListCell   *l3;
+		ListCell   *l4;
+		ListCell   *l5;
+		int			colnum = 0;
+
+		l2 = list_head(tf->coltypes);
+		l3 = list_head(tf->coltypmods);
+		l4 = list_head(tf->colexprs);
+		l5 = list_head(tf->coldefexprs);
+
+		appendStringInfoString(buf, " COLUMNS ");
+		foreach(l1, tf->colnames)
+		{
+			char	   *colname = strVal(lfirst(l1));
+			Oid			typid;
+			int32		typmod;
+			Node	   *colexpr;
+			Node	   *coldefexpr;
+			bool		ordinality = tf->ordinalitycol == colnum;
+			bool		notnull = bms_is_member(colnum, tf->notnulls);
+
+			typid = lfirst_oid(l2);
+			l2 = lnext(l2);
+			typmod = lfirst_int(l3);
+			l3 = lnext(l3);
+			colexpr = (Node *) lfirst(l4);
+			l4 = lnext(l4);
+			coldefexpr = (Node *) lfirst(l5);
+			l5 = lnext(l5);
+
+			if (colnum > 0)
+				appendStringInfoString(buf, ", ");
+			colnum++;
+
+			appendStringInfo(buf, "%s %s", quote_identifier(colname),
+							 ordinality ? "FOR ORDINALITY" :
+						format_type_with_typemod(typid, typmod));
+			if (ordinality)
+				continue;
+
+			if (coldefexpr != NULL)
+			{
+				appendStringInfoString(buf, " DEFAULT (");
+				get_rule_expr((Node *) coldefexpr, context, showimplicit);
+				appendStringInfoChar(buf, ')');
+			}
+			if (colexpr != NULL)
+			{
+				appendStringInfoString(buf, " PATH (");
+				get_rule_expr((Node *) colexpr, context, showimplicit);
+				appendStringInfoChar(buf, ')');
+			}
+			if (notnull)
+				appendStringInfoString(buf, " NOT NULL");
+		}
+	}
+
+	appendStringInfoChar(buf, ')');
+}
+
+/* ----------
  * get_from_clause			- Parse back a FROM clause
  *
  * "prefix" is the keyword that denotes the start of the list of FROM
@@ -9518,6 +9643,9 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 				if (rte->funcordinality)
 					appendStringInfoString(buf, " WITH ORDINALITY");
 				break;
+			case RTE_TABLEFUNC:
+				get_tablefunc(rte->tablefunc, context, true);
+				break;
 			case RTE_VALUES:
 				/* Values list RTE */
 				appendStringInfoChar(buf, '(');
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index e8bce3b806..fb2501a92c 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/tablefunc.h"
 #include "fmgr.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
@@ -145,6 +146,7 @@ static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
 static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
 					   ArrayBuildState *astate,
 					   PgXmlErrorContext *xmlerrcxt);
+static xmlChar *pg_xmlCharStrndup(char *str, size_t len);
 #endif   /* USE_LIBXML */
 
 static StringInfo query_to_xml_internal(const char *query, char *tablename,
@@ -165,6 +167,49 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
 						  char *tablename, bool nulls, bool tableforest,
 						  const char *targetns, bool top_level);
 
+/* XMLTABLE support */
+#ifdef USE_LIBXML
+/* random number to identify XmlTableContext */
+#define XMLTABLE_CONTEXT_MAGIC	46922182
+typedef struct XmlTableBuilderData
+{
+	int			magic;
+	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(TableFuncScanState *state);
+static void XmlTableSetDoc(TableFuncScanState *state, Datum value);
+static void XmlTableSetNamespace(TableFuncScanState *state, char *name,
+					 char *uri);
+static void XmlTableSetRowFilter(TableFuncScanState *state, char *path);
+static void XmlTableSetColumnFilter(TableFuncScanState *state, char *path,
+						int colnum);
+static bool XmlTableFetchRow(TableFuncScanState *state);
+static Datum XmlTableGetValue(TableFuncScanState *state, int colnum,
+				 bool *isnull);
+static void XmlTableDestroyBuilder(TableFuncScanState *state);
+
+const TableFuncRoutine XmlTableRoutine =
+{
+	XmlTableInitBuilder,
+	XmlTableSetDoc,
+	XmlTableSetNamespace,
+	XmlTableSetRowFilter,
+	XmlTableSetColumnFilter,
+	XmlTableFetchRow,
+	XmlTableGetValue,
+	XmlTableDestroyBuilder
+};
+
 #define NO_XML_SUPPORT() \
 	ereport(ERROR, \
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
@@ -1113,6 +1158,19 @@ xml_pnstrdup(const xmlChar *str, size_t len)
 	return result;
 }
 
+/* Ditto, except input is char* */
+static xmlChar *
+pg_xmlCharStrndup(char *str, size_t len)
+{
+	xmlChar    *result;
+
+	result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(result, str, len);
+	result[len] = '\0';
+
+	return result;
+}
+
 /*
  * str is the null-terminated input string.  Remaining arguments are
  * output arguments; each can be NULL if value is not wanted.
@@ -3811,13 +3869,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
 				(errcode(ERRCODE_DATA_EXCEPTION),
 				 errmsg("empty XPath expression")));
 
-	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
-	memcpy(string, datastr, len);
-	string[len] = '\0';
-
-	xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
-	memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
-	xpath_expr[xpath_len] = '\0';
+	string = pg_xmlCharStrndup(datastr, len);
+	xpath_expr = pg_xmlCharStrndup(VARDATA(xpath_expr_text), xpath_len);
 
 	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
 
@@ -4065,3 +4118,509 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * support functions for XMLTABLE
+ *
+ */
+#ifdef USE_LIBXML
+
+/*
+ * Returns private data from executor state. Ensure validity by check with
+ * MAGIC number.
+ */
+static inline XmlTableBuilderData *
+GetXmlTableBuilderPrivateData(TableFuncScanState *state, const char *fname)
+{
+	XmlTableBuilderData *result;
+
+	if (!IsA(state, TableFuncScanState))
+		elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+	result = (XmlTableBuilderData *) state->opaque;
+	if (result->magic != XMLTABLE_CONTEXT_MAGIC)
+		elog(ERROR, "%s called with invalid TableFuncScanState", fname);
+
+	return result;
+}
+#endif
+
+/*
+ * XmlTableInitBuilder
+ *		Fill in TableFuncScanState for XmlTable builder. Initialize XML parser.
+ */
+static void
+XmlTableInitBuilder(TableFuncScanState *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 */
+}
+
+/*
+ * XmlTableSetDoc
+ *		Install the input document
+ */
+static void
+XmlTableSetDoc(TableFuncScanState *state, Datum value)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	xmltype    *xmlval = DatumGetXmlP(value);
+	char	   *str;
+	xmlChar    *xstr;
+	int			length;
+	volatile xmlDocPtr doc = NULL;
+	volatile xmlXPathContextPtr xpathcxt = NULL;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetDoc");
+
+	/*
+	 * Use out function for casting to string (remove encoding property).
+	 * See comment in xml_out.
+	 */
+	str = xml_out_internal(xmlval, 0);
+
+	length = strlen(str);
+	xstr = pg_xmlCharStrndup(str, length);
+
+	PG_TRY();
+	{
+		doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL, 0);
+		if (doc == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+						"could not parse XML document");
+		xpathcxt = xmlXPathNewContext(doc);
+		if (xpathcxt == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate XPath context");
+		xpathcxt->node = xmlDocGetRootElement(doc);
+		if (xpathcxt->node == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not find root XML element");
+	}
+	PG_CATCH();
+	{
+		if (xpathcxt != NULL)
+			xmlXPathFreeContext(xpathcxt);
+		if (doc != NULL)
+			xmlFreeDoc(doc);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xtCxt->doc = doc;
+	xtCxt->xpathcxt = xpathcxt;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetNamespace
+ *		Add a namespace declaration
+ */
+static void
+XmlTableSetNamespace(TableFuncScanState *state, char *name, char *uri)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	if (name == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("DEFAULT namespace is not supported")));
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetNamespace");
+
+	if (xmlXPathRegisterNs(xtCxt->xpathcxt,
+						   pg_xmlCharStrndup(name, strlen(name)),
+						   pg_xmlCharStrndup(uri, strlen(uri))))
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"could not set XML namespace");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetRowFilter
+ *		Install the row-filter Xpath expression.
+ */
+static void
+XmlTableSetRowFilter(TableFuncScanState *state, char *path)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	xmlChar    *xstr;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetRowFilter");
+
+	if (*path == '\0')
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_EXCEPTION),
+				 errmsg("row path filter must not be empty string")));
+
+	xstr = pg_xmlCharStrndup(path, strlen(path));
+
+	xtCxt->xpathcomp = xmlXPathCompile(xstr);
+	if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
+		xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+					"invalid XPath expression");
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableSetColumnFilter
+ *		Install the column-filter Xpath expression, for the given column.
+ *
+ * The path can be NULL, when the only one result column is implicit. XXX fix
+ */
+static void
+XmlTableSetColumnFilter(TableFuncScanState *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 = pg_xmlCharStrndup(path, strlen(path));
+
+		xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+		if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
+						"invalid XPath expression");
+	}
+	else
+	{
+		Assert(!state->evalcols || colnum == state->ordinalitycol);
+
+		xtCxt->xpathscomp[colnum] = NULL;
+	}
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * XmlTableFetchRow
+ *		Prepare the next "current" tuple for upcoming GetValue calls.
+ *		Returns FALSE if the row-filter expression returned no more rows.
+ */
+static bool
+XmlTableFetchRow(TableFuncScanState *state)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableFetchRow");
+
+	/*
+	 * XmlTable returns table - set of composite values. The error context, is
+	 * used for producement more values, between two calls, there can be
+	 * created and used another libxml2 error context. It is libxml2 global
+	 * value, so it should be refreshed any time before any libxml2 usage,
+	 * that is finished by returning some value.
+	 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	if (xtCxt->xpathobj == NULL)
+	{
+		xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
+		if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		xtCxt->row_count = 0;
+	}
+
+	if (xtCxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (xtCxt->xpathobj->nodesetval != NULL)
+		{
+			if (xtCxt->row_count++ < xtCxt->xpathobj->nodesetval->nodeNr)
+				return true;
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+
+	return false;
+}
+
+/*
+ * XmlTableGetValue
+ *		Return the value for column number 'colnum' for the current row.  If
+ *		column -1 is requested, return representation of the whole row.
+ *
+ * This leaks memory, so be sure to reset often the context in which it's
+ * called.
+ */
+static Datum
+XmlTableGetValue(TableFuncScanState *state, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	XmlTableBuilderData *xtCxt;
+	Datum		result = (Datum) 0;
+	xmlNodePtr	cur;
+	char	   *cstr = NULL;
+	volatile xmlXPathObjectPtr xpathobj = NULL;
+
+	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
+
+	Assert(xtCxt->xpathobj &&
+		   xtCxt->xpathobj->type == XPATH_NODESET &&
+		   xtCxt->xpathobj->nodesetval != NULL);
+
+	/* Propagate context related error context to libxml2 */
+	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);
+
+	*isnull = false;
+
+	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
+
+	if (colnum == -1)
+	{
+		/*
+		 * Use whole row, when user doesn't specify a column. The target type
+		 * must be XMLOID.
+		 */
+		Assert(state->resultSlot->tts_tupleDescriptor->attrs[0]->atttypid == XMLOID);
+
+		result = PointerGetDatum(xml_xmlnodetoxmltype(cur, xtCxt->xmlerrcxt));
+
+		return result;
+	}
+
+	Assert(xtCxt->xpathscomp[colnum] != NULL);
+
+	/* fast leaving */
+	if (cur->type != XML_ELEMENT_NODE)
+		elog(ERROR, "unexpected xmlNode type");
+
+	PG_TRY();
+	{
+		Form_pg_attribute attr;
+
+		attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum];
+
+		/* Set current node as entry point for XPath evaluation */
+		xmlXPathSetContextNode(cur, xtCxt->xpathcxt);
+
+		/* Evaluate column path */
+		xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
+		if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
+			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		/*
+		 * There are four possible cases, depending on the number of nodes
+		 * returned by the XPath expression and the type of the target column:
+		 * a) XPath returns no nodes.  b) One node is returned, and column is
+		 * of type XML.  c) One node, column type other than XML.  d) Multiple
+		 * nodes are returned.
+		 */
+		if (xpathobj->type == XPATH_NODESET)
+		{
+			int			count = 0;
+			Oid			targettypid = attr->atttypid;
+
+			if (xpathobj->nodesetval != NULL)
+				count = xpathobj->nodesetval->nodeNr;
+
+			if (xpathobj->nodesetval == NULL || count == 0)
+			{
+				*isnull = true;
+			}
+			else if (count == 1 && targettypid == XMLOID)
+			{
+				text	   *textstr;
+
+				/* simple case, result is one value */
+				textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
+											   xtCxt->xmlerrcxt);
+				cstr = text_to_cstring(textstr);
+			}
+			else if (count == 1)
+			{
+				xmlChar    *str;
+
+				str = xmlNodeListGetString(xtCxt->doc,
+						   xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
+										   1);
+
+				if (str != NULL)
+				{
+					PG_TRY();
+					{
+						cstr = pstrdup((char *) str);
+					}
+					PG_CATCH();
+					{
+						xmlFree(str);
+						PG_RE_THROW();
+					}
+					PG_END_TRY();
+					xmlFree(str);
+				}
+				else
+				{
+					/* Return empty string when tag is empty */
+					cstr = "";
+				}
+			}
+			else
+			{
+				StringInfoData str;
+				int			i;
+
+				Assert(count > 1);
+
+				/*
+				 * When evaluating the XPath expression returns multiple
+				 * nodes, the result is the concatenation of them all. The
+				 * target type must be XML.
+				 */
+				if (targettypid != XMLOID)
+					ereport(ERROR,
+							(errcode(ERRCODE_CARDINALITY_VIOLATION),
+							 errmsg("more than one value returned by column XPath expression")));
+
+				/* Concatenate serialized values */
+				initStringInfo(&str);
+				for (i = 0; i < count; i++)
+				{
+					appendStringInfoText(&str,
+					   xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
+											xtCxt->xmlerrcxt));
+				}
+				cstr = str.data;
+			}
+		}
+		else if (xpathobj->type == XPATH_STRING)
+		{
+			cstr = (char *) xpathobj->stringval;
+		}
+		else
+			elog(ERROR, "unexpected XPath object type %u", xpathobj->type);
+
+		/*
+		 * By here, either cstr contains the result value, or the isnull flag
+		 * has been set.
+		 */
+		Assert(cstr || *isnull);
+
+		if (!*isnull)
+			result = InputFunctionCall(&state->in_functions[colnum],
+									   cstr,
+									   state->typioparams[colnum],
+									   attr->atttypmod);
+	}
+	PG_CATCH();
+	{
+		if (xpathobj != NULL)
+			xmlXPathFreeObject(xpathobj);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	xmlXPathFreeObject(xpathobj);
+
+	return result;
+#else
+	NO_XML_SUPPORT();
+#endif   /* not USE_LIBXML */
+}
+
+/*
+ * Release all libxml2 memory and related resources
+ */
+static void
+XmlTableDestroyBuilder(TableFuncScanState *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/include/executor/nodeTableFuncscan.h b/src/include/executor/nodeTableFuncscan.h
new file mode 100644
index 0000000000..529c929993
--- /dev/null
+++ b/src/include/executor/nodeTableFuncscan.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeTableFuncscan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeTableFuncscan.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODETABLEFUNCSCAN_H
+#define NODETABLEFUNCSCAN_H
+
+#include "nodes/execnodes.h"
+
+extern TableFuncScanState *ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecTableFuncScan(TableFuncScanState *node);
+extern void ExecEndTableFuncScan(TableFuncScanState *node);
+extern void ExecReScanTableFuncScan(TableFuncScanState *node);
+
+#endif   /* NODETABLEFUNCSCAN_H */
diff --git a/src/include/executor/tablefunc.h b/src/include/executor/tablefunc.h
new file mode 100644
index 0000000000..cacfeabe11
--- /dev/null
+++ b/src/include/executor/tablefunc.h
@@ -0,0 +1,65 @@
+/*-------------------------------------------------------------------------
+ *
+ * tablefunc.h
+ *				interface for TableFunc builder
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/tablefunc.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TABLEFUNC_H
+#define TABLEFUNC_H
+
+#include "nodes/execnodes.h"
+
+/*
+ * TableFuncRoutine holds function pointers used for generating content of
+ * table-producer functions, such as XMLTABLE.
+ *
+ * InitBuilder initialize table builder private objects.  The output tuple
+ * descriptor, input functions for the columns, and typioparams are passed
+ * from executor state.
+ *
+ * SetDoc is called to define the input document.  The table builder may
+ * apply additional transformations not exposed outside the table builder
+ * context.
+ *
+ * SetNamespace is called to pass namespace declarations from the table
+ * expression.  This function may be NULL if namespaces are not supported by
+ * the table builder.  Namespaces must be given before setting the row and
+ * column filters.  If the name is given as NULL, the entry shall be for the
+ * default namespace.
+ *
+ * SetRowFilter is called do define the row-generating filter, which shall be
+ * used to extract each row from the input document.
+ *
+ * SetColumnFilter is called once for each column, to define the column-
+ * generating filter for the given column.
+ *
+ * FetchRow shall be called repeatedly until it returns that no more rows are
+ * found in the document.  On each invocation it shall set state in the table
+ * builder context such that each subsequent GetValue call returns the 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 TableFuncRoutine
+{
+	void		(*InitBuilder) (TableFuncScanState *state);
+	void		(*SetDoc) (TableFuncScanState *state, Datum value);
+	void		(*SetNamespace) (TableFuncScanState *state, char *name,
+											 char *uri);
+	void		(*SetRowFilter) (TableFuncScanState *state, char *path);
+	void		(*SetColumnFilter) (TableFuncScanState *state, char *path,
+												int colnum);
+	bool		(*FetchRow) (TableFuncScanState *state);
+	Datum		(*GetValue) (TableFuncScanState *state, int colnum, bool *isnull);
+	void		(*DestroyBuilder) (TableFuncScanState *state);
+} TableFuncRoutine;
+
+#endif   /* TABLEFUNC_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9f41babf35..7461b8d8d0 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1582,6 +1582,35 @@ typedef struct ValuesScanState
 } ValuesScanState;
 
 /* ----------------
+ *		TableFuncScanState node
+ *
+ * Used in table-expression functions like XMLTABLE.
+ * ----------------
+ */
+typedef struct TableFuncScanState
+{
+	ScanState	ss;				/* its first field is NodeTag */
+	ExprState  *docexpr;		/* state for document expression */
+	ExprState  *rowexpr;		/* state for row-generating expression */
+	List	   *colexprs;		/* state for column-generating expression */
+	List	   *coldefexprs;	/* state for column default expressions */
+	List	   *ns_names;		/* list of str nodes with namespace names */
+	List	   *ns_uris;		/* list of states of namespace uri exprs */
+	Bitmapset  *notnulls;		/* nullability flag for each output column */
+	bool		evalcols;		/* true, when column was not defined by user */
+	int			ordinalitycol;	/* number of ordinality column or -1 */
+	void	   *opaque;			/* table builder private space */
+	const struct TableFuncRoutine *routine;		/* table builder methods */
+	FmgrInfo   *in_functions;	/* input function for each column */
+	Oid		   *typioparams;	/* typioparam for each column */
+	int			rownum;			/* row number to be output next */
+	MemoryContext buildercxt;	/* memory context used by builder */
+	MemoryContext perValueCxt;	/* short life context for a value evaluation */
+	Tuplestorestate *tupstore;	/* output tuple store */
+	TupleTableSlot *resultSlot; /* output tuple table slot */
+} TableFuncScanState;
+
+/* ----------------
  *	 CteScanState information
  *
  *		CteScan nodes are used to scan a CommonTableExpr query.
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 95dd8baadd..14ee98b78b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -61,6 +61,7 @@ typedef enum NodeTag
 	T_SubqueryScan,
 	T_FunctionScan,
 	T_ValuesScan,
+	T_TableFuncScan,
 	T_CteScan,
 	T_WorkTableScan,
 	T_ForeignScan,
@@ -109,6 +110,7 @@ typedef enum NodeTag
 	T_TidScanState,
 	T_SubqueryScanState,
 	T_FunctionScanState,
+	T_TableFuncScanState,
 	T_ValuesScanState,
 	T_CteScanState,
 	T_WorkTableScanState,
@@ -135,6 +137,7 @@ typedef enum NodeTag
 	 */
 	T_Alias,
 	T_RangeVar,
+	T_TableFunc,
 	T_Expr,
 	T_Var,
 	T_Const,
@@ -438,6 +441,8 @@ typedef enum NodeTag
 	T_RangeSubselect,
 	T_RangeFunction,
 	T_RangeTableSample,
+	T_RangeTableFunc,
+	T_RangeTableFuncCol,
 	T_TypeName,
 	T_ColumnDef,
 	T_IndexElem,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5afc3ebea0..e7f4205c00 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -575,6 +575,40 @@ typedef struct RangeTableSample
 } RangeTableSample;
 
 /*
+ * RangeTableFunc - a raw form of pseudo functions like XMLTABLE
+ */
+typedef struct RangeTableFunc
+{
+	NodeTag		type;
+	bool		lateral;		/* does it have LATERAL prefix? */
+	Node	   *docexpr;
+	Node	   *rowexpr;
+	List	   *namespaces;		/* list of namespaces */
+	List	   *columns;		/* list of columns (TableFuncCol) */
+	Alias	   *alias;			/* table alias & optional column aliases */
+	int			location;		/* token location, or -1 if unknown */
+} RangeTableFunc;
+
+/*
+ * RangeTableFuncCol - one column in a RangeTableFunc 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 RangeTableFuncCol
+{
+	NodeTag		type;
+	char	   *colname;		/* name of generated column */
+	TypeName   *typeName;		/* type of generated column */
+	bool		for_ordinality; /* whether this is FOR ORDINALITY */
+	bool		is_not_null;	/* nullability flag */
+	Node	   *colexpr;		/* column filter expression */
+	Node	   *coldefexpr;		/* column default value expr */
+	int			location;		/* token location, or -1 if unknown */
+} RangeTableFuncCol;
+
+/*
  * ColumnDef - column definition (used in various creates)
  *
  * If the column has a default value, we may have the value expression
@@ -871,6 +905,7 @@ typedef enum RTEKind
 	RTE_SUBQUERY,				/* subquery in FROM */
 	RTE_JOIN,					/* join */
 	RTE_FUNCTION,				/* function in FROM */
+	RTE_TABLEFUNC,				/* TableFunc(.., column list) */
 	RTE_VALUES,					/* VALUES (<exprlist>), (<exprlist>), ... */
 	RTE_CTE						/* common table expr (WITH list element) */
 } RTEKind;
@@ -932,6 +967,11 @@ typedef struct RangeTblEntry
 	bool		funcordinality; /* is this called WITH ORDINALITY? */
 
 	/*
+	 * Fields valid for a TableFunc RTE (else NIL/zero)
+	 */
+	TableFunc	   *tablefunc;
+
+	/*
 	 * Fields valid for a values RTE (else NIL):
 	 */
 	List	   *values_lists;	/* list of expression lists */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index f72f7a8978..c6bcc14411 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -496,6 +496,16 @@ typedef struct ValuesScan
 } ValuesScan;
 
 /* ----------------
+ *		TableFunc scan node
+ * ----------------
+ */
+typedef struct TableFuncScan
+{
+	Scan		scan;
+	TableFunc	   *tablefunc;		/* table function node */
+} TableFuncScan;
+
+/* ----------------
  *		CteScan node
  * ----------------
  */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 235bc75096..e7e568ae42 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"
 
 
@@ -73,6 +74,29 @@ typedef struct RangeVar
 } RangeVar;
 
 /*
+ * TableFunc - node for a table function, such as XMLTABLE.
+ */
+typedef struct TableFunc
+{
+	NodeTag		type;
+	List	   *ns_uris;		/* list of namespace uri */
+	List	   *ns_names;		/* list of namespace names */
+	Node	   *docexpr;		/* input document expression */
+	Node	   *rowexpr;		/* row filter expression */
+	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;		/* nullability flag for each output column */
+	int			ordinalitycol;	/* ordinality column, -1 if not used */
+	bool		evalcols;		/* true, when columns should be evaluated */
+	int			location;		/* token location, or -1 if unknown */
+} TableFunc;
+
+/*
  * IntoClause - target information for SELECT INTO, CREATE TABLE AS, and
  * CREATE MATERIALIZED VIEW
  *
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 72200fa531..d02aba1559 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -89,8 +89,12 @@ extern void cost_subqueryscan(SubqueryScanPath *path, PlannerInfo *root,
 				  RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_functionscan(Path *path, PlannerInfo *root,
 				  RelOptInfo *baserel, ParamPathInfo *param_info);
+extern void cost_tableexprscan(Path *path, PlannerInfo *root,
+				  RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_valuesscan(Path *path, PlannerInfo *root,
 				RelOptInfo *baserel, ParamPathInfo *param_info);
+extern void cost_tablefuncscan(Path *path, PlannerInfo *root,
+				RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_ctescan(Path *path, PlannerInfo *root,
 			 RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_recursive_union(Path *runion, Path *nrterm, Path *rterm);
@@ -181,6 +185,7 @@ extern void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel,
 					   double cte_rows);
+extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
 extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 53cad247dc..4588680f4f 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -82,8 +82,12 @@ extern SubqueryScanPath *create_subqueryscan_path(PlannerInfo *root,
 						 List *pathkeys, Relids required_outer);
 extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
 						 List *pathkeys, Relids required_outer);
+extern Path *create_tablexprscan_path(PlannerInfo *root, RelOptInfo *rel,
+						 List *pathkeys, Relids required_outer);
 extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel,
 					   Relids required_outer);
+extern Path *create_tablefuncscan_path(PlannerInfo *root, RelOptInfo *rel,
+						 List *pathkeys, Relids required_outer);
 extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel,
 					Relids required_outer);
 extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 985d6505ec..28c4dab258 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)
@@ -446,10 +447,12 @@ PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD)
+PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD)
 PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD)
+PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD)
 PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index b50992cfd9..3eed81966d 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/parser/parse_relation.h b/src/include/parser/parse_relation.h
index cfb2e5b88c..dcb6194b1a 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -91,6 +91,11 @@ extern RangeTblEntry *addRangeTableEntryForValues(ParseState *pstate,
 							Alias *alias,
 							bool lateral,
 							bool inFromCl);
+extern RangeTblEntry *addRangeTableEntryForTableFunc(ParseState *pstate,
+							TableFunc *tf,
+							Alias *alias,
+							bool lateral,
+							bool inFromCl);
 extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,
 						  List *colnames,
 						  JoinType jointype,
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index cc1fc390e8..e570b71c04 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/tablefunc.h"
 
 typedef struct varlena xmltype;
 
@@ -76,4 +77,6 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern const TableFuncRoutine XmlTableRoutine;
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119f1e..84ccd33a10 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,524 @@ 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)
+
+-- XMLTABLE with columns
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+CREATE VIEW xmltableview1 AS SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+    "xmltable"._id,
+    "xmltable".country_name,
+    "xmltable".country_id,
+    "xmltable".region_id,
+    "xmltable".size,
+    "xmltable".unit,
+    "xmltable".premier_name
+   FROM ( SELECT xmldata.data
+           FROM xmldata) x,
+    LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+             QUERY PLAN             
+------------------------------------
+ Nested Loop
+   ->  Seq Scan on xmldata
+   ->  TableFunc 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ a  
+----
+ 10
+(1 row)
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+SELECT * FROM xmltableview2;
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  DEFAULT namespace is not supported
+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.* 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ Japan        |         3
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+                                                                                    QUERY PLAN                                                                                    
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+         Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+         Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia      | AU         |         3 |      |      | not specified
+  2 |   2 | China          | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong       | HK         |         3 |      |      | not specified
+  4 |   4 | India          | IN         |         3 |      |      | not specified
+  5 |   5 | Japan          | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore      | SG         |         3 |  791 | km   | not specified
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+ 20 |   1 | Egypt          | EG         |         1 |      |      | not specified
+ 21 |   2 | Sudan          | SD         |         1 |      |      | not specified
+(11 rows)
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+         Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ERROR:  null is not allowed in column "size"
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a  
+----
+ 11
+ 12
+ 13
+ 14
+(4 rows)
+
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index d7027030c3..556c64b73a 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -827,3 +827,491 @@ 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.
+-- XMLTABLE with columns
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+CREATE VIEW xmltableview1 AS SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+    "xmltable"._id,
+    "xmltable".country_name,
+    "xmltable".country_id,
+    "xmltable".region_id,
+    "xmltable".size,
+    "xmltable".unit,
+    "xmltable".premier_name
+   FROM ( SELECT xmldata.data
+           FROM xmldata) x,
+    LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+             QUERY PLAN             
+------------------------------------
+ Nested Loop
+   ->  Seq Scan on xmldata
+   ->  TableFunc 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT * FROM xmltableview2;
+ERROR:  relation "xmltableview2" does not exist
+LINE 1: SELECT * FROM xmltableview2;
+                      ^
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  unsupported XML feature
+LINE 3:                       PASSING '<rows xmlns="http://x.y"><row...
+                                      ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+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.* 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+(0 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+                                                                                    QUERY PLAN                                                                                    
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+         Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+         Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmldata VALUES('<ROWS>
+                                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmldata VALUES('<ROWS>
+                                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+         Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name 
+----+-----+--------------+------------+-----------+------+------+--------------
+(0 rows)
+
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+ERROR:  unsupported XML feature
+LINE 1: INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D')...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a 
+---
+(0 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a 
+---
+(0 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a 
+---
+(0 rows)
+
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 530faf5daf..fe7a25da87 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,524 @@ 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)
+
+-- XMLTABLE with columns
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+CREATE VIEW xmltableview1 AS SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+SELECT * FROM xmltableview1;
+ id | _id | country_name | country_id | region_id | size | unit | premier_name  
+----+-----+--------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia    | AU         |         3 |      |      | not specified
+  2 |   2 | China        | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong     | HK         |         3 |      |      | not specified
+  4 |   4 | India        | IN         |         3 |      |      | not specified
+  5 |   5 | Japan        | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore    | SG         |         3 |  791 | km   | not specified
+(6 rows)
+
+\sv xmltableview1
+CREATE OR REPLACE VIEW public.xmltableview1 AS
+ SELECT "xmltable".id,
+    "xmltable"._id,
+    "xmltable".country_name,
+    "xmltable".country_id,
+    "xmltable".region_id,
+    "xmltable".size,
+    "xmltable".unit,
+    "xmltable".premier_name
+   FROM ( SELECT xmldata.data
+           FROM xmldata) x,
+    LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1;
+             QUERY PLAN             
+------------------------------------
+ Nested Loop
+   ->  Seq Scan on xmldata
+   ->  TableFunc 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- XMLNAMESPACES tests
+SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+ a  
+----
+ 10
+(1 row)
+
+CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz),
+                      '/zz:rows/zz:row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'zz:a');
+SELECT * FROM xmltableview2;
+ a  
+----
+ 10
+(1 row)
+
+SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'),
+                      '/rows/row'
+                      PASSING '<rows xmlns="http://x.y"><row><a>10</a></row></rows>'
+                      COLUMNS a int PATH 'a');
+ERROR:  DEFAULT namespace is not supported
+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.* 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
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+(7 rows)
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+ COUNTRY_NAME | REGION_ID 
+--------------+-----------
+ Japan        |         3
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+                                                                                    QUERY PLAN                                                                                    
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable"."COUNTRY_NAME", "xmltable"."REGION_ID"
+         Table Function Call: XMLTABLE(('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]'::text) PASSING (xmldata.data) COLUMNS "COUNTRY_NAME" text, "REGION_ID" integer)
+         Filter: ("xmltable"."COUNTRY_NAME" = 'Japan'::text)
+(8 rows)
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+  1 |   1 | Australia      | AU         |         3 |      |      | not specified
+  2 |   2 | China          | CN         |         3 |      |      | not specified
+  3 |   3 | HongKong       | HK         |         3 |      |      | not specified
+  4 |   4 | India          | IN         |         3 |      |      | not specified
+  5 |   5 | Japan          | JP         |         3 |      |      | Sinzo Abe
+  6 |   6 | Singapore      | SG         |         3 |  791 | km   | not specified
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+ 20 |   1 | Egypt          | EG         |         1 |      |      | not specified
+ 21 |   2 | Sudan          | SD         |         1 |      |      | not specified
+(11 rows)
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+ id | _id |  country_name  | country_id | region_id | size | unit | premier_name  
+----+-----+----------------+------------+-----------+------+------+---------------
+ 10 |   1 | Czech Republic | CZ         |         2 |      |      | Milos Zeman
+ 11 |   2 | Germany        | DE         |         2 |      |      | not specified
+ 12 |   3 | France         | FR         |         2 |      |      | not specified
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+   ->  Seq Scan on public.xmldata
+         Output: xmldata.data
+   ->  TableFunc Scan on "xmltable"
+         Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name
+         Table Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text))
+         Filter: ("xmltable".region_id = 2)
+(8 rows)
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+ERROR:  null is not allowed in column "size"
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+ proname | proowner | procost | pronargs | proargnames | proargtypes 
+---------+----------+---------+----------+-------------+-------------
+(0 rows)
+
+CREATE TABLE xmltest2(x xml, _path text);
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+ a 
+---
+ 1
+ 2
+ 3
+ 2
+(4 rows)
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+ a  
+----
+ 11
+ 12
+ 13
+ 14
+(4 rows)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30067..af635b945c 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,296 @@ 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>');
+
+-- 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.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id');
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY);
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.');
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*');
+
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text);
+SELECT * FROM xmltable('/root' passing '<root><element>a1a<!-- aaaa -->a2a<?aaaaa?> <!--z-->  bbbb<x>xxx</x>cccc</element></root>' COLUMNS element text PATH 'element/text()'); -- should fail
+
+-- CDATA test
+select * from xmltable('r' passing '<d><r><c><![CDATA[<hello> &"<>!<a>foo</a>]]></c></r><r><c>2</c></r></d>' columns c text);
+
+-- XML builtin entities
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent text);
+SELECT * FROM xmltable('/x/a' PASSING '<x><a><ent>&apos;</ent></a><a><ent>&quot;</ent></a><a><ent>&amp;</ent></a><a><ent>&lt;</ent></a><a><ent>&gt;</ent></a></x>' COLUMNS ent xml);
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+-- test qual
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) WHERE "COUNTRY_NAME" = 'Japan';
+
+-- should to work with more data
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="10">
+  <COUNTRY_ID>CZ</COUNTRY_ID>
+  <COUNTRY_NAME>Czech Republic</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID><PREMIER_NAME>Milos Zeman</PREMIER_NAME>
+</ROW>
+<ROW id="11">
+  <COUNTRY_ID>DE</COUNTRY_ID>
+  <COUNTRY_NAME>Germany</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+<ROW id="12">
+  <COUNTRY_ID>FR</COUNTRY_ID>
+  <COUNTRY_NAME>France</COUNTRY_NAME>
+  <REGION_ID>2</REGION_ID>
+</ROW>
+</ROWS>');
+
+INSERT INTO xmldata VALUES('<ROWS>
+<ROW id="20">
+  <COUNTRY_ID>EG</COUNTRY_ID>
+  <COUNTRY_NAME>Egypt</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+<ROW id="21">
+  <COUNTRY_ID>SD</COUNTRY_ID>
+  <COUNTRY_NAME>Sudan</COUNTRY_NAME>
+  <REGION_ID>1</REGION_ID>
+</ROW>
+</ROWS>');
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE',
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
+  WHERE region_id = 2;
+
+-- should fail, NULL value
+SELECT  xmltable.*
+   FROM (SELECT data FROM xmldata) x,
+        LATERAL XMLTABLE('/ROWS/ROW'
+                         PASSING data
+                         COLUMNS id int PATH '@id',
+                                  _id FOR ORDINALITY,
+                                  country_name text PATH 'COUNTRY_NAME' NOT NULL,
+                                  country_id text PATH 'COUNTRY_ID',
+                                  region_id int PATH 'REGION_ID',
+                                  size float PATH 'SIZE' NOT NULL,
+                                  unit text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
+-- if all is ok, then result is empty
+-- one line xml test
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc WHERE proname = 'f_leak'),
+   y AS (SELECT xmlelement(name proc,
+                           xmlforest(proname, proowner,
+                                     procost, pronargs,
+                                     proargnames, proargtypes)) as proc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/proc' PASSING proc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+
+-- multi line xml test, result should be empty too
+WITH
+   x AS (SELECT proname, proowner, procost::numeric, pronargs,
+                array_to_string(proargnames,',') as proargnames,
+                array_to_string(proargtypes,',') as proargtypes
+           FROM pg_proc),
+   y AS (SELECT xmlelement(name data,
+                           xmlagg(xmlelement(name proc,
+                                             xmlforest(proname, proowner, procost,
+                                                       pronargs, proargnames, proargtypes)))) as doc
+           FROM x),
+   z AS (SELECT xmltable.*
+           FROM y,
+                LATERAL xmltable('/data/proc' PASSING doc
+                                 COLUMNS proname name,
+                                         proowner oid,
+                                         procost float,
+                                         pronargs int,
+                                         proargnames text,
+                                         proargtypes text))
+   SELECT * FROM z
+   EXCEPT SELECT * FROM x;
+
+CREATE TABLE xmltest2(x xml, _path text);
+
+INSERT INTO xmltest2 VALUES('<d><r><ac>1</ac></r></d>', 'A');
+INSERT INTO xmltest2 VALUES('<d><r><bc>2</bc></r></d>', 'B');
+INSERT INTO xmltest2 VALUES('<d><r><cc>3</cc></r></d>', 'C');
+INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
+
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
+SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9f876ae264..990b2f7285 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1,3 +1,15 @@
+XmlTableBuilderData
+TableFuncRoutine
+XmlTableContext
+TableFuncBuilder
+TableFuncState
+TableFuncRawCol
+TableFunc
+TableFuncColumn
+SQLValueFunction
+max_parallel_hazard_context
+TriggerTransition
+SQLValueFunctionOp
 ABITVEC
 ACCESS_ALLOWED_ACE
 ACL_SIZE_INFORMATION
#142Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#141)
Re: patch: function xmltable

I've been giving this a look. I started by tweaking the docs once
again, and while verifying that the example works as expected, I
replayed what I have in sgml:

... begin SGML paste ...
<para>
For example, given the following XML document:
<screen><![CDATA[
<ROWS>
<ROW id="1">
<COUNTRY_ID>AU</COUNTRY_ID>
<COUNTRY_NAME>Australia</COUNTRY_NAME>
</ROW>
<ROW id="5">
<COUNTRY_ID>JP</COUNTRY_ID>
<COUNTRY_NAME>Japan</COUNTRY_NAME>
<PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
</ROW>
<ROW id="6">
<COUNTRY_ID>SG</COUNTRY_ID>
<COUNTRY_NAME>Singapore</COUNTRY_NAME>
<SIZE unit="km">791</SIZE>
</ROW>
</ROWS>
]]></screen>

the following query produces the result shown below:

<screen><![CDATA[
SELECT xmltable.*
FROM (SELECT data FROM xmldata) x,
LATERAL xmltable('//ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
ordinality FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME',
country_id text PATH 'COUNTRY_ID',
size float PATH 'SIZE[@unit = "km"]/text()',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
... end SGML paste ...

But the query doesn't actually return a table, but instead it fails with
this error:
ERROR: invalid input syntax for type double precision: ""
This is because of the "size" column (if I remove SIZE from the COLUMNS
clause, the query returns correctly). Apparently, for the rows where
SIZE is not given, we try to inssert an empty string instead of a NULL
value, which is what I expected.

I'm using your v44 code, but trimmed both the XML document used in SGML
as well as modified the query slightly to show additional features. But
those changes should not cause the above error ...

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#143Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#142)
Re: patch: function xmltable

Hi

2017-03-02 1:12 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

I've been giving this a look. I started by tweaking the docs once
again, and while verifying that the example works as expected, I
replayed what I have in sgml:

... begin SGML paste ...
<para>
For example, given the following XML document:
<screen><![CDATA[
<ROWS>
<ROW id="1">
<COUNTRY_ID>AU</COUNTRY_ID>
<COUNTRY_NAME>Australia</COUNTRY_NAME>
</ROW>
<ROW id="5">
<COUNTRY_ID>JP</COUNTRY_ID>
<COUNTRY_NAME>Japan</COUNTRY_NAME>
<PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
</ROW>
<ROW id="6">
<COUNTRY_ID>SG</COUNTRY_ID>
<COUNTRY_NAME>Singapore</COUNTRY_NAME>
<SIZE unit="km">791</SIZE>
</ROW>
</ROWS>
]]></screen>

the following query produces the result shown below:

<screen><![CDATA[
SELECT xmltable.*
FROM (SELECT data FROM xmldata) x,
LATERAL xmltable('//ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
ordinality FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME',
country_id text PATH 'COUNTRY_ID',
size float PATH 'SIZE[@unit =
"km"]/text()',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME'
DEFAULT 'not specified');
... end SGML paste ...

But the query doesn't actually return a table, but instead it fails with
this error:
ERROR: invalid input syntax for type double precision: ""
This is because of the "size" column (if I remove SIZE from the COLUMNS
clause, the query returns correctly). Apparently, for the rows where
SIZE is not given, we try to inssert an empty string instead of a NULL
value, which is what I expected.

I'm using your v44 code, but trimmed both the XML document used in SGML
as well as modified the query slightly to show additional features. But
those changes should not cause the above error ...

The example in doc is obsolete. Following example works without problems.

SELECT xmltable.*

FROM (SELECT data FROM xmldata) x,
LATERAL xmltable('//ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
ordinality FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME',
country_id text PATH 'COUNTRY_ID',
size float PATH 'SIZE[@unit = "km"]',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME'
DEFAULT 'not specified');

It is related to older variants of this patch, where I explicitly mapped
empty strings to NULL.

Now, I don't do it - I use libxml2 result with following mapping

No tag ... NULL
empty tag ... empty string

Important question is about mapping empty tags to Postgres. I prefer
current behave, because I have a possibility to differ between these states
on application level. If we returns NULL for empty tag, then there will not
be possible detect if XML has tag (although empty) or not. The change is
simple - just one row - but I am thinking so current behave is better.
There is possible risk of using /text() somewhere - it enforce a empty tag
with all negative impacts.

I prefer to fix doc in conformance with regress tests and append note about
mapping these corner cases from XML to relations.

What do you think about it?

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#144Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#143)
1 attachment(s)
Re: patch: function xmltable

2017-03-02 8:04 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

2017-03-02 1:12 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

I've been giving this a look. I started by tweaking the docs once
again, and while verifying that the example works as expected, I
replayed what I have in sgml:

... begin SGML paste ...
<para>
For example, given the following XML document:
<screen><![CDATA[
<ROWS>
<ROW id="1">
<COUNTRY_ID>AU</COUNTRY_ID>
<COUNTRY_NAME>Australia</COUNTRY_NAME>
</ROW>
<ROW id="5">
<COUNTRY_ID>JP</COUNTRY_ID>
<COUNTRY_NAME>Japan</COUNTRY_NAME>
<PREMIER_NAME>Sinzo Abe</PREMIER_NAME>
</ROW>
<ROW id="6">
<COUNTRY_ID>SG</COUNTRY_ID>
<COUNTRY_NAME>Singapore</COUNTRY_NAME>
<SIZE unit="km">791</SIZE>
</ROW>
</ROWS>
]]></screen>

the following query produces the result shown below:

<screen><![CDATA[
SELECT xmltable.*
FROM (SELECT data FROM xmldata) x,
LATERAL xmltable('//ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
ordinality FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME',
country_id text PATH 'COUNTRY_ID',
size float PATH 'SIZE[@unit =
"km"]/text()',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME'
DEFAULT 'not specified');
... end SGML paste ...

But the query doesn't actually return a table, but instead it fails with
this error:
ERROR: invalid input syntax for type double precision: ""
This is because of the "size" column (if I remove SIZE from the COLUMNS
clause, the query returns correctly). Apparently, for the rows where
SIZE is not given, we try to inssert an empty string instead of a NULL
value, which is what I expected.

I'm using your v44 code, but trimmed both the XML document used in SGML
as well as modified the query slightly to show additional features. But
those changes should not cause the above error ...

The example in doc is obsolete. Following example works without problems.

SELECT xmltable.*

FROM (SELECT data FROM xmldata) x,
LATERAL xmltable('//ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
ordinality FOR ORDINALITY,
country_name text PATH 'COUNTRY_NAME',
country_id text PATH 'COUNTRY_ID',
size float PATH 'SIZE[@unit = "km"]',
unit text PATH 'SIZE/@unit',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');

It is related to older variants of this patch, where I explicitly mapped
empty strings to NULL.

Now, I don't do it - I use libxml2 result with following mapping

No tag ... NULL
empty tag ... empty string

Important question is about mapping empty tags to Postgres. I prefer
current behave, because I have a possibility to differ between these states
on application level. If we returns NULL for empty tag, then there will not
be possible detect if XML has tag (although empty) or not. The change is
simple - just one row - but I am thinking so current behave is better.
There is possible risk of using /text() somewhere - it enforce a empty tag
with all negative impacts.

I prefer to fix doc in conformance with regress tests and append note
about mapping these corner cases from XML to relations.

What do you think about it?

It is documented already

"If the <literal>PATH</> matches an empty tag the result is an empty string"

Attached new patch

cleaned documentation
regress tests is more robust
appended comment in src related to generating empty string for empty tag

Regards

Pavel

Show quoted text

Regards

Pavel

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-45.patch.gzapplication/x-gzip; name=xmltable-45.patch.gzDownload
#145Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#144)
1 attachment(s)
Re: patch: function xmltable

Pavel Stehule wrote:

It is documented already

"If the <literal>PATH</> matches an empty tag the result is an empty string"

Hmm, okay. But what we have here is not an empty tag, but a tag that is
completely missing. I don't think those two cases should be treated in
the same way ...

Attached new patch

cleaned documentation
regress tests is more robust
appended comment in src related to generating empty string for empty tag

Thanks, I incorporated those changes. Here's v46. I rewrote the
documentation, and fixed a couple of incorrectly copied&pasted comments
in the new executor code; I think that one looks good. In the future we
could rewrite it to avoid the need for a tuplestore, but I think the
current approach is good enough for a pg10 implementation.

Barring serious problems, I intend to commit this later today.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-46.patch.gzapplication/x-gunzipDownload
��R�Xxmltable-46.patch�<is����_�hS)�uZ6���)?���(�$�M�@��i.��:��ow��pH1~N�~XVBs�F���'����LX�>�$��q4j�	�?
��d���
t�{�X<��>�9�k4��po�j�Z;;�����z}!������x����[;{{��#��~�b�����Nn�S��t�(|0��@��h�PV6����$�(L_���wR����8J�4�G�%�f*g�u�����I4i��?)�&l�%g�9���@�n���V��?��D$��#�C��)k�������v�>3����@<y�L�{��FwL a\
��5��Vcg���S�8ac}��R����l��/�o�����9�8��~F~��k7c���:SO��=���nX.� ���N���FG/���^�po����[B���f3x~��R<�Ju��6���W0x�+(�x��1�����l7�u��o�v3%B/,G7�&��K�s%&����E����E��{R$����v���f�����0�/�(X;N��'��`���4�1E4��(��l���g%yD�[2����ik��a�^
O�
[�lS������e���������v"b����<iOl�x�����}�����j��h��������+���I�8���@�u��?�|�0��/��w� �c3i1�%�r4������%) �#�^k�������G�?��d��?��N��o">�K����-��p~�>�?���|b�W7�����e�����g)�bq�s�U������ �Qcns��
��a���f��v�S1�x�}��{!kQ������d2'/>�4V{y,�v�
�qL�&���BO:��8�E,YD���,������g�`�>9��T iy���#�j�cg���y$�,#��c�����	h^�����]�i5�P�#�,h<@eI6�%	`��Y:��(ZWH">��H[�4 �'SE,�E��F�� Ld�%�r/q!�H��A#��r/#��C	��=J�iJ�8� lg4F�����~?������l��b�)&U),�Z4��Li�
rz�v�����%����a"��QD�.��V�����`���y���T��1
��P ?z��39y�6GA���BC��
=���=z`�P�u�L���Q�V�,���u���X~�&	����	��������G#����V�����
6��j�o��uj% %1��MTj97�����@|�@Q�F`LV�BW�W:�0�����p'��9<�">��������y����:�,����!8f��L���@��
��>\�
�������'���/���**�
�y�U"�>�4��P��
����F����B�g�������F?VP�����G�gD����=�G}��x�;Q���TC(����S.�B�:�j:���8<���yS����)#�.`��''�H���hsOI���%��'$8��F�bH���q ��<��2"�2[��5
���
)���J&nt9c@)�*d�����6���91�N�y��v�Ft����@r���
@�c&�K����$�����{�u��{9��� 2E��H>��r��3�� R��W�O[Y���S�j>;�+�^�90.a4#m��4�����R�
���Y>4���g~!���/����
vaDkYPU�c��j8�����jH�DH:����A�7���u�h���aJ��T�[3���]��(�K#���<�T�L�,�e$��J��$U~Y�B�y^"J�`��@�x��!�c�Q3?�B$H��ja_�12�M�U/���y�Pg����J:��?.�8
��v]� A�
2.��J��b����1p�QM�(0.O��C�V��I����CQlb�R��u�m=�`�f2�3`;!Y~5��B2Y�@��	$��P:s[�������m�@5���<O�x���4�I�T��~D9���4�����n&������D�� ���e��L[��2T�<��������]&�J�C���
�}����).&U��E�\H~}��`
R��$�������^�]Jdy��y6�0�����"���62�Y�#��Z�
@:����S���������)��)^mEn2�g�P�$=���9 B�u���/��g�W���Tm��8/f�AE0O�������������GE(�"����P`�
�B'��U�
�ZO0������� ��de0n��s4����d��Gw�a���'8��@�8�WX��Sl��(�1H/$gD��O"�|�Z��/3��:H_�s[�����]�������M�{�c����	Tv��b}���_�o�o�~�R�E�-��r���������N�C��<po����?�����	y���9(��������<,���ozg�����{��C��\����w��BO��H?o���?8�;h7�g��eT���Hu�
W)�)�slt��J(�q���WW�
�Gv:�6*�p�����Y{9I�H+*�MB�_��[�f�����x�Z�h'}�;o�Y��5H}�l��<_u.��
k�2�%��h����raEV�2��K����d�d��`\�_���ao�^��^�������>�*�ds}e����`5V�����2b��H�������Mf061R��u��H�p�?���������?�r���<��a��u�������-�J����>X��
����~re�{���T������?\/��<#���B��.�v������/�"��C�m���D����>]f�n8e�!�]�Rv�A�`��5j7K�������������H#�:�Y��Z�B �udI0�pER9����0�������������^������?�vv=A#c�����yzzj7�:''�x
��5������������H6!	Pe>�f�P��J���Z�i�����;z�����LuQ8�I�����?���LiG��nB) $T�����{!b�����$�7����#��MLo ����qn�]�0��deg���jC�te���7���1m�y��lA_���S�C2���'e�����T}���]�|T�tvK[���a6F:U�LUI��(���x��y���}��h�^��9q�@H���v��&�MV�
��A"��zb��)
@��.��c7��[��b�)2-Ql��
���
}����w�E����qi�O�n����R?�������@�8
igjt����<)t����������BM�`��*Sg�z��P��6V��
��F��m���qm��#�6O�S��2;�"�����j���X�l� ��D�1u�
���o��,^i���y������Bf=O����n���ly�x�N<��&�!4hP
Vb X��-�C�1����-�{W�B��[�%���C��d��h���C>��	�tE!�M���^�������X��=�h�����>��p|����^�;z���r��^����k��P�_��*�>�����"����=	��?��J��p������F"����#��NS_�3�O�TU�v��v���`��D�b�OQrOXTr|�{�������z�J���>�z�E�C�ZL)�[��7C��74�d���bN��Y�i;��P4�h@	5t����O�v�r
�y������)Ak�U���|>��0s�#{3c@�Ye���D�����q�*[z����q�iuu%�&�"�z\�0JE�����L_��t��@*�"�U���������r�%*�
�GX�����Pf���>0�����r���c���M��@��J+�����P�[a���0����U���X����Vj��
����7�H
w�(E5�=l��Z-G
o!��F���J
k��C"����Z
�+�����$��<�%����?WV�n�B>Q������{Lm��e7�����.O4�jV�`��`�S�T�M�J�l	=������f�0"�'1���4/���`�4\���1v�#1l4��������x<�a>���B*uP����� 4�^#�_'�L�O��h�)�~$�������M��9O_�_�\�jsO�*6���??���9dj�����'t���-?��7S�����	��ERw��F����;)�W����$zq#F��/��� Q[�����*��'�
�H/�3p�_Wd@�^R�K� w
�WP^��tr@��x�����Q�q0>8�����%�����B�oT�u�_��
o��ncq�V�� ������B,Ah�k	���%�9e�$���q���1��T�M3p�r�Y��U*�����n�-r�4Fy�l�D!/(N�8��R7�g�3�uW*�rPh��(���c�����H<�{����
C�En!���`����!������J'��~���^'��_n��6c19���Fc����`G�h��l���������I��S����9F^O��A�3$i6=SI"������&]�>Dw�f����UAH��R�T-�qc������9z^4e�/S�22�Ra�dP�9��?<p|*�m�x���%A�6��������I>���h�g��_//��[#��;�����z��������t=��J��j�����Q�Fcwr�����o��Co�-��e����v����p�k}��U~��H}���VI�;�v0��:�a��K/�S��!�Mq�{��~E�\�P���+�oV3]2�Xt���7���2�2����;������h�}�N<?��<�j�0��������>e�p�����r����$��!����]�����to��.5�+����E�����h�����vOz������e�O���R�������{OC��O�L<�/A�=9��j/�o�I�����l|.,�l#<��Gt���J�`�)������D����'!���<#����`}�-$��=�i���_��W�)Eq;`�G�f<`6��R�2@��V�&5g�5��8������&��
�`�~��<�]����������@����t��������B�(���-�����$��bBE+
&h�(�3��b�N����M��w��C�C������;�,�$u��{��|�`K��/�6Bufu�����w\���Xd�P�y�w`_�����2:����|1������q�����^�#xwQ����s��K2�@���W���m����ctc�K���'�qL����4����}�Qb
�����������_�ct�����5����}Po����8���t�8e���U'��9��B��}|E>�Rn���}����T���4����W����U'z-���F\u�S!���@�ST1�d2+�L����JQ~��4c�Z��l�����n���B�Ec�j��B��s������8��.�.�Gq��3��R�&���u	 CA����T2���}�lE�����\�w���4���gUC��V/��N(~R�/0�N5F��Y������8N�8�@�j|~�l�����f���W�/��h��o��-���.1t!�Xr�lPkWR7V���K>���K�=�S��a/�)__2�����s��u����
� �E5�������R���b���W0�E�>����d��������y����{��j����m}�����P����D����s�%����e���d ^.qhP���; �����V� ������<Moam��-@�f6J����C����"��YM������������<O�� *o:��b���"u�eU�y�5��8/6���sl��, u����1	$�R�o#)�E�g���(�+�	�W}�gMLwV����������2+�s������������Y�hi4KF�P	���J{���P��+�BtN&MN��v]�us�N�i�������k�H��
��]1�l������I�#�i����G�e���\���������I+Y�������vgwgwgfgfg�������O�K��zY���=���=LN���my=8b�q�x�w��*���3!j��/�J�S��x��
�%���s�$����Cv�����w^����Xg�.�t!�c:���������pF�R�k�����l����<3}h })�����*�o���\�tNc��-�����N����|i������1�g�}r"�Sp������[��2�G\�	L��^�X4����K0v���E.���6�?S�!^L�pT�d1 (?+q�F����l�\lV�qt]:���P�8�d�[4�
z3�%��|�C��B=��G�	]h���6qj��	ji���`�AK���NF��\�z��W�1P��b@� <%q"Hk���4�8O��do���NSw��K.j=��s��1<��DO�
��H~��J����������g�|�(�x@&��]v��p�Cp]G��@@l��/���
�U$��V����OQ��Z�����Q��KY�I���%��??%u��X���,���!y
e���-�e���2#N9��Es�,���$,�
������)y��&OF�����!J�(����|�DW��"/��3��x���PJybJ�#�_q4t�(��g�_��7�Z�P��n�$H����� ,q�@�d,���Q8�Cz��f"������_�.7�)Z_��u�\"u15�b�����X����u��H�c+�+4 ��8�{�p3J���kMt
I-PXnrT�TOY��?}%.d�E�G'"[|��9�����W��6W��C��Cm�!
s<Xe���Ek��W�b2�e�wN
� �h����>�<IOc�@�s4�-,�	~��[,��J�Y�4N��	nL
��J��I����u(r��(��#������No�+,��Pv�X���j�Bx��Y���V+�{K���9�T�B�&!&%h�mwoeOlr+���h]��\�����IX{�1�V�:W�
��%+��k�h&�byI
0�`]��u�!�N�{.
/0��5��S`A!~l�����~K�Dv�7�H����&U����Q���Q�y�W�aX���.{�A�4��_
���M���|P��@�xg�����z0�P�`��xI����X�.R�nmK�����I�@C��tx�<{������c�����<�:�)C]�/%I>,~�&V\��\+M����h�_4~0v��	1y��%��8�Io�{�l]�pv�SI����y|��moC)Jt}���������7nN��TQ��?dWx����G��y!Z���e�Xz9��,�^Q�&Tyn�1��A���)$���h���h�fn8���6�Rb,��/o�B3����F P!�~�/�����Y��������sX�!�Q�a��!� t`xy)pf�^���)*
��9*U"`��`�(��Mc�:jc��`H
��(����[o�6���#Bn���E�Rc�p��~?�����v�y0>�:����-y�@F ��R\��)��(�J��'&�z�����C+J�T�y%�����G_�M��`Pp{x4��J�V�EE_��;y��f�%2j�������gg'g������
�i�����DW[�'-P���<XQ�8Q�jtYZ��~uvH��9�����C��Q\�����`�8��W�8�9����nF"~W,�y�v����nN�����2���f ;�k�g��B�{@Ol!���[���iH��4N-rm�,����(�� ��%kr�i�s�0�h�/�)��~�@��t��b�)�%M�\Zv��x�K:�C!�<!
!)m
!u����*�P�V�RJ�o���c���XzXC6�7+`��Ey[��#�,���+dR�4�����
����cI�%Q����_�+��~���:��
:�^e�rZ"zR��SBjfXe+�`!O��M�Z��} N���V��cAQz<kgSH���V�����j���L3-�
��H��L���������/�h�$kP>����p2z��t|�B�
J���e�^�A�Ab�e$���L���7 4nUy�4$�"8�h)��&i�0�`Y�S�Z)�z�91,�>�O-'Im@��IQH��e�a�t�3�7)MPIB�nE_y]d��T����Z�d���N�dniH$%9���@��*�9S�����d�'���
�m�{%*c���X+��+J�h��481/������2_y�����������Y�X�,0~�
�������'3�����^D��)5
&W�G��)�u�bJ�?��Q_,k��cvn&�"�J(~�����q�=$��E%q�,�J����'���VM��l�S<�8�����L6�o��e��`0ddh&�������5x�d�k�V9���u,�i�&��f�!��C��� {7�����*��W��������P������q�c�?��2���=�	V���U���_L�e&����4u������0I���&#$�=��7�p$�On� >��@��c�'~
�Lz�g^��W�hx���W��������Vww��[�T����@w��4����t����a)�O
C,���u�����������3a9+ZO�f�������PJ�=f[i�g!�����r�MZ�E{�M��A�������~B�����=��V�n����������+f1�zw�����b������ytPR����b�����H�W������6��s��PP�no��u5�x��G?�����5cJ����J�4z�s�IbL)���_�.����:�w�����G�g��U'����t��z���9/�A6���%�*�eC�KKv���^��?G��d������� ��������G���
�X���:�
��y������V�0�z�_��M��n�r�L-����x��;?�����zk��k�9g����H�h�3��8;<~!���_=o�p�]���'2��E�ra�B��f�J?��J��
I��%Q���H���+����t�BzJ������\�Y���:�����.qb��3�-�8��52Q�o�b��-�[��H�{�rxl���O�\��*$�`A�ZJ����S����B�$�m��S�/���@�@�����8v����W)�,�=��\/����iVt�^oI�nX�xQ�-�1���!m8F��t��ps��1.���jU	9�p�;���k����~�1��Z
��V}c�\��:x�Hg"���y����#	J�FK0A+��b���&���w�N9�p�+�`f�1���:��J����n���Nf����U�����w�5b�X$Sz��^�Q[J��~�����
������q��t���#���2y����S�mfL������|�zo���y��]�LI��>!�S�)��.��Q,!�S�B|���o�+^���EdJ�je���=��})�n�I���������PD�����*o�_���&G>����'����!��^���=,':s����^������cb�B�-����,�0�.�m&2���f�~p�v*��6�z�����t��c2���n�O�����S���W�vM����>qS��>U�<���:qbw��������p�Z��|����CI�����1�����g���l�B{f99!I0��$�H��P��A]u}�����-��#0��IZ�%f�H#h�9�~�!=Kf�y�Bsz�S�fF�-3�Yd���k��F��l����4����%1�eP`�Uk(1�_�'�n1m�ur�N�HP3�� ��#)N�����3Io�m�!8�e���+�'$�j�����ZM���e�q�k��6�9�����$E�F<6���4%k��M��X��y�]:�I�f��9j��3�T�3�Tw�oUU]�^���a�'���]���[��B�����&��c���%�\�$8�
�j��`9����Hkk����a����BW�c|�0��a��X��e,-cU���R�<NaP���5�t�3����&��2dQ�����B���;
Rz�#�Vc`*��V��]�,���������FK����{eM�����W�MY����&��	u����/#"���������w��g�{gm�����fN�'���j
�&����Y6��\���v�68[����H���o����9������.������x��?�tk�E���r�F85�a�u�����z��vv�q6S�o���3G��5���Z��T�s��\u�@��TJ�F;�Y�W��Q�h�QvL�t	�yP��d�!���1��z�4��M�y�u���	mHO���T6��;~o{�9��uL0�PR�mJ*�]��Q��f�%Q]���<Q�P��T�]>�f[�����L:p�Og��3����YZRT�����:h�b���pV2\���#SS�c@�V;�Lw#�,�T��$��c��-��)f�
�=��p��a�0?
�1���A���z�,���Z7��T���������TM���z��o�#��}���*�����y�
�7��O�5������Aos��uo���K��\��{���������\��]�V��`�L-|��Y�Z��

�B�������CM�b}Q-���9JRK�O)Be�EK���w���N��i#�P�������R���n��4�l��E3���c���J:��A.T+[P��������e|�Ra�6N�l�f���������L��*���d��i��.����d�W�']��c��}A�$��onn�	h�N�K��Na��H������g.������@�b�1�������8��b�ki!!�d����i�o�����y���f@�����,$�_���j4���*�bn=��Af�I����ux^pP��dBR�>0�d�1���1Bc�f�m��j(=�8V�;>S���{��v����w�=��J��:P��(���H�#����V,}�L=�M�D�������g�������Tq�����4A����{.�bJ�F����V�7WL��j�/���Sp\S&2�������Q�W�*�N����9�B���Y��Y)�����L���e�,E����:89nB�"���L�|�[&VA����yt��A���o�����|g�_�w����2n�z����R��/����:qhs��G2�K����sOl�Q�l"���&0���Z@��u3�����x��u�^�������z��c�\�)�$X�1��|�lf��U���t���-;{tYl��W�U^���b]��c�[�
S7�q��Y60;:����o��
�=y�.����#��z���Cy��N:���8��k��#�&!cl�D��gb�F��6�K/1���9(r��	�X��L���i����Ql0��:������_��F�@�!�n����IY�k[�[���uN����5�c�X�����������E��d��W��O'g��N����8�
�u>�����7-4d�c�a�^oou���f�����qt���"��"!���p��\o�U��T������)����O�c~�������[$���[��������lM(A��(:��C(���Iy�&����y���;��x�����e���@l#���q0*8�)/|���+b�3���
�%�::�ln���%q6���?����\
s
��YfA�:'����(�n�F!��>gY�I��Th$�g{c�u�p�
�A��(��~�/�dg�����,1����&�[�!k��lkR����A�	G�s���x���v��Va���L�r��y3u��SIH���E����>xK���d���^�;�{
��&�J#���"�jb|!���ie��������}��~��<�6	�����x0��n��]����bEr�5��'��<�)�0��r_y���\�1~�o$�8 q���dA�:��O�~������H����R��������"G���d����<�H�c�xGb5>c�	l[�9\�E p�Q�t�M�>��w�8#%D7��������l,��6��a�=�S+�SE����l
� x��p�  ��&q�y|�o����p{�*�����uv�����UD����s����!&T��[2_.1C09B��^C��.qM��g��5�*����q'�G��&�r,w�����N����Y���-,����FYrp�+��/�4��`����g��X�Z�H�<�����I%��:@
����IU
��3(����z*����o�z@Y$��H��$0@��t��� *Hb`�xf�XOV�����hUJ�A���}z�{-/�����1���d�Bdq8WzO�S���o~��w�tg8����d)�`��E#���;���$l#����I�v������(�j1YyWy�KPX�����Y�yK���V,�"��K��1��1
y�����K����VT�_��[\�/���Ac=zR|�1�!"�����L�[{6�-x,����zT���M�*nP��H�8��!hV�#�_�����?�5�|3�����L02g��p'p�����A��bs��j��~���wI#{o ��hp#Z��0��	�:� ��+`h�e�����<�b)�F�Ap�(N.��m�C� �6�������+}+Wm���W�a����<Y��/�U���;����]M�����l��6$K��uT��56nF�������-���\��gQEDV����H/[Z<j��}��C��>��f������{��3��D�&FF�����j��jFi��zR��w���?��B�h��J���Hf��d1b������J@�*J$������W�&K/1�0�8�d������w������?�T%wC{��/�q/C�����[z�O16J������{�D���A�y�A���H���)�]���T,����)�Y:R����������������gcC���d��u��/3���f������G�z��<�Z����X�^�t�Nt��;G�O�d��m��}���lm�S�rE�Ti�On�y�����|V5���>����H���v��������P������&�E�5����a?t����g�"���~�� e�s������d6����5N�4���r��x�O�����V�I�v

���i�2n�A�	/mw,+��^T��h=a��XH��}(�mQ	S��)V�����y�L\��Eu��C�'���\=���v��&�F2�l�Q�^����l���#��&������92��_�]���G��6&>L>�5P�;��
	5����	0YHz�������77���1���[b���Knv�U��/����4��lh�.t�^���W�R����e"H��2������[�k�V��6�t=i
���B��gi��ZM=$M���
IL������������f�v��",����J0���h.2�����*����nys�X����I�_�Z"�{X�'K���� ���&���oY��L�P{c���a�\q���M�3���re�[�'�a�U[�[�p/`{2F�"I�g�8�Y����YW����b�L8��qD��dT*��atf��C����cA��2��F����dY�9:O)���91L����U2��O!I�E-���X��w���xn����'^x9%�pL�[���
%���~g���}1Kq*^�3�p�G��8����y��!rg�!���ECR1�l�Edh8aE&��l���R�-	�E����4�_����7bs�6��1�^��#�S���DQ�_�'$�jP[��'D9����k[3=�}���N�u	����������Mr5���������T��	����I��	�~}j���E%���7�����-`>��	�:Bx:<x�hO���]LW/�B�;�����?��N��[�mZ�	
$�����oO�[��'�������G�t�j�o����A�)��2��K�ak�,7j+���&#1������p�GE�2FAew����+�`�����	�j�V�,���_��]
�CL@��u~����Z������'�<��[���Z��d�D�����)�b�$����e�v��`m����q�?z�E�E�.+�g�-*UL.)�v ��v}���+��4��J���^��#����)�O�C�6���D���9d�TT�#�3
r"J���D�U�|G�Zb�����]��y���g�<<>h��������W�g/��e����V�xY�|�b���(BU���|��o9��<��m;����??YX�q�<��GQm�9^p��yz�\�s�=��Pt��<j�\�n,�f!s��NaS�3�V�S�3����B�QF!T`4�����(#Q*p�~�p*��~KHXu�[cU���D�����\�S��5�%��h4~�����c7��]?��~��������6�������V���N��B��5X-�����Gm��]��e�_P�EF����T��,���hE<��o��%�_"�����e����7O��\`�����N�M�A�Xa�	y��d�	�J�X�#�Oz�������N�3����������1�'c���bE��
���	�yw��M�8dG��m�[� ����(�fG����$��X������R�Y�&��9���������B�f;�H�'&}Z���{�(����}OAX�3���2
���:MJQ��E��n���U*�n�S��Gj@S�]������?G�w��lQ���[�nr�t�^���$��2�ze�������IU��y!=��j�@�U��7p�V��[��-�i��[Hz���Ie�%G���%-��=�k
#:4P�*T����Rk�x���������>t�~���$]�N�q�U.9�t����n�N�����j�)Z�{?�BK�H��|/iM��{�i\���PNTa�3i��~�I���.����k��N�������vq�c��N��ta�F���/]��!y�=���[��p|�����a�U�����b�����:-�I/���������������`�B$3��H-����EX�IC)n��BzU�$4�jg
������� ��>]V�D�l���n�����[��I��-w	:�S"&�����vc2l�BN���,[N���b(��b���`Z&'�'�9��j�ZO��e4o�7a���c�3co�/^��?f7�mrKep=���<!�H���5d�ngef�L��m"����wM�������=[�z��=��+r��:!G��=���8������]�('9@ms����n���,����v_�,��B��)�����q�)}7s��<��L��e���u0F�.z�G��(�~0�fBc
���/������5L6k�E:��]tt	3���F����� M��Is��6l����M���I�����^�������?� �-�������/���2��	T$�����S�k0'}/�e�
��m�bpFp�
���F�q;��_^���x5���=���	�\&!���o6��g['e���I�-���C;��r�a�%���\�bL�6�nmgc�R����v7
Y����,U��ww!"�*����l�G�N���}����o��"NG7�������={�2/Vi�����)�DX�^RK|�U�$�������e�U��`
n\4A����r����|5@�q�3��1���{e���l�������1���D����n�;��dGg���,��!���"MEW��
\7�[$`mnm��ykp���s���2b�g�*(�1���a��Y/x��\�t?|�,��F�7�[��/�TD`Q�x���N����T�vOY7
o}
h���Eq
YX�	�3B��� r��y�g�7k��%�R�8��������i�����]���8G�8nL'_��_{��������N5�����4�SE(�N��P��d��u0���(�4^r��S�~>���C�y�a�����Y��G��u9j���G��w�Z��p.���b���� ���(��c�)��L�
d\�}��O5y��!��|1��S��.�7
����x�/��9==Vt��Gk�1��>$�y���o[���o��	?�M$xK�
\�z~r��:=��h�/���jtYZ@�SI���TRT^���r���;3�eM���yTK�Z�������.��V�q�sz�)������R�����Y������=�`3���D��	��3t��?���9��JpIY�������a��"��E�W<2<�����3H~�
 �(�:��g$(�y��!�����,���%(���Q�r�Rz
���������p��4�>�p�6����}�c�B\���S�@�h���8������&�j8z�4P,X���H���c�����x��W�B$J��~��6xp�"��-��>H��
��m��C��b���pU����gXB��!��&z��}��Bs,�yfz��U�P��bD|��
��c J�����o��[\�P%����!%F(HP��V���
�%��dS}h�X�vCt@N��p� �%�GK	V�h�g���Q��D]�Z����/�or~��F��j�c�	�����^c���y ��7���/>�_�������S�����1�{�:���j�<������q.~7��OO����s�����)vt���9V;��H����#���_�
"��z	^�lB}�{�_/����9���������q����x}�3�x~�����v��������
��#�#�@_ �3����U����<;�����Z��	��g'��������@�#����]p���hK��������)�=�h���7��?��$�\����UN^]x1���l��TL�8��^�<����g������w�����|}-�
�N������R�������_��E�t���Xwm�x�%
���a�^v���DD��M����s�
l���R�c�e���D�z�`�(���D�&�\�Tc�����vK�+)�/]�J�����A��T��J��u7pg��KUJs��.���&���jv��u���S5�SIKK���i����dm�W����9����\ZS��J�k�����;�B|�k������@����B#�v=�7`�K���VG��O��j���[b�5���\Z��X��{�+��'��
^��C�[0P������S�h�~�S��^��x���s���� $�p�*�=��$���#>IM�o�����+j����YW�_�&�>��	��V?>�z����>������L�[-�����KrBy�e8	JH�p�j���e�,��56���Q����4c~��A:BQ�Qd����	�L��,!nY^y�\�8c�q#�x��*/���d��Nb%X]�I�^���An�>j��)U���,._��j[#���T`n21?}�>����~�M�p�K��HL/=T�L�&S>�����h����.��q������+A�"K��{i��7���j+'��

J;�d+h�j����/�_����U��
T�<
���|���Y��T�������P��@����qaA�U�?��^��A��B������8"� ���{��} ��f�xL���Dq�g����b/��A��L�����(~��s84�>3����������A���o��H�W��7J;H�y"�''�����Ld*\N�85_0n-��H�����{y�0g����[���i�����x������(B������ ���C#�����m���h��-��%e��H�]D�l����%�r�A�H"��������A��y2��@��O�|��U�B���ivu����P�p0.��Ka%	��`���	Wb3qRz�%U�L0�Nt��UN��V!���Cs����s��G~��~UX���.���(�_�BG��bJZm	�����*��R5���W��h��bd�>�
������Gzo-�a����{���W�����w%8� ���n�mps-��cV��B}����&I���.��Q��e�<���lW1����n���-�:O�D�������I����R[�!m��y����[�2���zC��j/ w]��!�A(8D�����QZ�W���^��x�����Jd�9�a}g��]�m������+�j{�Z���j�O@���'��[9��E���p�����X�~?�\���7K��o��jx���������:�]]�?�z9�`~�o��+��>��BQ?;gX���r
��.� ������F��\���0���%���"�u�>�U���=�2bre���h��/�`�l�J��3�)�Z�y]Q��c��].YTc���(��n���a9w�gF�t�"����Yi3p��Q���������N�����F<mU
�W�T�*x,��.)�
��#l���C���:�0�%��WBb����F�D����\\�ai�Vd<�%3A���3&��V���*�8(���I��"p�����&M��l�{"��5�MU��CG��Z����]�e1�
�@<�Od�2������-\�L9 b��.�b�e�c���`U�G��?$W��M��C����eKg��.8{� ���K@;��_
��X-b��#�A���j�7{��a8�w� �$D�18Aq��~�������{~v���wQ�!�]c�8S&��%@�h�����(����
��=���1g�|B,Ip~��,�=����l��4��{�(�\�K}��%alr����A���S,ALk��	�d(F���qC���;��������a^��AVX�(��w�Gy=�-�1�A��t(O�����h���4��Z��K_��r ]�dni�q����>�1	��!�U;��������;�0vx7�v�������gh:X%���;P�J�-p�V�D��_eP*���2��
�i�*$��� =m8�OL*���B�:G��P\1�G��P�#��&B��O�L�mm�n
s��3r�y���I�h!���SZ	�D=�Z�l��$i/�y�%HxK��2Jp������W���Q�/b�Q�=DA��O�"~<k�1s�~��vA��_����KH���v�N�N�b�����c�j��qC��4Te8�lE��p��0T��Ap���z@YY�=�~@wx|�|�<C�:��'�tX@V�$^��t+�X������?�l��
���7�'�<Ca[P]�6�Z�|�A�K:��V�2��5%&\`q�~
��}��G����,J�m`������g�`�f�����b�(�u�|~x|������P���dd��yq���t�����F[�����A�e�MT���4���<������anN+�7g+
��We�pe�����jb����e�jV�9�J�m��4��-!����C��&�o-�����{�0@�	u��E���&4�R�g��K�`&����B<�'%����$/���Y������/G�����Yu��3�$��a6k-Z6�y����o��&\r��_����L��;�8�'>NN�cx�Z�M�_��3+�T�G�$��d������p8���Q&;��M�-u��(y���
F��wuuE~61c�_���1�He-���xf�����@HY����/���K�+\�7���Z]��d��0AP2��\w�s�3�nH�����`@�q�`��T�Z�8`%PU�����Q���&��9|�*��e�5(�!������`H���B��29�IT����45.�iH�HU9iC�%%_�e�5
i���K��`m����(+�����������B��~���-P��$MO��D�|G�)V��D���<�PT8���yG�DC1�r�}���.}D���}�g���~)�K��&'��=�d4h|�>j'�L<*h����L{
�!�V7L�O�r�J��a���:7��C�GF�A��=k/��e
Kl������Y
{@t��I�)���Gl7�\Q����k�%����\8�	����f��1��b��j�b�c!�
��1\1L94%6�}�I�&��]c��J��D������q4�8H����i.�����A���A�S!���+���X����F��_5	���>6������R*�U>���&�[�i����	oq��M/��=���!��������hMg%x8�����A��~� 3�d���8����#DI��B���(���9����
	��:T��a	"[�����0������nO��l!���n��g�m#���\R&?����j�}#VZ��$�����Ik�����-�
M�i����mi���7*	�if:z`d"�����
?N�����4��&���n6P�z��\���
r����^B�����%h:�P{���@��T-��h����c�G!>��5�KI�D���q�@�`�.���+D����Z��l��"V��ZrE@�j�_04^���������QMO^ �7E�)�"\��Ej~���k�J�S��;9!��P�y��r�����]����������}���P��Z�anm�(�o�n
��S7�0v����Q��A	����k�����&�����Hf�T��`L���hOV�������xO
U<�H�e��3#��`�`�Y3O-��������M&���dB6v
b����?�$E����kY�<
�q%qh�T��U/2
�b�������H�y��=4��g�V�c���[2��D�;$����<Qw�������I�
��yx�9�<�_�����8WaA��j�c#h�j��KoF���I����������:�~�l�<=:l^� ��U���Mr��������s�+��A3�����
�*H��W�PYg�s������/��8�b7�P:�D��5�1�p������d���[2M
�5��O����Mpc��q�=���d��c}D��<:0z�-��YB�cP3�3%���/L�R2�Uo{o�^�T6���Q����3E�0J�F������r
���%�W�������P�F�����^&T{y���*)��p�c��+eF"~���A�;�����C���p��[!� 7������Jf����c	
�b�����h�z�<�;'}Lq�������)�+Ip�������_=��b�]G(����'t,L�8
���(���p	�s�������D���ZE�^�������{����6���������fK���m��2B����dC�������l��!jK��Nzk��1�8X����I��kU��r������R���Z�M����[��VP�Dwl_.7�2L���b<��s�0�x��������c_�B
�-F��*��sD�L:�;�B
��(�:��i�O"��v7�7���RT�2� �2��pL�22`�=�����)8�����>M��?z�.��@G��� ��1�lF���PH5�W��r�{i.����I�)�D�EW��N�|�^@������mb<W!!t#&�=����a�&��$u�J3�
tJ:13���������<���q�d�Et��Cw@v���!N"b���4����X�?�DE��5����%����V�����x��C�a��K�]78��] �?��X&$�:��/��#�(-(��c���<�5$.��Sy)�n4�<��M8��v6w��M���Q��(�J�����w����#cg�����~%�@+N�6��Y�py����B�%n�R�D�����'��n:!�����T�m�l�j�Ro�nV7��>N@�"o�r��m���m�>�2����D������B��Q���8�u�8����(����������L||]����<�A����C���v��"�����}A����~����D���M��}�Ub<���o� �����\�Y�s2�U��zJ�\�����$q�����N�K�������#���G=H���W
��E�1�������7R����+��$��p��?���;�0g��e��n�w P�f����,����%�(F=�cLD�S��T
X�l���Hj�m��TI	��1`�KA����[!���� i�)��6+����hF������j���\�nV;�?����a�f����a\��w�����R��YF^����wv���nP��6v���Jz���""%�1�����t�5G�.��J��
�b����`)|_��`�I�8,�&&�|�M�1D!�I�����=��--�����P�0����{�Os�����c�Vv��� �3Fo�k�"��(�����r;#��G9�.|���(.�1������Z���;����=-��-{�� �S���l^��QYw4]� �Lg-!���`�H9��g�o��9�O��&�'�c�7���"Do���?�0pu�z�sxk������%`��h5���-��x���TD5�UV�m����\N�����UCD1�G��	���}H
/�c��]�8"�����/��bI��BQ����{�]���:�v���ad�5�A���
0�x(*y��<�R�6�uk/��n;v/i$51Z�mWXc���i���$]H��w��0���N����F:�*=��8����V�N?��
^;�0f�)t��
)��\Zb]���$��r�K�i�0�J�A��Kv��a�����a;��a�n(*�Gt:4]��Remd|����aU�7-����'>g_������	}/@��
��|]�6��[�y� �'�
���Htj�=O��������rh3���c�ZX��#?�d���4*�\Z�rv?�4��qv������I���\�9m�����2������������i��Z�Q=�h3�h�p������5��I�������������E6]E��
�r��[V�P�W�rOf�����P�����%(@U��~���D(�0r����_`F�:|3����#yk�J2]��;���;�44������h�b��g�v'W�[���P��3}�,Ep7e�M]mSW���R5�d�-]s�"��`�)����������A;�:"�I'����d����%�Z���3d�*��''
������&��2y����7Q���Z��+��RyWSc�������bC��[�o~�����7Y����.)������d��)�z,'����wY<���M8Kt��� ir�12���AU����1�O$k���,�U�l�{�uv�����w.X`�*E���I���k��b�=��2TX�,���H���tIO����)mCBL�����P�)��z���?�[����	��J�������������F�����7�M�Ek����i���3G�����~4���+5O�����$�~(F�H�����������A���[q��.E�b^��a��#�&���;�t{���<�����Mp�j�E)��c)o@��� Xu��/��j�5��&�
,q���������~2�������x� ��e����:�RK	B����X+����?�6����`����::�^��p���<$�b��K��Kj���0���M��Nys/��<?=l�~����(MLi"����H(.�!��>.�,��^4~�EA�'��12�
T�hHhV8���%�&�������8����:���:5�Hn<1��NP3
0�&�l��	�^_�^��8l,ln��j����*,Wh��C%,\DA|��}R~����;�����!_�b�B?gLQ�/�������dcqc��xg�^D�"���
Gm
�__
A����f��n�����.D����k��Q�=�c.��Q���w+n��"ZV>�����������D]>�E�w`�!�G|�4�Yt�<����'a6s Q��Y�)i�(��|��s�7$�Zh����`��<�O�RI��?
G(2f�@��8�)�>����*t	���������u,�����V��Z'��j���3_Id��$�g���C���c�'-H^�	lK+����u�O�+��mN@�%��C�A���a��iW}b �Y�;���,}���<��G��Z�^\-�miVeErz���)�}LJ
���YV��U�
��8~�3�Up����z�0�w	���Y,�gc�vB�]�S�tA������<��b�;��pN�����(��^<�@�#N�K*�x+�k���+�,���Z���#k(Rs-v03��x%�D}����L���7G��S��`�b��|�h�r�,��YW��`��f����|�|����&'�@��^�1��=�k��F�T�X�X��q��QJ XlB������Z�HIo�� W�����X����� �C���������q�����>�t�jsCH�b�mV������$,��u��-�#0i�l���87�FB��������'���$���������Me1S��y�q�X^
�C�'�\(p�9��@�����
_��B����D��p�7��!KA/�H�9��@�\���)=���vL����r�\��m���i_�<���4(�����yf�Fig�V<�����o�"Xk����0s����I7m�2�L���.��sv�wL�I����R�;T9R``|��2��&R��;�����V(�y��a8�;'��������U�C7d�PX��X^�s�4��$���8��=�:i��h�����7�Z����N��� ��4+�J��xwq�s	��]Q&�0_��}F=�P�k=+T�4�X�f�T������u:x�K�Y���U���;C���W�������'g?�����(���)Z�R���0���E�c|���j[t�y����=#�
��F hI��?���T2�o(K��)��M��|�F.kP������f�����7�?����t� Bq(�5y,�p�y{�q&
���R![|��-���iI��RdD�L�=?#=
�Az��@������,������HR��>CK62�/��cy5
@���S�<MZ ��#�G����]�!��������wpPxP�b��
B���2�(�X���pb��F�Z�$\��T?-�I����
.D��zb�~��K,�W�;��
����XF��74yB�uJ���D*Q>E�d�[��'�Wti=M��	6��Nl�2��\U����������q@��/�,��F�o�)@[��*�1gw�JV���u�<;�?j��������h,��S����|EML�� ������%��bI@NO���HRAk�%���~����Uq���f\�3�?N�'�*G
��������w�pnE5[�t��i���nU�M%���C���%�K�,��@�����X:������`���-�D`��aFV��6�n��6�=c����k������5- AV���^m�Q�Q�����LI��.�K��\]�A�2�*�eRZ9{��z��ww9H<i�H�YRA�3E�I����L���e0a8�	Q��^
6��R�g�V����� ���k��$��?����e�%�2|d�#�F@l��1����BH�l��L�.T�^�~-���)[�,�p�@v�9s�!
B	%�]�LZp_��w��7o�9���_hF~����}����K����eS��ej*>��P�����SI�M���I������HD�4���S�������Kj��2L��spB�&HT��8��T{?�?:o����5�.�bo�xW��KK����&
����!o��e���~���eT/����O���G*����G)7�pD�&#�E�]P�{�W������#��2G�%���8@�!EA���rm����5���w8�������}���b�p�wh�x�&����7���B o�w�e@���fk��c#��b��
c�(��!j5�!��9��L�~G�1[*�/(R���(���M��uQ���	��]���$��|j���Q�����-~��3-
�q��,%(�r@��f��������bq�:h�7/,�����+@-h���YCucu�{�������lE%����0��w�nOY��\�V�K�P"y$�;��q����@��e��{�+�Ua'A��@��eO����j������D}����i��;��P��;��	K�����k����>2~�xyD�P�������|F? ����v�!R�+���+���hK�!)(S��v��}�D���,a$�������,�s�;v��j�i
�K�9��C�l�k�2�����5O�}
PL�4�����_R�l����Y��(�R ����j��y��w�`W�)��

��z����3 ����5����%BL��/��7��$��J�V�i�&��p��j)�s�����X��lC6SQ������Z�A�U�L^�����%�|�����M��'B@b@3�7."Yn����y_� �������t�_��
[q	�<�'2I���$������
#��{:L�7��KB�'F�I���C���Fe����A`��+Ory�"���*�C/B�a���n�����Z�,���`(��"�R(�<
�4��h�Y+oy����_���@ =��`���d��AN�Ro�?a@�4&v����U��(�%{!_�dw�{)Vu(�2C��E�E���#�I9���R������7(a�h�����7U"������ ����T���g��*:���Y�����H�	v4J�Ei��v����Y�Z3�@�F�!��Z��l�MQ�|���k"�����O��4�?:�*q�+Sb������V�2��_���h[�i��68F����=N������L��X\�c�s��%Zd�[e��8�XYHX-0l(,���v�0�^�����	��u����|p������X8���B�;�F��8�<�\��B��r�_#Y�!�(����O��	D��P��4()���ZH��tuK�"���5�t�!0]�j;F+���a���&#
�6��ECW��jbV���r=,-Y�w������D��,*��Z�,M�vzf�Z9�2��!�����$��2����+ES>�����<(^���J^��
��f+����e&a��&���m5��e����'����xUb��4�498�G�����
�q�
b�����jx�pl\x���������{�������x�����z� )��wR&H�S�7(/d�o�K���x#��g���x���
A����H;�EJ��y���{S��U���R��{����Ve/Q�>q.����Y��*f�>��R|����!yZC�B�0�&I����X0�b���'��6���)�)sK��+y��JHRb�~��a��3z�_e�5e/��z�{����1�\�eU��%�b�q�~,g�8y�
�P�H�����j�V��������/�}b���/m�xZ��/m
��b����nmP^��1�5!
�.`����'��cX���Z�v�lw�,8��A
7�������q�7�$���7]"Y)���>��m��Ub�p������;�L����Z��<�&����.�%f��!�p����:�u����E�.B�����@E�q'(*�}*���^g����(����u�<F��B
�������Z�D�@5����
����*s*$C�z���7�|A�+^uoo{��Q�)���&�
�<�"�&�)�6!��%^�d���A�.�GpK<�9�g��[�1��yt�lL40*��yc�����X�|i�@��O*(	��*�;�=j��p��zg�;���U��`6�K�
 ��"#�7���� 9����j6��W>�P��6=p���72�f�p+Y�(R�t�N/���)�������>��7[D�%2� ��O-0y�����!�� $��1	�Z|&�N��,�B!���������4��(UAL*�����D�����&m�A�q�2�2*���^��1����:-A{V6������*!�s&.�j7���������8�u�����4]���t9T��<��X�[{DW�
B����
$���rb����T
dq(�@�b�������	����������
�t���h��d8J{�KT�#�oP]���J�����!�z/��zR�D~��J$N�J���b�x���!7��G�a��~���_�h�mn����@�^?���.�;����5���f���L3![Dp��@Y�^�wgLa``
��5dv��.5�
��R�,TN�Lv�����Nq�^ow�p������a��|�LW?�M�,�0�2�h���
 �������OJ����Kf��`�m�i��<��`4:��C�+����QY�4~s����
c��m�������PzH�uBl��gol��x�1�Y��:B�`�1����N�@mt]�(YVf���j�T4l$�5}S"�*�E(n�[�G�_��#��:Db���7�uA������z3+j�	XQ�"�e�&��{���;����!nK����cv�*=�[!���X���5?��VLJ�6��\�:%�$��{�����Z�	z������td�li�:]�2�R�=�[�����CA.��?����b�k�pa��e��"E�8�=�f��HKF�����m�lz�}�����h���+�����c�`[��<��6����{�8*������G$a�u9zy�n�3'!�-��C=J�g�Y�����DI�(BF��^�P�`r �
�Z|���)�?	��`��!�y���|���!�Y8�R@l_�v����d�E
��yx�H��A@}�.t\��'���������Pl]�xB��>;F�h(z�V��H�����XA��j'����"b�v��@�X���2�����}G`M�"@/t�G���+�(P'd���N�r����,<"�s��N"+]�L�q;��X�!��@`%q��J�I���_��Fc�lC���'3O}>6j�%m&x�%�����������!��P������B<\>����\����2?�
����UD*( `��2����8���v���W�T66����i�mo3��6������-�H�T}�[�hq��@D�H�~��9F�]�QI?~� 
"��*�@x�0��n�Qh��)��j��.��~��Tg��	n
�xb���c��$5�wr�O�/Z��0�br��8�=z	�L���`��2'�C�mn�(�_&��
��S?�H2��������R����|L `�1J��S�qA�~���i;�y�m���-����J�V�����i{�����B����f�����
��0���C2r[x�DW�����	�.W�t�,��-�� I�����.}����������z��<�?�(����\�$R�3�I)��h���j�SJM�]Z��,��m�j�$����(K��^4d=�\�#���}V[����|VfEr&�������~b�.$w��X�t�.��K3��0�6���J04�=x�2�(��T�%;�
*��	J���x�T�I�h`�R���>1@A���f�R��Xm�4,���O4��V��l���*����.��z����JB�eWk��m#��
Y>�@z~���� �`�,�f���J�cV���8%�=�R1Gr�B�YE���]@���ri�'�w~F��~�VT�
V�rUp���1����_HU�:�JG�C�66������OA�1�h���*3�B?1rX�XKA1�`�^���������.X�3dQ��R�R6���Oi1T�����F
E���i��T*�z��� �vP�4tz^	�~ q��I��Q���^��%;�j5�SqNs�0�+	��9�*�D���q�u��9r��#��r���n���G��q��(/�R���'�*��t�fgO��c%i)�b��}brc�����a�D�H'�='��Zo���T:��N��9U81 d�&F)�8����j+[cUPYEg �����Ik�F��1�tpV�n�q�7������{��uH�\��Oy^�N�~/�r��vgg�R	v���]G�l�Sn��C�=�r&������?�`n�U����`��]M����R��H��_��w����l�k{YDF�\�,����)������'>����2��|M��_+��s��RB���e�CSR�Re*�!��/$�l�v�!cc��q%92�t_I����T��� !��d&�;��L����Y$�_)u�YY��&��hY��h��'j���m)��3U��������4�e�@;��[q����
��=Y��g�h��`50y���|A�P�!*��G���wx|qR�gMq%����N $����pR=���������P8�$�p�
�������%�l�5��Zmc��W*�v}w{��M�S��<U%`H��*�������oP�%�\`�%S��w.8��)�i�A�M����O�cz���8��!����9j�[0+�F�z�������j�Ud��
4���'�a�$/�}������p�q�+8��h.:�x�Z��XM�l�?���;E��+OX�����E�(����d���ipb-�d�%�<eIh|h�X��2��u�	^�`�]$��FfB���q�t�w3xAT����ET����:����@���U�"��2h��*`_N��\�E8t��.��H5t��wk��S����F��0��q�r�r���1����6�ip`Je���h���f���(��.D.�kB�4��LF��R�B/����i��,�7�z��c�1�q��Q�>��
����c����(~������MY,��^��5��oy��nu��6`w6�~�>N�Oo�d	����{����h����O'g����Cv����Q����oG����MYle1Q�?T�l7=��������p�Z�u�w��)iG��&
T���cW[���#UA�����3(`����p���T��P6t��*�6���
0��0�5������|������qc�b���W�Z��R*T�=�+����s��Z���j=?u�����
vTJ���7�����OO����/g�r��"��"P����B�S����<C��&�������~,����?k�X�7������J�
�7�>9n�KO#�2Eyw���������^�S�����[��J$P2Ie�Z�Q�	�+7q�r�SA�C���N�/��U�c�g,��+pR�8/��`t<��fr5�j�c��)����)��vE����Tw@_V��g�U!������K0���� �1�-�R=�x`�e�R���4�pbr�G>w����vpP�]s���N�]����Vu����)��8S��Q-id�����i���G~��-���yDu�\��{/����������y]5��tX�+�l������J���g���j��{�|atx��#
s���c�:�xU1u)}a����q����Z����:�^��W�[;��j��3�����e�QV���e�4�X�fm&�L<�k�h�I�Q��+��)ae���������S�6���E�v8��v�y}���$��6$�������jl!G
�O"wA�4�H�;D�}[A�t��qp	j�u��
��������Z5�V��������w��&�^@���Vm�B&V�����2]�^%�D�[~���?��!AN_}t������O�����.����}��T0?n.z��*V����z�x�[z3��___W"���a0�D��z7�����N5���Jw�]z��[|���=>y�./z�*�V`E��x/~�e��U�� �C�$��?����l�=�m������t�lq�za������%��8yu|q�s���������O�5H���'bw����9�<k��l��b����!�\m�M��M7��4�x#v���������S��!\�S���7�-N����w�V���Ni��������������<�G�~;x�n=wtn;���S:'�_�Cq������D�M�����WK�v��O�����u����h��&�/�6B/�2!zDV�\iy�����w�~~x�B�&x��}��V�x����}Y��X����k�4����S���U(�)��gM�$����������j���K�I	>a����/c1�HO[!�P�gY�J}$�`N1V��U��I�����r9����D5�M�P5�I�o�eC6r���[��O.���, �h�xxPl<�u���B�Z��@�1���#_���P�*�#��Ps����X�a\����D�I-�=�o/��Q4��6�������&~?h�@��@}�`w�wm7{��?W��W�h�zU���X&�^�|�P�A}�����5nX�w�/2B
�q|;�T��,�K������w�/r.
���=��w�/�$
���*^����6=&��^d�B�y{��]i��#ee��6
}�Uo���W�gN\���N\m��Z�s�;'����g��^���t�<=�o0aN���SI�g�?^��&R	����g-�Csd�u�T[$�
vL�)l��Ss?|E���I%���&�hLN��1��VJ�+T�f ��T��\���]�"�
�l> [V��M�K&�W�Z������,�*v������������	J]2I�*�&�����W#�Y������|-���������w����7�b��q�	����$u~�c���E�!����y���7�x=�wZ	/K��+�q]o���������O��3��+�dL���)�����q�������I&��?3X��k�f�W���$���A�U�1�8�t�����>8.OFU�8���E�Os������MI������(Cwm�--�Y�}�f�����y�\^����������[%�i0�nI�����[��,�ru��Y]t�'5�/��^��T�Y
�����s`)[��M�a!4)M���<D)#��G���<�����%��d��ih�_��Ow�)�)<��x5���Ed��!�ZG��/]\��t���
�x�k*]�����������f��,��br��|p�I��cZjQ������A)1I�wK8�%��l?G�.��lQOE#���K�������
d(f91$U"u:_�Wsb>������L���1�8sF�)��G�����b�`����b!�A+�DJ)���=�n'����]����}�e[��v���kW�,f��\	4�3u?��Bu���M����?���D����_��o#�����pK,0��mH'�|ZgN�������������zm���u���Z�������O!����4��"�8�PN�o�����I�>��=��M��2	%&*���2����	�k�����U�g~��
�^om��_���
~�{������������������g�y�.A<�&�-���/p��(��|j,��4���x� 4��n}0c�����zs��hKF���a_+Ro�4u�����}4f,�Rp�.M�#6q�}�4~�����7�`�_��	��������g�<��������*F���r�����#���:�y�O'MN��rF���Min���:�H�� e�1���u�T+�-�S��o�a4z"������Dc�s�j�z�w�����c�s�����`��~8����*��� �������F��	���d�4�����'����>�M�.4p�_������\���|v5��&�����k�y���{���k���P�����6���__T�����C���,����m.���c����B�������b��(!c��4YD�����f�*}
��7GAfSd��_1�LI�{��q�����c�����GD��
���y�-���#���@�=����Z �0j�:��a?y��@';-FG�$��9��/�X������1b��Mi�y�:Sb�d5lD�i.k��lN�c������Y�C�15����L����d�k�g�Br}�*$������P{�>�{��p�.��I�#��~G�{v����W�P�msvx�o�Z��+�U5���5��f!��>��\k5�<?���62A���ta�:�8�7���m@����5j������9?���A�'�X���}IL��I�����95��Nj��~�V���!�V��Uwn�}0V]5w.���33��][F,�3�\@t�|�l���ZZ� i�ep
A��k�_�~�K0�%�a�C��m�C�8Ak�(��o����!��K!���/~�1���-rO��(��]��A~����� ;e*_�8������I�v�$�����e������giPBk*�i������e��	,��qv��Z
]�����?�l��X@�������[���D�{�����@��x/s����-x�I��}VE�l+r*:i���a���II�
7��8�E�5���j����RN�e�(�^����'�2cE�!�6[=9Sd���.m���W~�g�����E2�$G%�?h�}Ph��������q�`d��<L��~�b�W���6	+KJg$�����hN�L�V�����4*�_^�f'NP�Q��[+H���?"�OI��	�l���S��u�V
��y��Vz_��*�Kf	�,��v�2�����W���]^?��m����_�z��];v�F��]l�kW?0]r>�����]���|�����}�����+�{�##,���Ado0b����Y�>{V��g��MO�r�!w��>��N��k��f:jemM����iu�x��V�H�?*����;���z�R��j�����N�|\���@�?���W�8��	s2PA�]/���8X3s�x$J\�G�k��~($+��>I��;��8����;h?l����+��b;x?Go�<P���
�*Pr��#o�����������������}��������q��>�������[\�w��zw����F�]�P�Tnu�z��4O��������=���������<��|���K�x�4��G���4p�4p��y�y���3�(��?z�yN�=J��_�h0O�HpVL�X�����x����#�s���J����,P�/�>��%��� ����Cp�u���`3q��J@8_������X�2%y����8���g�����5�����A��)(?����yy���i��5��C��K�������:�����u�'��g�#����h���B�CK�t7���p����;<�I�f�i5���������������C���*��wk���b�>����������������5p��K��Mo�����������{}����^_��k�NR)Se8�w��<\�S���s��s��s�}�9���a �I�����x�*�>sU����U�_��|oY���r~l��/j���b�
i~��
��~�����!�����ln.{0�25w��[s�O�v��:���~��~�8O�5OzS ��T�����s��s�����K�cN�B�������?S;��=8�;�O�W�f��uc7'���b7����9����Y�|�����	����D�?kg
$Lws�4��BikF����F��mU*����-w�4���ik*��^m�������I3���b�Z���7��<�������p�
�A%�/����U`T�T����t��%A���g��#�b-/z�*L���7�,8�,8�,�92~��z�(��	�[�C��*

��YS?	��LNV���g�����lbJS����`�zU���X&�^�|�P�A}�����5nX�w�/2B
�q|;�T��,�K������w�/r.
���=��w�/�$
���*^����6=&��^d�B�y{��]i{�m>s���|����f���9��V���3J�3J�y<"�%�%�������q��/�R�������W%�3J&���*��W��T��d�KS��������O���~'�k�O�#��Iq�MD������`�j!�gu����2���5�3��\19WLf+���"��!dkr>������L���1�Y����!��/��/_���X�t��/��~b�8������b���~p���9�>h�g�<|��L�R������a����������+y�md:\�Cn�Q�
���O�����T��6��62]V���;9��2q_+Y,�g���Bd3��)h�>E�q6��N�����S��}>}{�����eZ�R��J>5\z�EwD<j��Q�>�1z�&a�0��7h-((��I<�^�������p!8x?�kBa4xh�IM
�I������>�nS���*�'�`��_�����?l���a�/_�
P��`�j� ��K�O�����zr����������������������w�?�3w���w����{�<u��R9�}z�'���g�T2�)��I%�I%�I%������������\��
����*$�9������1D?���Gp�n��Q��]Eo�,�U7�l����������`U�5s`
��Y`��55��Z
(��n����L�3�4]��N����y~p��ju��3�3�v����)��0�?'YtC��o����:��W��[u�����[u�V��U��Xu���3���?y�S� >�P�P(!HXtM�+�/|	��4�0:�0Z��<��<�^�Tz
�4�>(4|���`u����q02�HS��2)������������1��_6�tyC��������������5����6P.��������U�5�?������������~�"����9�������oT*��No��5%��	 #��Y���v6 ����m����"���������u����/�/���`�Y������g�>{*�=[�;�ATM:o*��(��v�<]�/���j3���
/��������U��.������h=�8({��8���(7��9���D/��(����IQ��k��p��p���s$'�|�	��:g��b���3��s��e/���8�Y�I%t�=��S���/������&m�<��������f�V2�/�B�����@]�m������t����
��5
���>������>{�
G-}haG����_X���1��5�����-?�!��$a�����\������ch�]�9�u~���W�g�^���z�~(�/r��U.������������>��>m�������dn�����������[M�cNi�Q�����o�~�;�����C�����E�����^�����7��C�{�S�������N��T*��z5��;�y@�{�y���fnnV��-��_\}}��hc�O�~7�������E�����X#�4�pE��98��@��F�7_�!f�_G?��[�<[\�������[o�����������2�/b0
�|��p�������fC|i4������������F���x�:<���������FfQ�z
#146Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#145)
Re: patch: function xmltable

Dne 2. 3. 2017 18:14 napsal uživatel "Alvaro Herrera" <
alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

It is documented already

"If the <literal>PATH</> matches an empty tag the result is an empty

string"

Hmm, okay. But what we have here is not an empty tag, but a tag that is
completely missing. I don't think those two cases should be treated in
the same way ...

this information is not propagated from libxml2.

Attached new patch

cleaned documentation
regress tests is more robust
appended comment in src related to generating empty string for empty tag

Thanks, I incorporated those changes. Here's v46. I rewrote the
documentation, and fixed a couple of incorrectly copied&pasted comments
in the new executor code; I think that one looks good. In the future we
could rewrite it to avoid the need for a tuplestore, but I think the
current approach is good enough for a pg10 implementation.

Barring serious problems, I intend to commit this later today.

thank you very much

regards

Pavel

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#147Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#146)
Re: patch: function xmltable

So in the old (non-executor-node) implementation, you could attach WITH
ORDINALITY to the xmltable expression and it would count the output
rows, regardless of which XML document it comes from. With the new
implementation, the grammar no longer accepts it. To count output rows,
you still need to use row_number(). Maybe this is okay. This is the
example from the docs, and I add another XML document with two more rows
for xmltable. Look at the three numbering columns ...

CREATE TABLE xmldata AS SELECT
xml $$
<ROWS>
<ROW id="1">
<COUNTRY_ID>AU</COUNTRY_ID>
<COUNTRY_NAME>Australia</COUNTRY_NAME>
</ROW>
<ROW id="5">
<COUNTRY_ID>JP</COUNTRY_ID>
<COUNTRY_NAME>Japan</COUNTRY_NAME>
<PREMIER_NAME>Shinzo Abe</PREMIER_NAME>
<SIZE unit="sq_mi">145935</SIZE>
</ROW>
<ROW id="6">
<COUNTRY_ID>SG</COUNTRY_ID>
<COUNTRY_NAME>Singapore</COUNTRY_NAME>
<SIZE unit="sq_km">697</SIZE>
</ROW>
</ROWS>
$$ AS data;

insert into xmldata values ($$
<ROWS><ROW id="2"><COUNTRY_ID>CL</COUNTRY_ID><COUNTRY_NAME>Chile</COUNTRY_NAME></ROW>
<ROW id="3"><COUNTRY_ID>AR</COUNTRY_ID><COUNTRY_NAME>Argentina</COUNTRY_NAME></ROW></ROWS>$$);

SELECT ROW_NUMBER() OVER (), xmltable.*
FROM xmldata,
XMLTABLE('//ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
ordinality FOR ORDINALITY,
"COUNTRY_NAME" text,
country_id text PATH 'COUNTRY_ID',
size_sq_km float PATH 'SIZE[@unit = "sq_km"]',
size_other text PATH
'concat(SIZE[@unit!="sq_km"], " ", SIZE[@unit!="sq_km"]/@unit)',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified')
;

row_number │ id │ ordinality │ COUNTRY_NAME │ country_id │ size_sq_km │ size_other │ premier_name
────────────┼────┼────────────┼──────────────┼────────────┼────────────┼──────────────┼───────────────
1 │ 1 │ 1 │ Australia │ AU │ │ │ not specified
2 │ 5 │ 2 │ Japan │ JP │ │ 145935 sq_mi │ Shinzo Abe
3 │ 6 │ 3 │ Singapore │ SG │ 697 │ │ not specified
4 │ 2 │ 1 │ Chile │ CL │ │ │ not specified
5 │ 3 │ 2 │ Argentina │ AR │ │ │ not specified

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#148Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#147)
Re: patch: function xmltable

2017-03-02 19:32 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

So in the old (non-executor-node) implementation, you could attach WITH
ORDINALITY to the xmltable expression and it would count the output
rows, regardless of which XML document it comes from. With the new
implementation, the grammar no longer accepts it. To count output rows,
you still need to use row_number(). Maybe this is okay. This is the
example from the docs, and I add another XML document with two more rows
for xmltable. Look at the three numbering columns ...

It is expected - now tablefunc are not special case of SRF, so it lost all
SRF functionality. It is not critical lost - it supports internally FOR
ORDINALITY column, and classic ROW_NUMBER can be used. It can be enhanced
to support WITH ORDINALITY in future, but I have not any use case for it.

Regards

Pavel

Show quoted text

CREATE TABLE xmldata AS SELECT
xml $$
<ROWS>
<ROW id="1">
<COUNTRY_ID>AU</COUNTRY_ID>
<COUNTRY_NAME>Australia</COUNTRY_NAME>
</ROW>
<ROW id="5">
<COUNTRY_ID>JP</COUNTRY_ID>
<COUNTRY_NAME>Japan</COUNTRY_NAME>
<PREMIER_NAME>Shinzo Abe</PREMIER_NAME>
<SIZE unit="sq_mi">145935</SIZE>
</ROW>
<ROW id="6">
<COUNTRY_ID>SG</COUNTRY_ID>
<COUNTRY_NAME>Singapore</COUNTRY_NAME>
<SIZE unit="sq_km">697</SIZE>
</ROW>
</ROWS>
$$ AS data;

insert into xmldata values ($$
<ROWS><ROW id="2"><COUNTRY_ID>CL</COUNTRY_ID><COUNTRY_NAME>
Chile</COUNTRY_NAME></ROW>
<ROW id="3"><COUNTRY_ID>AR</COUNTRY_ID><COUNTRY_NAME>
Argentina</COUNTRY_NAME></ROW></ROWS>$$);

SELECT ROW_NUMBER() OVER (), xmltable.*
FROM xmldata,
XMLTABLE('//ROWS/ROW'
PASSING data
COLUMNS id int PATH '@id',
ordinality FOR ORDINALITY,
"COUNTRY_NAME" text,
country_id text PATH 'COUNTRY_ID',
size_sq_km float PATH 'SIZE[@unit = "sq_km"]',
size_other text PATH
'concat(SIZE[@unit!="sq_km"], " ",
SIZE[@unit!="sq_km"]/@unit)',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not
specified')
;

row_number │ id │ ordinality │ COUNTRY_NAME │ country_id │ size_sq_km │
size_other │ premier_name
────────────┼────┼────────────┼──────────────┼────────────┼─
───────────┼──────────────┼───────────────
1 │ 1 │ 1 │ Australia │ AU │ │
│ not specified
2 │ 5 │ 2 │ Japan │ JP │ │
145935 sq_mi │ Shinzo Abe
3 │ 6 │ 3 │ Singapore │ SG │ 697 │
│ not specified
4 │ 2 │ 1 │ Chile │ CL │ │
│ not specified
5 │ 3 │ 2 │ Argentina │ AR │ │
│ not specified

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#149Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#148)
1 attachment(s)
Re: patch: function xmltable

Pavel Stehule wrote:

2017-03-02 19:32 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

So in the old (non-executor-node) implementation, you could attach WITH
ORDINALITY to the xmltable expression and it would count the output
rows, regardless of which XML document it comes from. With the new
implementation, the grammar no longer accepts it. To count output rows,
you still need to use row_number(). Maybe this is okay. This is the
example from the docs, and I add another XML document with two more rows
for xmltable. Look at the three numbering columns ...

It is expected - now tablefunc are not special case of SRF, so it lost all
SRF functionality. It is not critical lost - it supports internally FOR
ORDINALITY column, and classic ROW_NUMBER can be used. It can be enhanced
to support WITH ORDINALITY in future, but I have not any use case for it.

Fine.

After looking at the new executor code a bit, I noticed that we don't
need the resultSlot anymore; we can use the ss_ScanTupleSlot instead.
Because resultSlot was being used in the xml.c code (which already
appeared a bit dubious to me), I changed the interface so that instead
the things that it read from it are passed as parameters -- namely, in
InitBuilder we pass natts, and in GetValue we pass typid and typmod.

Secondly, I noticed we have the FetchRow routine produce a minimal
tuple, put it in a slot; then its caller takes the slot and put the
tuple in the tuplestore. This is pointless; we can just have FetchRow
put the tuple in the tuplestore directly and not bother with any slot
manipulations there. This simplifies the code a bit.

Here's v47 with those changes.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-47.patch.gzapplication/x-gunzipDownload
#150Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#149)
1 attachment(s)
Re: patch: function xmltable

2017-03-02 22:35 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2017-03-02 19:32 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

So in the old (non-executor-node) implementation, you could attach WITH
ORDINALITY to the xmltable expression and it would count the output
rows, regardless of which XML document it comes from. With the new
implementation, the grammar no longer accepts it. To count output

rows,

you still need to use row_number(). Maybe this is okay. This is the
example from the docs, and I add another XML document with two more

rows

for xmltable. Look at the three numbering columns ...

It is expected - now tablefunc are not special case of SRF, so it lost

all

SRF functionality. It is not critical lost - it supports internally FOR
ORDINALITY column, and classic ROW_NUMBER can be used. It can be enhanced
to support WITH ORDINALITY in future, but I have not any use case for it.

Fine.

After looking at the new executor code a bit, I noticed that we don't
need the resultSlot anymore; we can use the ss_ScanTupleSlot instead.
Because resultSlot was being used in the xml.c code (which already
appeared a bit dubious to me), I changed the interface so that instead
the things that it read from it are passed as parameters -- namely, in
InitBuilder we pass natts, and in GetValue we pass typid and typmod.

I had similar feeling

Secondly, I noticed we have the FetchRow routine produce a minimal
tuple, put it in a slot; then its caller takes the slot and put the
tuple in the tuplestore. This is pointless; we can just have FetchRow
put the tuple in the tuplestore directly and not bother with any slot
manipulations there. This simplifies the code a bit.

has sense

attached update with fixed tests

Regards

Pavel

Show quoted text

Here's v47 with those changes.

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-48.patch.gzapplication/x-gzip; name=xmltable-48.patch.gzDownload
#151Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#150)
1 attachment(s)
Re: patch: function xmltable

Pavel Stehule wrote:

attached update with fixed tests

Heh, I noticed that you removed the libxml "context" lines that
differentiate xml.out from xml_2.out when doing this. My implementation
emits those lines, so it was failing for me. I restored them.

I also changed a few things to avoid copying into TableFuncScanState
things that come from the TableFunc itself, since the executor state
node can grab them from the plan node. Let's do that. So instead of
"evalcols" the code now checks that the column list is empty; and also,
read the ordinality column number from the plan node.

I have to bounce this back to you one more time, hopefully the last one
I hope. Two things:

1. Please verify that pg_stat_statements behaves correctly. The patch
doesn't have changes to contrib/ so without testing I'm guessing that it
doesn't work. I think something very simple should do.

2. As I've complained many times, I find the way we manage an empty
COLUMNS clause pretty bad. The standard doesn't require that syntax
(COLUMNS is required), and I don't like the implementation, so why not
provide the feature in a different way? My proposal is to change the
column options in gram.y to be something like this:

xmltable_column_option_el:
IDENT b_expr
{ $$ = makeDefElem($1, $2, @1); }
| DEFAULT b_expr
{ $$ = makeDefElem("default", $2, @1); }
| FULL VALUE_P
{ $$ = makeDefElem("full_value", NULL, @1); }
| NOT NULL_P
{ $$ = makeDefElem("is_not_null", (Node *) makeInteger(true), @1); }
| NULL_P
{ $$ = makeDefElem("is_not_null", (Node *) makeInteger(false), @1); }
;

Note the FULL VALUE. Then we can process it like

else if (strcmp(defel->defname, "full_value") == 0)
{
if (fc->colexpr != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("FULL ROW may not be specified together with PATH"),
parser_errposition(defel->location)));
fc->full_row = true;
}

So if you want the full XML value of the row, you have to specify it,

.. XMLTABLE ( ... COLUMNS ..., whole_row xml FULL VALUE, ... )

This has the extra feature that you can add, say, an ORDINALITY column
together with the XML value, something that you cannot do with the
current implementation.

It doesn't have to be FULL VALUE, but I couldn't think of anything
better. (I didn't want to add any new keywords for this.) If you have
a better idea, let's discuss.

Code-wise, this completely removes the "else" block in transformRangeTableFunc
which I marked with an XXX comment. That's a good thing -- let's get
rid of that. Also, it should remove the need for the separate "if
!columns" case in tfuncLoadRows. All those cases would become part of
the normal code path instead of special cases. I think
XmlTableSetColumnFilter doesn't need any change (we just don't call if
for the FULL VALUE row); and XmlTableGetValue needs a special case that
if the column filter is NULL (i.e. SetColumnFilter wasn't called for
that column) then return the whole row.

Of course, this opens an implementation issue: how do you annotate
things from parse analysis till execution? The current TableFunc
structure doesn't help, because there are only lists of column names and
expressions; and we can't use the case of a NULL colexpr, because that
case is already used by the column filter being the column name (a
feature required by the standard). A simple way would be to have a new
"colno" struct member, to store a column number for the column marked
FULL VALUE (just like ordinalitycol). This means you can't have more
than one of those FULL VALUE columns, but that seems okay.

(Of course, this means that the two cases that have no COLUMNS in the
"xmltable" production in gram.y should go away).

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

xmltable-49.patch.gzapplication/x-gunzipDownload
�
��X�\�w���������l=�v�(jUG�u�c��s�n�G� ��|���h��}g�%���9�auZE$���`�73��?��fs�K����kg)�?
��dy-��F���X<��m>�>�k��������lo���5���4�������;������m�-��%����y���=�����"��8
�x�f��dm��L_M�E��d�f�V��U������"���t���l���s�$�=�e��3��x��\r&��:��r�6�j�+Ei��9���4St��`(���(c����py�i�k����Qj3�~&3�i�=���-����|��Nk{���ce8`km�t��W�8?�?��}�w�P�$9�-���Nx��kL]e��{��Y�R6A-M�<?�6I���������!N���~cw��BG��F;|z��R<�Z}��6:x�g0|�3(y�����zW�Lv�����n��y� +�M��m���\I�0 m�k�t�/���)��y�m��eP=��c��P�`�$�C�>9������)�a����,����<*�#��x'7��^5o��)������1a+��Uc�8�_�# �{�\�N��Z'I�=��E����~��>b�[���5X�����u���%W�����a��O����`pv����_�u��!\�fi1�%�
<��'���_T(�D��bD��C_�n�Ql���,��]��|�?�}8�)�@L�,X��jR�7����9�D�,�J����^^����g����_���m���p��v�];������^`���x>������X�����S�^D�E#q��?d2��)�$��(�#CE��2!1���d0~�K'P�\$�%dq����v�y�e��(`r���@2���#�k�cg�e�����@�Q���c�����%��������f��0�=�"h:�eK���(O,�%I��+*$g~
�-
���T1�z�s���
A
N-Ld�)�r?u���[��MT��^�f�G�"�8�)�D���I���ssC�g���5�_a�]�e�Te0`j�L&3ei+��y����cg�+�KZ�D�q2c�.��6����{�����{���T��)�@�
0� ~���39y(6GE���AC���|��=���1p�s�L���S���L�n0LU�*U-���MR>�_YCJ�iB�"�+�<��n��^�����J>��j�oPM�j# #1��MTj97��q�K`�AM��E\V�B��+�f��4j�fT�d_4�jm��&DI{��wz==���$K���5r��x$�'�H:�������P�\�	~�1�b|����Q���']#��#�)�C%�QzM1�nX��B��fA�������>V0�����C�3*)�ZGU~��'q2�(@�����Kj���)C�P�N>��0�$��t'�����q}�h�c��D.����@+���S�9ui��a�		N0����/Y��3J��3����C���[|�B"��B�/t����K�.8�\��;r����Q��9'�4}��d�+h�0�����
��P*5��L(H����4��������Q�^1��p��
W�4w&�S�L�h�5�i�+��v���'�2�U��q	�!i������0��2��'3}��2?��w����C2�{oTkEPU����j8��=$`5�b""��u��"��|�4���e��3U�6L`�������l���8�\\�L�E,��J|�$U~����i^#J0�A�C<�p#j�cc6f|�9N�H��w���0��c:
�^1��yPg�@g%����jE�LIC�*H�A��y^E�W��9�(����$�Cy*$�"��y.@&]w�������d!����D+�>d<��v@��$nh%�t���@2H�!�t��������Hl��4���<_�x���4#I�L��~$9���4���Gn&[@�eFs�Y]EM����Ve-K�d)��I����'Iy��S�����au�h��������"V.$?D��N0�)uW���^ce�F�WD�y^�`��%�&�lc$�a�F&;�W�W�K�Hg�H;�-����w�UVBi�N�+J�k=�R$�c�l��b��(�|~q��8��*� fjq��a8S*:�{Z,��[g��|�_;H��P�"����H`�
�J'��Y���ZO1������� ���e
n��)����r2C�#���4���4�Ad�h������jh���} ��2+f�i�2g�����^w�@��_O��nz��m�\�{7}F����@�ao�� �����������O"��hs`�.��\~����ex�����i;���6��A����T����6P/sP5�W��OxT1�����?�_�{@��c����������Y��7���a��ww���:m|����*����z��*E5�Ef������Q'�/���_�l���F��w�B�&v:��|o��DN24���F��������Y����O�r��I�d�����?�h��5H}�l���Xu.���*k�2�%��x��C��ra�V>3�������t�d�Mg��_���ao������������5}6T:^������o
�����Q�����
}�i)�Q���l`l`����F��i�r'�s'.�r����
�,�������/�)\,{��!|P#;8�����'����O��
xY�
R>p���AR����E��0���!
��������y�n�,���6���Jz�:0f;zw�e����W�v!J�EU������,���R�^��
�7���L!��
z���!��0/t������f��gk���!<����n�e������d�O����f���w;�r�|�e��n26�:�����N��{r�`m�(����
d��9\#��0[6K8A���m�Y+���.J`��D(G�t�����r�x��ew0;H��Q����9B��-�"�)��2����t�	�AmRR��V����\*W�	����{����nF��1����M��p�[K��SI��5Z �lz����[�/��]��T�{v%\/��a4TF
6��LW��(g�fW��y��1b�o���WGq�PH�[(�����Y��
�	-� �vE>���< �Wg�V\���z/B��z���h���2{M_83�"��?�bI����I������f�����L�"��]G�SP�B��-hy�7����"W+�*�_��<�K8��!�
�3��q�T�c��T�F��!����m��c�1��S0��2��"���;�ju�vZ_@l�
�D�3w����Bq��,�i3������*��B�OC�}�E.��F�(����'�&�!2�P-V� X?���!���]������������c_!O����!�L��{w0`�N6D��
���Z=�@����6���9�;��#�!�H>������Sv�������W���.@G���G�;���"�l��}	�#on�"����Z_c/^x"����#�W���^�(�?�ZU��r<�a����wO�(��)N���z������G�cG�%���9�M���d����s��"���
�����	�6���������%��"DmX��VNn�~�i�,����mu�rg��`���������C���������|9_����2k{�cHE]m��k��jw��ry�Hwtg��q�Y}uK�'�&�f�g�N7����9c���!���jBl�N���]��rl+]���j��a�6TC��������(��r�����?������!����R��?�������SB���\�1k[Kg��!]���yoo��BA�f�{�����q���N!�E@������(:��o�v�JatE8�����\��k_�se��e$5�+�]������v}�RMu���D��F5c��5K%���dK��W��CPt��aY<
o6�~�������:(Cu7:�V��<��v�y
�1y�
��2����0�a�^��b�u�j%}�.�g���K2����Yt?�g�������7����c�J^&�7Ta������#�&����\!��-�{����j6B��(H8���,��C�4N�?��Aa����� 
���d�)��$G��Fm��S�\Z��5L&��f�_�d���~Z��� �
�V0^c��r��6�������#|���xr|����$��o�
x�%0|����2B�����n�?����In!�nkA��D,!h�kI�KZ8��(�8&��7vt�!�kJE�4;+���Q��^�Nj-�{l�S�������<��[�0�G(
�?��JM�@�����M*���#�y��.�K�)������e6*��5��?J3�O(�������{��J����V����p�w�[������m��;t��e��.t=��.�]zw��������0������!I���Jq�OL>5�Z�1M���|Z��>f��(u��27.XHl)����Y�W��3�p��Q�
�g���M������h���-GU�6����0�����A>^�vh�g��_?/V�n��F$��w�# ����<����B8�h��F%H�e��r����jM�����M���6�&[��U��o7��{8�k[�������@��p�������jO�A�/����R�-�M�k\p��+jf�Nn�+�t6Z�t��P�I�<���V��Wg�g6w�w�
/[���[q/�8�M=�=����`�bJ[?z��$�n���<����
[D��m�������wsvy��_y�4�/:7m�m���M���w�g��n�.���E���/�_�s�����/��D@��/�*<#!g���G��1	��>���/�%��]m�OG��D�/�#s����NE 8���9�8��u|e7��N�RL�S��v�/j�����s���`��v2b6��R���v���oq�9�wCh�C�L��:hP���1�����'��fd�mY �4!��|H�@us���0�J�(�{����:�%��jBC�&h�8r��b�Vxw;6��������>��P��S!�[\�$'u��L�|�<aSx���`N��Z�_'�`o���x����y����*�����8J�4�}1D�BL���/���OzE����8��!�9���B-p���]am���Q
~�e}s)$�iv���eC�C�8�-1h���1�,%�W�������9������Ua~�����M�y�rM���+Z&�U�t�����7��:AO+�t�P-�+��)=��`<�OK�%�U�n�M���������zu��h�����G����
Y��)�c2���Lx��n����W����	�S9,a�.
x�jB!o��I��K&�
�Cv�����8�c0�A"�:F�������y�z��PL�&�p��\�/��s^[��b�}0��&x"s6�
Z�P����KI@�4�������G���ng��������x��q���C��8q6n}I����������f%��8����y�#��Kp���5��Ul�o����KH���+�=�<SZ�y~I�}�������'���^4�B�V����M
�P
:jo�08����`���a�K�UM1��a���'���������?�%�-&���!���?�������r��c�#��J�����y?�5qxP��b<W �����V�����	Z-��<
o��h���zQ��5T64#3w��!�P�,��$��J^��8��L�����F����k���)sW�[^���d�c��j3J���?�
Q�*(d�@Vj5�6���X'�r���Fi��X=��50;XaHGA*����*����}M�z6�Z>�W!g[�g��e�,���JV��F���P�N!�9���&'M|Y���������mm�������5����v���ar����3�FXk�QF���������������I�4�]�]]]U]]]�������2������
����(�1=�alpzS%/���Iox�F����~Xr��	Q��:*�O�����{(�Rf�;���.��6�p�q�C
����#�o�G�=2�z*�!�]���t���qLy�7�A���UpF7�J����(5�d$+f����I���j�M�6\�9�/�q�����'� R:�@������}��y$����!�6S/���f��:�����[�c�31��U4���9����^��KXM��r����<`�Xn�b�C�����i���nD��l'6��l����Z�t�C��B��*�1#g���7Qb�Fm}��[�W��/./"�4k$e�����$���!�KHB9�����R_@�&0
@���}�>r��f��]���So�=����!*y'��A��!z�o(;��CpG>�@@���/�_������H��E�1rQ�����m�w!���uY����u�����
�z���4����x
u�r��D0p��%qF7cs��(�`V���[[�
�*�#�T������a[��el�R�F���'�i)a��7���rF�
�������0T}c���(�b%�sq+�*��%�Do���Zz~����D���q�B������)���4�(���q��r�*�D<>��Z�����a�D���6����A��]��k8%�+N:���|6�-���b�O�t��h�~I�6����(�m�cDK5�f{"�;���K�����c���;��q��	�F0x��EKK�.�o��
����B4��iL1�Lh'`2<-��[H�����������I+,��n<D�����������H��=��	.��~��Y��p�G5Fw8
.9�#����4���=z$��������4�3�fN_�[g?����xF6�����<�n�Q$<���N�Si��c1n[A#.��3V,#��#��'���R`���(L���W��6A��C�}��<�H���������7)z����p�o���Wb�0���&�����Q����y�WK�3V��tG�����36���\TB�b���W<������r�����)���:��wv�d�@m�z���Ao����5���GAL�(��I���g�v����R�����E�rI�9M��i���r1�*jW��%E��Ag��^X��0%���wC��`
6���Mc����=���C`Mq�8h��A��G>��R���b��*b������yf;�F 0$��a�/M���C��XC����zE��d�p����V�5�^�Z�'v�&� 1��\\������@���09�'U"`bB��d�.���6��L��`��� ���[o�"����.�
�upL��^N6{��:m��)�'j����s�&YY���YO�Z�@\Do���\��31#Ct��8��;����S0
T��Z����Y�����9�*
y�P�B����LE����x�89h����5���I�
���k�<@���\V4�_�Ro-����hDb-�������s$��%���>���t3��hX+6�.J���F��9E��
e�we�s#;�k����B�{@)�-!c�N��c�4$4N-�n�}��kiY:�@DHK����.<�ZP"@�`:	�}5�$;T2�������
2�
�>���XC�'/Db�(��
��k4�	�7�����yu���� 2����X������RRo��`�{C�4�������>,X:�q������l(�����I�O�*16%�v/���a�oX�tbX�Y���?+�C�+�X�J�����mq*�p��&y �|��F�$��b�����a9�&+� A1n�`��Q��o$;x������
���r������1E`�U-��m�>�wU�bn\�������Z����b�Djfyz�4G���E����S&��y�a"!��#��(�DBK����:^Vu����	�8������G5�&m����t������*��7ZJP���Z��L&7p�bi�\3.a9�$7V�j���C4�����Y�a|�����Ho��K(�o0�7Bk�\5�����FQ�s���PR�����CPQ~)�*fP�M0I���M	�F<PE�1�]�h3J^����p�"�:��3u����X��v��!�����a��-j
�������E���\�Z��.-@�M��?��H�����4j2H���������_��mHt���JUCc%�#�fH6�/�G�����\���U���ef�<fW�S	�m�#�������m*�#��7f|�"(�p��&A�N��G���i��<&��M*�2z)IH��5����
�
-0����V(���A4���@%P)�(,���)x�\�P[x'�h%�xvUXS�I�>�W?��0t��L��Y���K�X8�K�t�-�����{�0G�L-'#-�7�K�yaI.���\�xsc�1��jJ4�X�� ���'E�&���4S�	jnv�y�����tC��o�xC,~�5�^�P���m��J����_����"HBH^_M��H{>�T1DJX��I�5y�A;Y[����+��cEK��]������d�BL�6�J�a-5��<+i��q��%f�\�I{����hg��
U_���H�����W�?�}W3t*�$Y4]�'�?��-V�����AA)��A\�6��D��Z
�6�=��y��cKP$�Je�\�U���m���G'����)MNg|*���e�!c$�1e$}�~	vU�;>-s�cK���t�M{��/�-7�����*�U7�KK�����^�O�1�"E�*q���������2G't��K�#��dn�[�@��M��9�h��C�>�����M����	��M:���&����O���������['�^���zs�& ��Ih��:;<~)�B�_)=o�p�^0���2�]�b��v�������q�,\��]����.Wt,N�\���D��X�vs�[�@�Mx��Yg�M[U�Et�C���*qb�S�-�8��5RQ�ogb�-�[�I<�M���[/��Fm���
	�Yp��R�S��w���<	\b�C���?�BL��5�?���]�09�}
wuO0#���j����{��I�+��WjC��+���#�
�����b�"+n�������=\�*5%2��9v-�\��oS��]�"l����Ju��L:	&�B���]�q�$Z��	^10�,L��q�*�98cg������Y@Fk�����vK�j�����Y{+D���*D
�.������l,���E/��#�q��8����$�Y�EE���i����R��Zu��-��ma�xS�]�M
�6�Z����H���tm*2C��B35n*�K����M�`�2���q+�0vP�y���R������%�^-N�y�O���WY4�K�t�v�|�ny�D�P2]e������y��Dg�r��T|��k���XL{tL�S��������g����D�R��9.�J���BU�����9#�v�5�;Q���'����R��\=�����(��6��}�� ��x���wOul���������cc�"`�7G|�����o�n���4;�8�����������[��icN%7.6�=,��v�R\��UY��2�]�4�BK����I������,����4oR�M�q�������]�=����Nu�/�J;�v5��<J0!�i�fTv��*j�������G��N�
A���KD�S��W���tm�������
�P�R��������j����b�*�B'b�i�k�6�8�����$y�K���x6��`
�-!�@��a���.N����m���F��5����5���T��g��#F |�I��>��V�FQ�
/Fx��p %�x����z!9�8������VA^[��v9�p���rP�]����4�i<�`5��BZUYY�H�$��
(���o��,���}SZ���ru�,�*�t(�� i�4t,5���h���Yu���2I�^�[`�,���7�5����^����,d�[ks���:j�Z���RG�Uk�f����^����hb��uS'���Z���j��
IAs�J�K�]�"`�������G|E�+\�^�1��b��6I8�j�A���������-V=����*����uOR��O����"Z����B5��l�n/���D��6[)nS�}�J��
�sF�(!�&g_eN�v���]�!�B�pn8��|��S�n�����)�W�,��~���$1�l�J����n�2k�`H�&E(�����)V��(
X'^t9����5�����8s�i4$W~�f�z��9�@�I�����Z?�6+�k�7��+l��v�*F�A�1W!�1���p�1-u8��q��������I��X��:&���$�b�j���������,����J��
�[uZ�xi�u/M�n��uN�z�O!R�*G ������>~p���'�@��9<�/�T"���&^������!�xE�NG���MI��=v���CwE����A����s�$Pi����������oe���X�kT��	k���A�������V�nk[6
�[���Lv����9�1���_6S��1?���=�������@'�R������c|�#Pn�6N�l���$��<�I.���N�L��&��b�wa]
����^] �u5N�����I�U��*�6��;C�ze�["z�����d������){A���RRu2�8�|I���������������s{t���%K��
.�~�Pe)&����_������y����8UYr����p����<3�����8�lL�eg��X�)c��=DOS�F��7F���~��#6F��^�?�_H�N�������J�+S���v$��Lc?�D��������g�������S�]�[�(�{r�x��N�h�.\����n.��(�_X%'��fL:dU����E�N���J�r��?{�Mi`�!oR��+��M
(�x�@�?k��'�ME������uQ�*��Q9@� 2���0�]���Rm���L�+��T���������|�_����ul��(�7V�����g���D��K�C���E�7�u�����HLG��\�Ujxb[��g�&:������y|�D#��6)�����xu�&)�z-��tmj��[���h�5��������KT���Km�����)*����s*�,��m�$�k<�=N�l�����O��I����K����0#�����]l�s���>r��3
E�u�R�$�d�7�Eo���.0�-:��C�L���w#u �u�E]���������-�f��)]Uv�l�T��9	��}��j��h7�OLan?���;ev(U�����r�{mB��M8��I��Z�u��Z�����J[��^w?�2�)���EFT���D���7	�*�z*�����#�C��p
V�'�)?	�Z���"��h���-Zp�_��Wi�&���o�ip�!�qo�xw�&p����k;�$�x������vtl�@lC��KL
���s������O�7�+g�PQ�����V�wKg����c�a�MQ�%0���^��c�mr.�l������abZ���5��:N���s������$!q�A/��}8���,�:3�;�%��PI�ds��t1�T;K��Xu�=���P9��.
��V��5f2U���k�\]��TR*�t�����]���	�Kvq2[}u"{��b��	��D�&�`Mf�T1}|O�o�0k�g������	��oW�(��1i�'XM0�4�6��`e�kY�j��D�Ne�(o|5��������p������}#}5����u�����9�3)����N@�Y��E�>���g�#���L#[1�����!X����|��V`F�{c ���a���H�M��d����b,��#q\��N��wqas�(m@HN���A@��L"�y��������6e2�a�tv������T���ml���1�;��[1_��@0%B��]C���pM�����5�*����q7�G����r�w+����n����U������f��FYr6����/�N������&g��X�"x�}�w��r�$R�B�P3 �\P�dA��1�b���X�2mgK@A��"��,J�8fK���0����m0d10Z�3A`%+�Kr��P�.�l�!�H��>=�����DsB�����j�Fdq�Zz]������6F��t���
3Y`�'��%d��2!MT�m��r���"��|p8��N�����L(�/$�Q�d�E��o<G�=����E�VtL8lG�!�b����!��ah(��?o�Z�Hz�F��:����"! C����(�N1��T�u.kF�I�'!%*��6����6<���Da=*eH��l(��|t�T�C-�2�0Y#1F0~��'x���p+�<��5��������2�d��(�2�]A(6�e�Z0T-y���u���b�g�����M8��DPsP�	A��)`��V�"��\��kDlm�C� H>J������g^���jDB��"h�+<�T���j
 �����'k������k�h���i��8}�(��]��O�-l���d
@1MGE���pSzh������ 8�����U�����S-
��&["5pJ�X�!�b�b1C�K������f�Df��I062Z0�/e��P{�fWSJ���+�:N�c����T��SFV�����EL1Cj����-�s?�++)�L����_��'[Yv�����%���P����n����}bh��a^K�	�UH$��,bx�@/�)�G��#�'y�D����y�A���D�<|).d�����,��i���Y�R������������X��J�\Vc96E�z�����/v�Y�&�v��V�i��=����8�%�G�8�����P��%Y����5�
�����[���L?Q��'��xr������W5�6�H��u�qK���%���������0��n���f�y�5�f��r~����m���y�O��q����f���1�������2���8����V� '�f�"�����>�>"�	(L�B�UT�������������r���z��;���9{S����&5�L�x
q���zf.����:�����v�R6j�Nv���H�3��F�)hN����h�<�����?�/&�������8�����i��m���i�B�L$]�h�G����wj8���r�R3F=�r�
��x���:p���m����(������~�Q@�q����6��~��������%
���%
=i���L��1M�����z"H�b�����_�������G����s��"�����J1��h5
���c�n�Vc��W��7(�6�?n�K�%������{�j�*?�����b��[U��8���ku��jR�*%����U��Z��V��g@n��d�KK��t�o5}��7
FSV���y�uz9&���wB{���m���R�dm<����x����x,�����e��{)8n�N�9�z&� �����-�a�=�]H�cQKg�c����m��s��\���6�W����HZ�b��O#�;m������8q��J����mDj����JgZ!#��a���4�fM��'Pd��,�9M+5����:~�d����w�M��n�BV�X���!�S�Da���/����%�a~c�/`{J��BOH��s ��l_�����k��G�Mr5�<IE�������,��S.��I3\�����n�F��2�+n��l�� |f��u��d������+�;�2�^8�rw2�Y��@~��:��#�mZ�	$���I0�����-t���`�Xto
��������7F�j�`���us����5E���L�����g�L^�$��������n9��)����No'�Y�5�*cFE�������!!���t~����D������
�|5��{;��1~�>1v�e�S�%�IC��;>I�v��V`�m�8���0�R��%+�gf�*&I�/���^�T����;��IJ�IP�(�7���]��L��j<Fo���I�M�m��C�K�<��1M#�����KdZ��Q�%F�������:�����?���z]?���x����l������Y(@����l���Ws!B��R_����"G���o;�w�����'KK������ ��9����W�G��}���E�������Jyy=
�K�w
����
��io>�j�2
��io5�mG�R���K�S���[B���k����&Z�xU��B�* G�T��*9�'���N���?�q������^��q�`�3#,@��c,Z�{��e}go�XA�h��6�q)^�����/����/(��"���?�]y�Vg��[a�&�7����/1RA��R��I����
�X���5������F�'6�B^���w�rB��H���s"z}6�tf�Z�4����3��d�x�+�>@��������������N�c���
{!�b�!$�}�O�F��	���)�������{^�=����
�{���wsv�Qi���b����
��m�n�J
O	_��<��j)�`
����UL�����:�~�T�U��j��H
h�����m�]���!�e#���m�K�.g�����zI���,S�0��;[z/��&W�T��H~��R�*�B���[����F�l�ESQ7X������]�L�Z�i���Z���?xO�
�5�	m
�j������&��j{������L���t����4Y�N�v�M.�t����n�v�����j�Z�k?aB��H��/~��������+��������3	�A7�!� s�Z��v:��R�����:���j6�1
���*����t9����� ��o,���K.Fp.<�����\>3��t������;2�c
u������h�|{�~��cd3�cX-%���E��IC�D�9�$U��,���B���Y� ��>YV�D�����^�T����l�r��Z&	:�SB#�L�Q�u�j�� �L[��t=��S�g��E���y2�$�<v�1qUk�zj\/�y��Qzk=�[3v��������v#�!�$0W��Q����3 	�V����p&Pf�J5m�G�����y�$��a>�;W�+9��u�g��3��"w+���R~t��3�J���x�&&c��I	P����������r��8���/KA)����_
��<�N�����9��<��L��e���u0��z����$�~4����5xG��Sd`ds���0�X�N������.v�����}��-5:������q��>�����.`�d��X98��z�7�\�����>�/wT|��l.�~�@El�I�x=����o����\a��M\N��W�b�I�3�8!h���������Fx�������}\&!�i=�lv��>��%fN��h����o���(��s�-A�]?Kq��1��
z���v���S���s����Y�(���U�{���'C�����2t�W=A���vWEqs�q:zQ8`�N���#�yA������[�taEzI��e4m�n2@�-`7(��Z����F�	����*�|��W�w�W���"��h���������vl?E�
��0�%b���A�N"4���;i�K�2E"v�LS��U-q�fk���mN	~kp���s��)�Rb�g�*(�1�a��Y/x��$\�>y��F�7�[��/��GaQ�x���n���/��N_lY����4s���8���lW�	�=B��� r��y��o�EK����9po
\�YSM���>/xvm�by��1-�p��t��bj���`;c��O�P��U�%)����`���`�GaW����d�B��)���C�f~�n��U�{<�Z;���y��p��a��	
�B�Hk������hN�L6��)\��k��75��&k[�$�������y
�UXe��������k���szz" ��*��7�c(uCH����+��>����~��H���������{}zPo5U��et9�(� ��$��}{*�*��~Y��V������SK���Q��!7�g-����n�tN�x!{�Z��J�]�����i�N]E��S��1���Dh�	��3t��~��h�+�%e9�R�����
��b����exQ#������@�Obu���PpL��"�_c�FmY\��KP���� ��1��)����E�a3��i}��z���?u�C�B\��3������b/�	�F=<B]����/J!����D"s?��,����U�_0pM�(A3����1�@_x�w� �x{0l�_���D7��������=���~l�W �!;�G��c1��=��0�Wp�\uQA���8�`�9"����`,�6�����*��6M
�(������;j������`FI���
�o�E��x���
��-q?"(��:���%�R����p-�Bx=Z8�@�w��R�X�B�w�����5�GG����Q?h�����?����������~x���O��R���������w��O�����X�8����G'�M��G����I�-�vN��|�������+���U���s�r�|��<��_Z0������3���g������3	����a�7���G(��>n	 �3����u����<;�����Z��	��g'��������@�#��S�]/8��}4������'�9k�[���zz��������O�{�GP�9y�� n���Y��TL�+�6�~zu$�������w�,�����x�l�����k��4���i�I�����|q.��~t������6r\EC<'`����$�@m��s���s�VdT�(�����g����z�dX*���O.��\��_��<����
�)]�/��JH�������f�P.�Yu"����{�/���8 �(���|�������h��NU�N��[�����������6�����:I�����H���*�V�v��2
te�"W�/pr��hXB�>Yj������Fl}iS8�������]Q(��%�ik#_z��U��%�����:{R�aZq�%�9��5#��o�9��v;��:?N���w��8�8N�91 E�Sa������'����IRi\x�E��'��=7������l?L�}j���T>>��5�'	4C��?�A�6��R�Ke����6���.p��������K�Y��gl^�51�$bF���iP��E�@Q|k�bxng�2��d	�=�����H�����)�{Q��K�x�n���wcK(&�2����Z�
�p#�����`S�,��w����-iT���T����O:=�;��}S  ����������)�i�|�gbJ��A4�2�x�Wy2������%��^d�3e����pY9��6�rlL����gI��6i?<���U���%������b�px���G-:.�3������XE�u����&����
�!�(?�4�!o.�}Z����.�A�h�;���2��c�|4$�����u1�eyQ���|���y�������^ ��E�chF�V�:���J�38���m�����4����in���I";��K*2.�F��8_�0�-��X�����%�<l���LR��%��������~t���3j�|m>�B�K�F�r�Ip�B�vnTO��-u��e��ue�zu�i���wq���X�T�����:�Q��~�Xe�E�4��R,���d��� ����&_4�����h\Q�d-��`�B�	W"4��R4`��sN}bOV�o��&;�+��Q����~����(x&���LZx��x�_�B����J�b}hfol��;����b>s��2�i���f�>Vs�=�7��G:z-��������{v�U��P�od���]�k�
n��|��B<�P��\C~�s	��i/������|y����T0k���^E&r��D@x�'��m<@k��M6om�0,������C�,���i�7m �	������ ��<8L�p@Wpk�x�3�c{��2����Q4�e�.�������wJ�����e%pCI=���C�����e��#�T�V����b��������	�q���E���w��w*ezu�)��g����
//�Qo�)�M`/���b����b3���m%w9I����=2�2fB����e�/Y�����������[���9F����)K�(G�o9Cf+Tn���OI�*�/���Z<�
	�����"��������buk�����$���)X�6(��RK��|/����Xg��\[UWV%�=�
K���g�/�[�dz?��(��w	v��XC�}�Q,�/Fz��s_���i?�	�����P�S�h:FV����/O=1/w����;�Y�����tOe�����@��L���t��Q���EoWdYLk�=��������L���d$!l��<k!�����
&1�"85��G#p�S� �7�y����������g��Z�Je=H�%�����1M�AWP�X��a� �_p9�X����bl�w�h��."���0��!�7�#����+3�EA�K�uu���0f�(�{�
�#2���I	2(o��{��6#N�!$� I��*�-��J����|�b6��{�(�\�K��$�0�@	8���b��)� ��I[�^<.��w}�!�_�����A��[�0/BM���+X<\�;��\ ������`:��p�����?����Z4�����.F��H������iP�SN��y�)$�JbG����������;���;FRp��XDA�3�l�@���h%�8��h�E�`E0,���"]��9�*$��P!}}~�
��ThIy&Q�?�q��?FF������]'xBL�~��!��>�������!��ZX��!i��y�d	xK��YI�u���t��W�,���ZHJ���y8�Z�\��$��f,��M�
����������{u���������D��q��
�
C$E����%��.4�� 9
���J`����a��T����V�e������"��aY]H�h��_�+�c���zo[��F��y(�$�o���-;������<�����Y^�46���LhJL����}~
��v��#����!�m`���gx�'�4��������9�M������0����a�7���/��y�u���Y�D{��X�������*���ItX��G8E�+0�#q��S����j�����P��~��ad5�����g5�/�@p����Pp=���J/0�u�������gi[P���;a���6������OHS�y�.����YKW�u���<))�l%~�7�|N�_�2Z|f�i9b�r4��&�$�X0�Q��XS��&�q ���<�}K�-1����|}K<JL��2�`�x����8>M���9j�7�~���8X��Z<�'�H-��P��U�[��U(���W��oW�T
u���Ey���	A��w}}M3~>,�����Wc���Z��w��	���tJH��(\�5�_���{����u,��4�C��"#�K��uG�����+��^[i��k)=J��1��M��`�6�\�%��O!��NP�&�"!����y�iW�����_B
&��������6��3�$e��(%S.9��y�"Q��:J%�[�KJ������Y��t6X[@�H����a�6���/c'3�]��h���gKT�}
L,��$��;�#���-
8\������Y�5�4�0K4�NF=�;�����!�]�6x}8�"�[,������h�p
<K������E���N�<C�@�&��'f9���A���������+�`b��%��L��O-�%��r�Aa�'�;y|�;h�1�Q
��
O��ih�����bNRp�m0��1����n,��X�d��|�a)���)\�+����}�BE�B�1p���k�I�=^��t|N9��v�a`vJ��b:5���0��`$����������o$h�U�p(`�G�o"�{��.��\��>k5����Sd�>xHd+_h�w���>���(�!�������dC�i=�����d@��~� ��E���9���L�#�Em����a��I�{�>��#D��PI���C�fX���:<���Vf�\y����4 �\Q��,���������u��W�����3������r
.'�f&��!��#��_q�
<��Pa�,z��<����w���t����q�<�Bl6hSH�}
<�[��9���`�"8�?��2]�S#[�.��R�E�R����1��R� '�����BN�����T3�P���Y�����}��6P�cd)��N%��F*�P�.��	���N=HRN=��2����P�b���Z�p��w	D�K��j��!<�X`<V~40�k��P����#@td�.��t�D���N�XO��u���N��0�5^Q�	x��H���Ix�`Z��J0�ZfH�����0\��a�~�����R�[�����Pf9��r��T��)������$�[�'��r)��t��,�n�x
���x-���nWa5����[J0��=_TvI�7A�-�OAZgV� ^���l�0:��O�}t��.a>*�=5T��<=�2��H�.t)8��0��/���*{�R������L�KOA�a
��]��)tP�/*���6���o���`��E&n�"��A��������R=/���'a��e��������N�pB	��-���GY�Su/���*��&i>Tz�~8��,d��Y�UZl����^�i���������/m�a�'��������/��32���>��q�jC���Xo[�
)C���H�&����{c%��a����N��,���~&�J��QH�z����(�m`�e������ me�]�In������z��\���.�Lw�HQ�l�6��8�)��^o��=�'���Zl����}<�ANUBj����.%3~�w��k�R�,��ry;�:a���P%q��T)�8���d��`�+���X��`��u��U\�g��:�"h��Rkx"������6 �������`�KZ�
��V�`���������M�PM�u�[o�I�B�X/0��-'^F�!������tq|K������,��cG�Fy����x�;��#�@��'�u]]r$�=�v����E�#��5��z��"��]�J�cYO�N���c���<7�������,c
$�TI%��xS�!c�I��������a���H������1�'X}������!�
n��F�|���gG������o��P�"�E��aG�u�v�r�����/��;�fc�������.����!k�D��W9n��p�hH��-Hlv"����t��h������Fo������[��d���S�a��I_�E�v�S1���\����{Q��k�x�k-������,]�W��s�om��`�����)��E?���G�F��l ��d���[/d&�G�(G�����}���_��q���!��8�������Mqi�Et���KW\��i�!v"��E�B���Ml	��E�mw������%������S�s�?��C�a��GQn�v�.����N�� Rh����"��E*J�����x�@n��d��dS�
�?������[{���{H�I��I��Z����w�����#����M���a+I��8��� !y���\�%n�R�HN,�NVRC�^�i��C���ei����	j�R�����l���2C�V�(o�����uw����O�h��>TH^���%�nqme����G&��s��|W����<� E������{|
��Z�
�������s�`)�7�7bnWcZ>�����I��cCm6N���@�e�1�S�W���)�����.�^�$.[��;�/MK��8
K�tX;����@����R��(����=�x��}#u�������L��:L��{1CGB��br��;�NmB�o�����
(�^R�b�����?�2%��RgW��N[R3�-XU�IJ�Oo�����h0z+t[4-�.�^�MK��"H����#��7���i�W����DXvX�{���)+���l���f$�%��YF�"��vvw{��`��J��NJn(I:r�C""#�1����v�-G�8�"l��
�z�<���Z�
��S���b~X�ES��~�
�hw$���
��~��r�q�!�
���x����4�;�qP���#?���1y^������Q��Mgzr9#��G9�n���w^\��c�q;�g�H�uw�(���}��b�,"�S�lY��FaQ2w<��C.��[l6�Q�����t�:;9�L���G�MW�'�S�7^<��do��;(��{(������]�w�/j�ba5���%��x��]�5m*+�����''C�b�IvT"��v����Fd�����VSHM��:Z��R@�~u���W�b�
3�]j0y�aC}��ia�V<T�<L7�b
������o����,��-	f�gWXc�����S��d��>j�(,��'����-�32=u�����~yCK��e��������%�Xto�n������z���p
��I��=���tP3�j�� �2PY9]VdR�����#�s�4������7$J�������_j�����h}��@���ND�F����X�9(�+�6w���?f�����N�m�I����U,g��p�3�D��+�/�)����4����a��-c,k����d-�J����|TK>�J>�6���|ui\�V���M��w�hv���p�E�\Ep�\`�U@��X��HT�b�B�G�9���k~���Mc���.��#�%/��D�1��$��4���IT��#_SR|L^V�����a�GGs<����k�\�~k�BMW���]������t�-]M�K���5�u�m�73�>W��9�3���E�;���uB��:�T�q�����y+���Og����On)�����u1O��2~���`���6�WW 
)\^M�qq>���yJq�o�s�������g^dyO�=\QN���5�g0�b��fkl���-zn���Wh��"5I��F�+��C�K��h���|G|���oW�z�V%��y��Y���W��L�'L6�1O������0:���l?�\�������$}����3o�����n��r�a1��*�\��:�����v�j[���Q	t����n-eNj����Pn���{5
�M�%��d<�E�S��<���//���1D:$ON���9�����*W�����;VN,"^�)�B�����t�/���i�u��9���r����$zH�E]������� &��?A�nr����H�E����| ��C�������g
2]��� ����!I`�����|�����!��[�������wb����hX���X<��:/��*i��8����^W4%;���r� y~z���6l��7�6�p%���8N�.���#�]�Y���p�NDB�*��1:��
��n�q����)�a��>�
�8���X:���V0�Hn<1�h�P3
08�
�1-�O����������~�Z��.��Bk��)a!��?��\A{�K�b�5���tb�KA�A��/t/
C0V�*0#��j����h��(����=R|w�^�]����\G�
�__��r���f���J��L�.����$@���)��S.��I���/�S@D����y0��!�#e<����fg�"��X���M�}^���#e�@l$��50��&I�7�����_�=!��t�FM����2����5��Uc+����.��TN�z<�������y��f&
����*t
9������zwi�xL4{�(�\M~��9��+9�3�^��=~��z-Y��I��q������.e��G�����|��3�m�s�=�a�{�����J��O2G{�{��4,J�'�!���4H+���y4���H���:y~�#IA�^���
����U �z�B�M�.���P&�@��E��@��)���f����~�����oP�3��c���V��������&�N��e��������d�gy@%Y����~&{b�S�y����2y"���
,|��hw����BI#����������[�v��F����`1u?=�O��Qd{Q�H|���o1�r	D/o)a��!�����K������g��B�b*�XU��E�0hM#%�Pb�\��,��Kp����
+
�/�%������y����tUj�,�ZAr[����j�%cL���p���/22�������s���c�8PwL%���p�%�O�|<�|�|��Lygg
�����?`��.
u���5G-	�r�b���{A`CX�����J<
M�e�[�K!%boN�;P S[0�vJ�b�A��s�!��U~f�6��<D����y��x@����MG��h&������������J�)km<G�ll�-�=u����,s�&��%����S�4���U*y�*�/)�\���V�����.7J��������z?m����EU������K"��)O�q� ��}��?���d��]�w�S!mc.6u�������E-�1�oA0���\k�S=�*c	���:�%�v��B�Yp�kR5T�/)�O^��'/����N�~V���Q��] ""Ci3TG�0�F�������e��x�12(#G��������1Z
l����f����`c����������p���JL�����q�[m�����@����A�P�z��0���������,u>���wJ3�pI���[[J&������32�����1>C���_�i����d(���1�b#S���K/�'h��+
s����2
e#!�.�w������JMo�J�y�q��D�1�G����R��g�c���u,B�����O��I�N��dvp� �6��,�{��X"����Q�T����""]�[��	A�)�g2�X����a�4^�U�$�BN��S.'>�2�FR�`�LX�}=]�#(?����{�h�x#�Rz	��p������U>�d��g������\�F��8Ul��)W���e���OY�&P�Hv�x�_��B�V[C�`B�F�{$�8���J�q���j9C��k&5����/��'�����5e��Q����p����p���
m���$���"6F��B�*t�w���	���)�]vU)������l��j�id{�b��LG������Y5� A��>��~������-,�����z�]�K���.� ,0�_2	3��Vn=���1�Lt��������NHrDl��ryHea�o	U��^*6�����V�A��>��O~�7������RV@Q����F2i�-#�	6��E�'�\�����B'#J�KQ	�E��g,�[eGH���(E=�(���[N~��L��m��9���ByF>�"E#�/41���Zc��e+��h��F�p�AV+]J���M���,,���@l���$'RJ��_����2d�!���Ad��++�%$��	���U��G��V�n��d��pe�d�,���zH����)��	%�p�Iy�
&�j��>���w��}a��U��k^<�i6�}��%o��O� ���GZr8�������)E(�����s�9ls�
���	JV��s�!�x����;��C������#c�c#b+5������~�mi��!x�9z�\�E�6Z�fT�sx��'�-JQ�o��������Me�L���q6�k��GL����l�a�-~�M,��}m$-��+!���4�QJ �O�,68��fK!�Yv�bU�����b���������C��9	��S,�zk__~#o���&a�x���&�D�H'�h�����ZU�sc`�%�;�$��Q��x�m�=u�W^H�N���)������d�-
Wx��L�����.�,<���	�0�C�/G��f��9��j���Q�kD��������n�%=%����a�����\��,�^=��^�[��U
=�4
��L�$
��M�T$����s�|�n��*y��-FI��
�����.����Je���ZS�3�H��0���e�cv��:)! H|�F�����N��k9�
��	��C2��d��hy� ���9Igh���cvM�����_:(�V�d��D�j����&g����6'�GLz_	Ut���<^��/I����M
�&�%Z��	G��x���I��kF]�kwHH���B�!���&�Q��p���P,tf�_yP+���@�C�e�Q8�������p�f(� K,R��6BC�R���L�M�8Y����9y���5�,Us�����H��y'��H�)5J���Q�A#���`Q���eQ���B�q����J����P1$lQ$[4��9����������"5K������� 
�dx*��`�V���%;_�������	�(�a���'t���>��qHL,��H��xFK\�O3�2[r���Z����)#����D�Ln~m*�q�F#s�j���j�K����>�1M����h��`����^E��)�bd�=�x��\�EmU��'+K��5���j���0�X,
�ad�S�9��Gp��:�%�q��\S��)t��p2�����i�+�+�<�@N�kd����#��]Y��~w5�Lw�4�M�66TPtuKS!i[�
�ct���~$�g�c�a����o����"�Q������B`0�>K�	�FI++�����!T��acI��V���-��&��Y�t�0b�
�[�%l���4����E2z)n�{%8���K8G=P�7�AS,��z�K��f��3o5��Ff���Je�J���&_�k���e��Zs��B��P��d�P5��`j��~d,AL�I	�����D����u�%���
H�v�w&?�1y��l�
&a�o�GW��������vp�;~iH ���EU�5���j��Z��������j����I(s��`���P��>qV��I�l"d��{�z�� �UL��d ��A�B0n�HU���_2�=��&�xB�,�r�����������u�#��Llm#�2���pz��h��nR[$j����
g9�|?�g���	:�C>)�v����V���
&Y�o�oN}b��/m�6���IcD�� �3R������S���1���\"�cg=I��^����������ip�}�nP1-��cwn��;k�Z�D�R���}���(�D`�'(��@�M����E;�������?�2X��w$"(�=i`��--��X��7� O�e�U���%l�9�\(��T���w���ia���ybDU��\*��z��S����2��Z�w�������P#�`&�$�y�����j��[4��K<����{8����Qx5�i�_�u>6��
����k�;�e4�U�rc�+�+��f�d�HT\��F�������|i��@U�OF"�v��.�9��<n��p;�zg�3b��U��#����dp�NNC��~l�	�$,P�9����Y��������$�	�1"��-P�H>�������9�����wv>��7[�s>��A�WP��:�eG_���vC�
�c2E|&��^���&���	����r��f�}7���L
|[�����`��H��,zH�!6J���>�5.�y}�nj^�S�>i��m8r�[���������`,&���.�j��	��=��������r��4�U���t�Qh�<�D�[{D��
B[��
&�n
�U��8��b����'�^�'8��G�.f/�M�k�W��=}BW-  ���#��"�x�;u
����N�^�����z��Ic�O����[+�����1���\�&�J@G/��.�I@���%�t������uIL?���.�����5��f����y [Dp�����^�wwJ�``
�12;b�;}	G�N)*�(�}W9�q��3<W����7c��B���`W�-~Q�[�P�^� ��h���
`0z'S��o�	^����i�K�U��`�myR��2���3:*���wl�R��[aIj�&��U3�\�^"X��is�#������:���u���=�d���g�U'�b�e�
X�h�����=0a�dY�G1��U�S����R����,�S\��w����
�
&���G������Bd�U�%B2�[��D&`�����L�J���\,�Gr-<��5���)Tx,���	������3��n/�Y��#A^	��P~2U��.S��Y������/���_��wk�����x�%����s���Iyb���=*-{�=M���
�������^Ob����DZ���j��l+�C��#�P?�&��|�9�(�9�l!wLq����/�����d?�Sr�AJ�X�.G������88�62O��Y��!�Q?uQ:��0�����2�*;�7���Q�����C�����<Q�� �������T��2E�& ���0h1��b�Z;� W�~pp"�����(�J���%V�vL�@�Q���"�(���	������W�x����N`e��]�E�X��Q�+t�����d��%�.�y���^+T�3�����Qw��C���h=b��O()#������3�b�J��b;�?��E��1YO��i"1s��%�1
4�����A��������$�l0���7����l>�(Fhi��������]�*DD���UvD�u�N�h����;��Qr��8�1.{K�6'Y�D��<G�O��	n�y�E��������N�8�`�(�����<���AOMc1�e���o~���Q<&�aO���s��om7��]���S�h���%��X��%�����SEN��H���R�T�a:��~&�d<���/
uI?�~%�	%�@$�cT�����>�<�P�Y+�>d-���m�tJ�N���]���v,i�*���i�?[e�ze!�+��S#���A�P�_c�}�+$l� YI����
��#���h_�AF��N�G�=�Q��<�y��o��U�KYJU�TqK?��*M�k4�d��i(��E�.� Q����SVoSS�����p��4�2������-��o�>Y>CW�����FoG���E������@���b$�lA��(�_a�f�!
I2��}x�6@���.�i ���52l<P����[��L:����n��Id
�.x{/e��g
����J69�$��6������(����~��z����S�]�	�_��A�3+�N���>u�����iX�s�2}!IZ�jR�g��S��#��9���Y�J"B�)�������&��*�d�]��L�~�^\g"��?��sR���&��n�2<��������B��)���+�IW���c��KA1Lv�^�(��N����/YdQ��B�T4W��O�T�������>+��U*�t�F��$�vQ?5n^N K��<��Q���V����J%M�����a^2V\2���
��t�h��j����A{�� E]��������p�����"�
X�e�P����01��>5��4�e+�x��3������n����A��r���g�1�T-�(�j#%��?��L�m�i����
�����:KKl8m�
���%�Xq�3����y����	������=_m����.�z������	7 �N�Q7'�W���O�8����^�i3��{�.��7)�},�	���f�X�n��������e�X�M�/b��4f��T�M=����=�)g����|d�lSVC��r�V��3�9����T����������'���%kbN=�yrx��e���Z9���c�b!^�RY���*�2�aG��DJ��i5V3�.Gm����jS�'{&�oV���4qW�e�g������`�@wjB��U�KlP<t���
T�����
s�<j6Z��q���5��bI��W?Go	��/^�.�P��nx?6D�0�����r�{mvC�,1���u�Z-��~�T���v�2�\��5Q������.���i��%��R�1V0-Ux���&�������(����T~2������F�����K�O��<���X<3�eD�|�V��"k�����u�>p�%�-�X�k�}����#�N��������{M����HP7���F$������S�@*�����?	4��1%H0+
��
�Yc�(WU���K�y~@FF�^x�7�p�����L�w?=��m�N�`[��L /��m���Ao����H����wA��H�bv�$����Y�3��."������n	I�����NIa��F1�U�U[b$E�T�N�S*��/��7���G�pQ����x]��f��)(�ZJ#��M������"���1j-0�3��9)�����0YZ�;������8��ZJ]���`��5m&c,������vog������3�q�~r	�K������b��e����<9;���>���4N�G��F���������r�l`�����4��g?42k��bY,�^��S2T�hMl0���W������'��P�_��F��:�0�{�:])oK��aJ;h���)V�pL��<���rH����^5N�����{�rvR5�����{0r���a��kQO���D���z��>1��P����h�j��*����g�3�p��<��#P����\�S������C���&�������~$������Os��
����y����m�(�������,�(�TQ7��8�0��l�����R�����L&���*c���m���J�Pw(�6��/l��b����d�1��R
<3�::���	G�=��������wL)�<%�3��&����(V�KtG���g��jV������/K�%��.)�"�J�D%����>�%
-��-n����9�x��3iN�b���;�`�S*mW����nN�3���;�$j�1a�����Q�������&>{�7�$��F�>�)z+�gE��`�"
/CC;��}����AL�D�x����]5^����0:<��2iR��� m������_af����]0�o$�u+�nm�T
�w���J:��u��f�u�d�uw��K��c�<i����(�|����2o��Nq�[��-�����\g0��v���r���I(z���/��t�N�6r��@N��, r�����t����nG}���nF��7e��6H��]F�V+A�"�f��:��$�dC��]y�����?�x�^���U�I���g��������1����������I���|s��u������-���ak��nW�T)U77��+����t�ds�������d#�R]l���`oaL�T���/����������F�����M�{�+T�k
(B �����M���S�abT����XhMt� ��Ea�����������7�}�RY�'��[g?���_?�4~Z�A3|^��x�.����Y�%�7k�6����h�l��l�q<������l-������������������l�p������������tF�����h�f���5_6����`�{��;��M���s;������9�����<<;"�j4�~���r���~��&<3;���c�;Zdp�#T��b	2#zL�N�\au�����wZ???<~)X<{�>�?���m���J_6�9��v1��{��m7���g
{
��YU?	���d'��a%���/=F�8��DV�O���y��X?�K�@ �Y���*�Q�
.�jPz"K\����Z���>mQ�vV�U
��ta�X���Iw���I��B��FI����#D'�z,d(j�C��0�e}X���:2{57�O��G��@���h5���'S����wy�O�D�N�{�>���`��������^i���Eg�\O~_w���UD;���0z���B���&��?��`���(5����R�W�8.��?op���(�4��[�p���(�4����d���;��d�z�L
�����w������2p�P<�� ��x�so�\]�su~>;s�����w�z�����M���t�<=�7�1��:�A����L��L�4��j3��]�E���US-��+X1�����O��d�9&'��p���1%��'�P��X)�/Qm[��S+r�J�VtI�41P���lY�7�/��^5k1t�s�������-G`�;@�a.�\\�q����U�$;����������N�{�'A�bI'��s����5o��?x��@�c/������z�����0cbcz�����p$)K�C��'/~�b������U�g���y���ys�5����j>s�����>�p���Ob�5.SS�i�$u���L��L��_ZY�IN9��`�������'68���$��` k�.|:u���E*Y���<RJ�g[e�|���t�
6��_K���n���4���T�m&�5�|��[Z�[�-�yTI]����t�K)#�W��y<��S5�8,�+X�8��&e}0���(u���QD���)O<�8����������h���<Hw87R�F����Ua��#���tcN��.)�s��.X�i������9^X/]����������O�����X�0V>8c�6�1/��fQqJY���������
\Y��#BW~]�����+f�\aB�W�a�
���*�������O�$�*63�+S8}n9����d����l ����D���'E���X�t��/�EJ��pD����j�nW��\�oC�E��%�0�vi51k�s}$�����pV�YKJ|7=�;���[!b�3�'���T'�{h#�U1� r�!�y�9q��j�b���F��=�q''�y&�ke������?��f��S��;|���tF9���;���&��|������c����hx����	�l��-�>�������7p�E|����~��7�����o~������<{������6�?����M	�6�|U�~�kdMQ���%�YtG�����s��#�t~$��kOE[2����PRo�Gm���z������[����L\��?��?�>��/X��go��0|�=Zy���g��~����_�(�0R�PU?7{b��=�uc�J�n���-����t����$H=0� ��`:g8���M��{�"?��{����S1�8��~��U8u=�/���Cg��� �N�u?7������D�z;��~���YLj���F�V	���b�4�����'���>�R�~4p�_�-��������|v3��&�����k�y�������k����}�`#��v�?��(�y������f����*��z����������(�����|�jB>�hO�����}8k���'��s���8y���5���/��WH�^N�
�D�,����i�A�O
��v-������Q�=+<��A��w�d����[W_
����wp	��Y<*�2�}D��=t��� ��lF�/"��9$�a#>�\sYu�esV�����x�����;��9?�]��7+�KZ�~�$�gaLr~>�1�p�'�#8�~W���,��E?���Gp�g�����z���m[���;@V=4D9���f	�a4sL�����D�����@V��Qz����� ��Q�� �n�P�$�����,����y��@���o���$��l�aq���F��������|w�y������|wq����w���),��z&��E�"��.�D����%�7��n�����;Z��,,��p�&�c]�0��{(�-z�7�H�r�����r<��b�u>�,pM�\�<l}�cz�\��G!qn�%�~��O���.�h�-R��b���E�
&W���QT���/���5��n>KB�������P9�E�={������M`�'O�A��_��`����k�1\���6��t<�����+����(z�R��4����2�[!���{��L��UT�6q�&�����{���a����p����sZ�~ ����(���H�"1��*i�z�qb)sV���a���3E��s���b�U�F�����S�y�����=�����

��>X]�`5n���}����_B��-A)X��faE��X	����i.�����p1t�����������	*�!*Vwk+����G���)�`8����?������i���U�����B�eu�,���%���
���������j�����������w��w�p��kWo�����{v��/����������H/����x���(�Qq��W�2�"�8��,��F����\oO��l3��].=����{ �d�
o{+���N����eu>2�v%O:@*�	{���n�V*����v�<GB@��#% ��\����l�W>NR@����������(X3����7b�gK�?����O@S�&A�N4;
A��+��:t@Ec��e�{�����+o�AP���
�*Pr��#�
o/ol�
�QZd\d\d�g�������Wy��Ym9�����~��g������H������T*�jw��gi��1���;�2�������:w��l����/y�}�l�yY.2�-2��y�F����^����8���H��2]x�~}��"S\,Z>Cc�I�����9����H�� �����OP�/
�>�����`�������6���������pA�����$f�}Z%y������O�"K��YX��/���1�T~d#��AC�X?k.�����K�A#"�T��u6;���'��'+��j��EY��`�����1��m���6;��7x������j1�9v�(�W�y�K?����8g_U��������j}p���/��YLo��]�\X����l������Y�~->��p�Z�~-\����"��dU��p�&���E���'�\��[��[���������D,��-$��xa4�>�������_���bmY���r~l"�/j�-N���|��+�,����Y�-�g��L��"3�BK��g���6j��`��`9*.2�-�����3��a�,��-����Y�K�c��B���r�����vr�8zpNwL�v��Mk���nF�{�nZ;_7v3��+v���r��Y������~����O������2V�L���r��o�J�������<���3VU&���d"�?��������]�R�T��l�x2Z���u)�'��F8F�0���rGT�QmR
~�����Vg��?u&p�Z�^�
���eo�|p�|p�|�s$�\�������&oi��`(��gU�$�BB0;YOk��.���;�S���Y,V�"���������*>�o5�]���
���EA�a4�o����R�q���y;�[��E��a������E��a��4@%��>���$C���4`BP�?o/]�+�,�g�\�/��~�����`�)����E��E��?�G�"��"������?���X\�X\�����E��X&A�Te��*e�<���(�4#�_�\���@�p�w����T:�|�,x�����xv�*LQ�b��o���^&>_���c&~[+��`y��_D�;t���B�x���)�>7�>kJ@b8�U�� ���t2�^�� 2��OG������P;R����'NS���@3?s����g���MO���?��G�������o#�	��HqU�1��mH��l^gN������������zm��	v���Z�b�T����b���O�S�������l���`>�����������%��r���X��Ap�i�=��Ah&���`�������O��iY@���(�^E���un�Q���.	��puh�Z�R��t��$m)��������J��?7������D�z;��~���YLj���F�V	���b�4�����'����E������|�f��M|��������?�������A�x-�K*V�q���������,�N�?���"��"��"���wr�v1?c���0&9?���d�����E?���Gp����a�#��������F�JY��-���� ���V�\��0��&�XUsM������V �z�(=J����l�z�(�L���Y�Ty@�?i��<���_��s�Egl��A��y_'7�J?�����!���������:�Us�H�����IP�����2�B	���k
g�n�+0��E�E�\IH��rd�S��q�A������������O��0�+�ly�ot�o4�g�o4����9��K-z���wLy����5������k�������=���H�g`����}�lY��N
8�m�J��+7�������r�V.�����^uV@@J�?�$����!������M��C��g���b��u����[�W��f0�n�������_�?��/��(,M��oJ��0Ppu�>��/@Zp�3z^.�L������U�������dm=h�����N�*7��V�>���D/��0*�����b��k��p��p���s�/�|I6g��b=�.��-�9����9;g�3��m�
�!o�"C�����C0+��%����"��`}"0���E������i�}��@�o���u��t*��t�~#vM�X�6Rhz������ALZ��/N��m>�X��Z����������b�`�E�E�E�1�����B�:?@�.���Y����Po�/6E"�?_%�/��.H\�����������*.��.n���M������:�gqo5�����i�����!�Y��~����^g��f'���{	b����4����Y��l��Vv�n�T���*A�s���b���*w7+ps�R�l{����O�C�6���`���s�?v^M�������Q�+O����_7������@�1��:���2������^ >��`�~���G�vW�
..��������!�����w���
���h����GG'?6��F��a{�><�W���+.������y
#152Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#151)
Re: patch: function xmltable

2017-03-03 19:15 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

attached update with fixed tests

Heh, I noticed that you removed the libxml "context" lines that
differentiate xml.out from xml_2.out when doing this. My implementation
emits those lines, so it was failing for me. I restored them.

I also changed a few things to avoid copying into TableFuncScanState
things that come from the TableFunc itself, since the executor state
node can grab them from the plan node. Let's do that. So instead of
"evalcols" the code now checks that the column list is empty; and also,
read the ordinality column number from the plan node.

I have to bounce this back to you one more time, hopefully the last one
I hope. Two things:

1. Please verify that pg_stat_statements behaves correctly. The patch
doesn't have changes to contrib/ so without testing I'm guessing that it
doesn't work. I think something very simple should do.

2. As I've complained many times, I find the way we manage an empty
COLUMNS clause pretty bad. The standard doesn't require that syntax
(COLUMNS is required), and I don't like the implementation, so why not
provide the feature in a different way? My proposal is to change the
column options in gram.y to be something like this:

The clause COLUMNS is optional on Oracle and DB2

So I prefer a Oracle, DB2 design. If you are strongly against it, then we
can remove it to be ANSI/SQL only.

I am don't see an good idea to introduce third syntax.

xmltable_column_option_el:
IDENT b_expr
{ $$ = makeDefElem($1, $2, @1); }
| DEFAULT b_expr
{ $$ = makeDefElem("default", $2, @1); }
| FULL VALUE_P
{ $$ = makeDefElem("full_value", NULL,
@1); }
| NOT NULL_P
{ $$ = makeDefElem("is_not_null", (Node *)
makeInteger(true), @1); }
| NULL_P
{ $$ = makeDefElem("is_not_null", (Node *)
makeInteger(false), @1); }
;

Note the FULL VALUE. Then we can process it like

else if (strcmp(defel->defname, "full_value") == 0)
{
if (fc->colexpr != NULL)
ereport(ERROR,

(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("FULL ROW
may not be specified together with PATH"),

parser_errposition(defel->location)));
fc->full_row = true;
}

So if you want the full XML value of the row, you have to specify it,

.. XMLTABLE ( ... COLUMNS ..., whole_row xml FULL VALUE, ... )

This has the extra feature that you can add, say, an ORDINALITY column
together with the XML value, something that you cannot do with the
current implementation.

It doesn't have to be FULL VALUE, but I couldn't think of anything
better. (I didn't want to add any new keywords for this.) If you have
a better idea, let's discuss.

I don't see a introduction own syntax as necessary solution here - use
Oracle, DB2 compatible syntax, or ANSI.

It is partially corner case - the benefit of this case is almost bigger
compatibility with mentioned databases.

Code-wise, this completely removes the "else" block in
transformRangeTableFunc
which I marked with an XXX comment. That's a good thing -- let's get
rid of that. Also, it should remove the need for the separate "if
!columns" case in tfuncLoadRows. All those cases would become part of
the normal code path instead of special cases. I think
XmlTableSetColumnFilter doesn't need any change (we just don't call if
for the FULL VALUE row); and XmlTableGetValue needs a special case that
if the column filter is NULL (i.e. SetColumnFilter wasn't called for
that column) then return the whole row.

Of course, this opens an implementation issue: how do you annotate
things from parse analysis till execution? The current TableFunc
structure doesn't help, because there are only lists of column names and
expressions; and we can't use the case of a NULL colexpr, because that
case is already used by the column filter being the column name (a
feature required by the standard). A simple way would be to have a new
"colno" struct member, to store a column number for the column marked
FULL VALUE (just like ordinalitycol). This means you can't have more
than one of those FULL VALUE columns, but that seems okay.

(Of course, this means that the two cases that have no COLUMNS in the
"xmltable" production in gram.y should go away).

You are commiter, and you should to decide - as first I prefer current
state, as second a remove this part - it should be good for you too,
because code that you don't like will be left.

I dislike introduce new syntax - this case is not too important for this.

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#153Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#152)
Re: patch: function xmltable

2017-03-03 19:42 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2017-03-03 19:15 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

attached update with fixed tests

Heh, I noticed that you removed the libxml "context" lines that
differentiate xml.out from xml_2.out when doing this. My implementation
emits those lines, so it was failing for me. I restored them.

I also changed a few things to avoid copying into TableFuncScanState
things that come from the TableFunc itself, since the executor state
node can grab them from the plan node. Let's do that. So instead of
"evalcols" the code now checks that the column list is empty; and also,
read the ordinality column number from the plan node.

I have to bounce this back to you one more time, hopefully the last one
I hope. Two things:

1. Please verify that pg_stat_statements behaves correctly. The patch
doesn't have changes to contrib/ so without testing I'm guessing that it
doesn't work. I think something very simple should do.

2. As I've complained many times, I find the way we manage an empty
COLUMNS clause pretty bad. The standard doesn't require that syntax
(COLUMNS is required), and I don't like the implementation, so why not
provide the feature in a different way? My proposal is to change the
column options in gram.y to be something like this:

The clause COLUMNS is optional on Oracle and DB2

So I prefer a Oracle, DB2 design. If you are strongly against it, then we
can remove it to be ANSI/SQL only.

I am don't see an good idea to introduce third syntax.

xmltable_column_option_el:
IDENT b_expr
{ $$ = makeDefElem($1, $2, @1); }
| DEFAULT b_expr
{ $$ = makeDefElem("default", $2, @1); }
| FULL VALUE_P
{ $$ = makeDefElem("full_value", NULL,
@1); }
| NOT NULL_P
{ $$ = makeDefElem("is_not_null", (Node
*) makeInteger(true), @1); }
| NULL_P
{ $$ = makeDefElem("is_not_null", (Node
*) makeInteger(false), @1); }
;

Note the FULL VALUE. Then we can process it like

else if (strcmp(defel->defname, "full_value") ==
0)
{
if (fc->colexpr != NULL)
ereport(ERROR,

(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("FULL ROW
may not be specified together with PATH"),

parser_errposition(defel->location)));
fc->full_row = true;
}

So if you want the full XML value of the row, you have to specify it,

.. XMLTABLE ( ... COLUMNS ..., whole_row xml FULL VALUE, ... )

This has the extra feature that you can add, say, an ORDINALITY column
together with the XML value, something that you cannot do with the
current implementation.

It doesn't have to be FULL VALUE, but I couldn't think of anything
better. (I didn't want to add any new keywords for this.) If you have
a better idea, let's discuss.

I don't see a introduction own syntax as necessary solution here - use
Oracle, DB2 compatible syntax, or ANSI.

It is partially corner case - the benefit of this case is almost bigger
compatibility with mentioned databases.

Code-wise, this completely removes the "else" block in
transformRangeTableFunc
which I marked with an XXX comment. That's a good thing -- let's get
rid of that. Also, it should remove the need for the separate "if
!columns" case in tfuncLoadRows. All those cases would become part of
the normal code path instead of special cases. I think
XmlTableSetColumnFilter doesn't need any change (we just don't call if
for the FULL VALUE row); and XmlTableGetValue needs a special case that
if the column filter is NULL (i.e. SetColumnFilter wasn't called for
that column) then return the whole row.

Of course, this opens an implementation issue: how do you annotate
things from parse analysis till execution? The current TableFunc
structure doesn't help, because there are only lists of column names and
expressions; and we can't use the case of a NULL colexpr, because that
case is already used by the column filter being the column name (a
feature required by the standard). A simple way would be to have a new
"colno" struct member, to store a column number for the column marked
FULL VALUE (just like ordinalitycol). This means you can't have more
than one of those FULL VALUE columns, but that seems okay.

(Of course, this means that the two cases that have no COLUMNS in the
"xmltable" production in gram.y should go away).

You are commiter, and you should to decide - as first I prefer current
state, as second a remove this part - it should be good for you too,
because code that you don't like will be left.

I dislike introduce new syntax - this case is not too important for this.

I am able to prepare reduced version if we do a agreement

Regards

Pavel

Show quoted text

Regards

Pavel

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#154Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#152)
Re: patch: function xmltable

Pavel Stehule wrote:

2017-03-03 19:15 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

2. As I've complained many times, I find the way we manage an empty
COLUMNS clause pretty bad. The standard doesn't require that syntax
(COLUMNS is required), and I don't like the implementation, so why not
provide the feature in a different way? My proposal is to change the
column options in gram.y to be something like this:

The clause COLUMNS is optional on Oracle and DB2

So I prefer a Oracle, DB2 design. If you are strongly against it, then we
can remove it to be ANSI/SQL only.

I am don't see an good idea to introduce third syntax.

OK. I think trying to be syntax compatible with DB2 or Oracle is a lost
cause, because the syntax used in the XPath expressions seems different
-- I think Oracle uses XQuery (which we don't support) and DB2 uses ...
not sure what it is, but it doesn't work in our implementation
(stuff like '$d/employees/emp' in the row expression.)

In existing applications using those Oracle/DB2, is it common to omit
the COLUMNS clause? I searched for "xmltable oracle" and had a look at
the first few hits outside of the oracle docs:
http://viralpatel.net/blogs/oracle-xmltable-tutorial/
http://www.dba-oracle.com/t_xmltable.htm
http://stackoverflow.com/questions/12690868/how-to-use-xmltable-in-oracle
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:9533111800346252295
http://stackoverflow.com/questions/1222570/what-is-an-xmltable
https://community.oracle.com/thread/3955198

Not a single one of these omit the COLUMNS clause (though the second one
mentions that the clause can be omitted).

I also looked at a few samples with DB2 -- same thing; it is possible,
but is it common?

Anyway, I noticed that "xml PATH '.'" can be used to obtain the full XML
of the row, which I think is the feature I wanted, so I think we're
covered and we can omit the case with no COLUMNS, since we already have
the feature in another way. No need to implement anything further, and
we can rip out the special case I don't like. Example:

CREATE TABLE EMPLOYEES
(
id integer,
data XML
);
INSERT INTO EMPLOYEES
VALUES (1, '<Employees>
<Employee emplid="1111" type="admin">
<firstname>John</firstname>
<lastname>Watson</lastname>
<age>30</age>
<email>johnwatson@sh.com</email>
</Employee>
<Employee emplid="2222" type="admin">
<firstname>Sherlock</firstname>
<lastname>Homes</lastname>
<age>32</age>
<email>sherlock@sh.com</email>
</Employee>
<Employee emplid="3333" type="user">
<firstname>Jim</firstname>
<lastname>Moriarty</lastname>
<age>52</age>
<email>jim@sh.com</email>
</Employee>
<Employee emplid="4444" type="user">
<firstname>Mycroft</firstname>
<lastname>Holmes</lastname>
<age>41</age>
<email>mycroft@sh.com</email>
</Employee>
</Employees>');

This is with COLUMNS omitted:

alvherre=# select xmltable.* from employees, xmltable('/Employees/Employee' passing data);
xmltable
──────────────────────────────────────────
<Employee emplid="1111" type="admin"> ↵
<firstname>John</firstname> ↵
<lastname>Watson</lastname> ↵
<age>30</age> ↵
<email>johnwatson@sh.com</email>↵
</Employee>
<Employee emplid="2222" type="admin"> ↵
<firstname>Sherlock</firstname> ↵
<lastname>Homes</lastname> ↵
<age>32</age> ↵
<email>sherlock@sh.com</email> ↵
</Employee>
<Employee emplid="3333" type="user"> ↵
<firstname>Jim</firstname> ↵
<lastname>Moriarty</lastname> ↵
<age>52</age> ↵
<email>jim@sh.com</email> ↵
</Employee>
<Employee emplid="4444" type="user"> ↵
<firstname>Mycroft</firstname> ↵
<lastname>Holmes</lastname> ↵
<age>41</age> ↵
<email>mycroft@sh.com</email> ↵
</Employee>

and this is what you get with "xml PATH '.'" (I threw in ORDINALITY just
for fun):

alvherre=# select xmltable.* from employees, xmltable('/Employees/Employee' passing data columns row_number for ordinality, emp xml path '.');
row_number │ emp
────────────┼──────────────────────────────────────────
1 │ <Employee emplid="1111" type="admin"> ↵
│ <firstname>John</firstname> ↵
│ <lastname>Watson</lastname> ↵
│ <age>30</age> ↵
│ <email>johnwatson@sh.com</email>↵
│ </Employee>
2 │ <Employee emplid="2222" type="admin"> ↵
│ <firstname>Sherlock</firstname> ↵
│ <lastname>Homes</lastname> ↵
│ <age>32</age> ↵
│ <email>sherlock@sh.com</email> ↵
│ </Employee>
3 │ <Employee emplid="3333" type="user"> ↵
│ <firstname>Jim</firstname> ↵
│ <lastname>Moriarty</lastname> ↵
│ <age>52</age> ↵
│ <email>jim@sh.com</email> ↵
│ </Employee>
4 │ <Employee emplid="4444" type="user"> ↵
│ <firstname>Mycroft</firstname> ↵
│ <lastname>Holmes</lastname> ↵
│ <age>41</age> ↵
│ <email>mycroft@sh.com</email> ↵
│ </Employee>

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#155Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#154)
Re: patch: function xmltable

2017-03-03 21:04 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2017-03-03 19:15 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

2. As I've complained many times, I find the way we manage an empty
COLUMNS clause pretty bad. The standard doesn't require that syntax
(COLUMNS is required), and I don't like the implementation, so why not
provide the feature in a different way? My proposal is to change the
column options in gram.y to be something like this:

The clause COLUMNS is optional on Oracle and DB2

So I prefer a Oracle, DB2 design. If you are strongly against it, then we
can remove it to be ANSI/SQL only.

I am don't see an good idea to introduce third syntax.

OK. I think trying to be syntax compatible with DB2 or Oracle is a lost
cause, because the syntax used in the XPath expressions seems different
-- I think Oracle uses XQuery (which we don't support) and DB2 uses ...
not sure what it is, but it doesn't work in our implementation
(stuff like '$d/employees/emp' in the row expression.)

100% compatibility is not possible - but XPath is subset of XQuery and in
reality - the full XQuery examples of XMLTABLE is not often.

Almost all examples of usage XMLTABLE, what I found in blogs, uses XPath
only

In existing applications using those Oracle/DB2, is it common to omit
the COLUMNS clause? I searched for "xmltable oracle" and had a look at
the first few hits outside of the oracle docs:
http://viralpatel.net/blogs/oracle-xmltable-tutorial/
http://www.dba-oracle.com/t_xmltable.htm
http://stackoverflow.com/questions/12690868/how-to-use-xmltable-in-oracle
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:
9533111800346252295
http://stackoverflow.com/questions/1222570/what-is-an-xmltable
https://community.oracle.com/thread/3955198

Not a single one of these omit the COLUMNS clause (though the second one
mentions that the clause can be omitted).

I also looked at a few samples with DB2 -- same thing; it is possible,
but is it common?

I don't think so it is common - it is corner case - and I can live without
it well

Anyway, I noticed that "xml PATH '.'" can be used to obtain the full XML
of the row, which I think is the feature I wanted, so I think we're
covered and we can omit the case with no COLUMNS, since we already have
the feature in another way. No need to implement anything further, and
we can rip out the special case I don't like. Example:

yes,

Show quoted text

CREATE TABLE EMPLOYEES
(
id integer,
data XML
);
INSERT INTO EMPLOYEES
VALUES (1, '<Employees>
<Employee emplid="1111" type="admin">
<firstname>John</firstname>
<lastname>Watson</lastname>
<age>30</age>
<email>johnwatson@sh.com</email>
</Employee>
<Employee emplid="2222" type="admin">
<firstname>Sherlock</firstname>
<lastname>Homes</lastname>
<age>32</age>
<email>sherlock@sh.com</email>
</Employee>
<Employee emplid="3333" type="user">
<firstname>Jim</firstname>
<lastname>Moriarty</lastname>
<age>52</age>
<email>jim@sh.com</email>
</Employee>
<Employee emplid="4444" type="user">
<firstname>Mycroft</firstname>
<lastname>Holmes</lastname>
<age>41</age>
<email>mycroft@sh.com</email>
</Employee>
</Employees>');

This is with COLUMNS omitted:

alvherre=# select xmltable.* from employees,
xmltable('/Employees/Employee' passing data);
xmltable
──────────────────────────────────────────
<Employee emplid="1111" type="admin"> ↵
<firstname>John</firstname> ↵
<lastname>Watson</lastname> ↵
<age>30</age> ↵
<email>johnwatson@sh.com</email>↵
</Employee>
<Employee emplid="2222" type="admin"> ↵
<firstname>Sherlock</firstname> ↵
<lastname>Homes</lastname> ↵
<age>32</age> ↵
<email>sherlock@sh.com</email> ↵
</Employee>
<Employee emplid="3333" type="user"> ↵
<firstname>Jim</firstname> ↵
<lastname>Moriarty</lastname> ↵
<age>52</age> ↵
<email>jim@sh.com</email> ↵
</Employee>
<Employee emplid="4444" type="user"> ↵
<firstname>Mycroft</firstname> ↵
<lastname>Holmes</lastname> ↵
<age>41</age> ↵
<email>mycroft@sh.com</email> ↵
</Employee>

and this is what you get with "xml PATH '.'" (I threw in ORDINALITY just
for fun):

alvherre=# select xmltable.* from employees,
xmltable('/Employees/Employee' passing data columns row_number for
ordinality, emp xml path '.');
row_number │ emp
────────────┼──────────────────────────────────────────
1 │ <Employee emplid="1111" type="admin"> ↵
│ <firstname>John</firstname> ↵
│ <lastname>Watson</lastname> ↵
│ <age>30</age> ↵
│ <email>johnwatson@sh.com</email>↵
│ </Employee>
2 │ <Employee emplid="2222" type="admin"> ↵
│ <firstname>Sherlock</firstname> ↵
│ <lastname>Homes</lastname> ↵
│ <age>32</age> ↵
│ <email>sherlock@sh.com</email> ↵
│ </Employee>
3 │ <Employee emplid="3333" type="user"> ↵
│ <firstname>Jim</firstname> ↵
│ <lastname>Moriarty</lastname> ↵
│ <age>52</age> ↵
│ <email>jim@sh.com</email> ↵
│ </Employee>
4 │ <Employee emplid="4444" type="user"> ↵
│ <firstname>Mycroft</firstname> ↵
│ <lastname>Holmes</lastname> ↵
│ <age>41</age> ↵
│ <email>mycroft@sh.com</email> ↵
│ </Employee>

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#156Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#154)
1 attachment(s)
Re: patch: function xmltable

Hi

I used your idea about special columns when COLUMNS are not explicitly
defined.

All lines that you are dislike removed.

Now, almost all code, related to this behave, is in next few lines.

+   /*
+    * Use implicit column when it is necessary. The COLUMNS clause is
optional
+    * on Oracle and DB2. In this case a result is complete row of XML type.
+    */
+   if (rtf->columns == NIL)
+   {
+       RangeTableFuncCol *fc = makeNode(RangeTableFuncCol);
+       A_Const *n = makeNode(A_Const);
+
+       fc->colname = "xmltable";
+       fc->typeName = makeTypeNameFromOid(XMLOID, -1);
+       n->val.type = T_String;
+       n->val.val.str = ".";
+       n->location = -1;
+
+       fc->colexpr = (Node *) n;
+       rtf->columns = list_make1(fc);
+   }

all regress tests passing.

Regards

Pavel

Attachments:

xmltable-50.patch.gzapplication/x-gzip; name=xmltable-50.patch.gzDownload
#157Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#156)
Re: patch: function xmltable

Pavel Stehule wrote:

Hi

I used your idea about special columns when COLUMNS are not explicitly
defined.

All lines that you are dislike removed.

I just pushed XMLTABLE, after some additional changes. Please test it
thoroughly and report any problems.

I didn't add the change you proposed here to keep COLUMNS optional;
instead, I just made COLUMNS mandatory. I think what you propose here
is not entirely out of the question, but you left out ruleutils.c
support for it, so I decided to leave it aside for now so that I could
get this patch out of my plate once and for all. If you really want
that feature, you can submit another patch for it and discuss with the
RMT whether it belongs in PG10 or not.

Some changes I made:
* I added some pg_stat_statements support. It works fine for simple
tests, but deeper testing of it would be appreciated.

* I removed the "buildercxt" memory context. It seemed mostly
pointless, and I was disturbed by the MemoryContextResetOnly().
Per-value memory still uses the per-value memory context, but the rest
of the stuff is in the per-query context, which should be pretty much
the same.

* Desultory stylistic changes

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#158Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#157)
Re: patch: function xmltable

2017-03-08 17:01 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

Hi

I used your idea about special columns when COLUMNS are not explicitly
defined.

All lines that you are dislike removed.

I just pushed XMLTABLE, after some additional changes. Please test it
thoroughly and report any problems.

Thank you

I didn't add the change you proposed here to keep COLUMNS optional;
instead, I just made COLUMNS mandatory. I think what you propose here
is not entirely out of the question, but you left out ruleutils.c
support for it, so I decided to leave it aside for now so that I could
get this patch out of my plate once and for all. If you really want
that feature, you can submit another patch for it and discuss with the
RMT whether it belongs in PG10 or not.

It is interesting feature - because it replaces XPATH function, but not
important enough.

For daily work the default schema support is much more interesting.

Some changes I made:
* I added some pg_stat_statements support. It works fine for simple
tests, but deeper testing of it would be appreciated.

* I removed the "buildercxt" memory context. It seemed mostly
pointless, and I was disturbed by the MemoryContextResetOnly().
Per-value memory still uses the per-value memory context, but the rest
of the stuff is in the per-query context, which should be pretty much
the same.

* Desultory stylistic changes

ok

Regards

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#159Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#158)
Re: patch: function xmltable

Pavel Stehule wrote:

2017-03-08 17:01 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

I didn't add the change you proposed here to keep COLUMNS optional;
instead, I just made COLUMNS mandatory. I think what you propose here
is not entirely out of the question, but you left out ruleutils.c
support for it, so I decided to leave it aside for now so that I could
get this patch out of my plate once and for all. If you really want
that feature, you can submit another patch for it and discuss with the
RMT whether it belongs in PG10 or not.

It is interesting feature - because it replaces XPATH function, but not
important enough.

OK.

For daily work the default schema support is much more interesting.

Let's see that one, then. It was part of the original submission so
depending on how the patch we looks can still cram it in. But other
patches have priority for me now.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#160Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#159)
Re: patch: function xmltable

2017-03-08 17:32 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

Pavel Stehule wrote:

2017-03-08 17:01 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

I didn't add the change you proposed here to keep COLUMNS optional;
instead, I just made COLUMNS mandatory. I think what you propose here
is not entirely out of the question, but you left out ruleutils.c
support for it, so I decided to leave it aside for now so that I could
get this patch out of my plate once and for all. If you really want
that feature, you can submit another patch for it and discuss with the
RMT whether it belongs in PG10 or not.

It is interesting feature - because it replaces XPATH function, but not
important enough.

OK.

For daily work the default schema support is much more interesting.

Let's see that one, then. It was part of the original submission so
depending on how the patch we looks can still cram it in. But other
patches have priority for me now.

It is theme for 11

Thank you very much

Pavel

Show quoted text

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#161Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#160)
Re: patch: function xmltable

Pavel Stehule wrote:

2017-03-08 17:32 GMT+01:00 Alvaro Herrera <alvherre@2ndquadrant.com>:

For daily work the default schema support is much more interesting.

Let's see that one, then. It was part of the original submission so
depending on how the patch we looks can still cram it in. But other
patches have priority for me now.

It is theme for 11

Ah, great.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers