Hi

I am sending a initial implementation of xmltable function:

The code is not clean now, but it does almost of expected work. The usage
is simple. It is fast - 16K entries in 400ms.

I invite any help with documentation and testing.

The full ANSI/SQL, or Oracle compatible implementation is not possible due
limits of libxml2, but for typical usage it should to work well. It doesn't
need any new reserved keyword, so there should not be hard barriers for
accepting (when this work will be complete).

Example:

postgres=# SELECT * FROM xmldata;
┌──────────────────────────────────────────────────────────────────┐
│                               data                               │
╞══════════════════════════════════════════════════════════════════╡
│ <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>                                                          │
└──────────────────────────────────────────────────────────────────┘
(1 row)
postgres=# SELECT  xmltable.*
postgres-#    FROM (SELECT data FROM xmldata) x,
postgres-#         LATERAL xmltable('/ROWS/ROW'
postgres(#                          PASSING data
postgres(#                          COLUMNS id int PATH '@id',
postgres(#                                   country_name text PATH
'COUNTRY_NAME',
postgres(#                                   country_id text PATH
'COUNTRY_ID',
postgres(#                                   region_id int PATH 'REGION_ID',
postgres(#                                   size float PATH 'SIZE',
postgres(#                                   unit text PATH 'SIZE/@unit',
postgres(#                                   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 │
└────┴──────────────┴────────────┴───────────┴──────┴──────┴───────────────┘
(6 rows)

Regards

Pavel
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 69bf65d..326fa62 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -58,7 +58,6 @@
 #include "utils/typcache.h"
 #include "utils/xml.h"
 
-
 /* static function decls */
 static Datum ExecEvalArrayRef(ArrayRefExprState *astate,
 				 ExprContext *econtext,
@@ -149,6 +148,8 @@ static Datum ExecEvalMinMax(MinMaxExprState *minmaxExpr,
 			   bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext,
 			bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalXmlTable(XmlTableState *xmltable, ExprContext *econtext,
+			bool *isnull, ExprDoneCond *isDone);
 static Datum ExecEvalNullIf(FuncExprState *nullIfExpr,
 			   ExprContext *econtext,
 			   bool *isNull, ExprDoneCond *isDone);
@@ -2073,6 +2074,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 	MemoryContext oldcontext;
 	bool		direct_function_call;
 	bool		first_time = true;
+	bool		xmltable_call;
 
 	callerContext = CurrentMemoryContext;
 
@@ -2118,6 +2120,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 		 * This path is similar to ExecMakeFunctionResult.
 		 */
 		direct_function_call = true;
+		xmltable_call = false;
 
 		/*
 		 * Initialize function cache if first time through
@@ -2172,10 +2175,16 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 			}
 		}
 	}
+	else if (funcexpr && IsA(funcexpr, XmlTableState))
+	{
+		direct_function_call = false;
+		xmltable_call = true;
+	}
 	else
 	{
 		/* Treat funcexpr as a generic expression */
 		direct_function_call = false;
+		xmltable_call = false;
 		InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 	}
 
@@ -2213,6 +2222,14 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 			pgstat_end_function_usage(&fcusage,
 									  rsinfo.isDone != ExprMultipleResult);
 		}
+		else if (xmltable_call)
+		{
+			XmlTableState *xmltable = (XmlTableState *) funcexpr;
+
+			xmltable->rsinfo = &rsinfo;
+			result = ExecEvalExpr(funcexpr, econtext,
+								  &fcinfo.isnull, &rsinfo.isDone);
+		}
 		else
 		{
 			result = ExecEvalExpr(funcexpr, econtext,
@@ -3737,6 +3754,178 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext,
 	return (Datum) 0;
 }
 
+static Datum
+ExecEvalXmlTable(XmlTableState *xmltable,
+				   ExprContext *econtext,
+				   bool *isNull, ExprDoneCond *isDone)
+{
+	Datum		*values;
+	bool		*nulls;
+	int			i;
+	int			ncols = xmltable->ncolumns;
+	ReturnSetInfo	*rsinfo = xmltable->rsinfo;
+	Tuplestorestate *tupstore;
+	MemoryContext	per_query_ctx;
+	MemoryContext	oldctx;
+	TupleDesc tupdesc = xmltable->rsinfo->expectedDesc;
+	Datum	value;
+	bool	isnull;
+	char **paths;
+	PgXmlTableContext	*xmltableCxt;
+	text		*xpath_expr_text;
+	xmltype		*data;
+	FmgrInfo	*in_functions;
+	Oid			*typioparams;
+
+	/* first execution, prepare in_function array */
+	if (xmltable->in_functions == NULL)
+	{
+		Oid			in_func_oid;
+		Form_pg_attribute *attr;
+
+		per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+		oldctx = MemoryContextSwitchTo(per_query_ctx);
+
+		attr = tupdesc->attrs;
+		Assert(tupdesc->natts == ncols);
+
+		in_functions = xmltable->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+		typioparams = xmltable->typioparams = palloc(sizeof(Oid) * ncols);
+		for(i = 0; i < ncols; i++)
+		{
+			getTypeInputInfo(attr[i]->atttypid,
+								&in_func_oid, &typioparams[i]);
+			fmgr_info(in_func_oid, &in_functions[i]);
+		}
+
+		MemoryContextSwitchTo(oldctx);
+	}
+	else
+	{
+		in_functions = xmltable->in_functions;
+		typioparams = xmltable->typioparams;
+	}
+
+	paths = palloc(sizeof(char *) * ncols);
+	for (i = 0; i < ncols; i++)
+	{
+		char		*curr_path;
+		bool		use_prefix = false;
+		bool		use_suffix = true;
+		int		j;
+		char		*last_char_ptr;
+
+		if (xmltable->path_exprs[i] != NULL)
+		{
+			value = ExecEvalExpr(xmltable->path_exprs[i], econtext, &isnull, NULL);
+
+			if (isnull)
+				elog(ERROR, "expression of PATH of column %s is NULL",
+								  NameStr(tupdesc->attrs[i]->attname));
+			paths[i] = text_to_cstring(DatumGetTextP(value));
+		}
+		else
+			paths[i] = NameStr(tupdesc->attrs[i]->attname);
+
+		/* When doesn't start with any special cha, we should to append ref to parent node */
+		curr_path = paths[i];
+		if (curr_path[0] != '.' && curr_path[0] != '/')
+			use_prefix = true;
+
+		last_char_ptr = curr_path + strlen(curr_path) - 1;
+		if (*last_char_ptr == ')' || *last_char_ptr == '/')
+			use_suffix = false;
+
+		/* path shoud be reference to attribute */
+		if (use_suffix)
+		{
+			for (j = strlen(curr_path); j > 0; j--)
+			{
+				if (*last_char_ptr == '/')
+					break;
+				if (*last_char_ptr-- == '@')
+				{
+					use_suffix = false;
+					break;
+				}
+			}
+		}
+
+		if (use_prefix || use_suffix)
+		{
+			char *buffer = palloc(strlen(curr_path) + 1 + 2 + 7);
+
+			sprintf(buffer, "%s%s%s", use_prefix ? "./" : "", curr_path, use_suffix ? "/text()" : "");
+			paths[i] = buffer;
+		}
+	}
+
+	value = ExecEvalExpr(xmltable->query_string, econtext, &isnull, NULL);
+	if (isnull)
+		elog(ERROR, "query string should not be NULL");
+	xpath_expr_text = DatumGetTextP(value);
+
+	value = ExecEvalExpr(xmltable->expr, econtext, &isnull, NULL);
+	if (isnull)
+		elog(ERROR, "input value should not be NULL (ToDo: should be fixed in future)");
+	data = DatumGetXmlP(value);
+
+	values = palloc0(sizeof(Datum) * ncols);
+	nulls = palloc0(sizeof(bool) * ncols);
+
+	xmltableCxt = makeXmlTableCxt(xpath_expr_text, data, ncols, paths, in_functions, typioparams, tupdesc);
+
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldctx = MemoryContextSwitchTo(per_query_ctx);
+	tupstore = tuplestore_begin_heap(false, false, work_mem);
+
+	for(;;)
+	{
+		MemoryContextSwitchTo(oldctx);
+
+		if (XmlTableGetRow(xmltableCxt))
+		{
+			bool		isnull;
+			Datum		value;
+			HeapTuple		tuple;
+
+			for (i = 0; i < ncols; i++)
+			{
+				value = XmlTableGetValue(xmltableCxt, i, &isnull);
+				if (isnull && xmltable->default_exprs[i] != NULL)
+					value = ExecEvalExpr(xmltable->default_exprs[i], econtext, &isnull, NULL);
+
+				if (isnull)
+					nulls[i] = true;
+				else
+				{
+					values[i] = value;
+					nulls[i] = false;
+				}
+			}
+			tuple = heap_form_tuple(tupdesc, values, nulls);
+
+			MemoryContextSwitchTo(per_query_ctx);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+		else
+			break;
+	}
+
+	MemoryContextSwitchTo(per_query_ctx);
+	tuplestore_donestoring(tupstore);
+
+	XmlTableCxtFree(xmltableCxt);
+
+	*isDone = ExprSingleResult;
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = CreateTupleDescCopy(tupdesc);		/* this destroy passed tupdesc, use copy! */
+
+	return (Datum) 0;
+}
+
+
 /* ----------------------------------------------------------------
  *		ExecEvalNullIf
  *
@@ -5107,6 +5296,37 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				state = (ExprState *) xstate;
 			}
 			break;
+		case T_XmlTable:
+			{
+				XmlTable *xt = (XmlTable *) node;
+				XmlTableState *xstate = makeNode(XmlTableState);
+				ListCell	   *l;
+				int				i = 0;
+				int				ncols;
+
+				xstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalXmlTable;
+				xstate->query_string = ExecInitExpr((Expr *) xt->query_string, parent);
+				xstate->expr = ExecInitExpr((Expr *) xt->expr, parent);
+
+				ncols = list_length(xt->coldeflist);
+				xstate->ncolumns = ncols;
+				xstate->path_exprs = palloc0(sizeof(ExprState *) * ncols);
+				xstate->default_exprs = palloc0(sizeof(ExprState *) * ncols);
+
+				foreach(l, xt->coldeflist)
+				{
+					ColumnDef *def = (ColumnDef *) lfirst(l);
+
+					if (def->cooked_path)
+						xstate->path_exprs[i] = ExecInitExpr((Expr *) def->cooked_path, parent);
+					if (def->cooked_default)
+						xstate->default_exprs[i] = ExecInitExpr((Expr *) def->cooked_default, parent);
+					i++;
+				}
+
+				state = (ExprState *) xstate;
+			}
+			break;
 		case T_NullTest:
 			{
 				NullTest   *ntest = (NullTest *) node;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3244c76..3f8baf8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2553,6 +2553,7 @@ _copyRangeTableSample(const RangeTableSample *from)
 	return newnode;
 }
 
+
 static TypeCast *
 _copyTypeCast(const TypeCast *from)
 {
@@ -2607,6 +2608,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);
@@ -2688,6 +2691,19 @@ _copyXmlSerialize(const XmlSerialize *from)
 	return newnode;
 }
 
+static XmlTable *
+_copyXmlTable(const XmlTable *from)
+{
+	XmlTable *newnode = makeNode(XmlTable);
+
+	COPY_NODE_FIELD(query_string);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(coldeflist);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 static RoleSpec *
 _copyRoleSpec(const RoleSpec *from)
 {
@@ -5062,6 +5078,9 @@ copyObject(const void *from)
 		case T_XmlSerialize:
 			retval = _copyXmlSerialize(from);
 			break;
+		case T_XmlTable:
+			retval = _copyXmlTable(from);
+			break;
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1eb6799..e6e61c3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2339,6 +2339,7 @@ _equalRangeTableSample(const RangeTableSample *a, const RangeTableSample *b)
 	return true;
 }
 
+
 static bool
 _equalIndexElem(const IndexElem *a, const IndexElem *b)
 {
@@ -2365,6 +2366,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);
@@ -2610,6 +2613,17 @@ _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
 }
 
 static bool
+_equalXmlTable(const XmlTable *a, const XmlTable *b)
+{
+	COMPARE_NODE_FIELD(query_string);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(coldeflist);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 {
 	COMPARE_SCALAR_FIELD(roletype);
@@ -3366,6 +3380,9 @@ equal(const void *a, const void *b)
 		case T_XmlSerialize:
 			retval = _equalXmlSerialize(a, b);
 			break;
+		case T_XmlTable:
+			retval = _equalXmlTable(a, b);
+			break;
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index cd39167..b728d6b 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -226,6 +226,12 @@ exprType(const Node *expr)
 			else
 				type = XMLOID;
 			break;
+		case T_XmlTable:
+			if (((const XmlTable *) expr)->coldeflist == NIL)
+				type = XMLOID;
+			else
+				type = RECORDOID;
+			break;
 		case T_NullTest:
 			type = BOOLOID;
 			break;
@@ -720,6 +726,8 @@ expression_returns_set_walker(Node *node, void *context)
 		return false;
 	if (IsA(node, XmlExpr))
 		return false;
+	if (IsA(node, XmlTable))
+		return true;
 
 	return expression_tree_walker(node, expression_returns_set_walker,
 								  context);
@@ -1096,6 +1104,9 @@ exprSetCollation(Node *expr, Oid collation)
 				   (collation == DEFAULT_COLLATION_OID) :
 				   (collation == InvalidOid));
 			break;
+		case T_XmlTable:
+			Assert(!OidIsValid(collation));		/* result is always XML */
+			break;
 		case T_NullTest:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
@@ -1512,6 +1523,9 @@ exprLocation(const Node *expr)
 			/* XMLSERIALIZE keyword should always be the first thing */
 			loc = ((const XmlSerialize *) expr)->location;
 			break;
+		case T_XmlTable:
+			loc = ((const XmlTable *) expr)->location;
+			break;
 		case T_GroupingSet:
 			loc = ((const GroupingSet *) expr)->location;
 			break;
@@ -2067,6 +2081,27 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_XmlTable:
+			{
+				XmlTable *xt = (XmlTable *) node;
+				ListCell	*l;
+
+				if (walker(xt->query_string, context))
+					return true;
+				if (walker(xt->expr, context))
+					return true;
+
+				foreach(l, xt->coldeflist)
+				{
+					ColumnDef	*def = (ColumnDef *) lfirst(l);
+
+					if (walker(def->cooked_default, context))
+						return true;
+					if (walker(def->cooked_path, context))
+						return true;
+				}
+			}
+			break;
 		case T_NullTest:
 			return walker(((NullTest *) node)->arg, context);
 		case T_BooleanTest:
@@ -2781,6 +2816,35 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_XmlTable:
+			{
+				XmlTable	*xt = (XmlTable *) node;
+				XmlTable	*newnode;
+				List	   *resultlist = NIL;
+				ListCell	*l;
+
+				FLATCOPY(newnode, xt, XmlTable);
+				MUTATE(newnode->query_string, xt->query_string, Node *);
+				MUTATE(newnode->expr, xt->expr, Node *);
+
+				resultlist = NIL;
+				foreach(l, xt->coldeflist)
+				{
+					ColumnDef *def = (ColumnDef *) lfirst(l);
+					ColumnDef *newdef;
+
+					FLATCOPY(newdef, def, ColumnDef);
+					MUTATE(newdef->cooked_default, def->cooked_default, Node *);
+					MUTATE(newdef->cooked_path, def->cooked_path, Node *);
+
+					resultlist = lappend(resultlist, newdef);
+				}
+
+				newnode->coldeflist = resultlist;
+
+				return (Node *) newnode;
+			}
+			break;
 		case T_NullTest:
 			{
 				NullTest   *ntest = (NullTest *) node;
@@ -3547,6 +3611,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 */
@@ -3575,6 +3641,18 @@ raw_expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_XmlTable:
+			{
+				XmlTable *xt = (XmlTable *) node;
+
+				if (walker(xt->query_string, context))
+					return true;
+				if (walker(xt->expr, context))
+					return true;
+				if (walker(xt->coldeflist, context))
+					return true;
+			}
+			break;
 		case T_WithClause:
 			return walker(((WithClause *) node)->ctes, context);
 		case T_InferClause:
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index acaf4ea..d69bc16 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2562,6 +2562,17 @@ _outXmlSerialize(StringInfo str, const XmlSerialize *node)
 }
 
 static void
+_outXmlTable(StringInfo str, const XmlTable *node)
+{
+	WRITE_NODE_TYPE("XMLTABLE");
+
+	WRITE_NODE_FIELD(query_string);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(coldeflist);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
 _outColumnDef(StringInfo str, const ColumnDef *node)
 {
 	WRITE_NODE_TYPE("COLUMNDEF");
@@ -2575,6 +2586,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);
@@ -3845,6 +3858,9 @@ outNode(StringInfo str, const void *obj)
 			case T_XmlSerialize:
 				_outXmlSerialize(str, obj);
 				break;
+			case T_XmlTable:
+				_outXmlTable(str, obj);
+				break;
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
 				break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0cae446..3ae4722 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -132,7 +132,7 @@ typedef struct ImportQual
 #define parser_yyerror(msg)  scanner_yyerror(msg, yyscanner)
 #define parser_errposition(pos)  scanner_errposition(pos, yyscanner)
 
-static void base_yyerror(YYLTYPE *yylloc, core_yyscan_t yyscanner,
+static void base_yyerror(YYLTYPE *yylloc,	 core_yyscan_t yyscanner,
 						 const char *msg);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
@@ -227,6 +227,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	XmlTableColOpt		*xmltablecolopt;
 }
 
 %type <node>	stmt schema_stmt
@@ -372,6 +373,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				create_generic_options alter_generic_options
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
+				XmltableColumnList
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -540,6 +542,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <node>	xmltable_column
+%type <xmltablecolopt>	xmltable_column_option
+%type <list>			opt_xmltable_column_option_list xmltable_column_option_list
+
 /*
  * 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
@@ -571,7 +577,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
-	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
+	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
 	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
 	CROSS CSV CUBE CURRENT_P
@@ -615,7 +621,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 PATH PASSWORD PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
@@ -645,7 +651,7 @@ 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
+	XMLPI XMLROOT XMLSERIALIZE XMLTABLE
 
 	YEAR_P YES_P
 
@@ -12617,6 +12623,24 @@ func_expr_common_subexpr:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
+			| XMLTABLE '(' c_expr xmlexists_argument ')'
+				{
+					XmlTable *n = makeNode(XmlTable);
+					n->query_string = $3;
+					n->expr = $4;
+					n->coldeflist = NIL;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
+			| XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmltableColumnList ')'
+				{
+					XmlTable *n = makeNode(XmlTable);
+					n->query_string = $3;
+					n->expr = $4;
+					n->coldeflist = $6;
+					n->location = @1;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -12692,6 +12716,66 @@ xmlexists_argument:
 				}
 		;
 
+xmltable_column_option:
+			DEFAULT c_expr
+				{
+					$$ = palloc(sizeof(XmlTableColOpt));
+
+					$$->typ = XMLTABLE_COLUMN_OPT_DEFAULT;
+					$$->expr = $2;
+				}
+			| PATH c_expr
+				{
+					$$ = palloc(sizeof(XmlTableColOpt));
+
+					$$->typ = XMLTABLE_COLUMN_OPT_PATH;
+					$$->expr = $2;
+				}
+		;
+
+xmltable_column_option_list:
+			xmltable_column_option									{ $$ = list_make1($1); }
+			| xmltable_column_option_list xmltable_column_option	{ $$ = lappend($1, $2); }
+		;
+
+opt_xmltable_column_option_list:
+			xmltable_column_option_list			{ $$ = $1; }
+			| /* EMPTY */						{ $$ = NULL; }
+		;
+
+xmltable_column:
+			ColId Typename opt_xmltable_column_option_list
+				{
+					ListCell	*l;
+					ColumnDef		*c = makeNode(ColumnDef);
+
+					c->colname = $1;
+					c->typeName = $2;
+					c->raw_default = NULL;
+					c->raw_path = NULL;
+					c->storage = 55;
+
+					foreach(l, $3)
+					{
+						XmlTableColOpt *opt = (XmlTableColOpt *) lfirst(l);
+
+						if (opt->typ == XMLTABLE_COLUMN_OPT_PATH)
+							c->raw_path = opt->expr;
+						else if (opt->typ == XMLTABLE_COLUMN_OPT_DEFAULT)
+							c->raw_default = opt->expr;
+						else
+							elog(ERROR, "unknown xmltable column option");
+					}
+
+					c->location = @1;
+					$$ = (Node *) c;
+				}
+		;
+
+XmltableColumnList:
+				xmltable_column								{ $$ = list_make1($1); }
+				| XmltableColumnList ',' xmltable_column	{ $$ = lappend($1, $3); }
+		;
 
 /*
  * Aggregate decoration clauses
@@ -13755,6 +13839,7 @@ unreserved_keyword:
 			| CLASS
 			| CLOSE
 			| CLUSTER
+			| COLUMNS
 			| COMMENT
 			| COMMENTS
 			| COMMIT
@@ -13888,6 +13973,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PATH
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -14057,6 +14143,7 @@ col_name_keyword:
 			| 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 751de4b..b409b93 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -113,7 +113,7 @@ transformFromClause(ParseState *pstate, List *frmList)
 
 	/*
 	 * The grammar will have produced a list of RangeVars, RangeSubselects,
-	 * RangeFunctions, and/or JoinExprs. Transform each one (possibly adding
+	 * RangeFunctions and/or JoinExprs. Transform each one (possibly adding
 	 * entries to the rtable), check for duplicate refnames, and then add it
 	 * to the joinlist and namespace.
 	 *
@@ -821,7 +821,6 @@ transformRangeTableSample(ParseState *pstate, RangeTableSample *rts)
 	return tablesample;
 }
 
-
 /*
  * transformFromClauseItem -
  *	  Transform a FROM-clause item, adding any required entries to the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index cead212..0d779cf 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -99,6 +99,7 @@ static Node *transformAExprIn(ParseState *pstate, A_Expr *a);
 static Node *transformAExprBetween(ParseState *pstate, A_Expr *a);
 static Node *transformBoolExpr(ParseState *pstate, BoolExpr *a);
 static Node *transformFuncCall(ParseState *pstate, FuncCall *fn);
+static Node *transformXmlTable(ParseState *pstate, XmlTable *xt);
 static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref);
 static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c);
 static Node *transformSubLink(ParseState *pstate, SubLink *sublink);
@@ -314,6 +315,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			result = transformXmlSerialize(pstate, (XmlSerialize *) expr);
 			break;
 
+		case T_XmlTable:
+			result = transformXmlTable(pstate, (XmlTable *) expr);
+			break;
+
 		case T_NullTest:
 			{
 				NullTest   *n = (NullTest *) expr;
@@ -1474,6 +1479,50 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
 }
 
 static Node *
+transformXmlTable(ParseState *pstate, XmlTable *xt)
+{
+	ListCell	*l;
+
+	XmlTable *newxt = makeNode(XmlTable);
+
+	newxt->query_string = coerce_to_specific_type(pstate,
+								transformExprRecurse(pstate, xt->query_string),
+										 TEXTOID,
+										 "XMLTABLE");
+	newxt->expr = coerce_to_specific_type(pstate,
+								transformExprRecurse(pstate, xt->expr),
+										 XMLOID,
+										 "XMLTABLE");
+
+	newxt->location = xt->location;
+
+	foreach(l, xt->coldeflist)
+	{
+		ColumnDef *def = (ColumnDef *) lfirst(l);
+
+		if (def->raw_default)
+		{
+			Oid		targetType = typenameTypeId(pstate, def->typeName);;
+
+			def->cooked_default = coerce_to_specific_type(pstate,
+										transformExprRecurse(pstate, def->raw_default),
+												 targetType,
+												 "XMLTYPE");
+		}
+
+		if (def->raw_path)
+			def->cooked_path = coerce_to_specific_type(pstate,
+									transformExprRecurse(pstate, def->raw_path),
+											 TEXTOID,
+											 "XMLTABLE");
+	}
+
+	newxt->coldeflist = xt->coldeflist;
+
+	return (Node *) newxt;
+}
+
+static Node *
 transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
 {
 	SubLink    *sublink;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index fc93063..a4de207 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1794,6 +1794,9 @@ FigureColnameInternal(Node *node, char **name)
 		case T_XmlSerialize:
 			*name = "xmlserialize";
 			return 2;
+		case T_XmlTable:
+			*name = "xmltable";
+			return 2;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d68ca7a..e6dd75e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8022,6 +8022,38 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_XmlTable:
+			{
+				XmlTable    *xt = (XmlTable *) node;
+
+				appendStringInfoString(buf, "XMLTABLE(");
+				get_rule_expr((Node *) xt->query_string, context, true);
+				appendStringInfoString(buf, " PASSING ");
+				get_rule_expr((Node *) xt->expr, context, true);
+
+				if (xt->coldeflist != NIL)
+				{
+					ListCell	*l;
+					bool isFirst = true;
+
+					appendStringInfoString(buf, " COLUMNS ");
+					foreach(l, xt->coldeflist)
+					{
+						ColumnDef *def = (ColumnDef *) lfirst(l);
+
+						if (!isFirst)
+							appendStringInfoString(buf, ", ");
+						else
+							isFirst = false;
+						appendStringInfo(buf, "%s", def->colname);
+					}
+					appendStringInfoChar(buf, ' ');
+				}
+
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		case T_NullTest:
 			{
 				NullTest   *ntest = (NullTest *) node;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 7ed5bcb..e5a7701 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -115,6 +115,27 @@ struct PgXmlErrorContext
 	xmlExternalEntityLoader saved_entityfunc;
 };
 
+struct PgXmlTableContext
+{
+	int			ncols;
+	char	  **paths;
+	TupleDesc			tupdesc;
+
+	PgXmlErrorContext  *xmlerrcxt;
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr doc;
+	xmlXPathContextPtr xpathctx;
+	xmlXPathCompExprPtr xpathcomp;
+	xmlXPathObjectPtr xpathobj;
+
+	volatile xmlXPathCompExprPtr *xpathscomp;
+
+	FmgrInfo		*in_functions;
+	Oid				*typioparams;
+
+	long int		rc;
+};
+
 static xmlParserInputPtr xmlPgEntityLoader(const char *URL, const char *ID,
 				  xmlParserCtxtPtr ctxt);
 static void xml_errorHandler(void *data, xmlErrorPtr error);
@@ -4073,3 +4094,356 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
 	return 0;
 #endif   /* not USE_LIBXML */
 }
+
+/*
+ * 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;
+}
+
+/*
+ * Support for XMLTABLE function
+ */
+PgXmlTableContext *
+makeXmlTableCxt(text *xpath_expr_text, xmltype *data, int ncols, char **paths, FmgrInfo *in_functions, Oid *typioparams, TupleDesc tupdesc)
+{
+#ifdef USE_LIBXML
+	PgXmlTableContext *cxt = palloc(sizeof(struct PgXmlTableContext));
+	PgXmlErrorContext *xmlerrcxt;
+
+	volatile xmlParserCtxtPtr ctxt = NULL;
+	volatile xmlDocPtr doc = NULL;
+	volatile xmlXPathContextPtr xpathctx = NULL;
+	volatile xmlXPathCompExprPtr xpathcomp = NULL;
+	volatile xmlXPathObjectPtr xpathobj = NULL;
+	volatile xmlXPathCompExprPtr *xpathscomp = NULL;
+	volatile xmlXPathCompExprPtr _xpathcomp = NULL;
+	int32		len;
+	int32		xpath_len;
+	xmlChar	   *string;
+	xmlChar	   *xpath_expr;
+	int				i;
+
+	cxt->ncols = ncols;
+	cxt->paths = paths;
+	cxt->in_functions = in_functions;
+	cxt->typioparams = typioparams;
+	cxt->tupdesc = tupdesc;
+	cxt->rc = 0;
+
+	cxt->xmlerrcxt = xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+	len = VARSIZE(data) - VARHDRSZ;
+	xpath_len = VARSIZE(xpath_expr_text) - VARHDRSZ;
+	if (xpath_len == 0)
+		ereport(ERROR,
+					(errcode(ERRCODE_DATA_EXCEPTION),
+					 errmsg("empty XPath expression")));
+
+	string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+	memcpy(string, VARDATA(data), 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';
+
+	cxt->xpathscomp = xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * ncols);
+
+	PG_TRY();
+	{
+		xmlInitParser();
+
+		cxt->ctxt = ctxt = xmlNewParserCtxt();
+		if (ctxt == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate parser context");
+		cxt->doc = doc = xmlCtxtReadMemory(ctxt, (char *) string, len, NULL, NULL, 0);
+		if (doc == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+						"could not parse XML document");
+		cxt->xpathctx = xpathctx = xmlXPathNewContext(doc);
+		if (xpathctx == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+						"could not allocate XPath context");
+		xpathctx->node = xmlDocGetRootElement(doc);
+		if (xpathctx->node == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not find root XML element");
+
+		cxt->xpathcomp = xpathcomp = xmlXPathCompile(xpath_expr);
+		if (xpathcomp == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"invalid XPath expression");
+
+		cxt->xpathobj = xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx);
+		if (xpathobj == NULL || xmlerrcxt->err_occurred)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"could not create XPath object");
+
+		for(i = 0; i < ncols; i++)
+		{
+			int32 l = strlen(paths[i]);
+			xmlChar *buffer = palloc((l + 1) * sizeof(xmlChar));
+			memcpy(buffer, paths[i], l);
+			buffer[l] = '\0';
+
+			_xpathcomp = xmlXPathCompile(buffer);
+			if (_xpathcomp == NULL || xmlerrcxt->err_occurred)
+				xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+						"invalid XPath expression");
+			cxt->xpathscomp[i] = _xpathcomp;
+		}
+	}
+	PG_CATCH();
+	{
+		if (xpathobj)
+			xmlXPathFreeObject(xpathobj);
+		if (xpathcomp)
+			xmlXPathFreeCompExpr(xpathcomp);
+		if (_xpathcomp)
+			xmlXPathFreeCompExpr(_xpathcomp);
+		if (xpathctx)
+			xmlXPathFreeContext(xpathctx);
+		if (doc)
+			xmlFreeDoc(doc);
+		if (ctxt)
+			xmlFreeParserCtxt(ctxt);
+
+		if (xpathscomp)
+		{
+			int		i;
+
+			for (i = 0; i < ncols; i++)
+			{
+				if (xpathscomp[i])
+					xmlXPathFreeCompExpr(xpathscomp[i]);
+			}
+			pfree((void *) xpathscomp);
+		}
+
+		pg_xml_done(xmlerrcxt, true);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return cxt;
+#else
+	NO_XML_SUPPORT();
+	return NULL;
+#endif   /* not USE_LIBXML */
+
+}
+
+void
+XmlTableCxtFree(PgXmlTableContext *cxt)
+{
+#ifdef USE_LIBXML
+
+	if (cxt->xpathobj)
+		xmlXPathFreeObject(cxt->xpathobj);
+	if (cxt->xpathcomp)
+		xmlXPathFreeCompExpr(cxt->xpathcomp);
+	if (cxt->xpathctx)
+		xmlXPathFreeContext(cxt->xpathctx);
+	if (cxt->doc)
+		xmlFreeDoc(cxt->doc);
+	if (cxt->ctxt)
+		xmlFreeParserCtxt(cxt->ctxt);
+
+	if (cxt->xpathscomp)
+	{
+		int		i;
+
+		for (i = 0; i < cxt->ncols; i++)
+		{
+			if (cxt->xpathscomp[i])
+				xmlXPathFreeCompExpr(cxt->xpathscomp[i]);
+		}
+		pfree((void *) cxt->xpathscomp);
+	}
+
+	pg_xml_done(cxt->xmlerrcxt, true);
+
+	pfree(cxt);
+#else
+	NO_XML_SUPPORT();
+	return;
+#endif   /* not USE_LIBXML */
+}
+
+bool
+XmlTableGetRow(PgXmlTableContext *cxt)
+{
+#ifdef USE_LIBXML
+	if (cxt->xpathobj->type == XPATH_NODESET)
+	{
+		if (cxt->xpathobj->nodesetval != NULL)
+		{
+			if (cxt->rc < cxt->xpathobj->nodesetval->nodeNr)
+			{
+				cxt->rc++;
+				return true;
+			}
+		}
+	}
+
+	return false;
+#else
+	NO_XML_SUPPORT();
+	return false;
+#endif   /* not USE_LIBXML */
+
+}
+
+Datum
+XmlTableGetValue(PgXmlTableContext *cxt, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+	volatile xmlXPathObjectPtr _xpathobj = NULL;
+
+	if (cxt->xpathobj->type == XPATH_NODESET &&
+		  cxt->xpathobj->nodesetval != NULL)
+	{
+		xmlNodePtr		cur;
+	
+		Assert(cxt->rc <= cxt->xpathobj->nodesetval->nodeNr);
+
+		cur = cxt->xpathobj->nodesetval->nodeTab[cxt->rc - 1];
+
+		if (cur->type == XML_ELEMENT_NODE)
+		{
+
+			PG_TRY();
+			{
+				xmlXPathSetContextNode(cur, cxt->xpathctx);
+				_xpathobj = xmlXPathCompiledEval(cxt->xpathscomp[colnum], cxt->xpathctx);
+				if (_xpathobj == NULL || cxt->xmlerrcxt->err_occurred)
+					xml_ereport(cxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+							"could not create XPath object");
+
+				switch (_xpathobj->type)
+				{
+					case XPATH_NODESET:
+						{
+							if (_xpathobj->nodesetval != NULL)
+							{
+								int result = _xpathobj->nodesetval->nodeNr;
+
+								if (result == 0)
+									*isnull = true;
+								else if (result == 1)
+								{
+									char *cstr = xml_xmlnodetostr(_xpathobj->nodesetval->nodeTab[0],
+																 cxt->xmlerrcxt);
+									Datum value = InputFunctionCall(&cxt->in_functions[colnum],
+																    cstr,
+																    cxt->typioparams[colnum],
+																    cxt->tupdesc->attrs[colnum]->atttypmod);
+
+									*isnull = false;
+									return value;
+								}
+								else
+									elog(ERROR, "too much rows");
+							}
+							else
+								*isnull = false;
+						break;
+						}
+					case XPATH_STRING:
+						*isnull = false;
+						return PointerGetDatum(cstring_to_text((char *) _xpathobj->stringval));
+						break;
+					default:
+						elog(ERROR, "xpath expression result type %d is unsupported",
+							 _xpathobj->type);
+						return 0;			/* keep compiler quiet */
+				}
+
+				//xmlXPathFreeContext(_xpathctx);
+				xmlXPathFreeObject(_xpathobj);
+			}
+			PG_CATCH();
+			{
+				if (_xpathobj)
+					xmlXPathFreeObject(_xpathobj);
+
+				XmlTableCxtFree(cxt);
+
+				PG_RE_THROW();
+			}
+			PG_END_TRY();
+		}
+	}
+
+	*isnull = true;
+
+	return (Datum) 0;
+#else
+	NO_XML_SUPPORT();
+	return (Datum) 0;
+#endif   /* not USE_LIBXML */
+
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d179ae..7fc30c2 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"
@@ -39,7 +40,9 @@ static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 							oidvector *declared_args,
 							Node *call_expr);
 static TypeFuncClass get_type_func_class(Oid typid);
-
+static TypeFuncClass xmltable_get_result_type(XmlTable *expr,
+						 Oid *resultTypeId,
+						 TupleDesc *resultTupleDesc);
 
 /*
  * init_MultiFuncCall
@@ -243,6 +246,10 @@ get_expr_result_type(Node *expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
+	else if (expr && IsA(expr, XmlTable))
+		result = xmltable_get_result_type((XmlTable *) expr,
+										  resultTypeId,
+										  resultTupleDesc);
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
@@ -1398,3 +1405,36 @@ TypeGetTupleDesc(Oid typeoid, List *colaliases)
 
 	return tupdesc;
 }
+
+static TypeFuncClass 
+xmltable_get_result_type(XmlTable *xt,
+						 Oid *resultTypeId,
+						 TupleDesc *resultTupleDesc)
+{
+	ListCell	*l;
+	int			i = 1;
+	TupleDesc	tupdesc;
+
+	tupdesc = CreateTemplateTupleDesc(list_length(xt->coldeflist), false);
+
+	foreach(l, xt->coldeflist)
+	{
+		ColumnDef *def = (ColumnDef *) lfirst(l);
+
+		Oid		typId = typenameTypeId(NULL, def->typeName);
+
+		TupleDescInitEntry(tupdesc, i++, def->colname, typId, -1, 0);
+	}
+
+	if (resultTypeId)
+		*resultTypeId = RECORDOID;
+
+	if (tupdesc->tdtypeid == RECORDOID &&
+		tupdesc->tdtypmod < 0)
+		assign_record_type_typmod(tupdesc);
+	if (resultTupleDesc)
+		*resultTupleDesc = tupdesc;
+
+	return TYPEFUNC_COMPOSITE;
+}
+
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e7fd7bd..63f0f7f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -959,6 +959,23 @@ typedef struct XmlExprState
 } XmlExprState;
 
 /* ----------------
+ *		XmlTableState node
+ * ----------------
+ */
+typedef struct XmlTableState
+{
+	ExprState			xprstate;
+	ExprState		   *query_string;		/* ExprStates for query string */
+	ExprState		   *expr;				/* ExprStates for XML parameter */
+	int					ncolumns;			/* number of columns */
+	ExprState		  **path_exprs;			/* ExprStates for column PATH expressions  */
+	ExprState		  **default_exprs;		/* ExprStates for defaults */
+	ReturnSetInfo	   *rsinfo;				/* link to outer world */
+	FmgrInfo		   *in_functions;		/* array of input function for each column */
+	Oid				   *typioparams;		/* array of column types for in_function */
+} XmlTableState;
+
+/* ----------------
  *		NullTestState node
  * ----------------
  */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6b850e4..d255c4a 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -212,6 +212,7 @@ typedef enum NodeTag
 	T_CoalesceExprState,
 	T_MinMaxExprState,
 	T_XmlExprState,
+	T_XmlTableState,
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
@@ -447,6 +448,7 @@ typedef enum NodeTag
 	T_LockingClause,
 	T_RowMarkClause,
 	T_XmlSerialize,
+	T_XmlTable,
 	T_WithClause,
 	T_InferClause,
 	T_OnConflictClause,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1481fff..c815eff 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -565,6 +565,7 @@ typedef struct RangeTableSample
 	int			location;		/* method name location, or -1 if unknown */
 } RangeTableSample;
 
+
 /*
  * ColumnDef - column definition (used in various creates)
  *
@@ -595,6 +596,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 (untransformed parse tree) */
+	Node	   *cooked_path;	/* path value (transformed expr tree *) */
 	CollateClause *collClause;	/* untransformed COLLATE spec, if any */
 	Oid			collOid;		/* collation OID (InvalidOid if not set) */
 	List	   *constraints;	/* other constraints on column */
@@ -697,6 +700,30 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/*
+ * XmlTable - implementation of Xmltable function
+ *
+ */
+typedef enum
+{
+	XMLTABLE_COLUMN_OPT_PATH,
+	XMLTABLE_COLUMN_OPT_DEFAULT
+} XmlTableColOptType;
+
+typedef struct
+{
+	XmlTableColOptType		typ;
+	Node					*expr;
+} XmlTableColOpt;
+
+typedef struct XmlTable
+{
+	NodeTag		type;
+	Node	   *query_string;	/* XPath query for row selection */
+	Node	   *expr;			/* XML content */
+	List	   *coldeflist;		/* definition of columns of produced table */
+	int			location;		/* method name location, or -1 if unknown */
+} XmlTable;
 
 /****************************************************************************
  *	Nodes for a Query tree
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..0872675 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)
@@ -442,6 +444,7 @@ 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/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..c42e8e7 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -45,6 +45,7 @@ typedef enum
 
 /* struct PgXmlErrorContext is private to xml.c */
 typedef struct PgXmlErrorContext PgXmlErrorContext;
+typedef struct PgXmlTableContext PgXmlTableContext;
 
 #define DatumGetXmlP(X)		((xmltype *) PG_DETOAST_DATUM(X))
 #define XmlPGetDatum(X)		PointerGetDatum(X)
@@ -109,4 +110,9 @@ extern int	xmlbinary;			/* XmlBinaryType, but int for guc enum */
 
 extern int	xmloption;			/* XmlOptionType, but int for guc enum */
 
+extern PgXmlTableContext *makeXmlTableCxt(text *xpath_expr_text, xmltype *data, int ncols, char **paths, FmgrInfo *in_functions, Oid *typioparams, TupleDesc tupdesc);
+extern void XmlTableCxtFree(PgXmlTableContext *cxt);
+extern bool XmlTableGetRow(PgXmlTableContext *cxt);
+extern Datum XmlTableGetValue(PgXmlTableContext *cxt, int colnum, bool *isnull);
+
 #endif   /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..6392659 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +928,57 @@ 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)
 
+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>');
+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 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
+(6 rows)
+
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 08a0b30..00445a3 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -270,3 +270,50 @@ 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>');
+
+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>');
+
+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 text PATH 'SIZE/@unit',
+                                  premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified');
+
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to