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