diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 1815c84..3b11bc4 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -10327,9 +10327,9 @@ SELECT xml_is_well_formed_document(' @@ -10430,6 +10430,276 @@ SELECT xpath_exists('/my:a/text()', 'test + + + <literal>xmltable</literal> + + + xmltable + + + +xmltable( + xmlnamespaces(namespace uri AS namespace name, ...) + rowexpr PASSING BY REF xml BY REF + COLUMNS name type + PATH columnexpr + DEFAULT expr + NOT NULL | NULL , ... +) + + + + The xmltable produces a table based on the + passed XML value, an xpath filter to extract rows, and an + optional set of column definitions. + + + + For XML document: + + + AU + Australia + 3 + + + CN + China + 3 + + + HK + HongKong + 3 + + + IN + India + 3 + + + JP + Japan + 3Sinzo Abe + + + SG + Singapore + 3791 + + +]]> + + the following query produces the result: + + + + + + The optional xmlnamespaces clause is a comma-separated list of + namespaces of the form namespace-URI AS + namespace-name. It specifies the XML namespaces used in + the document and their aliases. The default namespace is not supported + yet. + +10' + COLUMNS a int PATH 'a'); + + a +---- + 10 +]]> + + + + The required rowexpr argument is an xpath + expression that is evaluated against the supplied XML document to + obtain an ordered sequence of XML nodes. This sequence is what + xmltable transforms into output rows. + + + + + The SQL/XML standard specifies the the + xmltable construct should take an XQuery + expression as the first argument, but PostgreSQL currently only + supports XPath, which is a subset of XQuery. + + + + + The PASSING xml clause provides the + XML document to operate on. + The BY REF clauses have no effect in + PostgreSQL, but are allowed for SQL conformance and compatibility + with other implementations. Per the SQL/XML standard, the first + BY REF is required, the second is optional. + Passing BY VALUE is not supported. Multiple + comma-separated terms in the PASSING clause are not supported. + AS aliases are not supported. The argument must be + a well-formed XML document; fragments/forests are not accepted. + + + + The optional COLUMNS clause specifies the list + of colums in the generated table. If the whole + COLUMNS list is omitted, then the result set is a + single column where each row is a field of xml type + containing the data matched by the rowexpr. + Otherwise each entry describes a single column. See the syntax + summary above for the format. The column name and type are + required; the path and not-null specifier are optional. + 10'); + + xmltable +------------------------------ + 10 +]]> + + + + + Column names and types are identifiers so unlike normal + function arguments they may not be specified as bind parameters, + column references, or expressions. The path and default are + ordinary values. + + + + + The PATH for a column is an xpath expression that is + evaluated for each row, relative to the result of the + rowexpr, to find the value of the column. + If no PATH is given then the column name is used as an + implicit path. + + + + The text body of the XML matched by the PATH expression is + used as the column value. Multiple text() nodes within + an element are concatenated in order. Any child elements, processing + instructions, and comments are ignored, but the text contents of child + elements are concatenated to the result: + a1aa2a bbbbxxxcccc' COLUMNS element text); +element +----------------- + a1aa2a bbbbcccc + (1 row) +]]> + Note that the whitespace-only text() node between two non-text + elements is preserved, and that leading whitespace on a text() + node is not flattened. + + + + A PATH expression that matches multiple top-level elements + results in an error: + 12' COLUMNS element text); +ERROR: more than one value returned by column XPath expression +]]> + This applies to text() nodes too, so avoid using explicit text nodes + in your path expressions unless this behaviour is desired: + a1aa2a' COLUMNS element text PATH 'element/text()'); +ERROR: more than one value returned by column XPath expression +]]> + If the PATH matches an empty tag the result is an empty string + (not NULL). Any xsi:nil attributes are ignored. + + + + The five predefined XML entities 'apos;, + 'quot;, 'amp;, 'lt; + and 'gt; are expanded to their corresponding characters + ', ", &, < + and > on processing. If the output column format is + xml then the characters <, > and + & are converted to XML entities, but " and + ' are left unchanged. This means that 'apos; + and 'quot; entities in XML input get expanded in xml + output columns. There is no way to prevent their expansion. + + + + If the path expression does not match for a given row but a + DEFAULT expression is specified, the resulting + default value is used. If no DEFAULT is given the + field will be NULL. Unlike normal queries, it is possible for + a DEFAULT expression to reference the value of output columns + that appear prior to it in the column-list, so the default of one + column may be based on the value of another column. + + + + Columns may be marked NOT NULL. If the xpath expression + for a NOT NULL column does not match anything and there + is no DEFAULT or the DEFAULT also evaluated + to null then the function terminates with an ERROR. + + + + + Unlike normal PostgreSQL functions, the PATH and + DEFAULT arguments to xmltable are not evaluated + to a simple value before calling the function. PATH + expressions are normally evaluated exactly once per input row + , and DEFAULT expressions each time a default is + needed for a field. If the expression qualifies as stable or immutable + the repeat evaluation may be skipped. Effectively xmltable + behaves more like a subquery than a function call. This means that you + can usefully use volatile functions like nextval in + DEFAULT expressions, your PATH expressions may + depend on other parts of the XML document, etc. + + + + + A column marked with the + FOR ORDINALITY keyword will be populated with + row numbers that match the order in which the the output rows appeared + in the original input XML document. Only one column should be + marked FOR ORDINALITY. + + + + Only XPath query language is supported. PostgreSQL doesn't support XQuery + language. Then the syntax of xmltable doesn't + allow to use XQuery related functionality - the name of xml expression + (clause AS) is not allowed, and only one xml expression + should be passed to xmltable function as parameter. + + diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 19dd0b2..adbe3db 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -43,6 +43,7 @@ #include "catalog/pg_type.h" #include "executor/execdebug.h" #include "executor/nodeSubplan.h" +#include "executor/tableexpr.h" #include "funcapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -185,6 +186,15 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate, ExprContext *econtext, bool *isNull); +static Datum ExecEvalTableExpr(TableExprState *tstate, ExprContext *econtext, + bool *isnull); +static Datum ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext, + bool *isNull); +static Datum tabexprFetchRow(TableExprState *tstate, ExprContext *econtext, + bool *isNull); +static void tabexprInitialize(TableExprState *tstate, ExprContext *econtext, + Datum doc); +static void ShutdownTableExpr(Datum arg); /* ---------------------------------------------------------------- @@ -2033,7 +2043,15 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, else { result = ExecEvalExpr(funcexpr, econtext, &fcinfo.isnull); - rsinfo.isDone = ExprSingleResult; + + /* + * Any other expressions except TableExpr produces single result + * only. + */ + if (funcexpr && IsA(funcexpr, TableExprState)) + rsinfo.isDone = ((TableExprState *) funcexpr)->isDone; + else + rsinfo.isDone = ExprSingleResult; } /* Which protocol does function want to use? */ @@ -4209,6 +4227,349 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, return 0; /* keep compiler quiet */ } +/* ---------------------------------------------------------------- + * ExecEvalTableExpr + * + * Returns a Datum for a table expression (such as XMLTABLE). + * + * Note: ExecEvalTableExpr is executed only the first time through in a + * given plan; it changes the ExprState's function pointer to pass control + * directly to ExecEvalTableExprFast after making one-time initialization. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalTableExpr(TableExprState *tstate, ExprContext *econtext, + bool *isNull) +{ + const TableExprRoutine *routine = tstate->routine; + Datum result; + MemoryContext oldcxt; + Datum value; + TupleDesc tupdesc = tstate->resultSlot->tts_tupleDescriptor; + int natts = tupdesc->natts; + int i; + + Assert(tstate->opaque == NULL); + + /* + * The first time around, create the table builder context and initialize + * it with the document content. + */ + + /* Fill in table builder opaque area */ + oldcxt = MemoryContextSwitchTo(tstate->buildercxt); + routine->InitBuilder(tstate); + MemoryContextSwitchTo(oldcxt); + + /* Register shutdown callback to clean up tableexpr builder state */ + RegisterExprContextCallback(econtext, ShutdownTableExpr, + PointerGetDatum(tstate)); + + /* + * If evaluating the document expression returns NULL, the table + * expression is empty and we return immediately. + */ + value = ExecEvalExpr(tstate->docexpr, econtext, isNull); + if (*isNull) + { + tstate->isDone = ExprEndResult; + return (Datum) 0; + } + + /* otherwise, pass the document value to the table builder */ + tabexprInitialize(tstate, econtext, value); + + /* + * Fill in the necessary fmgr infos. + */ + for (i = 0; i < natts; i++) + { + Oid in_funcid; + + getTypeInputInfo(tupdesc->attrs[i]->atttypid, + &in_funcid, &tstate->typioparams[i]); + fmgr_info(in_funcid, &tstate->in_functions[i]); + } + + /* skip all of the above on future executions of node */ + tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExprFast; + + /* Fetch and return one row per call from the table builder */ + PG_TRY(); + { + result = tabexprFetchRow(tstate, econtext, isNull); + } + PG_CATCH(); + { + ShutdownTableExpr(PointerGetDatum(tstate)); + PG_RE_THROW(); + } + PG_END_TRY(); + + return result; +} + +/* ---------------------------------------------------------------- + * ExecEvalTableExpr + * + * Returns a Datum for a table expression (such as XMLTABLE). + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalTableExprFast(TableExprState *exprstate, ExprContext *econtext, + bool *isNull) +{ + TableExprState *tstate = (TableExprState *) exprstate; + Datum result; + + /* Fetch and return one row per call from the table builder */ + PG_TRY(); + { + result = tabexprFetchRow(tstate, econtext, isNull); + } + PG_CATCH(); + { + ShutdownTableExpr(PointerGetDatum(tstate)); + PG_RE_THROW(); + } + PG_END_TRY(); + + return result; +} + +/* + * callback function in case a TableExpr needs to be shut down before + * it has been run to completion. + */ +static void +ShutdownTableExpr(Datum arg) +{ + TableExprState *tstate = (TableExprState *) arg; + const TableExprRoutine *routine = tstate->routine; + + if (tstate->opaque != NULL) + { + routine->DestroyBuilder(tstate); + ExecDropSingleTupleTableSlot(tstate->resultSlot); + MemoryContextDelete(tstate->buildercxt); + } +} + +/* ---------------------------------------------------------------- + * ExecMakeTableExprResultSet + * ---------------------------------------------------------------- + */ +Datum +ExecMakeTableExprResultSet(TableExprState *texpr, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + Datum result; + + result = ExecEvalExpr((ExprState *) texpr, + econtext, + isNull); + *isDone = texpr->isDone; + + return result; +} + +/* + * Fill in namespace declarations, the row filter, and column filters in a + * table expression builder context. + */ +static void +tabexprInitialize(TableExprState *tstate, ExprContext *econtext, Datum doc) +{ + const TableExprRoutine *routine; + TupleDesc tupdesc; + MemoryContext oldcxt; + ListCell *lc1, + *lc2; + bool isnull; + int colno; + Datum value; + + routine = tstate->routine; + + /* + * The content can be bigger document and transformation to cstring can be + * expensive. The table builder is better place for this task - pass value + * as Datum. Evaluate builder function in special memory context + */ + oldcxt = MemoryContextSwitchTo(tstate->buildercxt); + routine->SetDoc(tstate, doc); + MemoryContextSwitchTo(oldcxt); + + /* Evaluate namespace specifications */ + forboth(lc1, tstate->ns_uris, lc2, tstate->ns_names) + { + ExprState *expr = (ExprState *) lfirst(lc1); + char *ns_name = strVal(lfirst(lc2)); + char *ns_uri; + + value = ExecEvalExpr((ExprState *) expr, econtext, &isnull); + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("namespace URI must not be null"))); + ns_uri = TextDatumGetCString(value); + + MemoryContextSwitchTo(tstate->buildercxt); + routine->SetNamespace(tstate, ns_name, ns_uri); + MemoryContextSwitchTo(oldcxt); + } + + /* Install the row filter expression into the table builder context */ + value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull); + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("row filter expression must not be null"))); + + MemoryContextSwitchTo(tstate->buildercxt); + routine->SetRowFilter(tstate, TextDatumGetCString(value)); + MemoryContextSwitchTo(oldcxt); + + /* + * Install the column filter expressions into the table builder context. + * If an expression is given, use that; otherwise the column name itself + * is the column filter. + */ + colno = 0; + tupdesc = tstate->resultSlot->tts_tupleDescriptor; + foreach(lc1, tstate->colexprs) + { + char *colfilter; + + if (tstate->evalcols && colno != tstate->ordinalitycol) + { + ExprState *colexpr = lfirst(lc1); + + if (colexpr != NULL) + { + value = ExecEvalExpr(colexpr, econtext, &isnull); + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("column filter expression must not be null"), + errdetail("Filter for column \"%s\" is null.", + NameStr(tupdesc->attrs[colno]->attname)))); + colfilter = TextDatumGetCString(value); + } + else + colfilter = NameStr(tupdesc->attrs[colno]->attname); + } + else + colfilter = NULL; + + MemoryContextSwitchTo(tstate->buildercxt); + routine->SetColumnFilter(tstate, colfilter, colno); + MemoryContextSwitchTo(oldcxt); + + colno++; + } +} + +/* + * Fetch one more row from a TableExpr table builder; if one can be obtained, + * push the values for each column onto the output. + */ +static Datum +tabexprFetchRow(TableExprState *tstate, ExprContext *econtext, + bool *isNull) +{ + const TableExprRoutine *routine = tstate->routine; + MemoryContext oldcxt; + bool gotrow; + Datum result; + + /* Fetch a row */ + oldcxt = MemoryContextSwitchTo(tstate->buildercxt); + gotrow = routine->FetchRow(tstate); + MemoryContextSwitchTo(oldcxt); + + if (gotrow) + { + TupleDesc tupdesc = tstate->resultSlot->tts_tupleDescriptor; + Datum *values = tstate->resultSlot->tts_values; + bool *nulls = tstate->resultSlot->tts_isnull; + ListCell *cell = list_head(tstate->coldefexprs); + int natts = tupdesc->natts; + int colno = 0; + bool isnull; + + ExecClearTuple(tstate->resultSlot); + + /* + * Obtain the value of each column for this row, installing it into + * the values/isnull arrays. + */ + for (colno = 0; colno < natts; colno++) + { + if (colno == tstate->ordinalitycol) + { + /* fast path when column is ordinality */ + values[colno] = Int32GetDatum(tstate->rownum++); + nulls[colno] = false; + } + else + { + /* slow path: fetch value from builder */ + MemoryContextSwitchTo(tstate->perValueCxt); + values[colno] = routine->GetValue(tstate, + tstate->evalcols ? colno : -1, + &isnull); + MemoryContextSwitchTo(oldcxt); + + /* No value? Evaluate and apply the default, if any */ + if (isnull && cell != NULL) + { + ExprState *coldefexpr = (ExprState *) lfirst(cell); + + if (coldefexpr != NULL) + values[colno] = ExecEvalExpr(coldefexpr, econtext, + &isnull); + } + + /* Verify a possible NOT NULL constraint */ + if (isnull && bms_is_member(colno, + ((TableExpr *) tstate->xprstate.expr)->notnulls)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null is not allowed in column \"%s\"", + NameStr(tupdesc->attrs[colno]->attname)))); + + nulls[colno] = isnull; + } + + /* advance list of default expressions */ + if (cell != NULL) + cell = lnext(cell); + } + + ExecStoreVirtualTuple(tstate->resultSlot); + + *isNull = false; + tstate->isDone = ExprMultipleResult; + + result = ExecFetchSlotTupleDatum(tstate->resultSlot); + + /* reset one row life context */ + MemoryContextReset(tstate->perValueCxt); + } + else + { + /* no more rows */ + *isNull = true; + tstate->isDone = ExprEndResult; + + result = (Datum) 0; + } + + return result; +} /* * ExecEvalExprSwitchContext @@ -4953,6 +5314,53 @@ ExecInitExpr(Expr *node, PlanState *parent) state = makeNode(ExprState); state->evalfunc = ExecEvalCurrentOfExpr; break; + case T_TableExpr: + { + TableExpr *te = (TableExpr *) node; + TableExprState *tstate; + TypeFuncClass functypclass; + TupleDesc tupdesc; + int natts; + + functypclass = get_expr_result_type((Node *) node, NULL, &tupdesc); + Assert(functypclass == TYPEFUNC_COMPOSITE); + Assert(tupdesc->natts == te->colcount); + natts = tupdesc->natts; + + tstate = makeNode(TableExprState); + tstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalTableExpr; + + /* Only XmlTableBuilder is supported currently */ + tstate->routine = &XmlTableExprRoutine; + tstate->buildercxt = + AllocSetContextCreate(CurrentMemoryContext, + "TableExpr builder context", + ALLOCSET_DEFAULT_SIZES); + tstate->perValueCxt = + AllocSetContextCreate(tstate->buildercxt, + "TableExpr per value context", + ALLOCSET_DEFAULT_SIZES); + tstate->opaque = NULL; /* initialized at runtime */ + tstate->resultSlot = MakeSingleTupleTableSlot(tupdesc); + tstate->ns_names = te->ns_names; + tstate->ns_uris = (List *) + ExecInitExpr((Expr *) te->ns_uris, parent); + /* these are allocated now and initialized later */ + tstate->in_functions = palloc(sizeof(FmgrInfo) * natts); + tstate->typioparams = palloc(sizeof(Oid) * natts); + tstate->evalcols = te->evalcols; + tstate->ordinalitycol = te->ordinalitycol; + tstate->rownum = 1; + tstate->docexpr = ExecInitExpr((Expr *) te->docexpr, parent); + tstate->rowexpr = ExecInitExpr((Expr *) te->rowexpr, parent); + tstate->colexprs = (List *) + ExecInitExpr((Expr *) te->colexprs, parent); + tstate->coldefexprs = (List *) + ExecInitExpr((Expr *) te->coldefexprs, parent); + + state = (ExprState *) tstate; + } + break; case T_TargetEntry: { TargetEntry *tle = (TargetEntry *) node; diff --git a/src/backend/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c index eae0f1d..a2da042 100644 --- a/src/backend/executor/nodeProjectSet.c +++ b/src/backend/executor/nodeProjectSet.c @@ -166,6 +166,21 @@ ExecProjectSRF(ProjectSetState *node, bool continuing) node->pending_srf_tuples = true; hassrf = true; } + else if (IsA(gstate->arg, TableExprState)) + { + /* + * Evaluate TableExpr - possibly continuing previously started + * output. + */ + *result = ExecMakeTableExprResultSet((TableExprState *) gstate->arg, + econtext, isnull, isdone); + + if (*isdone != ExprEndResult) + hasresult = true; + if (*isdone == ExprMultipleResult) + node->pending_srf_tuples = true; + hassrf = true; + } else { /* Non-SRF tlist expression, just evaluate normally. */ diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 30d733e..d1c6f8b 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1445,6 +1445,33 @@ _copyScalarArrayOpExpr(const ScalarArrayOpExpr *from) } /* + * _copyTableExpr + */ +static TableExpr * +_copyTableExpr(const TableExpr *from) +{ + TableExpr *newnode = makeNode(TableExpr); + + COPY_NODE_FIELD(ns_names); + COPY_NODE_FIELD(ns_uris); + COPY_NODE_FIELD(docexpr); + COPY_NODE_FIELD(rowexpr); + COPY_SCALAR_FIELD(colcount); + COPY_NODE_FIELD(colnames); + COPY_NODE_FIELD(coltypes); + COPY_NODE_FIELD(coltypmods); + COPY_NODE_FIELD(colcollations); + COPY_NODE_FIELD(colexprs); + COPY_NODE_FIELD(coldefexprs); + COPY_BITMAPSET_FIELD(notnulls); + COPY_SCALAR_FIELD(ordinalitycol); + COPY_SCALAR_FIELD(evalcols); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* * _copyBoolExpr */ static BoolExpr * @@ -2964,6 +2991,36 @@ _copyFuncWithArgs(const FuncWithArgs *from) return newnode; } +static TableExprFunc * +_copyTableExprFunc(const TableExprFunc *from) +{ + TableExprFunc *newnode = makeNode(TableExprFunc); + + COPY_NODE_FIELD(docexpr); + COPY_NODE_FIELD(rowexpr); + COPY_NODE_FIELD(namespaces); + COPY_NODE_FIELD(columns); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +static TableExprFuncCol * +_copyTableExprFuncCol(const TableExprFuncCol *from) +{ + TableExprFuncCol *newnode = makeNode(TableExprFuncCol); + + COPY_STRING_FIELD(colname); + COPY_SCALAR_FIELD(for_ordinality); + COPY_NODE_FIELD(typeName); + COPY_SCALAR_FIELD(notnull); + COPY_NODE_FIELD(colexpr); + COPY_NODE_FIELD(coldefexpr); + COPY_LOCATION_FIELD(location); + + return newnode; +} + static AccessPriv * _copyAccessPriv(const AccessPriv *from) { @@ -4666,6 +4723,9 @@ copyObject(const void *from) case T_ScalarArrayOpExpr: retval = _copyScalarArrayOpExpr(from); break; + case T_TableExpr: + retval = _copyTableExpr(from); + break; case T_BoolExpr: retval = _copyBoolExpr(from); break; @@ -5275,6 +5335,12 @@ copyObject(const void *from) case T_FuncWithArgs: retval = _copyFuncWithArgs(from); break; + case T_TableExprFunc: + retval = _copyTableExprFunc(from); + break; + case T_TableExprFuncCol: + retval = _copyTableExprFuncCol(from); + break; case T_AccessPriv: retval = _copyAccessPriv(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 55c73b7..3a6b17f 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -388,6 +388,28 @@ _equalScalarArrayOpExpr(const ScalarArrayOpExpr *a, const ScalarArrayOpExpr *b) } static bool +_equalTableExpr(const TableExpr *a, const TableExpr *b) +{ + COMPARE_NODE_FIELD(ns_names); + COMPARE_NODE_FIELD(ns_uris); + COMPARE_NODE_FIELD(docexpr); + COMPARE_NODE_FIELD(rowexpr); + COMPARE_SCALAR_FIELD(colcount); + COMPARE_NODE_FIELD(colnames); + COMPARE_NODE_FIELD(coltypes); + COMPARE_NODE_FIELD(coltypes); + COMPARE_NODE_FIELD(colcollations); + COMPARE_NODE_FIELD(colexprs); + COMPARE_NODE_FIELD(coldefexprs); + COMPARE_BITMAPSET_FIELD(notnulls); + COMPARE_SCALAR_FIELD(ordinalitycol); + COMPARE_SCALAR_FIELD(evalcols); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool _equalBoolExpr(const BoolExpr *a, const BoolExpr *b) { COMPARE_SCALAR_FIELD(boolop); @@ -2688,6 +2710,32 @@ _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b) } static bool +_equalTableExprFunc(const TableExprFunc *a, const TableExprFunc *b) +{ + COMPARE_NODE_FIELD(docexpr); + COMPARE_NODE_FIELD(rowexpr); + COMPARE_NODE_FIELD(namespaces); + COMPARE_NODE_FIELD(columns); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalTableExprFuncCol(const TableExprFuncCol *a, const TableExprFuncCol *b) +{ + COMPARE_STRING_FIELD(colname); + COMPARE_SCALAR_FIELD(for_ordinality); + COMPARE_NODE_FIELD(typeName); + COMPARE_SCALAR_FIELD(notnull); + COMPARE_NODE_FIELD(colexpr); + COMPARE_NODE_FIELD(coldefexpr); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b) { COMPARE_SCALAR_FIELD(xmloption); @@ -2937,6 +2985,9 @@ equal(const void *a, const void *b) case T_ScalarArrayOpExpr: retval = _equalScalarArrayOpExpr(a, b); break; + case T_TableExpr: + retval = _equalTableExpr(a, b); + break; case T_BoolExpr: retval = _equalBoolExpr(a, b); break; @@ -3533,6 +3584,12 @@ equal(const void *a, const void *b) case T_FuncWithArgs: retval = _equalFuncWithArgs(a, b); break; + case T_TableExprFunc: + retval = _equalTableExprFunc(a, b); + break; + case T_TableExprFuncCol: + retval = _equalTableExprFuncCol(a, b); + break; case T_AccessPriv: retval = _equalAccessPriv(a, b); break; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index c2f03b7..4355519 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -95,6 +95,10 @@ exprType(const Node *expr) case T_ScalarArrayOpExpr: type = BOOLOID; break; + case T_TableExpr: + /* result is record OID always */ + type = RECORDOID; + break; case T_BoolExpr: type = BOOLOID; break; @@ -694,6 +698,9 @@ expression_returns_set_walker(Node *node, void *context) /* else fall through to check args */ } + if (IsA(node, TableExpr)) + return true; + /* Avoid recursion for some cases that can't return a set */ if (IsA(node, Aggref)) return false; @@ -793,6 +800,9 @@ exprCollation(const Node *expr) case T_ScalarArrayOpExpr: coll = InvalidOid; /* result is always boolean */ break; + case T_TableExpr: + coll = InvalidOid; /* result is composite, XML or JSON */ + break; case T_BoolExpr: coll = InvalidOid; /* result is always boolean */ break; @@ -1035,6 +1045,10 @@ exprSetCollation(Node *expr, Oid collation) case T_ScalarArrayOpExpr: Assert(!OidIsValid(collation)); /* result is always boolean */ break; + case T_TableExpr: + Assert(!OidIsValid(collation)); /* result is always composite, + * XML or JSON */ + break; case T_BoolExpr: Assert(!OidIsValid(collation)); /* result is always boolean */ break; @@ -1279,6 +1293,9 @@ exprLocation(const Node *expr) exprLocation((Node *) saopexpr->args)); } break; + case T_TableExpr: + loc = ((const TableExpr *) expr)->location; + break; case T_BoolExpr: { const BoolExpr *bexpr = (const BoolExpr *) expr; @@ -1558,6 +1575,9 @@ exprLocation(const Node *expr) case T_PartitionRangeDatum: loc = ((const PartitionRangeDatum *) expr)->location; break; + case T_TableExprFunc: + loc = ((const TableExprFunc *) expr)->location; + break; default: /* for any other node type it's just unknown... */ loc = -1; @@ -1981,6 +2001,22 @@ expression_tree_walker(Node *node, return true; } break; + case T_TableExpr: + { + TableExpr *te = (TableExpr *) node; + + if (walker(te->ns_uris, context)) + return true; + if (walker(te->docexpr, context)) + return true; + if (walker(te->rowexpr, context)) + return true; + if (walker(te->colexprs, context)) + return true; + if (walker(te->coldefexprs, context)) + return true; + } + break; case T_BoolExpr: { BoolExpr *expr = (BoolExpr *) node; @@ -2598,6 +2634,20 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_TableExpr: + { + TableExpr *te = (TableExpr *) node; + TableExpr *newnode; + + FLATCOPY(newnode, te, TableExpr); + MUTATE(newnode->ns_uris, te->ns_uris, List *); + MUTATE(newnode->docexpr, te->docexpr, Node *); + MUTATE(newnode->rowexpr, te->rowexpr, Node *); + MUTATE(newnode->colexprs, te->colexprs, List *); + MUTATE(newnode->coldefexprs, te->coldefexprs, List *); + return (Node *) newnode; + } + break; case T_BoolExpr: { BoolExpr *expr = (BoolExpr *) node; @@ -3628,6 +3678,30 @@ raw_expression_tree_walker(Node *node, break; case T_CommonTableExpr: return walker(((CommonTableExpr *) node)->ctequery, context); + case T_TableExprFunc: + { + TableExprFunc *te = (TableExprFunc *) node; + + if (walker(te->docexpr, context)) + return true; + if (walker(te->rowexpr, context)) + return true; + if (walker(te->namespaces, context)) + return true; + if (walker(te->columns, context)) + return true; + } + break; + case T_TableExprFuncCol: + { + TableExprFuncCol *terc = (TableExprFuncCol *) node; + + if (walker(terc->colexpr, context)) + return true; + if (walker(terc->coldefexpr, 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 1560ac3..3630c63 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1172,6 +1172,28 @@ _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node) } static void +_outTableExpr(StringInfo str, const TableExpr *node) +{ + WRITE_NODE_TYPE("TABLEEXPR"); + + WRITE_NODE_FIELD(ns_names); + WRITE_NODE_FIELD(ns_uris); + WRITE_NODE_FIELD(docexpr); + WRITE_NODE_FIELD(rowexpr); + WRITE_INT_FIELD(colcount); + WRITE_NODE_FIELD(colnames); + WRITE_NODE_FIELD(coltypes); + WRITE_NODE_FIELD(coltypmods); + WRITE_NODE_FIELD(colcollations); + WRITE_NODE_FIELD(colexprs); + WRITE_NODE_FIELD(coldefexprs); + WRITE_BITMAPSET_FIELD(notnulls); + WRITE_INT_FIELD(ordinalitycol); + WRITE_BOOL_FIELD(evalcols); + WRITE_LOCATION_FIELD(location); +} + +static void _outBoolExpr(StringInfo str, const BoolExpr *node) { char *opstr = NULL; @@ -3352,6 +3374,32 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node) WRITE_NODE_FIELD(value); } +static void +_outTableExprFunc(StringInfo str, const TableExprFunc *node) +{ + WRITE_NODE_TYPE("TABLEXPRFUNC"); + + WRITE_NODE_FIELD(docexpr); + WRITE_NODE_FIELD(rowexpr); + WRITE_NODE_FIELD(namespaces); + WRITE_NODE_FIELD(columns); + WRITE_LOCATION_FIELD(location); +} + +static void +_outTableExprFuncCol(StringInfo str, const TableExprFuncCol *node) +{ + WRITE_NODE_TYPE("TABLEEXPRFUNCCOL"); + + WRITE_STRING_FIELD(colname); + WRITE_BOOL_FIELD(for_ordinality); + WRITE_NODE_FIELD(typeName); + WRITE_BOOL_FIELD(notnull); + WRITE_NODE_FIELD(colexpr); + WRITE_NODE_FIELD(coldefexpr); + WRITE_LOCATION_FIELD(location); +} + /* * outNode - * converts a Node into ascii string and append it to 'str' @@ -3553,6 +3601,9 @@ outNode(StringInfo str, const void *obj) case T_ScalarArrayOpExpr: _outScalarArrayOpExpr(str, obj); break; + case T_TableExpr: + _outTableExpr(str, obj); + break; case T_BoolExpr: _outBoolExpr(str, obj); break; @@ -3957,6 +4008,12 @@ outNode(StringInfo str, const void *obj) case T_PartitionRangeDatum: _outPartitionRangeDatum(str, obj); break; + case T_TableExprFunc: + _outTableExprFunc(str, obj); + break; + case T_TableExprFuncCol: + _outTableExprFuncCol(str, obj); + break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index dcfa6ee..745921b 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -744,6 +744,33 @@ _readScalarArrayOpExpr(void) } /* + * _readTableExprNode + */ +static TableExpr * +_readTableExprNode(void) +{ + READ_LOCALS(TableExpr); + + READ_NODE_FIELD(ns_names); + READ_NODE_FIELD(ns_uris); + READ_NODE_FIELD(docexpr); + READ_NODE_FIELD(rowexpr); + READ_INT_FIELD(colcount); + READ_NODE_FIELD(colnames); + READ_NODE_FIELD(coltypes); + READ_NODE_FIELD(coltypmods); + READ_NODE_FIELD(colcollations); + READ_NODE_FIELD(colexprs); + READ_NODE_FIELD(coldefexprs); + READ_BITMAPSET_FIELD(notnulls); + READ_INT_FIELD(ordinalitycol); + READ_BOOL_FIELD(evalcols); + READ_LOCATION_FIELD(location); + + READ_DONE(); +} + +/* * _readBoolExpr */ static BoolExpr * @@ -2383,6 +2410,8 @@ parseNodeString(void) return_value = _readNullIfExpr(); else if (MATCH("SCALARARRAYOPEXPR", 17)) return_value = _readScalarArrayOpExpr(); + else if (MATCH("TABLEEXPR", 9)) + return_value = _readTableExprNode(); else if (MATCH("BOOLEXPR", 8)) return_value = _readBoolExpr(); else if (MATCH("SUBLINK", 7)) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 4b5902f..9a756a4 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -5194,6 +5194,8 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel, PathTarget *thistarget = (PathTarget *) lfirst(lc1); bool contains_srfs = (bool) lfirst_int(lc2); + + /* If this level doesn't contain SRFs, do regular projection */ if (contains_srfs) newpath = (Path *) create_set_projection_path(root, diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index d589dc2..17f9fdb 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -819,6 +819,11 @@ expression_returns_set_rows(Node *clause) return clamp_row_est(get_func_rows(expr->opfuncid)); } } + if (IsA(clause, TableExpr)) + { + /* What is adequate estimation 10, 100, 1000? */ + return 100.0; + } return 1.0; } diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c index cca5db8..dd6d6f8 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -877,6 +877,13 @@ split_pathtarget_at_srfs(PlannerInfo *root, split_pathtarget_walker((Node *) ((OpExpr *) node)->args, &context); } + else if (IsA(node, TableExpr)) + { + /* Same as above, but for table expressions like XMLTABLE */ + target_contains_srfs = true; + split_pathtarget_walker((Node *) ((OpExpr *) node)->args, + &context); + } else { /* Not a top-level SRF, so recursively examine expression */ @@ -937,7 +944,8 @@ split_pathtarget_walker(Node *node, split_pathtarget_context *context) else if ((IsA(node, FuncExpr) && ((FuncExpr *) node)->funcretset) || (IsA(node, OpExpr) && - ((OpExpr *) node)->opretset)) + ((OpExpr *) node)->opretset) || + IsA(node, TableExpr)) { /* * Pass SRFs down to the child plan level for evaluation, and mark diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a8e35fe..b4eeb68 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -549,6 +549,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type xmlexists_argument %type document_or_content %type xml_whitespace_option +%type xmltable_column_list xmltable_column_opt_list +%type xmltable_column_el +%type xmltable_column_opt_el +%type xml_namespace_list +%type xml_namespace_el %type func_application func_expr_common_subexpr %type func_expr func_expr_windowless @@ -606,10 +611,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 @@ -680,8 +685,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 @@ -12623,6 +12628,7 @@ a_expr: c_expr { $$ = $1; } } ; + /* * Restricted expressions * @@ -13190,6 +13196,48 @@ func_expr_common_subexpr: n->location = @1; $$ = (Node *)n; } + | XMLTABLE '(' c_expr xmlexists_argument ')' + { + TableExprFunc *n = makeNode(TableExprFunc); + n->rowexpr = $3; + n->docexpr = $4; + n->columns = NIL; + n->namespaces = NIL; + n->location = @1; + $$ = (Node *)n; + } + | XMLTABLE '(' c_expr xmlexists_argument COLUMNS xmltable_column_list ')' + { + TableExprFunc *n = makeNode(TableExprFunc); + n->rowexpr = $3; + n->docexpr = $4; + n->columns = $6; + n->namespaces = NIL; + n->location = @1; + $$ = (Node *)n; + } + | XMLTABLE '(' XMLNAMESPACES '(' xml_namespace_list ')' ',' + c_expr xmlexists_argument ')' + { + TableExprFunc *n = makeNode(TableExprFunc); + n->rowexpr = $8; + n->docexpr = $9; + n->columns = NIL; + n->namespaces = $5; + n->location = @1; + $$ = (Node *)n; + } + | XMLTABLE '(' XMLNAMESPACES '(' xml_namespace_list ')' ',' + c_expr xmlexists_argument COLUMNS xmltable_column_list ')' + { + TableExprFunc *n = makeNode(TableExprFunc); + n->rowexpr = $8; + n->docexpr = $9; + n->columns = $11; + n->namespaces = $5; + n->location = @1; + $$ = (Node *)n; + } ; /* @@ -13265,6 +13313,132 @@ xmlexists_argument: } ; +xmltable_column_list: xmltable_column_el { $$ = list_make1($1); } + | xmltable_column_list ',' xmltable_column_el { $$ = lappend($1, $3); } + ; + +xmltable_column_el: + ColId Typename + { + TableExprFuncCol *fc = makeNode(TableExprFuncCol); + + fc->colname = $1; + fc->for_ordinality = false; + fc->typeName = $2; + fc->notnull = false; + fc->colexpr = NULL; + fc->coldefexpr = NULL; + fc->location = @1; + + $$ = (Node *) fc; + } + | ColId Typename xmltable_column_opt_list + { + TableExprFuncCol *fc = makeNode(TableExprFuncCol); + ListCell *option; + bool nullability_seen = false; + + fc->colname = $1; + fc->for_ordinality = false; + fc->typeName = $2; + fc->notnull = false; + fc->colexpr = NULL; + fc->coldefexpr = NULL; + fc->location = @1; + + foreach(option, $3) + { + DefElem *defel = (DefElem *) lfirst(option); + + if (strcmp(defel->defname, "default") == 0) + { + if (fc->coldefexpr != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only one DEFAULT value is allowed"), + parser_errposition(defel->location))); + fc->coldefexpr = defel->arg; + } + else if (strcmp(defel->defname, "path") == 0) + { + if (fc->colexpr != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only one PATH value per column is allowed"), + parser_errposition(defel->location))); + fc->colexpr = defel->arg; + } + else if (strcmp(defel->defname, "notnull") == 0) + { + if (nullability_seen) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant NULL / NOT NULL declarations for column \"%s\"", fc->colname), + parser_errposition(defel->location))); + fc->notnull = intVal(defel->arg); + nullability_seen = true; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized column option \"%s\"", + defel->defname), + parser_errposition(defel->location))); + } + } + $$ = (Node *) fc; + } + | ColId FOR ORDINALITY + { + TableExprFuncCol *fc = makeNode(TableExprFuncCol); + + fc->colname = $1; + fc->for_ordinality = true; + /* other fields are ignored, initialized by makeNode */ + fc->location = @1; + + $$ = (Node *) fc; + } + ; + +xmltable_column_opt_list: xmltable_column_opt_el { $$ = list_make1($1); } + | xmltable_column_opt_list xmltable_column_opt_el { $$ = lappend($1, $2); } + ; + +xmltable_column_opt_el: + IDENT b_expr + { $$ = makeDefElem($1, $2, @1); } + | DEFAULT b_expr + { $$ = makeDefElem("default", $2, @1); } + | NOT NULL_P + { $$ = makeDefElem("notnull", (Node *) makeInteger(true), @1); } + | NULL_P + { $$ = makeDefElem("notnull", (Node *) makeInteger(false), @1); } + ; + +xml_namespace_list: xml_namespace_el { $$ = list_make1($1); } + | xml_namespace_list ',' xml_namespace_el { $$ = lappend($1, $3); } + ; + +xml_namespace_el: + b_expr AS ColLabel + { + $$ = makeNode(ResTarget); + $$->name = $3; + $$->indirection = NIL; + $$->val = $1; + $$->location = @1; + } + | DEFAULT b_expr + { + $$ = makeNode(ResTarget); + $$->name = NULL; + $$->indirection = NIL; + $$->val = $2; + $$->location = @1; + } + ; /* * Aggregate decoration clauses @@ -14299,6 +14473,7 @@ unreserved_keyword: | CLASS | CLOSE | CLUSTER + | COLUMNS | COMMENT | COMMENTS | COMMIT @@ -14604,10 +14779,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 2a2ac32..2c3f3cd 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -1096,9 +1096,9 @@ coerce_to_boolean(ParseState *pstate, Node *node, } /* - * coerce_to_specific_type() - * Coerce an argument of a construct that requires a specific data type. - * Also check that input is not a set. + * coerce_to_specific_type_typmod() + * Coerce an argument of a construct that requires a specific data type, + * with a specific typmod. Also check that input is not a set. * * Returns the possibly-transformed node tree. * @@ -1106,9 +1106,9 @@ coerce_to_boolean(ParseState *pstate, Node *node, * processing is wanted. */ Node * -coerce_to_specific_type(ParseState *pstate, Node *node, - Oid targetTypeId, - const char *constructName) +coerce_to_specific_type_typmod(ParseState *pstate, Node *node, + Oid targetTypeId, int32 targetTypmod, + const char *constructName) { Oid inputTypeId = exprType(node); @@ -1117,7 +1117,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); @@ -1144,6 +1144,25 @@ coerce_to_specific_type(ParseState *pstate, Node *node, return node; } +/* + * coerce_to_specific_type() + * Coerce an argument of a construct that requires a specific data type. + * Also check that input is not a set. + * + * Returns the possibly-transformed node tree. + * + * As with coerce_type, pstate may be NULL if no special unknown-Param + * processing is wanted. + */ +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 add3be6..6fc765a 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -15,6 +15,7 @@ #include "postgres.h" +#include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "miscadmin.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 *transformTableExprFunc(ParseState *pstate, TableExprFunc * 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, @@ -376,6 +378,10 @@ transformExprRecurse(ParseState *pstate, Node *expr) break; } + case T_TableExprFunc: + result = transformTableExprFunc(pstate, (TableExprFunc *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -2747,6 +2753,217 @@ transformCollateClause(ParseState *pstate, CollateClause *c) } /* + * Transform a table expression - TableExprFunc is raw form of TableExpr. + * + * Transform the namespace clauses, the document-generating expression, the + * row-generating expression, the column-generating expressions, and the + * default value expressions. + */ +static Node * +transformTableExprFunc(ParseState *pstate, TableExprFunc * t) +{ + TableExpr *newt = makeNode(TableExpr); + const char *constructName; + Oid docType; + + /* Currently only XMLTABLE is supported */ + constructName = "XMLTABLE"; + docType = XMLOID; + + /* TableExpr is SRF, check target SRF usage */ + check_srf_call_placement(pstate, t->location); + + /* Transform and apply typecast to the row-generating expression ... */ + Assert(t->rowexpr != NULL); + newt->rowexpr = coerce_to_specific_type(pstate, + transformExprRecurse(pstate, t->rowexpr), + TEXTOID, + constructName); + /* ... and to the document itself */ + Assert(t->docexpr != NULL); + newt->docexpr = coerce_to_specific_type(pstate, + transformExprRecurse(pstate, t->docexpr), + docType, + constructName); + + /* undef ordinality column number */ + newt->ordinalitycol = -1; + + /* Columns, if any, also need to be transformed */ + if (t->columns != NIL) + { + ListCell *col; + char **names; + int i = 0; + + newt->evalcols = true; + + newt->colcount = list_length(t->columns); + names = palloc(sizeof(char *) * newt->colcount); + + foreach(col, t->columns) + { + TableExprFuncCol *rawc = (TableExprFuncCol *) lfirst(col); + Oid typid; + int32 typmod; + Node *colexpr; + Node *coldefexpr; + int j; + + newt->colnames = lappend(newt->colnames, + makeString(pstrdup(rawc->colname))); + + /* + * Determine the type and typmod for the new column. FOR + * ORDINALITY columns are INTEGER per spec; the others are + * user-specified. + */ + if (rawc->for_ordinality) + { + if (newt->ordinalitycol != -1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only one FOR ORDINALITY column is allowed"), + parser_errposition(pstate, rawc->location))); + + typid = INT4OID; + typmod = -1; + newt->ordinalitycol = i; + } + else + { + if (rawc->typeName->setof) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("column \"%s\" cannot be declared SETOF", + rawc->colname), + parser_errposition(pstate, rawc->location))); + + typenameTypeIdAndMod(pstate, rawc->typeName, + &typid, &typmod); + } + + newt->coltypes = lappend_oid(newt->coltypes, typid); + newt->coltypmods = lappend_int(newt->coltypmods, typmod); + newt->colcollations = lappend_oid(newt->colcollations, + type_is_collatable(typid) ? DEFAULT_COLLATION_OID : InvalidOid); + + /* Transform the PATH and DEFAULT expressions */ + if (rawc->colexpr) + colexpr = coerce_to_specific_type(pstate, + transformExprRecurse(pstate, rawc->colexpr), + TEXTOID, + constructName); + else + colexpr = NULL; + + if (rawc->coldefexpr) + coldefexpr = coerce_to_specific_type_typmod(pstate, + transformExprRecurse(pstate, rawc->coldefexpr), + typid, typmod, + constructName); + else + coldefexpr = NULL; + + newt->colexprs = lappend(newt->colexprs, colexpr); + newt->coldefexprs = lappend(newt->coldefexprs, coldefexpr); + + if (rawc->notnull) + newt->notnulls = bms_add_member(newt->notnulls, i); + + /* make sure column names are unique */ + for (j = 0; j < i; j++) + if (strcmp(names[j], rawc->colname) == 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("column name \"%s\" is not unique", + rawc->colname), + parser_errposition(pstate, rawc->location))); + names[i] = rawc->colname; + + i++; + } + newt->evalcols = true; + pfree(names); + } + else + { + newt->evalcols = false; + + /* + * When there are not explicit column, define the implicit one. The + * rules for implicit column can be different. Currently only XMLTABLE + * is supported. + */ + newt->colcount = 1; + newt->colnames = list_make1(makeString(pstrdup("xmltable"))); + newt->coltypes = list_make1_oid(XMLOID); + newt->coltypmods = list_make1_int(-1); + newt->colcollations = list_make1_oid(InvalidOid); + } + + /* Namespaces, if any, also need to be transformed */ + if (t->namespaces != NIL) + { + ListCell *ns; + ListCell *lc2; + List *ns_uris = NIL; + List *ns_names = NIL; + bool default_ns_seen = false; + + foreach(ns, t->namespaces) + { + ResTarget *r = (ResTarget *) lfirst(ns); + + Assert(IsA(r, ResTarget)); + + ns_uris = lappend(ns_uris, + coerce_to_specific_type(pstate, + transformExprRecurse(pstate, r->val), + TEXTOID, + constructName)); + + /* Verify consistency of name list: no dupes, only one DEFAULT */ + if (r->name != NULL) + { + foreach(lc2, ns_names) + { + char *name = strVal(lfirst(lc2)); + + if (name == NULL) + continue; + if (strcmp(name, r->name) == 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("namespace name \"%s\" is not unique", + name), + parser_errposition(pstate, r->location))); + } + } + else + { + if (default_ns_seen) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only one default namespace is allowed"), + parser_errposition(pstate, r->location))); + default_ns_seen = true; + } + + /* Note the string may be NULL */ + ns_names = lappend(ns_names, makeString(r->name)); + } + + newt->ns_uris = ns_uris; + newt->ns_names = ns_names; + } + + newt->location = t->location; + + return (Node *) newt; +} + +/* * 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 081a8dd..7579245 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1868,6 +1868,14 @@ FigureColnameInternal(Node *node, char **name) case T_XmlSerialize: *name = "xmlserialize"; return 2; + case T_TableExprFunc: + + /* + * Make TableExpr act like a regular function. Only XMLTABLE expr + * is supported in this moment. + */ + *name = "xmltable"; + return 2; default: break; } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index f26175e..fe42f77 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8548,6 +8548,120 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_TableExpr: + { + TableExpr *te = (TableExpr *) node; + + /* + * Deparse TableExpr - the is only one TableExpr producer, the + * function XMLTABLE. + */ + + /* c_expr shoud be closed in brackets */ + appendStringInfoString(buf, "XMLTABLE("); + + if (te->ns_uris != NIL) + { + ListCell *lc1, + *lc2; + bool first = true; + + appendStringInfoString(buf, "XMLNAMESPACES ("); + forboth(lc1, te->ns_uris, lc2, te->ns_names) + { + Node *expr = (Node *) lfirst(lc1); + char *name = strVal(lfirst(lc2)); + + if (!first) + appendStringInfoString(buf, ", "); + else + first = false; + + if (name != NULL) + { + get_rule_expr(expr, context, true); + appendStringInfo(buf, " AS %s", name); + } + else + { + appendStringInfoString(buf, "DEFAULT "); + get_rule_expr(expr, context, true); + } + } + appendStringInfoString(buf, "), "); + } + + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) te->rowexpr, context, true); + appendStringInfoString(buf, ") PASSING ("); + get_rule_expr((Node *) te->docexpr, context, true); + appendStringInfoChar(buf, ')'); + + if (te->evalcols) + { + ListCell *l1; + ListCell *l2; + ListCell *l3; + ListCell *l4; + ListCell *l5; + int colnum = 0; + + l2 = list_head(te->coltypes); + l3 = list_head(te->coltypmods); + l4 = list_head(te->colexprs); + l5 = list_head(te->coldefexprs); + + appendStringInfoString(buf, " COLUMNS "); + foreach(l1, te->colnames) + { + char *colname = strVal(lfirst(l1)); + Oid typid; + int32 typmod; + Node *colexpr; + Node *coldefexpr; + bool ordinality = te->ordinalitycol == colnum; + bool notnull = bms_is_member(colnum, te->notnulls); + + typid = lfirst_oid(l2); + l2 = lnext(l2); + typmod = lfirst_int(l3); + l3 = lnext(l3); + colexpr = (Node *) lfirst(l4); + l4 = lnext(l4); + coldefexpr = (Node *) lfirst(l5); + l5 = lnext(l5); + + if (colnum > 0) + appendStringInfoString(buf, ", "); + colnum++; + + appendStringInfo(buf, "%s %s", quote_identifier(colname), + ordinality ? "FOR ORDINALITY" : + format_type_with_typemod(typid, typmod)); + if (ordinality) + continue; + + if (coldefexpr != NULL) + { + appendStringInfoString(buf, " DEFAULT ("); + get_rule_expr((Node *) coldefexpr, context, true); + appendStringInfoChar(buf, ')'); + } + if (colexpr != NULL) + { + appendStringInfoString(buf, " PATH ("); + get_rule_expr((Node *) colexpr, context, true); + appendStringInfoChar(buf, ')'); + } + if (notnull) + appendStringInfoString(buf, " NOT NULL"); + } + } + + 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 e8bce3b..244416e 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -73,6 +73,7 @@ #include "commands/dbcommands.h" #include "executor/executor.h" #include "executor/spi.h" +#include "executor/tableexpr.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "libpq/pqformat.h" @@ -145,6 +146,7 @@ static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt); static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj, ArrayBuildState *astate, PgXmlErrorContext *xmlerrcxt); +static xmlChar *pg_xmlCharStrndup(char *str, size_t len); #endif /* USE_LIBXML */ static StringInfo query_to_xml_internal(const char *query, char *tablename, @@ -165,6 +167,49 @@ static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result, char *tablename, bool nulls, bool tableforest, const char *targetns, bool top_level); +/* XMLTABLE support */ +#ifdef USE_LIBXML +/* random number to identify XmlTableContext */ +#define XMLTABLE_CONTEXT_MAGIC 46922182 +typedef struct XmlTableBuilderData +{ + int magic; + char *def_namespace_name; + long int row_count; + PgXmlErrorContext *xmlerrcxt; + xmlParserCtxtPtr ctxt; + xmlDocPtr doc; + xmlXPathContextPtr xpathcxt; + xmlXPathCompExprPtr xpathcomp; + xmlXPathObjectPtr xpathobj; + xmlXPathCompExprPtr *xpathscomp; +} XmlTableBuilderData; +#endif + +static void XmlTableInitBuilder(TableExprState *state); +static void XmlTableSetDoc(TableExprState *state, Datum value); +static void XmlTableSetNamespace(TableExprState *state, char *name, + char *uri); +static void XmlTableSetRowFilter(TableExprState *state, char *path); +static void XmlTableSetColumnFilter(TableExprState *state, char *path, + int colnum); +static bool XmlTableFetchRow(TableExprState *state); +static Datum XmlTableGetValue(TableExprState *state, int colnum, + bool *isnull); +static void XmlTableDestroyBuilder(TableExprState *state); + +const TableExprRoutine XmlTableExprRoutine = +{ + XmlTableInitBuilder, + XmlTableSetDoc, + XmlTableSetNamespace, + XmlTableSetRowFilter, + XmlTableSetColumnFilter, + XmlTableFetchRow, + XmlTableGetValue, + XmlTableDestroyBuilder +}; + #define NO_XML_SUPPORT() \ ereport(ERROR, \ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \ @@ -1113,6 +1158,19 @@ xml_pnstrdup(const xmlChar *str, size_t len) return result; } +/* Ditto, except input is char* */ +static xmlChar * +pg_xmlCharStrndup(char *str, size_t len) +{ + xmlChar *result; + + result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar)); + memcpy(result, str, len); + result[len] = '\0'; + + return result; +} + /* * str is the null-terminated input string. Remaining arguments are * output arguments; each can be NULL if value is not wanted. @@ -3811,13 +3869,8 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("empty XPath expression"))); - string = (xmlChar *) palloc((len + 1) * sizeof(xmlChar)); - memcpy(string, datastr, 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'; + string = pg_xmlCharStrndup(datastr, len); + xpath_expr = pg_xmlCharStrndup(VARDATA(xpath_expr_text), xpath_len); xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); @@ -4065,3 +4118,502 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS) return 0; #endif /* not USE_LIBXML */ } + +/* + * support functions for XMLTABLE + * + */ +#ifdef USE_LIBXML + +/* + * Returns private data from executor state. Ensure validity by check with + * MAGIC number. + */ +static inline XmlTableBuilderData * +GetXmlTableBuilderPrivateData(TableExprState *state, const char *fname) +{ + XmlTableBuilderData *result; + + if (!IsA(state, TableExprState)) + elog(ERROR, "%s called with invalid TableExprState", fname); + result = (XmlTableBuilderData *) state->opaque; + if (result->magic != XMLTABLE_CONTEXT_MAGIC) + elog(ERROR, "%s called with invalid TableExprState", fname); + + return result; +} +#endif + +/* + * XmlTableInitBuilder + * Fill in TableExprState for XmlTable builder. Initialize XML parser. + */ +static void +XmlTableInitBuilder(TableExprState *state) +{ +#ifdef USE_LIBXML + volatile xmlParserCtxtPtr ctxt = NULL; + XmlTableBuilderData *xtCxt; + PgXmlErrorContext *xmlerrcxt; + + xtCxt = palloc0(sizeof(XmlTableBuilderData)); + xtCxt->magic = XMLTABLE_CONTEXT_MAGIC; + xtCxt->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * + state->resultSlot->tts_tupleDescriptor->natts); + + xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); + + PG_TRY(); + { + xmlInitParser(); + + ctxt = xmlNewParserCtxt(); + if (ctxt == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate parser context"); + } + PG_CATCH(); + { + if (ctxt != NULL) + xmlFreeParserCtxt(ctxt); + + pg_xml_done(xmlerrcxt, true); + + PG_RE_THROW(); + } + PG_END_TRY(); + + xtCxt->xmlerrcxt = xmlerrcxt; + xtCxt->ctxt = ctxt; + + state->opaque = xtCxt; +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableSetDoc + * Install the input document + */ +static void +XmlTableSetDoc(TableExprState *state, Datum value) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + xmltype *xmlval = DatumGetXmlP(value); + xmlChar *xstr; + int length; + volatile xmlDocPtr doc = NULL; + volatile xmlXPathContextPtr xpathcxt = NULL; + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetDoc"); + + length = VARSIZE(xmlval) - VARHDRSZ; + xstr = pg_xmlCharStrndup(VARDATA(xmlval), length); + + PG_TRY(); + { + doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL, 0); + if (doc == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT, + "could not parse XML document"); + xpathcxt = xmlXPathNewContext(doc); + if (xpathcxt == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate XPath context"); + xpathcxt->node = xmlDocGetRootElement(doc); + if (xpathcxt->node == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, + "could not find root XML element"); + } + PG_CATCH(); + { + if (xpathcxt != NULL) + xmlXPathFreeContext(xpathcxt); + if (doc != NULL) + xmlFreeDoc(doc); + + PG_RE_THROW(); + } + PG_END_TRY(); + + xtCxt->doc = doc; + xtCxt->xpathcxt = xpathcxt; +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableSetNamespace + * Add a namespace declaration + */ +static void +XmlTableSetNamespace(TableExprState *state, char *name, char *uri) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + + if (name == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DEFAULT namespace is not supported"))); + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetNamespace"); + + if (xmlXPathRegisterNs(xtCxt->xpathcxt, + pg_xmlCharStrndup(name, strlen(name)), + pg_xmlCharStrndup(uri, strlen(uri)))) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION, + "could not set XML namespace"); +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableSetRowFilter + * Install the row-filter Xpath expression. + */ +static void +XmlTableSetRowFilter(TableExprState *state, char *path) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + xmlChar *xstr; + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetRowFilter"); + + if (path == '\0') + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("row path filter must not be empty string"))); + + xstr = pg_xmlCharStrndup(path, strlen(path)); + + xtCxt->xpathcomp = xmlXPathCompile(xstr); + if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION, + "invalid XPath expression"); +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableSetColumnFilter + * Install the column-filter Xpath expression, for the given column. + * + * The path can be NULL, when the only one result column is implicit. XXX fix + */ +static void +XmlTableSetColumnFilter(TableExprState *state, char *path, int colnum) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + xmlChar *xstr; + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetColumnFilter"); + + if (path != NULL) + { + if (*path == '\0') + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("column path filter must not be empty string"))); + + xstr = pg_xmlCharStrndup(path, strlen(path)); + + xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr); + if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION, + "invalid XPath expression"); + } + else + { + Assert(!state->evalcols || colnum == state->ordinalitycol); + + xtCxt->xpathscomp[colnum] = NULL; + } +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableFetchRow + * Prepare the next "current" tuple for upcoming GetValue calls. + * Returns FALSE if the row-filter expression returned no more rows. + */ +static bool +XmlTableFetchRow(TableExprState *state) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableFetchRow"); + + /* + * XmlTable returns table - set of composite values. The error context, is + * used for producement more values, between two calls, there can be + * created and used another libxml2 error context. It is libxml2 global + * value, so it should be refreshed any time before any libxml2 usage, + * that is finished by returning some value. + */ + xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler); + + if (xtCxt->xpathobj == NULL) + { + 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->row_count = 0; + } + + if (xtCxt->xpathobj->type == XPATH_NODESET) + { + if (xtCxt->xpathobj->nodesetval != NULL) + { + if (xtCxt->row_count++ < xtCxt->xpathobj->nodesetval->nodeNr) + return true; + } + } + + return false; +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ + + return false; +} + +/* + * XmlTableGetValue + * Return the value for column number 'colnum' for the current row. If + * column -1 is requested, return representation of the whole row. + * + * This leaks memory, so be sure to reset often the context in which it's + * called. + */ +static Datum +XmlTableGetValue(TableExprState *state, int colnum, bool *isnull) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + Datum result = (Datum) 0; + xmlNodePtr cur; + char *cstr = NULL; + volatile xmlXPathObjectPtr xpathobj = NULL; + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue"); + + Assert(xtCxt->xpathobj && + xtCxt->xpathobj->type == XPATH_NODESET && + xtCxt->xpathobj->nodesetval != NULL); + + /* Propagate context related error context to libxml2 */ + xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler); + + *isnull = false; + + cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1]; + + if (colnum == -1) + { + /* + * Use whole row, when user doesn't specify a column. The target type + * must be XMLOID. + */ + Assert(state->resultSlot->tts_tupleDescriptor->attrs[0]->atttypid == XMLOID); + + result = PointerGetDatum(xml_xmlnodetoxmltype(cur, xtCxt->xmlerrcxt)); + + return result; + } + + Assert(xtCxt->xpathscomp[colnum] != NULL); + + /* fast leaving */ + if (cur->type != XML_ELEMENT_NODE) + elog(ERROR, "unexpected xmlNode type"); + + PG_TRY(); + { + Form_pg_attribute attr; + + attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum]; + + /* Set current node as entry point for XPath evaluation */ + xmlXPathSetContextNode(cur, xtCxt->xpathcxt); + + /* Evaluate column path */ + xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt); + if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, + "could not create XPath object"); + + /* + * There are four possible cases, depending on the number of nodes + * returned by the XPath expression and the type of the target column: + * a) XPath returns no nodes. b) One node is returned, and column is + * of type XML. c) One node, column type other than XML. d) Multiple + * nodes are returned. + */ + if (xpathobj->type == XPATH_NODESET) + { + int count = 0; + Oid targettypid = attr->atttypid; + + if (xpathobj->nodesetval != NULL) + count = xpathobj->nodesetval->nodeNr; + + if (xpathobj->nodesetval == NULL || count == 0) + { + *isnull = true; + } + else if (count == 1 && targettypid == XMLOID) + { + text *textstr; + + /* simple case, result is one value */ + textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0], + xtCxt->xmlerrcxt); + cstr = text_to_cstring(textstr); + } + else if (count == 1) + { + xmlChar *str; + + str = xmlNodeListGetString(xtCxt->doc, + xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode, + 1); + + if (str != NULL) + { + PG_TRY(); + { + cstr = pstrdup((char *) str); + } + PG_CATCH(); + { + xmlFree(str); + PG_RE_THROW(); + } + PG_END_TRY(); + xmlFree(str); + } + else + { + /* Return empty string when tag is empty */ + cstr = ""; + } + } + else + { + StringInfoData str; + int i; + + Assert(count > 1); + + /* + * When evaluating the XPath expression returns multiple + * nodes, the result is the concatenation of them all. The + * target type must be XML. + */ + if (targettypid != XMLOID) + ereport(ERROR, + (errcode(ERRCODE_CARDINALITY_VIOLATION), + errmsg("more than one value returned by column XPath expression"))); + + /* Concatenate serialized values */ + initStringInfo(&str); + for (i = 0; i < count; i++) + { + appendStringInfoText(&str, + xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i], + xtCxt->xmlerrcxt)); + } + cstr = str.data; + } + } + else if (xpathobj->type == XPATH_STRING) + { + cstr = (char *) xpathobj->stringval; + } + else + elog(ERROR, "unexpected XPath object type %u", xpathobj->type); + + /* + * By here, either cstr contains the result value, or the isnull flag + * has been set. + */ + Assert(cstr || *isnull); + + if (!*isnull) + result = InputFunctionCall(&state->in_functions[colnum], + cstr, + state->typioparams[colnum], + attr->atttypmod); + } + PG_CATCH(); + { + if (xpathobj != NULL) + xmlXPathFreeObject(xpathobj); + PG_RE_THROW(); + } + PG_END_TRY(); + + xmlXPathFreeObject(xpathobj); + + return result; +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} + +/* + * Release all libxml2 memory and related resources + */ +static void +XmlTableDestroyBuilder(TableExprState *state) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableDestroyBuilder"); + + /* Propagate context related error context to libxml2 */ + xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler); + + if (xtCxt->xpathscomp != NULL) + { + int i; + + for (i = 0; i < state->resultSlot->tts_tupleDescriptor->natts; i++) + if (xtCxt->xpathscomp[i] != NULL) + xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]); + } + + if (xtCxt->xpathobj != NULL) + xmlXPathFreeObject(xtCxt->xpathobj); + if (xtCxt->xpathcomp != NULL) + xmlXPathFreeCompExpr(xtCxt->xpathcomp); + if (xtCxt->xpathcxt != NULL) + xmlXPathFreeContext(xtCxt->xpathcxt); + if (xtCxt->doc != NULL) + xmlFreeDoc(xtCxt->doc); + if (xtCxt->ctxt != NULL) + xmlFreeParserCtxt(xtCxt->ctxt); + + pg_xml_done(xtCxt->xmlerrcxt, true); + + /* not valid anymore */ + xtCxt->magic = 0; + state->opaque = NULL; + +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index c55da54..069c60c2 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -242,6 +242,29 @@ get_expr_result_type(Node *expr, NULL, resultTypeId, resultTupleDesc); + else if (expr && IsA(expr, TableExpr)) + { + TupleDesc tupdesc; + TableExpr *te = (TableExpr *) expr; + + /* + * the result type of TableExpr is determinable, although it is not + * stored in system catalog. We can solve this issue here. It is + * similar to functions with polymorphic OUT parameters. + */ + tupdesc = BuildDescFromLists(te->colnames, + te->coltypes, + te->coltypmods, + te->colcollations); + assign_record_type_typmod(tupdesc); + + if (resultTypeId) + *resultTypeId = tupdesc->tdtypeid; + if (resultTupleDesc) + *resultTupleDesc = tupdesc; + + return TYPEFUNC_COMPOSITE; + } else { /* handle as a generic expression; no chance to resolve RECORD */ diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 02dbe7b..1727ed8 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -256,6 +256,10 @@ extern Datum ExecMakeFunctionResultSet(FuncExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +extern Datum ExecMakeTableExprResultSet(TableExprState *texpr, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone); extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext, bool *isNull); extern ExprState *ExecInitExpr(Expr *node, PlanState *parent); diff --git a/src/include/executor/tableexpr.h b/src/include/executor/tableexpr.h new file mode 100644 index 0000000..a54d947 --- /dev/null +++ b/src/include/executor/tableexpr.h @@ -0,0 +1,65 @@ +/*------------------------------------------------------------------------- + * + * tableexpr.h + * interface for TableExpr builder + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/executor/tableexpr.h + * + *------------------------------------------------------------------------- + */ +#ifndef TABLEEXPR_H +#define TABLEEXPR_H + +#include "nodes/execnodes.h" + +/* + * TableExprRoutine holds function pointers used for generating content of + * table-expression functions, such as XMLTABLE. + * + * InitBuilder initialize table builder private objects. The output tuple + * descriptor, input functions for the columns, and typioparams are passed + * from executor state. + * + * SetDoc is called to define the input document. The table builder may + * apply additional transformations not exposed outside the table builder + * context. + * + * SetNamespace is called to pass namespace declarations from the table + * expression. This function may be NULL if namespaces are not supported by + * the table builder. Namespaces must be given before setting the row and + * column filters. If the name is given as NULL, the entry shall be for the + * default namespace. + * + * SetRowFilter is called do define the row-generating filter, which shall be + * used to extract each row from the input document. + * + * SetColumnFilter is called once for each column, to define the column- + * generating filter for the given column. + * + * FetchRow shall be called repeatedly until it returns that no more rows are + * found in the document. On each invocation it shall set state in the table + * builder context such that each subsequent GetValue call returns the value + * for the indicated column for the row being processed. + * + * DestroyBuilder shall release all resources associated with a table builder + * context. It may be called either because all rows have been consumed, or + * because an error ocurred while processing the table expression. + */ +typedef struct TableExprRoutine +{ + void (*InitBuilder) (TableExprState *state); + void (*SetDoc) (TableExprState *state, Datum value); + void (*SetNamespace) (TableExprState *state, char *name, + char *uri); + void (*SetRowFilter) (TableExprState *state, char *path); + void (*SetColumnFilter) (TableExprState *state, char *path, + int colnum); + bool (*FetchRow) (TableExprState *state); + Datum (*GetValue) (TableExprState *state, int colnum, bool *isnull); + void (*DestroyBuilder) (TableExprState *state); +} TableExprRoutine; + +#endif /* TABLEEXPR_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index f9bcdd6..e26889a 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1019,6 +1019,33 @@ typedef struct DomainConstraintState ExprState *check_expr; /* for CHECK, a boolean expression */ } DomainConstraintState; +/* ---------------- + * TableExprState node + * + * Used in table-expression functions like XMLTABLE. + * ---------------- + */ +typedef struct TableExprState +{ + ExprState xprstate; + const struct TableExprRoutine *routine; /* table builder methods */ + MemoryContext buildercxt; /* memory context used by builder */ + MemoryContext perValueCxt; /* short life context for a value evaluation */ + void *opaque; /* table builder private space */ + TupleTableSlot *resultSlot; /* output tuple table slot */ + List *ns_names; /* list of str nodes with namespace names */ + List *ns_uris; /* list of states of namespace uri exprs */ + FmgrInfo *in_functions; /* input function for each column */ + Oid *typioparams; /* typioparam for each column */ + bool evalcols; /* true, when column was not defined by user */ + int ordinalitycol; /* number of ordinality column or -1 */ + int rownum; /* row number to be output next */ + ExprState *docexpr; /* state for document expression */ + ExprState *rowexpr; /* state for row-generating expression */ + List *colexprs; /* state for column-generating expression */ + List *coldefexprs; /* state for column default expressions */ + ExprDoneCond isDone; /* status for ValuePerCall mode */ +} TableExprState; /* ---------------------------------------------------------------- * Executor State Trees diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index fa4932a..a0a2fb3 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -149,6 +149,7 @@ typedef enum NodeTag T_DistinctExpr, T_NullIfExpr, T_ScalarArrayOpExpr, + T_TableExpr, T_BoolExpr, T_SubLink, T_SubPlan, @@ -199,6 +200,7 @@ typedef enum NodeTag T_ArrayRefExprState, T_FuncExprState, T_ScalarArrayOpExprState, + T_TableExprState, T_BoolExprState, T_SubPlanState, T_AlternativeSubPlanState, @@ -451,6 +453,8 @@ typedef enum NodeTag T_GroupingSet, T_WindowClause, T_FuncWithArgs, + T_TableExprFunc, + T_TableExprFuncCol, T_AccessPriv, T_CreateOpClassItem, T_TableLikeClause, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 07a8436..e447079 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -172,7 +172,6 @@ typedef struct Query int stmt_len; /* length in bytes; 0 means "rest of string" */ } Query; - /**************************************************************************** * Supporting data structures for Parse Trees * @@ -238,6 +237,37 @@ typedef struct ParamRef } ParamRef; /* + * TableExprFunc - a TableExpr in raw form. + */ +typedef struct TableExprFunc +{ + NodeTag type; + Node *docexpr; + Node *rowexpr; + List *namespaces; /* list of namespaces */ + List *columns; /* list of columns (TableExprFuncCol) */ + int location; /* token location, or -1 if unknown */ +} TableExprFunc; + +/* + * TableExprFuncCol - one column in a TableExpr column list, in raw form. + * + * If for_ordinality is true (FOR ORDINALITY), then the column is an int4 + * column and the rest of the fields are ignored. + */ +typedef struct TableExprFuncCol +{ + NodeTag type; + char *colname; /* name of generated column */ + bool for_ordinality; /* whether this is FOR ORDINALITY */ + TypeName *typeName; /* type of generated column */ + bool notnull; /* nullability flag */ + Node *colexpr; /* column filter expression */ + Node *coldefexpr; /* column default value expr */ + int location; /* token location, or -1 if unknown */ +} TableExprFuncCol; + +/* * A_Expr - infix, prefix, and postfix expressions */ typedef enum A_Expr_Kind diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index f72ec24..3acb3fb 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -18,6 +18,7 @@ #define PRIMNODES_H #include "access/attnum.h" +#include "nodes/bitmapset.h" #include "nodes/pg_list.h" @@ -522,6 +523,29 @@ typedef struct ScalarArrayOpExpr } ScalarArrayOpExpr; /* + * TableExpr - expression node for a table expression, such as XMLTABLE. + */ +typedef struct TableExpr +{ + Expr xpr; + List *ns_uris; /* list of namespace uri */ + List *ns_names; /* list of namespace names */ + Node *docexpr; /* input document expression */ + Node *rowexpr; /* row filter expression */ + int colcount; /* number of output columns */ + List *colnames; /* column names (list of String) */ + List *coltypes; /* OID list of column type OIDs */ + List *coltypmods; /* integer list of column typmods */ + List *colcollations; /* OID list of column collation OIDs */ + List *colexprs; /* list of column filter expressions */ + List *coldefexprs; /* list of column default expressions */ + Bitmapset *notnulls; /* nullability flag for each output column */ + int ordinalitycol; /* ordinality column, -1 if not used */ + bool evalcols; /* true, when columns should be evaluated */ + int location; /* token location, or -1 if unknown */ +} TableExpr; + +/* * BoolExpr - expression node for the basic Boolean operators AND, OR, NOT * * Notice the arguments are given as a List. For NOT, of course the list diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 985d650..28c4dab 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -82,6 +82,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) @@ -446,10 +447,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 b50992c..3eed819 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 cc1fc39..a3db437 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -18,6 +18,7 @@ #include "fmgr.h" #include "nodes/execnodes.h" #include "nodes/primnodes.h" +#include "executor/tableexpr.h" typedef struct varlena xmltype; @@ -76,4 +77,6 @@ extern int xmlbinary; /* XmlBinaryType, but int for guc enum */ extern int xmloption; /* XmlOptionType, but int for guc enum */ +extern const TableExprRoutine XmlTableExprRoutine; + #endif /* XML_H */ diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index f21e119..d471b47 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -948,3 +948,435 @@ SELECT XMLPARSE(DOCUMENT '  (1 row) +-- XMLPATH tests +CREATE TABLE xmldata(data xml); +INSERT INTO xmldata VALUES(' + + AU + Australia + 3 + + + CN + China + 3 + + + HK + HongKong + 3 + + + IN + India + 3 + + + JP + Japan + 3Sinzo Abe + + + SG + Singapore + 3791 + +'); +-- XMLTABLE without columns +SELECT * FROM XMLTABLE('/rows/row' PASSING '1020'); + xmltable +------------- + + + 10+ + 20+ + +(1 row) + +SELECT XMLTABLE('/rows/row' PASSING '1020'); + xmltable +------------- + (" + + 10+ + 20+ + ") +(1 row) + +-- XMLTABLE with columns +SELECT xmltable.* + FROM (SELECT data FROM xmldata) x, + LATERAL XMLTABLE('/ROWS/ROW' + PASSING data + COLUMNS id int PATH '@id', + _id FOR ORDINALITY, + country_name text PATH 'COUNTRY_NAME' NOT NULL, + country_id text PATH 'COUNTRY_ID', + region_id int PATH 'REGION_ID', + size float PATH 'SIZE', + unit text PATH 'SIZE/@unit', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'); + id | _id | country_name | country_id | region_id | size | unit | premier_name +----+-----+--------------+------------+-----------+------+------+--------------- + 1 | 1 | Australia | AU | 3 | | | not specified + 2 | 2 | China | CN | 3 | | | not specified + 3 | 3 | HongKong | HK | 3 | | | not specified + 4 | 4 | India | IN | 3 | | | not specified + 5 | 5 | Japan | JP | 3 | | | Sinzo Abe + 6 | 6 | Singapore | SG | 3 | 791 | km | not specified +(6 rows) + +CREATE VIEW xmltableview1 AS SELECT xmltable.* + FROM (SELECT data FROM xmldata) x, + LATERAL XMLTABLE('/ROWS/ROW' + PASSING data + COLUMNS id int PATH '@id', + _id FOR ORDINALITY, + country_name text PATH 'COUNTRY_NAME' NOT NULL, + country_id text PATH 'COUNTRY_ID', + region_id int PATH 'REGION_ID', + size float PATH 'SIZE', + unit text PATH 'SIZE/@unit', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'); +SELECT * FROM xmltableview1; + id | _id | country_name | country_id | region_id | size | unit | premier_name +----+-----+--------------+------------+-----------+------+------+--------------- + 1 | 1 | Australia | AU | 3 | | | not specified + 2 | 2 | China | CN | 3 | | | not specified + 3 | 3 | HongKong | HK | 3 | | | not specified + 4 | 4 | India | IN | 3 | | | not specified + 5 | 5 | Japan | JP | 3 | | | Sinzo Abe + 6 | 6 | Singapore | SG | 3 | 791 | km | not specified +(6 rows) + +\sv xmltableview1 +CREATE OR REPLACE VIEW public.xmltableview1 AS + SELECT "xmltable".id, + "xmltable"._id, + "xmltable".country_name, + "xmltable".country_id, + "xmltable".region_id, + "xmltable".size, + "xmltable".unit, + "xmltable".premier_name + FROM ( SELECT xmldata.data + FROM xmldata) x, + LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) "xmltable"(id, _id, country_name, country_id, region_id, size, unit, premier_name) +EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1; + QUERY PLAN +----------------------------------- + Nested Loop + -> Seq Scan on xmldata + -> Function Scan on "xmltable" +(3 rows) + +EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop + Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name + -> Seq Scan on public.xmldata + Output: xmldata.data + -> Function Scan on "xmltable" + Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name + Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) +(7 rows) + +-- XMLNAMESPACES tests +SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz), + '/zz:rows/zz:row' + PASSING '10' + COLUMNS a int PATH 'zz:a'); + a +---- + 10 +(1 row) + +CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz), + '/zz:rows/zz:row' + PASSING '10' + COLUMNS a int PATH 'zz:a'); +SELECT * FROM xmltableview2; + a +---- + 10 +(1 row) + +SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'), + '/rows/row' + PASSING '10' + COLUMNS a int PATH 'a'); +ERROR: DEFAULT namespace is not supported +SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '1020'); + xmltable +---------- + 10 + 20 +(2 rows) + +SELECT (XMLTABLE('/rows/row/a/text()' PASSING '1020')).*; + xmltable +---------- + 10 + 20 +(2 rows) + +-- used in prepare statements +PREPARE pp AS +SELECT xmltable.* + FROM (SELECT data FROM xmldata) x, + LATERAL XMLTABLE('/ROWS/ROW' + PASSING data + COLUMNS id int PATH '@id', + _id FOR ORDINALITY, + country_name text PATH 'COUNTRY_NAME' NOT NULL, + country_id text PATH 'COUNTRY_ID', + region_id int PATH 'REGION_ID', + size float PATH 'SIZE', + unit text PATH 'SIZE/@unit', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'); +EXECUTE pp; + id | _id | country_name | country_id | region_id | size | unit | premier_name +----+-----+--------------+------------+-----------+------+------+--------------- + 1 | 1 | Australia | AU | 3 | | | not specified + 2 | 2 | China | CN | 3 | | | not specified + 3 | 3 | HongKong | HK | 3 | | | not specified + 4 | 4 | India | IN | 3 | | | not specified + 5 | 5 | Japan | JP | 3 | | | Sinzo Abe + 6 | 6 | Singapore | SG | 3 | 791 | km | not specified +(6 rows) + +SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata; + xmltable +------------------------------------------------------------------ + (" + + AU + + Australia + + 3 + + ") + (" + + CN + + China + + 3 + + ") + (" + + HK + + HongKong + + 3 + + ") + (" + + IN + + India + + 3 + + ") + (" + + JP + + Japan + + 3Sinzo Abe+ + ") + (" + + SG + + Singapore + + 3791 + + ") +(6 rows) + +SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata; + xmltable +------------------------------------------------------------------ + (" + + JP + + Japan + + 3Sinzo Abe+ + ") +(1 row) + +SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata; + xmltable +------------------------------------------------------------------ + (" + + IN + + India + + 3 + + ") + (" + + JP + + Japan + + 3Sinzo Abe+ + ") +(2 rows) + +SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s; + COUNTRY_NAME | REGION_ID +--------------+----------- + India | 3 + Japan | 3 +(2 rows) + +SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int); + COUNTRY_NAME | REGION_ID +--------------+----------- + India | 3 + Japan | 3 +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int); + id | COUNTRY_NAME | REGION_ID +----+--------------+----------- + 1 | India | 3 + 2 | Japan | 3 +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int); + id | COUNTRY_NAME | REGION_ID +----+--------------+----------- + 4 | India | 3 + 5 | Japan | 3 +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id'); + id +---- + 4 + 5 +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY); + id +---- + 1 + 2 +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.'); + id | COUNTRY_NAME | REGION_ID | rawdata +----+--------------+-----------+------------------------------------------------------------------ + 4 | India | 3 | + + | | | IN + + | | | India + + | | | 3 + + | | | + 5 | Japan | 3 | + + | | | JP + + | | | Japan + + | | | 3Sinzo Abe+ + | | | +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*'); + id | COUNTRY_NAME | REGION_ID | rawdata +----+--------------+-----------+----------------------------------------------------------------------------------------------------------------------------- + 4 | India | 3 | INIndia3 + 5 | Japan | 3 | JPJapan3Sinzo Abe +(2 rows) + +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text); + element +------------------- + a1aa2a bbbbcccc +(1 row) + +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail +ERROR: more than one value returned by column XPath expression +-- CDATA test +select * from xmltable('r' passing ' &"<>!foo]]>2' columns c text); + c +------------------------- + &"<>!foo + 2 +(2 rows) + +-- XML builtin entities +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent text); + ent +----- + ' + " + & + < + > +(5 rows) + +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent xml); + ent +------------------ + ' + " + & + < + > +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +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'); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop + Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name + -> Seq Scan on public.xmldata + Output: xmldata.data + -> Function Scan on "xmltable" + Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name + Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) +(7 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT 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') FROM xmldata; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + ProjectSet + Output: XMLTABLE(('/ROWS/ROW'::text) PASSING (data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) + -> Seq Scan on public.xmldata + Output: data +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT (xmltable).* FROM + (SELECT 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') FROM xmldata) s; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Subquery Scan on s + Output: (s."xmltable").id, (s."xmltable")._id, (s."xmltable").country_name, (s."xmltable").country_id, (s."xmltable").region_id, (s."xmltable").size, (s."xmltable").unit, (s."xmltable").premier_name + -> ProjectSet + Output: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) + -> Seq Scan on public.xmldata + Output: xmldata.data +(6 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT (XMLTABLE('/rows/row/a/text()' PASSING '1020')).*; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------- + Result + Output: ((XMLTABLE(('/rows/row/a/text()'::text) PASSING ('1020'::xml))))."xmltable" + -> ProjectSet + Output: XMLTABLE(('/rows/row/a/text()'::text) PASSING ('1020'::xml)) + -> Result +(5 rows) + diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index d702703..73f1995 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -827,3 +827,343 @@ SELECT XMLPARSE(DOCUMENT ' + + AU + Australia + 3 + + + CN + China + 3 + + + HK + HongKong + 3 + + + IN + India + 3 + + + JP + Japan + 3Sinzo Abe + + + SG + Singapore + 3791 + +'); +ERROR: unsupported XML feature +LINE 1: INSERT INTO xmldata VALUES(' + ^ +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 '1020'); +ERROR: unsupported XML feature +LINE 1: SELECT * FROM XMLTABLE('/rows/row' PASSING '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 '1020'); +ERROR: unsupported XML feature +LINE 1: SELECT XMLTABLE('/rows/row' PASSING '10... + ^ +DETAIL: This functionality requires the server to be built with libxml support. +HINT: You need to rebuild PostgreSQL using --with-libxml. +-- XMLTABLE with columns +SELECT xmltable.* + FROM (SELECT data FROM xmldata) x, + LATERAL XMLTABLE('/ROWS/ROW' + PASSING data + COLUMNS id int PATH '@id', + _id FOR ORDINALITY, + country_name text PATH 'COUNTRY_NAME' NOT NULL, + country_id text PATH 'COUNTRY_ID', + region_id int PATH 'REGION_ID', + size float PATH 'SIZE', + unit text PATH 'SIZE/@unit', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'); + id | _id | country_name | country_id | region_id | size | unit | premier_name +----+-----+--------------+------------+-----------+------+------+-------------- +(0 rows) + +CREATE VIEW xmltableview1 AS SELECT xmltable.* + FROM (SELECT data FROM xmldata) x, + LATERAL XMLTABLE('/ROWS/ROW' + PASSING data + COLUMNS id int PATH '@id', + _id FOR ORDINALITY, + country_name text PATH 'COUNTRY_NAME' NOT NULL, + country_id text PATH 'COUNTRY_ID', + region_id int PATH 'REGION_ID', + size float PATH 'SIZE', + unit text PATH 'SIZE/@unit', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'); +SELECT * FROM xmltableview1; + id | _id | country_name | country_id | region_id | size | unit | premier_name +----+-----+--------------+------------+-----------+------+------+-------------- +(0 rows) + +\sv xmltableview1 +CREATE OR REPLACE VIEW public.xmltableview1 AS + SELECT "xmltable".id, + "xmltable"._id, + "xmltable".country_name, + "xmltable".country_id, + "xmltable".region_id, + "xmltable".size, + "xmltable".unit, + "xmltable".premier_name + FROM ( SELECT xmldata.data + FROM xmldata) x, + LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) "xmltable"(id, _id, country_name, country_id, region_id, size, unit, premier_name) +EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1; + QUERY PLAN +----------------------------------- + Nested Loop + -> Seq Scan on xmldata + -> Function Scan on "xmltable" +(3 rows) + +EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop + Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name + -> Seq Scan on public.xmldata + Output: xmldata.data + -> Function Scan on "xmltable" + Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name + Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) +(7 rows) + +-- XMLNAMESPACES tests +SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz), + '/zz:rows/zz:row' + PASSING '10' + COLUMNS a int PATH 'zz:a'); +ERROR: unsupported XML feature +LINE 3: PASSING '10' + COLUMNS a int PATH 'zz:a'); +ERROR: unsupported XML feature +LINE 3: PASSING '10' + COLUMNS a int PATH 'a'); +ERROR: unsupported XML feature +LINE 3: PASSING '1020'); +ERROR: unsupported XML feature +LINE 1: ...LECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '1020')).*; +ERROR: unsupported XML feature +LINE 1: SELECT (XMLTABLE('/rows/row/a/text()' PASSING 'a1aa2a bbbbxxxcccc' COLUMNS element text); +ERROR: unsupported XML feature +LINE 1: SELECT * FROM xmltable('/root' passing 'a1aa1aa2a bbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail +ERROR: unsupported XML feature +LINE 1: SELECT * FROM xmltable('/root' passing 'a1a &"<>!foo]]>2' columns c text); +ERROR: unsupported XML feature +LINE 1: select * from xmltable('r' passing ''"&<>' COLUMNS ent text); +ERROR: unsupported XML feature +LINE 1: SELECT * FROM xmltable('/x/a' PASSING '''"&<>' COLUMNS ent xml); +ERROR: unsupported XML feature +LINE 1: SELECT * FROM xmltable('/x/a' PASSING '' Seq Scan on public.xmldata + Output: xmldata.data + -> Function Scan on "xmltable" + Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name + Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) +(7 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT 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') FROM xmldata; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + ProjectSet + Output: XMLTABLE(('/ROWS/ROW'::text) PASSING (data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) + -> Seq Scan on public.xmldata + Output: data +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT (xmltable).* FROM + (SELECT 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') FROM xmldata) s; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Subquery Scan on s + Output: (s."xmltable").id, (s."xmltable")._id, (s."xmltable").country_name, (s."xmltable").country_id, (s."xmltable").region_id, (s."xmltable").size, (s."xmltable").unit, (s."xmltable").premier_name + -> ProjectSet + Output: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) + -> Seq Scan on public.xmldata + Output: xmldata.data +(6 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT (XMLTABLE('/rows/row/a/text()' PASSING '1020')).*; +ERROR: unsupported XML feature +LINE 2: SELECT (XMLTABLE('/rows/row/a/text()' PASSING '  (1 row) +-- XMLPATH tests +CREATE TABLE xmldata(data xml); +INSERT INTO xmldata VALUES(' + + AU + Australia + 3 + + + CN + China + 3 + + + HK + HongKong + 3 + + + IN + India + 3 + + + JP + Japan + 3Sinzo Abe + + + SG + Singapore + 3791 + +'); +-- XMLTABLE without columns +SELECT * FROM XMLTABLE('/rows/row' PASSING '1020'); + xmltable +------------- + + + 10+ + 20+ + +(1 row) + +SELECT XMLTABLE('/rows/row' PASSING '1020'); + xmltable +------------- + (" + + 10+ + 20+ + ") +(1 row) + +-- XMLTABLE with columns +SELECT xmltable.* + FROM (SELECT data FROM xmldata) x, + LATERAL XMLTABLE('/ROWS/ROW' + PASSING data + COLUMNS id int PATH '@id', + _id FOR ORDINALITY, + country_name text PATH 'COUNTRY_NAME' NOT NULL, + country_id text PATH 'COUNTRY_ID', + region_id int PATH 'REGION_ID', + size float PATH 'SIZE', + unit text PATH 'SIZE/@unit', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'); + id | _id | country_name | country_id | region_id | size | unit | premier_name +----+-----+--------------+------------+-----------+------+------+--------------- + 1 | 1 | Australia | AU | 3 | | | not specified + 2 | 2 | China | CN | 3 | | | not specified + 3 | 3 | HongKong | HK | 3 | | | not specified + 4 | 4 | India | IN | 3 | | | not specified + 5 | 5 | Japan | JP | 3 | | | Sinzo Abe + 6 | 6 | Singapore | SG | 3 | 791 | km | not specified +(6 rows) + +CREATE VIEW xmltableview1 AS SELECT xmltable.* + FROM (SELECT data FROM xmldata) x, + LATERAL XMLTABLE('/ROWS/ROW' + PASSING data + COLUMNS id int PATH '@id', + _id FOR ORDINALITY, + country_name text PATH 'COUNTRY_NAME' NOT NULL, + country_id text PATH 'COUNTRY_ID', + region_id int PATH 'REGION_ID', + size float PATH 'SIZE', + unit text PATH 'SIZE/@unit', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'); +SELECT * FROM xmltableview1; + id | _id | country_name | country_id | region_id | size | unit | premier_name +----+-----+--------------+------------+-----------+------+------+--------------- + 1 | 1 | Australia | AU | 3 | | | not specified + 2 | 2 | China | CN | 3 | | | not specified + 3 | 3 | HongKong | HK | 3 | | | not specified + 4 | 4 | India | IN | 3 | | | not specified + 5 | 5 | Japan | JP | 3 | | | Sinzo Abe + 6 | 6 | Singapore | SG | 3 | 791 | km | not specified +(6 rows) + +\sv xmltableview1 +CREATE OR REPLACE VIEW public.xmltableview1 AS + SELECT "xmltable".id, + "xmltable"._id, + "xmltable".country_name, + "xmltable".country_id, + "xmltable".region_id, + "xmltable".size, + "xmltable".unit, + "xmltable".premier_name + FROM ( SELECT xmldata.data + FROM xmldata) x, + LATERAL XMLTABLE(('/ROWS/ROW'::text) PASSING (x.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) "xmltable"(id, _id, country_name, country_id, region_id, size, unit, premier_name) +EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1; + QUERY PLAN +----------------------------------- + Nested Loop + -> Seq Scan on xmldata + -> Function Scan on "xmltable" +(3 rows) + +EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop + Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name + -> Seq Scan on public.xmldata + Output: xmldata.data + -> Function Scan on "xmltable" + Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name + Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) +(7 rows) + +-- XMLNAMESPACES tests +SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz), + '/zz:rows/zz:row' + PASSING '10' + COLUMNS a int PATH 'zz:a'); + a +---- + 10 +(1 row) + +CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz), + '/zz:rows/zz:row' + PASSING '10' + COLUMNS a int PATH 'zz:a'); +SELECT * FROM xmltableview2; + a +---- + 10 +(1 row) + +SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'), + '/rows/row' + PASSING '10' + COLUMNS a int PATH 'a'); +ERROR: DEFAULT namespace is not supported +SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '1020'); + xmltable +---------- + 10 + 20 +(2 rows) + +SELECT (XMLTABLE('/rows/row/a/text()' PASSING '1020')).*; + xmltable +---------- + 10 + 20 +(2 rows) + +-- used in prepare statements +PREPARE pp AS +SELECT xmltable.* + FROM (SELECT data FROM xmldata) x, + LATERAL XMLTABLE('/ROWS/ROW' + PASSING data + COLUMNS id int PATH '@id', + _id FOR ORDINALITY, + country_name text PATH 'COUNTRY_NAME' NOT NULL, + country_id text PATH 'COUNTRY_ID', + region_id int PATH 'REGION_ID', + size float PATH 'SIZE', + unit text PATH 'SIZE/@unit', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'); +EXECUTE pp; + id | _id | country_name | country_id | region_id | size | unit | premier_name +----+-----+--------------+------------+-----------+------+------+--------------- + 1 | 1 | Australia | AU | 3 | | | not specified + 2 | 2 | China | CN | 3 | | | not specified + 3 | 3 | HongKong | HK | 3 | | | not specified + 4 | 4 | India | IN | 3 | | | not specified + 5 | 5 | Japan | JP | 3 | | | Sinzo Abe + 6 | 6 | Singapore | SG | 3 | 791 | km | not specified +(6 rows) + +SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata; + xmltable +------------------------------------------------------------------ + (" + + AU + + Australia + + 3 + + ") + (" + + CN + + China + + 3 + + ") + (" + + HK + + HongKong + + 3 + + ") + (" + + IN + + India + + 3 + + ") + (" + + JP + + Japan + + 3Sinzo Abe+ + ") + (" + + SG + + Singapore + + 3791 + + ") +(6 rows) + +SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata; + xmltable +------------------------------------------------------------------ + (" + + JP + + Japan + + 3Sinzo Abe+ + ") +(1 row) + +SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata; + xmltable +------------------------------------------------------------------ + (" + + IN + + India + + 3 + + ") + (" + + JP + + Japan + + 3Sinzo Abe+ + ") +(2 rows) + +SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s; + COUNTRY_NAME | REGION_ID +--------------+----------- + India | 3 + Japan | 3 +(2 rows) + +SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int); + COUNTRY_NAME | REGION_ID +--------------+----------- + India | 3 + Japan | 3 +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int); + id | COUNTRY_NAME | REGION_ID +----+--------------+----------- + 1 | India | 3 + 2 | Japan | 3 +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int); + id | COUNTRY_NAME | REGION_ID +----+--------------+----------- + 4 | India | 3 + 5 | Japan | 3 +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id'); + id +---- + 4 + 5 +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY); + id +---- + 1 + 2 +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.'); + id | COUNTRY_NAME | REGION_ID | rawdata +----+--------------+-----------+------------------------------------------------------------------ + 4 | India | 3 | + + | | | IN + + | | | India + + | | | 3 + + | | | + 5 | Japan | 3 | + + | | | JP + + | | | Japan + + | | | 3Sinzo Abe+ + | | | +(2 rows) + +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*'); + id | COUNTRY_NAME | REGION_ID | rawdata +----+--------------+-----------+----------------------------------------------------------------------------------------------------------------------------- + 4 | India | 3 | INIndia3 + 5 | Japan | 3 | JPJapan3Sinzo Abe +(2 rows) + +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text); + element +------------------- + a1aa2a bbbbcccc +(1 row) + +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail +ERROR: more than one value returned by column XPath expression +-- CDATA test +select * from xmltable('r' passing ' &"<>!foo]]>2' columns c text); + c +------------------------- + &"<>!foo + 2 +(2 rows) + +-- XML builtin entities +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent text); + ent +----- + ' + " + & + < + > +(5 rows) + +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent xml); + ent +------------------ + ' + " + & + < + > +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +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'); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop + Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name + -> Seq Scan on public.xmldata + Output: xmldata.data + -> Function Scan on "xmltable" + Output: "xmltable".id, "xmltable"._id, "xmltable".country_name, "xmltable".country_id, "xmltable".region_id, "xmltable".size, "xmltable".unit, "xmltable".premier_name + Function Call: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) +(7 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT 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') FROM xmldata; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + ProjectSet + Output: XMLTABLE(('/ROWS/ROW'::text) PASSING (data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) + -> Seq Scan on public.xmldata + Output: data +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT (xmltable).* FROM + (SELECT 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') FROM xmldata) s; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Subquery Scan on s + Output: (s."xmltable").id, (s."xmltable")._id, (s."xmltable").country_name, (s."xmltable").country_id, (s."xmltable").region_id, (s."xmltable").size, (s."xmltable").unit, (s."xmltable").premier_name + -> ProjectSet + Output: XMLTABLE(('/ROWS/ROW'::text) PASSING (xmldata.data) COLUMNS id integer PATH ('@id'::text), _id FOR ORDINALITY, country_name text PATH ('COUNTRY_NAME'::text) NOT NULL, country_id text PATH ('COUNTRY_ID'::text), region_id integer PATH ('REGION_ID'::text), size double precision PATH ('SIZE'::text), unit text PATH ('SIZE/@unit'::text), premier_name text DEFAULT ('not specified'::text) PATH ('PREMIER_NAME'::text)) + -> Seq Scan on public.xmldata + Output: xmldata.data +(6 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT (XMLTABLE('/rows/row/a/text()' PASSING '1020')).*; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------- + Result + Output: ((XMLTABLE(('/rows/row/a/text()'::text) PASSING ('1020'::xml))))."xmltable" + -> ProjectSet + Output: XMLTABLE(('/rows/row/a/text()'::text) PASSING ('1020'::xml)) + -> Result +(5 rows) + diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index 08a0b30..7c4e3136 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -270,3 +270,178 @@ SELECT XMLPARSE(DOCUMENT ']> SELECT XMLPARSE(DOCUMENT ']>&c;'); -- This might or might not load the requested DTD, but it mustn't throw error. SELECT XMLPARSE(DOCUMENT ' '); + +-- XMLPATH tests +CREATE TABLE xmldata(data xml); +INSERT INTO xmldata VALUES(' + + AU + Australia + 3 + + + CN + China + 3 + + + HK + HongKong + 3 + + + IN + India + 3 + + + JP + Japan + 3Sinzo Abe + + + SG + Singapore + 3791 + +'); + +-- XMLTABLE without columns +SELECT * FROM XMLTABLE('/rows/row' PASSING '1020'); +SELECT XMLTABLE('/rows/row' PASSING '1020'); + +-- XMLTABLE with columns +SELECT xmltable.* + FROM (SELECT data FROM xmldata) x, + LATERAL XMLTABLE('/ROWS/ROW' + PASSING data + COLUMNS id int PATH '@id', + _id FOR ORDINALITY, + country_name text PATH 'COUNTRY_NAME' NOT NULL, + country_id text PATH 'COUNTRY_ID', + region_id int PATH 'REGION_ID', + size float PATH 'SIZE', + unit text PATH 'SIZE/@unit', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'); + +CREATE VIEW xmltableview1 AS SELECT xmltable.* + FROM (SELECT data FROM xmldata) x, + LATERAL XMLTABLE('/ROWS/ROW' + PASSING data + COLUMNS id int PATH '@id', + _id FOR ORDINALITY, + country_name text PATH 'COUNTRY_NAME' NOT NULL, + country_id text PATH 'COUNTRY_ID', + region_id int PATH 'REGION_ID', + size float PATH 'SIZE', + unit text PATH 'SIZE/@unit', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'); + +SELECT * FROM xmltableview1; + +\sv xmltableview1 + +EXPLAIN (COSTS OFF) SELECT * FROM xmltableview1; +EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1; + +-- XMLNAMESPACES tests +SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz), + '/zz:rows/zz:row' + PASSING '10' + COLUMNS a int PATH 'zz:a'); + +CREATE VIEW xmltableview2 AS SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz), + '/zz:rows/zz:row' + PASSING '10' + COLUMNS a int PATH 'zz:a'); + +SELECT * FROM xmltableview2; + +SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'), + '/rows/row' + PASSING '10' + COLUMNS a int PATH 'a'); + +SELECT * FROM XMLTABLE('/rows/row/a/text()' PASSING '1020'); + +SELECT (XMLTABLE('/rows/row/a/text()' PASSING '1020')).*; + +-- used in prepare statements +PREPARE pp AS +SELECT xmltable.* + FROM (SELECT data FROM xmldata) x, + LATERAL XMLTABLE('/ROWS/ROW' + PASSING data + COLUMNS id int PATH '@id', + _id FOR ORDINALITY, + country_name text PATH 'COUNTRY_NAME' NOT NULL, + country_id text PATH 'COUNTRY_ID', + region_id int PATH 'REGION_ID', + size float PATH 'SIZE', + unit text PATH 'SIZE/@unit', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'); + +EXECUTE pp; + +SELECT xmltable('/ROWS/ROW' PASSING data) FROM xmldata; +SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan"]' PASSING data) FROM xmldata; +SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data) FROM xmldata; +SELECT (xmltable).* FROM (SELECT xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int) FROM xmldata) s; +SELECT xmltable.* FROM xmldata,LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS "COUNTRY_NAME" text, "REGION_ID" int); +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY, "COUNTRY_NAME" text, "REGION_ID" int); +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int); +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id'); +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id FOR ORDINALITY); +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.'); +SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*'); + +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text); +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail + +-- CDATA test +select * from xmltable('r' passing ' &"<>!foo]]>2' columns c text); + +-- XML builtin entities +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent text); +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent xml); + +EXPLAIN (VERBOSE, COSTS OFF) +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'); + +EXPLAIN (VERBOSE, COSTS OFF) SELECT 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') FROM xmldata; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT (xmltable).* FROM + (SELECT 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') FROM xmldata) s; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT (XMLTABLE('/rows/row/a/text()' PASSING '1020')).*; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 993880d..aec32ca 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1,3 +1,15 @@ +XmlTableBuilderData +TableExprRoutine +XmlTableContext +TableExprBuilder +TableExprState +TableExprRawCol +TableExpr +TableExprColumn +SQLValueFunction +max_parallel_hazard_context +TriggerTransition +SQLValueFunctionOp ABITVEC ACCESS_ALLOWED_ACE ACL_SIZE_INFORMATION