patch: function xmltable
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> </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> </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> </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>');
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> </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> </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> </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>');
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> </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> </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> </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>');
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> </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> </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> </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>');
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
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
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.
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 byThis 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
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 byThis structure should be reused by JSON_TABLE function. Now, it is little
bit strange, because there is only XMLTABLE implementation - and I haveto
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 laterCurrently 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. XMLTABLEusing
two XPATH expressions - for row data cutting and next for column data
cutting (from row data). The our XPATH functions is pretty simple mappedto
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 functionXmlTableGetValue
is used for getting one column value, the function XmlTableGetRowValue is
used for getting one value too, but in special case, when there are notany
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 designedfor
usage inside a query.
Makes sense.
I had to decide between enhancing ColumnDef node or
introduction new special node. Because there are more special attributesand
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
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
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
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> </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> </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> </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>');
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:
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
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
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
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
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 pgindentThanks.
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> </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> </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> </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 './*');
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
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
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
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
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
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
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 COPYstatement
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 Idump
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 ofelement
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 bemuch
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:
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
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
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. Whenit
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
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�W xmltable-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&