2016-08-23 21:00 GMT+02:00 Pavel Stehule <[email protected]>:
> Hi
>
> 2016-08-19 10:58 GMT+02:00 Pavel Stehule <[email protected]>:
>
>> 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
>
> Regards
>
> Pavel
>
>
>> Regards
>>
>> Pavel
>>
>
>
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>');
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers