Hi
2016-08-07 11:15 GMT+02:00 Pavel Stehule <[email protected]>:
> Hi
>
> I am sending a initial implementation of xmltable function:
>
> The code is not clean now, but it does almost of expected work. The usage
> is simple. It is fast - 16K entries in 400ms.
>
> I invite any help with documentation and testing.
>
> The full ANSI/SQL, or Oracle compatible implementation is not possible due
> limits of libxml2, but for typical usage it should to work well. It doesn't
> need any new reserved keyword, so there should not be hard barriers for
> accepting (when this work will be complete).
>
> Example:
>
> postgres=# SELECT * FROM xmldata;
> ┌──────────────────────────────────────────────────────────────────┐
> │ data │
> ╞══════════════════════════════════════════════════════════════════╡
> │ <ROWS> ↵│
> │ <ROW id="1"> ↵│
> │ <COUNTRY_ID>AU</COUNTRY_ID> ↵│
> │ <COUNTRY_NAME>Australia</COUNTRY_NAME> ↵│
> │ <REGION_ID>3</REGION_ID> ↵│
> │ </ROW> ↵│
> │ <ROW id="2"> ↵│
> │ <COUNTRY_ID>CN</COUNTRY_ID> ↵│
> │ <COUNTRY_NAME>China</COUNTRY_NAME> ↵│
> │ <REGION_ID>3</REGION_ID> ↵│
> │ </ROW> ↵│
> │ <ROW id="3"> ↵│
> │ <COUNTRY_ID>HK</COUNTRY_ID> ↵│
> │ <COUNTRY_NAME>HongKong</COUNTRY_NAME> ↵│
> │ <REGION_ID>3</REGION_ID> ↵│
> │ </ROW> ↵│
> │ <ROW id="4"> ↵│
> │ <COUNTRY_ID>IN</COUNTRY_ID> ↵│
> │ <COUNTRY_NAME>India</COUNTRY_NAME> ↵│
> │ <REGION_ID>3</REGION_ID> ↵│
> │ </ROW> ↵│
> │ <ROW id="5"> ↵│
> │ <COUNTRY_ID>JP</COUNTRY_ID> ↵│
> │ <COUNTRY_NAME>Japan</COUNTRY_NAME> ↵│
> │ <REGION_ID>3</REGION_ID><PREMIER_NAME>Sinzo Abe</PREMIER_NAME>↵│
> │ </ROW> ↵│
> │ <ROW id="6"> ↵│
> │ <COUNTRY_ID>SG</COUNTRY_ID> ↵│
> │ <COUNTRY_NAME>Singapore</COUNTRY_NAME> ↵│
> │ <REGION_ID>3</REGION_ID><SIZE unit="km">791</SIZE> ↵│
> │ </ROW> ↵│
> │ </ROWS> │
> └──────────────────────────────────────────────────────────────────┘
> (1 row)
> postgres=# SELECT xmltable.*
> postgres-# FROM (SELECT data FROM xmldata) x,
> postgres-# LATERAL xmltable('/ROWS/ROW'
> postgres(# PASSING data
> postgres(# COLUMNS id int PATH '@id',
> postgres(# country_name text PATH
> 'COUNTRY_NAME',
> postgres(# country_id text PATH
> 'COUNTRY_ID',
> postgres(# region_id int PATH
> 'REGION_ID',
> postgres(# size float PATH 'SIZE',
> postgres(# unit text PATH 'SIZE/@unit',
> postgres(# premier_name text PATH
> 'PREMIER_NAME' DEFAULT 'not specified');
> ┌────┬──────────────┬────────────┬───────────┬──────┬──────┬
> ───────────────┐
> │ id │ country_name │ country_id │ region_id │ size │ unit │ premier_name
> │
> ╞════╪══════════════╪════════════╪═══════════╪══════╪══════╪
> ═══════════════╡
> │ 1 │ Australia │ AU │ 3 │ ¤ │ ¤ │ not specified
> │
> │ 2 │ China │ CN │ 3 │ ¤ │ ¤ │ not specified
> │
> │ 3 │ HongKong │ HK │ 3 │ ¤ │ ¤ │ not specified
> │
> │ 4 │ India │ IN │ 3 │ ¤ │ ¤ │ not specified
> │
> │ 5 │ Japan │ JP │ 3 │ ¤ │ ¤ │ Sinzo Abe
> │
> │ 6 │ Singapore │ SG │ 3 │ 791 │ km │ not specified
> │
> └────┴──────────────┴────────────┴───────────┴──────┴──────┴
> ───────────────┘
> (6 rows)
>
> Regards
>
>
I am sending updated version - the code is not better, but there is full
functionality implemented.
* xmlnamespaces,
* default xmlnamespace,
* ordinality column,
* NOT NULL constraint,
* mode without explicitly defined columns.
Lot of bugs was fixed - it is ready for some playing.
tests, comments, notes, comparing with other db are welcome. Some behave is
based by libxml2 possibilities - so only XPath is supported.
Regards
Pavel
> Pavel
>
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 69bf65d..a20c576 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -58,7 +58,6 @@
#include "utils/typcache.h"
#include "utils/xml.h"
-
/* static function decls */
static Datum ExecEvalArrayRef(ArrayRefExprState *astate,
ExprContext *econtext,
@@ -149,6 +148,8 @@ static Datum ExecEvalMinMax(MinMaxExprState *minmaxExpr,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalXmlTable(XmlTableState *xmltable, ExprContext *econtext,
+ bool *isnull, ExprDoneCond *isDone);
static Datum ExecEvalNullIf(FuncExprState *nullIfExpr,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
@@ -2073,6 +2074,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
MemoryContext oldcontext;
bool direct_function_call;
bool first_time = true;
+ bool xmltable_call;
callerContext = CurrentMemoryContext;
@@ -2118,6 +2120,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
* This path is similar to ExecMakeFunctionResult.
*/
direct_function_call = true;
+ xmltable_call = false;
/*
* Initialize function cache if first time through
@@ -2172,10 +2175,16 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
}
}
}
+ else if (funcexpr && IsA(funcexpr, XmlTableState))
+ {
+ direct_function_call = false;
+ xmltable_call = true;
+ }
else
{
/* Treat funcexpr as a generic expression */
direct_function_call = false;
+ xmltable_call = false;
InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
}
@@ -2213,6 +2222,14 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
pgstat_end_function_usage(&fcusage,
rsinfo.isDone != ExprMultipleResult);
}
+ else if (xmltable_call)
+ {
+ XmlTableState *xmltable = (XmlTableState *) funcexpr;
+
+ xmltable->rsinfo = &rsinfo;
+ result = ExecEvalExpr(funcexpr, econtext,
+ &fcinfo.isnull, &rsinfo.isDone);
+ }
else
{
result = ExecEvalExpr(funcexpr, econtext,
@@ -3737,6 +3754,338 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext,
return (Datum) 0;
}
+/*************************************************
+ * Note:
+ *
+ * libxml2 doesn't support default namespace in XPath
+ * expressions, so we can inject (but it needs XPath parser
+ * or we should not to support it.
+ *************************************************
+ */
+
+/*
+ * This mechanism is too simple - for real usage the real
+ * XPATH expression is necessary.
+ */
+static char *
+inject_default_ns(char *xpath_expr)
+{
+ StringInfo str = makeStringInfo();
+ char *buffer;
+ char *bufptr;
+ char *xpath_expr_ptr;
+
+ buffer = palloc(strlen(xpath_expr));
+ bufptr = buffer;
+ xpath_expr_ptr = xpath_expr;
+
+ *bufptr = '\0';
+
+ while (*xpath_expr_ptr != '\0')
+ {
+ if (*xpath_expr_ptr == '/')
+ {
+ if (*buffer != '\0')
+ {
+ if (strchr(buffer, '(') != NULL || strchr(buffer, '.') != NULL ||
+ strchr(buffer, '@') != NULL || strchr(buffer, ':') != NULL )
+ {
+ appendStringInfoString(str, buffer);
+ }
+ else
+ {
+ appendStringInfoString(str, "pgdefxmlns:");
+ appendStringInfoString(str, buffer);
+ }
+ }
+ bufptr = buffer;
+ *bufptr = '\0';
+ appendStringInfoChar(str, '/');
+ xpath_expr_ptr++;
+ }
+ else
+ {
+ *bufptr++ = *xpath_expr_ptr++;
+ *bufptr = '\0';
+ }
+ }
+ if (*buffer != '\0')
+ {
+ if (strchr(buffer, '(') != NULL || strchr(buffer, '.') != NULL || strchr(buffer, '@') != NULL || strchr(buffer, ':') != NULL )
+ {
+ appendStringInfoString(str, buffer);
+ }
+ else
+ {
+ appendStringInfoString(str, "pgdefxmlns:");
+ appendStringInfoString(str, buffer);
+ }
+ }
+
+ pfree(buffer);
+
+ return str->data;
+}
+
+static Datum
+ExecEvalXmlTable(XmlTableState *xmltable,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ Datum *values;
+ bool *nulls;
+ int i;
+ int ncols = xmltable->ncolumns;
+ ReturnSetInfo *rsinfo = xmltable->rsinfo;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldctx;
+ TupleDesc tupdesc;
+ Datum value;
+ bool isnull;
+ char **paths;
+ PgXmlTableContext *xmltableCxt;
+ text *xpath_expr_text;
+ xmltype *data;
+ FmgrInfo *in_functions;
+ Oid *typioparams;
+ ListCell *l;
+ int64 rc = 1;
+ int nnamespaces;
+ char **namespaces_uri;
+ char **namespaces_name;
+ bool use_default_ns = false;
+
+ /* check valid usage */
+ if (xmltable->rsinfo == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("xmltable function is called in unsupported expression context")));
+
+ tupdesc = xmltable->rsinfo->expectedDesc;
+
+ /* first execution, prepare in_function array */
+ if (xmltable->in_functions == NULL)
+ {
+ Oid in_func_oid;
+ Form_pg_attribute *attr;
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldctx = MemoryContextSwitchTo(per_query_ctx);
+
+ attr = tupdesc->attrs;
+ Assert(tupdesc->natts == ncols);
+
+ in_functions = xmltable->in_functions = palloc(sizeof(FmgrInfo) * ncols);
+ typioparams = xmltable->typioparams = palloc(sizeof(Oid) * ncols);
+ for(i = 0; i < ncols; i++)
+ {
+ getTypeInputInfo(attr[i]->atttypid,
+ &in_func_oid, &typioparams[i]);
+ fmgr_info(in_func_oid, &in_functions[i]);
+ }
+
+ MemoryContextSwitchTo(oldctx);
+ }
+ else
+ {
+ in_functions = xmltable->in_functions;
+ typioparams = xmltable->typioparams;
+ }
+
+ nnamespaces = list_length(xmltable->namespaces);
+ if (nnamespaces > 0)
+ {
+ namespaces_uri = palloc0(sizeof(char *) * nnamespaces);
+ namespaces_name = palloc0(sizeof(char *) * nnamespaces);
+ i = 0;
+
+ foreach(l, xmltable->namespaces)
+ {
+ ResTarget *rt = (ResTarget *) lfirst(l);
+
+ value = ExecEvalExpr((ExprState *) rt->val, econtext, &isnull, NULL);
+ if (isnull)
+ elog(ERROR, "NULL is not allowed in xml namespaces specification");
+
+ namespaces_uri[i] = text_to_cstring(DatumGetTextP(value));
+ namespaces_name[i] = rt->name;
+ if (rt->name != NULL)
+ {
+ int j;
+ for (j = 0; j < i; j++)
+ if (namespaces_name[j] != NULL && strcmp(namespaces_name[j], rt->name) == 0)
+ elog(ERROR, "namespaces name are not unique \"%s\"", rt->name);
+ }
+ else
+ {
+ namespaces_name[i] = "pgdefxmlns";
+ use_default_ns = true;
+ }
+ i++;
+ }
+ }
+ else
+ {
+ namespaces_uri = NULL;
+ namespaces_name = NULL;
+ }
+
+ paths = palloc(sizeof(char *) * ncols);
+ for (i = 0; i < ncols; i++)
+ {
+ char *curr_path;
+ bool use_prefix = false;
+ bool use_suffix = true;
+ int j;
+ char *last_char_ptr;
+
+ if (xmltable->path_exprs[i] != NULL)
+ {
+ value = ExecEvalExpr(xmltable->path_exprs[i], econtext, &isnull, NULL);
+
+ if (isnull)
+ elog(ERROR, "expression of PATH of column %s is NULL",
+ NameStr(tupdesc->attrs[i]->attname));
+ paths[i] = text_to_cstring(DatumGetTextP(value));
+ }
+ else
+ paths[i] = NameStr(tupdesc->attrs[i]->attname);
+
+ /* When doesn't start with any special cha, we should to append ref to parent node */
+ curr_path = paths[i];
+ if (curr_path[0] != '.' && curr_path[0] != '/')
+ use_prefix = true;
+
+ last_char_ptr = curr_path + strlen(curr_path) - 1;
+ if (*last_char_ptr == ')' || *last_char_ptr == '/')
+ use_suffix = false;
+
+ /* path shoud be reference to attribute */
+ if (use_suffix)
+ {
+ for (j = strlen(curr_path); j > 0; j--)
+ {
+ if (*last_char_ptr == '/')
+ break;
+ if (*last_char_ptr-- == '@')
+ {
+ use_suffix = false;
+ break;
+ }
+ }
+ }
+
+ /* don't use suffix when target type is a XML */
+ if (use_suffix && tupdesc->attrs[i]->atttypid == XMLOID)
+ use_suffix = false;
+
+ if (use_prefix || use_suffix)
+ {
+ char *buffer = palloc(strlen(curr_path) + 1 + 2 + 7);
+
+ sprintf(buffer, "%s%s%s", use_prefix ? "./" : "", curr_path, use_suffix ? "/text()" : "");
+ if (!use_default_ns)
+ paths[i] = buffer;
+ else
+ paths[i] = inject_default_ns(buffer);
+ }
+ }
+
+ value = ExecEvalExpr(xmltable->query_string, econtext, &isnull, NULL);
+ if (isnull)
+ elog(ERROR, "query string should not be NULL");
+ xpath_expr_text = DatumGetTextP(value);
+
+ if (use_default_ns)
+ xpath_expr_text = cstring_to_text(inject_default_ns(text_to_cstring(xpath_expr_text)));
+
+ value = ExecEvalExpr(xmltable->expr, econtext, &isnull, NULL);
+ if (isnull)
+ elog(ERROR, "input value should not be NULL (ToDo: should be fixed in future)");
+ data = DatumGetXmlP(value);
+
+ values = palloc0(sizeof(Datum) * ncols);
+ nulls = palloc0(sizeof(bool) * ncols);
+
+ xmltableCxt = makeXmlTableCxt(xpath_expr_text, data, ncols, paths,
+ in_functions, typioparams, tupdesc,
+ nnamespaces, namespaces_uri, namespaces_name,
+ xmltable->with_auto_col);
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldctx = MemoryContextSwitchTo(per_query_ctx);
+ tupstore = tuplestore_begin_heap(false, false, work_mem);
+
+ PG_TRY();
+ {
+ for(;;)
+ {
+ MemoryContextSwitchTo(oldctx);
+
+ if (XmlTableGetRow(xmltableCxt))
+ {
+ bool isnull;
+ Datum value;
+ HeapTuple tuple;
+
+ for (i = 0; i < ncols; i++)
+ {
+ value = XmlTableGetValue(xmltableCxt, i, &isnull);
+
+ if (isnull && xmltable->default_exprs[i] != NULL)
+ value = ExecEvalExpr(xmltable->default_exprs[i], econtext, &isnull, NULL);
+
+ if (isnull)
+ {
+
+ nulls[i] = true;
+ if (xmltable->is_not_nulls[i])
+ elog(ERROR, "value is null");
+ }
+ else
+ {
+ values[i] = value;
+ nulls[i] = false;
+ }
+
+ }
+ if (xmltable->ordinality_colnum > 0)
+ {
+ values[xmltable->ordinality_colnum - 1] = Int8GetDatum(rc++);
+ nulls[xmltable->ordinality_colnum - 1] = false;
+ }
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+
+ MemoryContextSwitchTo(per_query_ctx);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ else
+ break;
+ }
+
+ MemoryContextSwitchTo(per_query_ctx);
+ tuplestore_donestoring(tupstore);
+
+ XmlTableCxtFree(xmltableCxt);
+ }
+ PG_CATCH();
+ {
+ XmlTableCxtFree(xmltableCxt);
+
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ *isDone = ExprSingleResult;
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = CreateTupleDescCopy(tupdesc); /* this destroy passed tupdesc, use copy! */
+
+ return (Datum) 0;
+}
+
/* ----------------------------------------------------------------
* ExecEvalNullIf
*
@@ -5107,6 +5456,57 @@ ExecInitExpr(Expr *node, PlanState *parent)
state = (ExprState *) xstate;
}
break;
+ case T_XmlTable:
+ {
+ XmlTable *xt = (XmlTable *) node;
+ XmlTableState *xstate = makeNode(XmlTableState);
+ ListCell *l;
+ int i = 0;
+ int ncols;
+
+ xstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalXmlTable;
+ xstate->query_string = ExecInitExpr((Expr *) xt->query_string, parent);
+ xstate->expr = ExecInitExpr((Expr *) xt->expr, parent);
+
+ ncols = list_length(xt->coldeflist);
+ xstate->ncolumns = ncols;
+ xstate->path_exprs = palloc0(sizeof(ExprState *) * ncols);
+ xstate->default_exprs = palloc0(sizeof(ExprState *) * ncols);
+ xstate->with_auto_col = xt->with_auto_col;
+
+ xstate->is_not_nulls = palloc0(sizeof(bool) * ncols);
+
+ foreach(l, xt->namespaces)
+ {
+ ResTarget *res = (ResTarget *) lfirst(l);
+ ResTarget *newres = makeNode(ResTarget);
+
+ newres->name = res->name;
+ newres->val = (Node *) ExecInitExpr((Expr *) res->val, parent);
+ newres->location = res->location;
+
+ xstate->namespaces = lappend(xstate->namespaces, newres);
+ }
+
+ foreach(l, xt->coldeflist)
+ {
+ ColumnDef *def = (ColumnDef *) lfirst(l);
+
+ if (def->cooked_path)
+ xstate->path_exprs[i] = ExecInitExpr((Expr *) def->cooked_path, parent);
+ if (def->cooked_default)
+ xstate->default_exprs[i] = ExecInitExpr((Expr *) def->cooked_default, parent);
+
+ if (def->for_ordinality)
+ xstate->ordinality_colnum = i + 1;
+ if (def->is_not_null)
+ xstate->is_not_nulls[i] = true;
+ i++;
+ }
+
+ state = (ExprState *) xstate;
+ }
+ break;
case T_NullTest:
{
NullTest *ntest = (NullTest *) node;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3244c76..7b4b422 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2553,6 +2553,7 @@ _copyRangeTableSample(const RangeTableSample *from)
return newnode;
}
+
static TypeCast *
_copyTypeCast(const TypeCast *from)
{
@@ -2607,6 +2608,8 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
+ COPY_NODE_FIELD(raw_path);
+ COPY_NODE_FIELD(cooked_path);
COPY_NODE_FIELD(collClause);
COPY_SCALAR_FIELD(collOid);
COPY_NODE_FIELD(constraints);
@@ -2688,6 +2691,20 @@ _copyXmlSerialize(const XmlSerialize *from)
return newnode;
}
+static XmlTable *
+_copyXmlTable(const XmlTable *from)
+{
+ XmlTable *newnode = makeNode(XmlTable);
+
+ COPY_NODE_FIELD(namespaces);
+ COPY_NODE_FIELD(query_string);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(coldeflist);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static RoleSpec *
_copyRoleSpec(const RoleSpec *from)
{
@@ -5062,6 +5079,9 @@ copyObject(const void *from)
case T_XmlSerialize:
retval = _copyXmlSerialize(from);
break;
+ case T_XmlTable:
+ retval = _copyXmlTable(from);
+ break;
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1eb6799..259d9e1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2339,6 +2339,7 @@ _equalRangeTableSample(const RangeTableSample *a, const RangeTableSample *b)
return true;
}
+
static bool
_equalIndexElem(const IndexElem *a, const IndexElem *b)
{
@@ -2365,6 +2366,8 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
+ COMPARE_NODE_FIELD(raw_path);
+ COMPARE_NODE_FIELD(cooked_path);
COMPARE_NODE_FIELD(collClause);
COMPARE_SCALAR_FIELD(collOid);
COMPARE_NODE_FIELD(constraints);
@@ -2610,6 +2613,18 @@ _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
}
static bool
+_equalXmlTable(const XmlTable *a, const XmlTable *b)
+{
+ COMPARE_NODE_FIELD(namespaces);
+ COMPARE_NODE_FIELD(query_string);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(coldeflist);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
_equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
{
COMPARE_SCALAR_FIELD(roletype);
@@ -3366,6 +3381,9 @@ equal(const void *a, const void *b)
case T_XmlSerialize:
retval = _equalXmlSerialize(a, b);
break;
+ case T_XmlTable:
+ retval = _equalXmlTable(a, b);
+ break;
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index cd39167..86b2c66 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -226,6 +226,9 @@ exprType(const Node *expr)
else
type = XMLOID;
break;
+ case T_XmlTable:
+ type = RECORDOID;
+ break;
case T_NullTest:
type = BOOLOID;
break;
@@ -720,6 +723,8 @@ expression_returns_set_walker(Node *node, void *context)
return false;
if (IsA(node, XmlExpr))
return false;
+ if (IsA(node, XmlTable))
+ return true;
return expression_tree_walker(node, expression_returns_set_walker,
context);
@@ -895,6 +900,12 @@ exprCollation(const Node *expr)
else
coll = InvalidOid;
break;
+ /*
+ * result is composite or XML
+ */
+ case T_XmlTable:
+ coll = InvalidOid;
+ break;
case T_NullTest:
coll = InvalidOid; /* result is always boolean */
break;
@@ -1096,6 +1107,9 @@ exprSetCollation(Node *expr, Oid collation)
(collation == DEFAULT_COLLATION_OID) :
(collation == InvalidOid));
break;
+ case T_XmlTable:
+ Assert(!OidIsValid(collation)); /* result is always XML */
+ break;
case T_NullTest:
Assert(!OidIsValid(collation)); /* result is always boolean */
break;
@@ -1512,6 +1526,9 @@ exprLocation(const Node *expr)
/* XMLSERIALIZE keyword should always be the first thing */
loc = ((const XmlSerialize *) expr)->location;
break;
+ case T_XmlTable:
+ loc = ((const XmlTable *) expr)->location;
+ break;
case T_GroupingSet:
loc = ((const GroupingSet *) expr)->location;
break;
@@ -2067,6 +2084,35 @@ expression_tree_walker(Node *node,
return true;
}
break;
+ case T_XmlTable:
+ {
+ XmlTable *xt = (XmlTable *) node;
+ ListCell *l;
+
+ if (walker(xt->query_string, context))
+ return true;
+ if (walker(xt->expr, context))
+ return true;
+
+ foreach(l, xt->namespaces)
+ {
+ ResTarget *rt = (ResTarget *) lfirst(l);
+
+ if (walker(rt->val, context))
+ return true;
+ }
+
+ foreach(l, xt->coldeflist)
+ {
+ ColumnDef *def = (ColumnDef *) lfirst(l);
+
+ if (walker(def->cooked_default, context))
+ return true;
+ if (walker(def->cooked_path, context))
+ return true;
+ }
+ }
+ break;
case T_NullTest:
return walker(((NullTest *) node)->arg, context);
case T_BooleanTest:
@@ -2781,6 +2827,45 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
+ case T_XmlTable:
+ {
+ XmlTable *xt = (XmlTable *) node;
+ XmlTable *newnode;
+ List *resultlist = NIL;
+ ListCell *l;
+
+ FLATCOPY(newnode, xt, XmlTable);
+ MUTATE(newnode->query_string, xt->query_string, Node *);
+ MUTATE(newnode->expr, xt->expr, Node *);
+
+ resultlist = NIL;
+ foreach(l, xt->namespaces)
+ {
+ ResTarget *rt = (ResTarget *) lfirst(l);
+ ResTarget *newrt;
+
+ FLATCOPY(newrt, rt, ResTarget);
+ MUTATE(newrt->val, rt->val, Node *);
+ resultlist = lappend(resultlist, newrt);
+ }
+ newnode->namespaces = resultlist;
+
+ resultlist = NIL;
+ foreach(l, xt->coldeflist)
+ {
+ ColumnDef *def = (ColumnDef *) lfirst(l);
+ ColumnDef *newdef;
+
+ FLATCOPY(newdef, def, ColumnDef);
+ MUTATE(newdef->cooked_default, def->cooked_default, Node *);
+ MUTATE(newdef->cooked_path, def->cooked_path, Node *);
+ resultlist = lappend(resultlist, newdef);
+ }
+ newnode->coldeflist = resultlist;
+
+ return (Node *) newnode;
+ }
+ break;
case T_NullTest:
{
NullTest *ntest = (NullTest *) node;
@@ -3547,6 +3632,8 @@ raw_expression_tree_walker(Node *node,
return true;
if (walker(coldef->raw_default, context))
return true;
+ if (walker(coldef->raw_path, context))
+ return true;
if (walker(coldef->collClause, context))
return true;
/* for now, constraints are ignored */
@@ -3575,6 +3662,20 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
+ case T_XmlTable:
+ {
+ XmlTable *xt = (XmlTable *) node;
+
+ if (walker(xt->namespaces, context))
+ return true;
+ if (walker(xt->query_string, context))
+ return true;
+ if (walker(xt->expr, context))
+ return true;
+ if (walker(xt->coldeflist, context))
+ return true;
+ }
+ break;
case T_WithClause:
return walker(((WithClause *) node)->ctes, context);
case T_InferClause:
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index acaf4ea..9e10c88 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2562,6 +2562,18 @@ _outXmlSerialize(StringInfo str, const XmlSerialize *node)
}
static void
+_outXmlTable(StringInfo str, const XmlTable *node)
+{
+ WRITE_NODE_TYPE("XMLTABLE");
+
+ WRITE_NODE_FIELD(namespaces);
+ WRITE_NODE_FIELD(query_string);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(coldeflist);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
_outColumnDef(StringInfo str, const ColumnDef *node)
{
WRITE_NODE_TYPE("COLUMNDEF");
@@ -2575,6 +2587,8 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
+ WRITE_NODE_FIELD(raw_path);
+ WRITE_NODE_FIELD(cooked_path);
WRITE_NODE_FIELD(collClause);
WRITE_OID_FIELD(collOid);
WRITE_NODE_FIELD(constraints);
@@ -3845,6 +3859,9 @@ outNode(StringInfo str, const void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_XmlTable:
+ _outXmlTable(str, obj);
+ break;
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
break;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a69af7c..6ead0b7 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -806,6 +806,11 @@ expression_returns_set_rows_walker(Node *node, double *count)
if (expr->funcretset)
*count *= get_func_rows(expr->funcid);
}
+ if (IsA(node, XmlTable))
+ {
+ *count = 100;
+ return false;
+ }
if (IsA(node, OpExpr))
{
OpExpr *expr = (OpExpr *) node;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0cae446..f672f4c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -132,7 +132,7 @@ typedef struct ImportQual
#define parser_yyerror(msg) scanner_yyerror(msg, yyscanner)
#define parser_errposition(pos) scanner_errposition(pos, yyscanner)
-static void base_yyerror(YYLTYPE *yylloc, core_yyscan_t yyscanner,
+static void base_yyerror(YYLTYPE *yylloc, core_yyscan_t yyscanner,
const char *msg);
static Node *makeColumnRef(char *colname, List *indirection,
int location, core_yyscan_t yyscanner);
@@ -227,6 +227,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ XmlTableColOpt *xmltablecolopt;
}
%type <node> stmt schema_stmt
@@ -372,6 +373,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
create_generic_options alter_generic_options
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
+ XmltableColumnList
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -540,6 +542,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <node> xmltable_column
+%type <target> xmlnamespace_el
+%type <xmltablecolopt> xmltable_column_option
+%type <list> opt_xmltable_column_option_list xmltable_column_option_list
+%type <list> xmlnamespace_list
+%type <boolean> disallow_null
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -571,7 +580,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
- CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
+ CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
CROSS CSV CUBE CURRENT_P
@@ -615,7 +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 PATH PASSWORD PLACING PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
@@ -644,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
@@ -12617,6 +12626,44 @@ func_expr_common_subexpr:
n->location = @1;
$$ = (Node *)n;
}
+ | XMLTABLE '(' c_expr xmlexists_argument ')'
+ {
+ XmlTable *n = makeNode(XmlTable);
+ n->query_string = $3;
+ n->expr = $4;
+ n->coldeflist = NIL;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
+ | XMLTABLE '(' XMLNAMESPACES '(' xmlnamespace_list ')' c_expr xmlexists_argument ')'
+ {
+ XmlTable *n = makeNode(XmlTable);
+ n->namespaces = $5;
+ n->query_string = $7;
+ n->expr = $8;
+ n->coldeflist = NIL;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
+ | XMLTABLE '(' c_expr xmlexists_argument COLUMNS XmltableColumnList ')'
+ {
+ XmlTable *n = makeNode(XmlTable);
+ n->query_string = $3;
+ n->expr = $4;
+ n->coldeflist = $6;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
+ | XMLTABLE '(' XMLNAMESPACES '(' xmlnamespace_list ')' c_expr xmlexists_argument COLUMNS XmltableColumnList ')'
+ {
+ XmlTable *n = makeNode(XmlTable);
+ n->namespaces = $5;
+ n->query_string = $7;
+ n->expr = $8;
+ n->coldeflist = $10;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
;
/*
@@ -12663,6 +12710,28 @@ xml_attribute_el: a_expr AS ColLabel
}
;
+xmlnamespace_list: xmlnamespace_el { $$ = list_make1($1); }
+ | xmlnamespace_list ',' xmlnamespace_el { $$ = lappend($1, $3); }
+ ;
+
+xmlnamespace_el: 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;
+ }
+ ;
+
document_or_content: DOCUMENT_P { $$ = XMLOPTION_DOCUMENT; }
| CONTENT_P { $$ = XMLOPTION_CONTENT; }
;
@@ -12692,6 +12761,108 @@ xmlexists_argument:
}
;
+xmltable_column_option:
+ DEFAULT c_expr
+ {
+ $$ = palloc(sizeof(XmlTableColOpt));
+
+ $$->typ = XMLTABLE_COLUMN_OPT_DEFAULT;
+ $$->expr = $2;
+ }
+ | PATH c_expr
+ {
+ $$ = palloc(sizeof(XmlTableColOpt));
+
+ $$->typ = XMLTABLE_COLUMN_OPT_PATH;
+ $$->expr = $2;
+ }
+ ;
+
+xmltable_column_option_list:
+ xmltable_column_option { $$ = list_make1($1); }
+ | xmltable_column_option_list xmltable_column_option { $$ = lappend($1, $2); }
+ ;
+
+opt_xmltable_column_option_list:
+ xmltable_column_option_list { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+xmltable_column:
+ ColId Typename opt_xmltable_column_option_list disallow_null
+ {
+ ListCell *l;
+ ColumnDef *c = makeNode(ColumnDef);
+ bool path_is_def = false;
+ bool default_is_def = false;
+
+ c->colname = $1;
+ c->typeName = $2;
+ c->raw_default = NULL;
+ c->raw_path = NULL;
+ c->storage = 55;
+ c->is_not_null = $4;
+
+ foreach(l, $3)
+ {
+ XmlTableColOpt *opt = (XmlTableColOpt *) lfirst(l);
+
+ if (opt->typ == XMLTABLE_COLUMN_OPT_PATH)
+ {
+ if (path_is_def)
+ elog(ERROR, "duplicate PATH");
+ else
+ path_is_def = true;
+
+ c->raw_path = opt->expr;
+ }
+ else if (opt->typ == XMLTABLE_COLUMN_OPT_DEFAULT)
+ {
+ if (default_is_def)
+ elog(ERROR, "duplicit DEFAULT");
+ else
+ default_is_def = true;
+
+ c->raw_default = opt->expr;
+ }
+ else
+ elog(ERROR, "unknown xmltable column option");
+ }
+
+ c->location = @1;
+ $$ = (Node *) c;
+ }
+ | ColId FOR ORDINALITY
+ {
+ ColumnDef *c = makeNode(ColumnDef);
+
+ c->colname = $1;
+ c->for_ordinality = true;
+ c->location = @1;
+ $$ = (Node *) c;
+ }
+ ;
+
+disallow_null:
+ NOT NULL_P
+ {
+ $$ = true;
+ }
+ | NULL_P
+ {
+ $$ = false;
+ }
+ | /* EMPTY */
+ {
+ $$ = false;
+ }
+ ;
+
+XmltableColumnList:
+ xmltable_column { $$ = list_make1($1); }
+ | XmltableColumnList ',' xmltable_column { $$ = lappend($1, $3); }
+ ;
+
/*
* Aggregate decoration clauses
@@ -13755,6 +13926,7 @@ unreserved_keyword:
| CLASS
| CLOSE
| CLUSTER
+ | COLUMNS
| COMMENT
| COMMENTS
| COMMIT
@@ -13888,6 +14060,7 @@ unreserved_keyword:
| PARTITION
| PASSING
| PASSWORD
+ | PATH
| PLANS
| POLICY
| PRECEDING
@@ -14053,10 +14226,12 @@ col_name_keyword:
| XMLELEMENT
| XMLEXISTS
| XMLFOREST
+ | XMLNAMESPACES
| XMLPARSE
| XMLPI
| XMLROOT
| XMLSERIALIZE
+ | XMLTABLE
;
/* Type/function identifier --- keywords that can be type or function names.
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..b409b93 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -113,7 +113,7 @@ transformFromClause(ParseState *pstate, List *frmList)
/*
* The grammar will have produced a list of RangeVars, RangeSubselects,
- * RangeFunctions, and/or JoinExprs. Transform each one (possibly adding
+ * RangeFunctions and/or JoinExprs. Transform each one (possibly adding
* entries to the rtable), check for duplicate refnames, and then add it
* to the joinlist and namespace.
*
@@ -821,7 +821,6 @@ transformRangeTableSample(ParseState *pstate, RangeTableSample *rts)
return tablesample;
}
-
/*
* transformFromClauseItem -
* Transform a FROM-clause item, adding any required entries to the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index cead212..9af33cb 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -99,6 +99,7 @@ static Node *transformAExprIn(ParseState *pstate, A_Expr *a);
static Node *transformAExprBetween(ParseState *pstate, A_Expr *a);
static Node *transformBoolExpr(ParseState *pstate, BoolExpr *a);
static Node *transformFuncCall(ParseState *pstate, FuncCall *fn);
+static Node *transformXmlTable(ParseState *pstate, XmlTable *xt);
static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref);
static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c);
static Node *transformSubLink(ParseState *pstate, SubLink *sublink);
@@ -314,6 +315,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformXmlSerialize(pstate, (XmlSerialize *) expr);
break;
+ case T_XmlTable:
+ result = transformXmlTable(pstate, (XmlTable *) expr);
+ break;
+
case T_NullTest:
{
NullTest *n = (NullTest *) expr;
@@ -1474,6 +1479,94 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
}
static Node *
+transformXmlTable(ParseState *pstate, XmlTable *xt)
+{
+ ListCell *l;
+ XmlTable *newxt = makeNode(XmlTable);
+ bool found_default_ns = false;
+
+ newxt->query_string = coerce_to_specific_type(pstate,
+ transformExprRecurse(pstate, xt->query_string),
+ TEXTOID,
+ "XMLTABLE");
+ newxt->expr = coerce_to_specific_type(pstate,
+ transformExprRecurse(pstate, xt->expr),
+ XMLOID,
+ "XMLTABLE");
+
+ newxt->location = xt->location;
+
+ foreach(l, xt->namespaces)
+ {
+ ResTarget *res = (ResTarget *) lfirst(l);
+ ResTarget *newres;
+
+ newres = makeNode(ResTarget);
+
+ if (res->name == NULL)
+ {
+ if (found_default_ns)
+ elog(ERROR, "only one default namespace is allowed");
+ found_default_ns = true;
+ }
+
+ newres->name = res->name;
+ newres->val = coerce_to_specific_type(pstate,
+ transformExprRecurse(pstate, res->val),
+ TEXTOID,
+ "XMLTABLE");
+ newxt->namespaces = lappend(newxt->namespaces, newres);
+ newres->location = res->location;
+ }
+
+ /* Append default column, where none is defined */
+ if (xt->coldeflist == NIL)
+ {
+ ColumnDef *col = makeNode(ColumnDef);
+
+ col->colname = "xmltable"; /* ToDo: what name should be used? Using pg convention now */
+ col->typeName = makeTypeNameFromOid(XMLOID, -1);
+ col->location = -1;
+ col->cooked_path = (Node *) makeConst(TEXTOID, -1, -1, -1, PointerGetDatum(cstring_to_text(".")), false, false);
+
+ xt->coldeflist = list_make1(col);
+ newxt->with_auto_col = true;
+ }
+
+ foreach(l, xt->coldeflist)
+ {
+ ColumnDef *def = (ColumnDef *) lfirst(l);
+
+ if (def->raw_default)
+ {
+ Oid targetType = typenameTypeId(pstate, def->typeName);
+
+ def->cooked_default = coerce_to_specific_type(pstate,
+ transformExprRecurse(pstate, def->raw_default),
+ targetType,
+ "XMLTABLE");
+ }
+
+ if (def->raw_path)
+ def->cooked_path = coerce_to_specific_type(pstate,
+ transformExprRecurse(pstate, def->raw_path),
+ TEXTOID,
+ "XMLTABLE");
+
+ if (def->for_ordinality)
+ {
+ if (newxt->with_ordinality)
+ elog(ERROR, "only one FOR ORDINALITY column is allowd");
+ newxt->with_ordinality = true;
+ }
+ }
+
+ newxt->coldeflist = xt->coldeflist;
+
+ return (Node *) newxt;
+}
+
+static Node *
transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
{
SubLink *sublink;
@@ -2259,7 +2352,7 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
i = 0;
foreach(lc, x->args)
{
- Node *e = (Node *) lfirst(lc);
+ Node *e = (Node *) lfirst(lc);
Node *newe;
newe = transformExprRecurse(pstate, e);
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index fc93063..a4de207 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1794,6 +1794,9 @@ FigureColnameInternal(Node *node, char **name)
case T_XmlSerialize:
*name = "xmlserialize";
return 2;
+ case T_XmlTable:
+ *name = "xmltable";
+ return 2;
default:
break;
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d68ca7a..e6dd75e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8022,6 +8022,38 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_XmlTable:
+ {
+ XmlTable *xt = (XmlTable *) node;
+
+ appendStringInfoString(buf, "XMLTABLE(");
+ get_rule_expr((Node *) xt->query_string, context, true);
+ appendStringInfoString(buf, " PASSING ");
+ get_rule_expr((Node *) xt->expr, context, true);
+
+ if (xt->coldeflist != NIL)
+ {
+ ListCell *l;
+ bool isFirst = true;
+
+ appendStringInfoString(buf, " COLUMNS ");
+ foreach(l, xt->coldeflist)
+ {
+ ColumnDef *def = (ColumnDef *) lfirst(l);
+
+ if (!isFirst)
+ appendStringInfoString(buf, ", ");
+ else
+ isFirst = false;
+ appendStringInfo(buf, "%s", def->colname);
+ }
+ appendStringInfoChar(buf, ' ');
+ }
+
+ appendStringInfoChar(buf, ')');
+ }
+ break;
+
case T_NullTest:
{
NullTest *ntest = (NullTest *) node;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 7ed5bcb..ccad864 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -115,6 +115,28 @@ struct PgXmlErrorContext
xmlExternalEntityLoader saved_entityfunc;
};
+struct PgXmlTableContext
+{
+ int ncols;
+ char **paths;
+ TupleDesc tupdesc;
+
+ PgXmlErrorContext *xmlerrcxt;
+ volatile xmlParserCtxtPtr ctxt;
+ volatile xmlDocPtr doc;
+ volatile xmlXPathContextPtr xpathctx;
+ volatile xmlXPathCompExprPtr xpathcomp;
+ volatile xmlXPathObjectPtr xpathobj;
+
+ volatile xmlXPathCompExprPtr *xpathscomp;
+
+ FmgrInfo *in_functions;
+ Oid *typioparams;
+
+ long int rc;
+ bool skip_second_search;
+};
+
static xmlParserInputPtr xmlPgEntityLoader(const char *URL, const char *ID,
xmlParserCtxtPtr ctxt);
static void xml_errorHandler(void *data, xmlErrorPtr error);
@@ -142,6 +164,7 @@ static bool print_xml_decl(StringInfo buf, const xmlChar *version,
static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg,
bool preserve_whitespace, int encoding);
static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
+static char *xml_xmlnodetostr(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
ArrayBuildState *astate,
PgXmlErrorContext *xmlerrcxt);
@@ -4073,3 +4096,398 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS)
return 0;
#endif /* not USE_LIBXML */
}
+
+/*
+ * Convert XML node to cstring (dump subtree in case of element,
+ * return value otherwise)
+ */
+#ifdef USE_LIBXML
+
+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 /* USE_LIBXML */
+
+/*
+ * Support for XMLTABLE function
+ */
+PgXmlTableContext *
+makeXmlTableCxt(text *xpath_expr_text, xmltype *data, int ncols, char **paths,
+ FmgrInfo *in_functions, Oid *typioparams, TupleDesc tupdesc,
+ int ns_count, char **namespaces_uri, char **namespaces_name,
+ bool skip_second_search)
+{
+#ifdef USE_LIBXML
+ PgXmlTableContext *cxt = palloc0(sizeof(struct PgXmlTableContext));
+ PgXmlErrorContext *xmlerrcxt;
+
+ volatile xmlParserCtxtPtr ctxt = NULL;
+ volatile xmlDocPtr doc = NULL;
+ volatile xmlXPathContextPtr xpathctx = NULL;
+ volatile xmlXPathCompExprPtr xpathcomp = NULL;
+ volatile xmlXPathObjectPtr xpathobj = NULL;
+ volatile xmlXPathCompExprPtr *xpathscomp = NULL;
+ volatile xmlXPathCompExprPtr _xpathcomp = NULL;
+ int32 len;
+ int32 xpath_len;
+ xmlChar *string;
+ xmlChar *xpath_expr;
+ int i;
+
+ cxt->ncols = ncols;
+ cxt->paths = paths;
+ cxt->in_functions = in_functions;
+ cxt->typioparams = typioparams;
+ cxt->tupdesc = tupdesc;
+ cxt->rc = 0;
+ cxt->skip_second_search = true;
+
+ cxt->xmlerrcxt = xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+ len = VARSIZE(data) - VARHDRSZ;
+ xpath_len = VARSIZE(xpath_expr_text) - VARHDRSZ;
+ if (xpath_len == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_EXCEPTION),
+ errmsg("empty XPath expression")));
+
+ string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar));
+ memcpy(string, VARDATA(data), len);
+ string[len] = '\0';
+
+ xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar));
+ memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len);
+ xpath_expr[xpath_len] = '\0';
+
+ cxt->xpathscomp = xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * ncols);
+
+ PG_TRY();
+ {
+
+ xmlInitParser();
+
+ cxt->ctxt = ctxt = xmlNewParserCtxt();
+ if (ctxt == NULL || xmlerrcxt->err_occurred)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+ "could not allocate parser context");
+ cxt->doc = doc = xmlCtxtReadMemory(ctxt, (char *) string, len, NULL, NULL, 0);
+ if (doc == NULL || xmlerrcxt->err_occurred)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
+ "could not parse XML document");
+ cxt->xpathctx = xpathctx = xmlXPathNewContext(doc);
+ if (xpathctx == NULL || xmlerrcxt->err_occurred)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+ "could not allocate XPath context");
+ xpathctx->node = xmlDocGetRootElement(doc);
+ if (xpathctx->node == NULL || xmlerrcxt->err_occurred)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+ "could not find root XML element");
+
+ /* register namespaces */
+ for (i = 0; i < ns_count; i++)
+ {
+ if (xmlXPathRegisterNs(xpathctx,
+ (xmlChar *) namespaces_name[i],
+ (xmlChar *) namespaces_uri[i]))
+ elog(ERROR, "could not register XML namespace with name \"%s\" and URI \"%s\"",
+ namespaces_name[i], namespaces_uri[i]);
+ }
+
+ cxt->xpathcomp = xpathcomp = xmlXPathCompile(xpath_expr);
+ if (xpathcomp == NULL || xmlerrcxt->err_occurred)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+ "invalid XPath expression");
+
+ cxt->xpathobj = xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx);
+ if (xpathobj == NULL || xmlerrcxt->err_occurred)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+ "could not create XPath object");
+
+ for(i = 0; i < ncols; i++)
+ {
+ int32 l = strlen(paths[i]);
+ xmlChar *buffer = palloc((l + 1) * sizeof(xmlChar));
+ memcpy(buffer, paths[i], l);
+ buffer[l] = '\0';
+
+ _xpathcomp = xmlXPathCompile(buffer);
+ if (_xpathcomp == NULL || xmlerrcxt->err_occurred)
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+ "invalid XPath expression");
+ cxt->xpathscomp[i] = _xpathcomp;
+ }
+
+ }
+ PG_CATCH();
+ {
+ XmlTableCxtFree(cxt);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return cxt;
+
+#else
+ NO_XML_SUPPORT();
+ return NULL;
+#endif /* not USE_LIBXML */
+
+}
+
+void
+XmlTableCxtFree(PgXmlTableContext *cxt)
+{
+#ifdef USE_LIBXML
+
+ if (cxt->xpathobj)
+ xmlXPathFreeObject(cxt->xpathobj);
+ if (cxt->xpathcomp)
+ xmlXPathFreeCompExpr(cxt->xpathcomp);
+ if (cxt->xpathctx)
+ xmlXPathFreeContext(cxt->xpathctx);
+ if (cxt->doc)
+ xmlFreeDoc(cxt->doc);
+ if (cxt->ctxt)
+ xmlFreeParserCtxt(cxt->ctxt);
+
+ if (cxt->xpathscomp)
+ {
+ int i;
+
+ for (i = 0; i < cxt->ncols; i++)
+ {
+ if (cxt->xpathscomp[i])
+ {
+ xmlXPathFreeCompExpr(cxt->xpathscomp[i]);
+ cxt->xpathscomp[i] = NULL;
+ }
+ }
+ pfree((void *) cxt->xpathscomp);
+ cxt->xpathscomp = NULL;
+ }
+
+ pg_xml_done(cxt->xmlerrcxt, true);
+
+ pfree(cxt);
+#else
+ NO_XML_SUPPORT();
+ return;
+#endif /* not USE_LIBXML */
+}
+
+bool
+XmlTableGetRow(PgXmlTableContext *cxt)
+{
+#ifdef USE_LIBXML
+ if (cxt->xpathobj->type == XPATH_NODESET)
+ {
+ if (cxt->xpathobj->nodesetval != NULL)
+ {
+ if (cxt->rc < cxt->xpathobj->nodesetval->nodeNr)
+ {
+ cxt->rc++;
+ return true;
+ }
+ }
+ }
+
+ return false;
+#else
+ NO_XML_SUPPORT();
+ return false;
+#endif /* not USE_LIBXML */
+
+}
+
+Datum
+XmlTableGetValue(PgXmlTableContext *cxt, int colnum, bool *isnull)
+{
+#ifdef USE_LIBXML
+ volatile xmlXPathObjectPtr _xpathobj = NULL;
+ Datum value = (Datum) 0;
+
+ if (cxt->xpathobj->type == XPATH_NODESET &&
+ cxt->xpathobj->nodesetval != NULL)
+ {
+ xmlNodePtr cur;
+
+ Assert(cxt->rc <= cxt->xpathobj->nodesetval->nodeNr);
+
+ cur = cxt->xpathobj->nodesetval->nodeTab[cxt->rc - 1];
+
+ if (cur->type == XML_ELEMENT_NODE)
+ {
+ PG_TRY();
+ {
+ xmlXPathSetContextNode(cur, cxt->xpathctx);
+ _xpathobj = xmlXPathCompiledEval(cxt->xpathscomp[colnum], cxt->xpathctx);
+ if (_xpathobj == NULL || cxt->xmlerrcxt->err_occurred)
+ xml_ereport(cxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+ "could not create XPath object");
+
+ switch (_xpathobj->type)
+ {
+ case XPATH_NODESET:
+ {
+ if (_xpathobj->nodesetval != NULL)
+ {
+ int result = _xpathobj->nodesetval->nodeNr;
+
+ if (result == 0)
+ {
+ *isnull = true;
+ }
+ else if (result == 1)
+ {
+ char *cstr = xml_xmlnodetostr(_xpathobj->nodesetval->nodeTab[0],
+ cxt->xmlerrcxt);
+
+ value = InputFunctionCall(&cxt->in_functions[colnum],
+ cstr,
+ cxt->typioparams[colnum],
+ cxt->tupdesc->attrs[colnum]->atttypmod);
+ *isnull = false;
+ }
+ else
+ {
+ StringInfoData str;
+ int i;
+
+ if (cxt->tupdesc->attrs[colnum]->atttypid != XMLOID)
+ elog(ERROR, "too much rows");
+
+ initStringInfo(&str);
+
+ for (i = 0; i < result; i++)
+ {
+ char *cstr;
+
+ cstr = xml_xmlnodetostr(_xpathobj->nodesetval->nodeTab[i],
+ cxt->xmlerrcxt);
+ appendStringInfoString(&str, cstr);
+ }
+
+ value = InputFunctionCall(&cxt->in_functions[colnum],
+ str.data,
+ cxt->typioparams[colnum],
+ cxt->tupdesc->attrs[colnum]->atttypmod);
+ *isnull = false;
+ }
+ }
+ else
+ *isnull = true;
+ }
+ break;
+ case XPATH_STRING:
+ value = InputFunctionCall(&cxt->in_functions[colnum],
+ (char *) _xpathobj->stringval,
+ cxt->typioparams[colnum],
+ cxt->tupdesc->attrs[colnum]->atttypmod);
+ *isnull = false;
+ break;
+ default:
+ elog(ERROR, "unexpected type");
+ return 0;
+ }
+
+ xmlXPathFreeObject(_xpathobj);
+ _xpathobj = NULL;
+ }
+ PG_CATCH();
+ {
+ if (_xpathobj)
+ xmlXPathFreeObject(_xpathobj);
+
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ }
+ else
+ {
+ char *cstr;
+
+ /* second search requires element */
+ if (!cxt->skip_second_search)
+ elog(ERROR, "unexpected result");
+
+ cstr = xml_xmlnodetostr(cur,
+ cxt->xmlerrcxt);
+
+ value = InputFunctionCall(&cxt->in_functions[colnum],
+ cstr,
+ cxt->typioparams[colnum],
+ cxt->tupdesc->attrs[colnum]->atttypmod);
+
+ *isnull = false;
+ }
+ }
+
+ Assert(*isnull || value != (Datum) 0);
+
+ return value;
+
+#else
+ NO_XML_SUPPORT();
+ return (Datum) 0;
+#endif /* not USE_LIBXML */
+
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d179ae..64d6c9d 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -20,6 +20,7 @@
#include "funcapi.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
+#include "parser/parse_type.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
@@ -39,7 +40,9 @@ static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
oidvector *declared_args,
Node *call_expr);
static TypeFuncClass get_type_func_class(Oid typid);
-
+static TypeFuncClass xmltable_get_result_type(XmlTable *expr,
+ Oid *resultTypeId,
+ TupleDesc *resultTupleDesc);
/*
* init_MultiFuncCall
@@ -243,6 +246,10 @@ get_expr_result_type(Node *expr,
NULL,
resultTypeId,
resultTupleDesc);
+ else if (expr && IsA(expr, XmlTable))
+ result = xmltable_get_result_type((XmlTable *) expr,
+ resultTypeId,
+ resultTupleDesc);
else
{
/* handle as a generic expression; no chance to resolve RECORD */
@@ -1398,3 +1405,48 @@ TypeGetTupleDesc(Oid typeoid, List *colaliases)
return tupdesc;
}
+
+static TypeFuncClass
+xmltable_get_result_type(XmlTable *xt,
+ Oid *resultTypeId,
+ TupleDesc *resultTupleDesc)
+{
+ ListCell *l;
+ int i = 0;
+ TupleDesc tupdesc;
+
+ tupdesc = CreateTemplateTupleDesc(list_length(xt->coldeflist), false);
+
+ foreach(l, xt->coldeflist)
+ {
+ ColumnDef *def = (ColumnDef *) lfirst(l);
+ Oid typId;
+ int32 typmod;
+
+ if (def->for_ordinality)
+ {
+ typId = INT8OID;
+ typmod = -1;
+ }
+ else
+ typenameTypeIdAndMod(NULL, def->typeName, &typId, &typmod);
+
+ TupleDescInitEntry(tupdesc, i + 1, def->colname, typId, -1, 0);
+
+ tupdesc->attrs[i]->attnotnull = def->is_not_null;
+
+ i++;
+ }
+
+ if (resultTypeId)
+ *resultTypeId = RECORDOID;
+
+ if (tupdesc->tdtypeid == RECORDOID &&
+ tupdesc->tdtypmod < 0)
+ assign_record_type_typmod(tupdesc);
+ if (resultTupleDesc)
+ *resultTupleDesc = tupdesc;
+
+ return TYPEFUNC_COMPOSITE;
+}
+
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e7fd7bd..90fe9dd 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -959,6 +959,27 @@ typedef struct XmlExprState
} XmlExprState;
/* ----------------
+ * XmlTableState node
+ * ----------------
+ */
+typedef struct XmlTableState
+{
+ ExprState xprstate;
+ List *namespaces; /* list of ResTarget namespaces */
+ ExprState *query_string; /* ExprStates for query string */
+ ExprState *expr; /* ExprStates for XML parameter */
+ int ncolumns; /* number of columns */
+ ExprState **path_exprs; /* ExprStates for column PATH expressions */
+ ExprState **default_exprs; /* ExprStates for defaults */
+ ReturnSetInfo *rsinfo; /* link to outer world */
+ FmgrInfo *in_functions; /* array of input function for each column */
+ Oid *typioparams; /* array of column types for in_function */
+ bool *is_not_nulls; /* allow or disallow null */
+ int ordinality_colnum; /* number of column with ordinary */
+ bool with_auto_col; /* one result column is automatic */
+} XmlTableState;
+
+/* ----------------
* NullTestState node
* ----------------
*/
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6b850e4..d255c4a 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -212,6 +212,7 @@ typedef enum NodeTag
T_CoalesceExprState,
T_MinMaxExprState,
T_XmlExprState,
+ T_XmlTableState,
T_NullTestState,
T_CoerceToDomainState,
T_DomainConstraintState,
@@ -447,6 +448,7 @@ typedef enum NodeTag
T_LockingClause,
T_RowMarkClause,
T_XmlSerialize,
+ T_XmlTable,
T_WithClause,
T_InferClause,
T_OnConflictClause,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1481fff..71f8ff1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -565,6 +565,7 @@ typedef struct RangeTableSample
int location; /* method name location, or -1 if unknown */
} RangeTableSample;
+
/*
* ColumnDef - column definition (used in various creates)
*
@@ -592,9 +593,12 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool for_ordinality; /* column will contains row numbers */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
+ Node *raw_path; /* path value (untransformed parse tree) */
+ Node *cooked_path; /* path value (transformed expr tree *) */
CollateClause *collClause; /* untransformed COLLATE spec, if any */
Oid collOid; /* collation OID (InvalidOid if not set) */
List *constraints; /* other constraints on column */
@@ -697,6 +701,33 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/*
+ * XmlTable - implementation of Xmltable function
+ *
+ */
+typedef enum
+{
+ XMLTABLE_COLUMN_OPT_PATH,
+ XMLTABLE_COLUMN_OPT_DEFAULT
+} XmlTableColOptType;
+
+typedef struct
+{
+ XmlTableColOptType typ;
+ Node *expr;
+} XmlTableColOpt;
+
+typedef struct XmlTable
+{
+ NodeTag type;
+ List *namespaces; /* The list of namespaces - optional */
+ Node *query_string; /* XPath query for row selection */
+ Node *expr; /* XML content */
+ List *coldeflist; /* definition of columns of produced table */
+ bool with_ordinality; /* true when ordinality column is used */
+ bool with_auto_col; /* result column is automatic */
+ int location; /* method name location, or -1 if unknown */
+} XmlTable;
/****************************************************************************
* Nodes for a Query tree
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..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/utils/xml.h b/src/include/utils/xml.h
index 2eab8a5..0cac321 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -45,6 +45,7 @@ typedef enum
/* struct PgXmlErrorContext is private to xml.c */
typedef struct PgXmlErrorContext PgXmlErrorContext;
+typedef struct PgXmlTableContext PgXmlTableContext;
#define DatumGetXmlP(X) ((xmltype *) PG_DETOAST_DATUM(X))
#define XmlPGetDatum(X) PointerGetDatum(X)
@@ -109,4 +110,12 @@ extern int xmlbinary; /* XmlBinaryType, but int for guc enum */
extern int xmloption; /* XmlOptionType, but int for guc enum */
+extern PgXmlTableContext *makeXmlTableCxt(text *xpath_expr_text, xmltype *data, int ncols, char **paths,
+ FmgrInfo *in_functions, Oid *typioparams, TupleDesc tupdesc,
+ int nnamespaces, char **namespaces_uri, char **namespaces_name,
+ bool skip_second_search);
+extern void XmlTableCxtFree(PgXmlTableContext *cxt);
+extern bool XmlTableGetRow(PgXmlTableContext *cxt);
+extern Datum XmlTableGetValue(PgXmlTableContext *cxt, int colnum, bool *isnull);
+
#endif /* XML_H */
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index f21e119..a80725b 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -948,3 +948,98 @@ 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>');
+ERROR: xmltable function is called in unsupported expression context
+-- 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..fb03d8d 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -928,3 +928,98 @@ 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>');
+ERROR: xmltable function is called in unsupported expression context
+-- 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>');
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers