diff options
Diffstat (limited to 'contrib/xml2/xpath.c')
-rw-r--r-- | contrib/xml2/xpath.c | 893 |
1 files changed, 893 insertions, 0 deletions
diff --git a/contrib/xml2/xpath.c b/contrib/xml2/xpath.c new file mode 100644 index 00000000000..b4fc8287986 --- /dev/null +++ b/contrib/xml2/xpath.c @@ -0,0 +1,893 @@ +/* Parser interface for DOM-based parser (libxml) rather than + stream-based SAX-type parser */ + +#include "postgres.h" +#include "fmgr.h" +#include "executor/spi.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "lib/stringinfo.h" + +/* libxml includes */ + +#include <libxml/xpath.h> +#include <libxml/tree.h> +#include <libxml/xmlmemory.h> +#include <libxml/xmlerror.h> +#include <libxml/parserInternals.h> + +/* declarations */ + +static void *pgxml_palloc(size_t size); +static void *pgxml_repalloc(void *ptr, size_t size); +static void pgxml_pfree(void *ptr); +static char *pgxml_pstrdup(const char *string); +static void pgxml_errorHandler (void * ctxt, const char *msg, ...); + +void elog_error(int level, char *explain, int force); +void pgxml_parser_init(void); + +static xmlChar *pgxmlNodeSetToText(xmlNodeSetPtr nodeset, + xmlChar * toptagname, xmlChar * septagname, + xmlChar * plainsep); + +text *pgxml_result_to_text(xmlXPathObjectPtr res, xmlChar *toptag, + xmlChar *septag, xmlChar *plainsep); + +xmlChar *pgxml_texttoxmlchar(text *textstring); + +static xmlXPathObjectPtr pgxml_xpath(text *document, xmlChar* xpath); + + +Datum pgxml_parse(PG_FUNCTION_ARGS); +Datum xpath_nodeset(PG_FUNCTION_ARGS); +Datum xpath_string(PG_FUNCTION_ARGS); +Datum xpath_number(PG_FUNCTION_ARGS); +Datum xpath_bool(PG_FUNCTION_ARGS); +Datum xpath_list(PG_FUNCTION_ARGS); +Datum xpath_table(PG_FUNCTION_ARGS); + +/* Global variables */ +char *errbuf; /* per line error buffer */ +char *pgxml_errorMsg = NULL; /* overall error message */ + +/* Convenience macros */ + +#define GET_TEXT(cstrp) DatumGetTextP(DirectFunctionCall1(textin, CStringGetDatum(cstrp))) +#define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp))) + +#define ERRBUF_SIZE 200 + +/* memory handling passthrough functions (e.g. palloc, pstrdup are + currently macros, and the others might become so...) */ + +static void * +pgxml_palloc(size_t size) +{ +/* elog(DEBUG1,"Alloc %d in CMC %x",size,CurrentMemoryContext); */ + return palloc(size); +} + +static void * +pgxml_repalloc(void *ptr, size_t size) +{ +/* elog(DEBUG1,"ReAlloc in CMC %x",CurrentMemoryContext);*/ + return repalloc(ptr, size); +} + +static void +pgxml_pfree(void *ptr) +{ +/* elog(DEBUG1,"Free in CMC %x",CurrentMemoryContext); */ + return pfree(ptr); +} + +static char * +pgxml_pstrdup(const char *string) +{ + return pstrdup(string); +} + +/* The error handling function. This formats an error message and sets + * a flag - an ereport will be issued prior to return + */ + +static void +pgxml_errorHandler (void * ctxt, const char *msg, ...) +{ + va_list args; + + va_start(args, msg); + vsnprintf(errbuf, ERRBUF_SIZE, msg, args); + va_end(args); + /* Now copy the argument across */ + if (pgxml_errorMsg == NULL) + { + pgxml_errorMsg = pstrdup(errbuf); + } +else + { + int32 xsize = strlen(pgxml_errorMsg); + pgxml_errorMsg = repalloc(pgxml_errorMsg, + (size_t) (xsize + strlen(errbuf) + 1)); + strncpy(&pgxml_errorMsg[xsize-1],errbuf,strlen(errbuf)); + pgxml_errorMsg[xsize+strlen(errbuf)-1]='\0'; + + } + memset(errbuf,0,ERRBUF_SIZE); +} + +/* This function reports the current message at the level specified */ +void elog_error(int level, char *explain, int force) +{ + if (force || (pgxml_errorMsg != NULL)) + { + if (pgxml_errorMsg == NULL) + { + ereport(level,(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg(explain))); + } + else + { + ereport(level,(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg("%s:%s",explain,pgxml_errorMsg))); + pfree(pgxml_errorMsg); + } + } +} + +void +pgxml_parser_init() +{ + /* + * This code could also set parser settings from user-supplied info. + * Quite how these settings are made is another matter :) + */ + + xmlMemSetup(pgxml_pfree, pgxml_palloc, pgxml_repalloc, pgxml_pstrdup); + xmlInitParser(); + + xmlSetGenericErrorFunc(NULL, pgxml_errorHandler); + + xmlSubstituteEntitiesDefault(1); + xmlLoadExtDtdDefaultValue = 1; + + pgxml_errorMsg = NULL; + + errbuf = palloc(200); + memset(errbuf,0,200); + +} + + +/* Returns true if document is well-formed */ + +PG_FUNCTION_INFO_V1(pgxml_parse); + +Datum +pgxml_parse(PG_FUNCTION_ARGS) +{ + /* called as pgxml_parse(document) */ + xmlDocPtr doctree; + text *t = PG_GETARG_TEXT_P(0); /* document buffer */ + int32 docsize = VARSIZE(t) - VARHDRSZ; + + pgxml_parser_init(); + + doctree = xmlParseMemory((char *) VARDATA(t), docsize); + if (doctree == NULL) + { + xmlCleanupParser(); + PG_RETURN_BOOL(false); /* i.e. not well-formed */ + } + xmlCleanupParser(); + xmlFreeDoc(doctree); + PG_RETURN_BOOL(true); +} + + +static xmlChar +* +pgxmlNodeSetToText(xmlNodeSetPtr nodeset, + xmlChar * toptagname, + xmlChar * septagname, + xmlChar * plainsep) +{ + /* Function translates a nodeset into a text representation */ + + /* + * iterates over each node in the set and calls xmlNodeDump to write + * it to an xmlBuffer -from which an xmlChar * string is returned. + */ + + /* each representation is surrounded by <tagname> ... </tagname> */ + /* plainsep is an ordinary (not tag) seperator - if used, then + * nodes are cast to string as output method */ + + + xmlBufferPtr buf; + xmlChar *result; + int i; + + buf = xmlBufferCreate(); + + if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0)) + { + xmlBufferWriteChar(buf, "<"); + xmlBufferWriteCHAR(buf, toptagname); + xmlBufferWriteChar(buf, ">"); + } + if (nodeset != NULL) + { + for (i = 0; i < nodeset->nodeNr; i++) + { + + if (plainsep != NULL) { + xmlBufferWriteCHAR(buf, + xmlXPathCastNodeToString(nodeset->nodeTab[i])); + + /* If this isn't the last entry, write the plain sep. */ + if (i < (nodeset->nodeNr)-1) { + xmlBufferWriteChar(buf, plainsep); + } + } else { + + + if ((septagname != NULL) && (xmlStrlen(septagname) > 0)) + { + xmlBufferWriteChar(buf, "<"); + xmlBufferWriteCHAR(buf, septagname); + xmlBufferWriteChar(buf, ">"); + } + xmlNodeDump(buf, + nodeset->nodeTab[i]->doc, + nodeset->nodeTab[i], + 1, 0); + + if ((septagname != NULL) && (xmlStrlen(septagname) > 0)) + { + xmlBufferWriteChar(buf, "</"); + xmlBufferWriteCHAR(buf, septagname); + xmlBufferWriteChar(buf, ">"); + } + } + } + } + + if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0)) + { + xmlBufferWriteChar(buf, "</"); + xmlBufferWriteCHAR(buf, toptagname); + xmlBufferWriteChar(buf, ">"); + } + result = xmlStrdup(buf->content); + xmlBufferFree(buf); + return result; +} + + +/* Translate a PostgreSQL "varlena" -i.e. a variable length parameter + * into the libxml2 representation + */ + +xmlChar * +pgxml_texttoxmlchar(text *textstring) +{ + xmlChar *res; + int32 txsize; + + txsize = VARSIZE(textstring) - VARHDRSZ; + res = (xmlChar *) palloc(txsize + 1); + memcpy((char *) res, VARDATA(textstring), txsize); + res[txsize] = '\0'; + return res; +} + +/* Public visible XPath functions */ + +/* This is a "raw" xpath function. Check that it returns child elements + * properly + */ + +PG_FUNCTION_INFO_V1(xpath_nodeset); + +Datum +xpath_nodeset(PG_FUNCTION_ARGS) +{ + xmlChar *xpath, *toptag, *septag; + int32 pathsize; + text + *xpathsupp, + *xpres; + + /* PG_GETARG_TEXT_P(0) is document buffer */ + xpathsupp = PG_GETARG_TEXT_P(1); /* XPath expression */ + + toptag = pgxml_texttoxmlchar(PG_GETARG_TEXT_P(2)); + septag = pgxml_texttoxmlchar(PG_GETARG_TEXT_P(3)); + + pathsize = VARSIZE(xpathsupp) - VARHDRSZ; + + xpath = pgxml_texttoxmlchar(xpathsupp); + + xpres = pgxml_result_to_text( + pgxml_xpath(PG_GETARG_TEXT_P(0),xpath), + toptag,septag,NULL); + + /* xmlCleanupParser(); done by result_to_text routine */ + pfree((void *) xpath); + + if (xpres == NULL) + { + PG_RETURN_NULL(); + } + PG_RETURN_TEXT_P(xpres); +} + +// The following function is almost identical, but returns the elements in +// a list. + +PG_FUNCTION_INFO_V1(xpath_list); + +Datum +xpath_list(PG_FUNCTION_ARGS) +{ + xmlChar *xpath, *plainsep; + int32 pathsize; + text + *xpathsupp, + *xpres; + + /* PG_GETARG_TEXT_P(0) is document buffer */ + xpathsupp = PG_GETARG_TEXT_P(1); /* XPath expression */ + + plainsep = pgxml_texttoxmlchar(PG_GETARG_TEXT_P(2)); + + pathsize = VARSIZE(xpathsupp) - VARHDRSZ; + + xpath = pgxml_texttoxmlchar(xpathsupp); + + xpres = pgxml_result_to_text( + pgxml_xpath(PG_GETARG_TEXT_P(0),xpath), + NULL,NULL,plainsep); + + /* xmlCleanupParser(); done by result_to_text routine */ + pfree((void *) xpath); + + if (xpres == NULL) + { + PG_RETURN_NULL(); + } + PG_RETURN_TEXT_P(xpres); +} + + +PG_FUNCTION_INFO_V1(xpath_string); + +Datum +xpath_string(PG_FUNCTION_ARGS) +{ + xmlChar *xpath; + int32 pathsize; + text + *xpathsupp, + *xpres; + + /* PG_GETARG_TEXT_P(0) is document buffer */ + xpathsupp = PG_GETARG_TEXT_P(1); /* XPath expression */ + + pathsize = VARSIZE(xpathsupp) - VARHDRSZ; + + /* We encapsulate the supplied path with "string()" + * = 8 chars + 1 for NUL at end */ + /* We could try casting to string using the libxml function? */ + + xpath =(xmlChar *) palloc(pathsize + 9); + memcpy((char *) (xpath+7), VARDATA(xpathsupp), pathsize); + strncpy((char *) xpath, "string(",7); + xpath[pathsize+7] = ')'; + xpath[pathsize+8] = '\0'; + + xpres = pgxml_result_to_text( + pgxml_xpath(PG_GETARG_TEXT_P(0),xpath), + NULL,NULL,NULL); + + xmlCleanupParser(); + pfree((void *) xpath); + + if (xpres == NULL) + { + PG_RETURN_NULL(); + } + PG_RETURN_TEXT_P(xpres); +} + + +PG_FUNCTION_INFO_V1(xpath_number); + +Datum +xpath_number(PG_FUNCTION_ARGS) +{ + xmlChar *xpath; + int32 pathsize; + text + *xpathsupp; + + float4 fRes; + + xmlXPathObjectPtr res; + + /* PG_GETARG_TEXT_P(0) is document buffer */ + xpathsupp = PG_GETARG_TEXT_P(1); /* XPath expression */ + + pathsize = VARSIZE(xpathsupp) - VARHDRSZ; + + xpath = pgxml_texttoxmlchar(xpathsupp); + + res = pgxml_xpath(PG_GETARG_TEXT_P(0),xpath); + pfree((void *) xpath); + + if (res == NULL) + { + xmlCleanupParser(); + PG_RETURN_NULL(); + } + + fRes = xmlXPathCastToNumber(res); + xmlCleanupParser(); + if (xmlXPathIsNaN(fRes)) + { + PG_RETURN_NULL(); + } + + PG_RETURN_FLOAT4(fRes); + +} + + +PG_FUNCTION_INFO_V1(xpath_bool); + +Datum +xpath_bool(PG_FUNCTION_ARGS) +{ + xmlChar *xpath; + int32 pathsize; + text + *xpathsupp; + + int bRes; + + xmlXPathObjectPtr res; + + /* PG_GETARG_TEXT_P(0) is document buffer */ + xpathsupp = PG_GETARG_TEXT_P(1); /* XPath expression */ + + pathsize = VARSIZE(xpathsupp) - VARHDRSZ; + + xpath = pgxml_texttoxmlchar(xpathsupp); + + res = pgxml_xpath(PG_GETARG_TEXT_P(0),xpath); + pfree((void *) xpath); + + if (res == NULL) + { + xmlCleanupParser(); + PG_RETURN_BOOL(false); + } + + bRes = xmlXPathCastToBoolean(res); + xmlCleanupParser(); + PG_RETURN_BOOL(bRes); + +} + + + +/* Core function to evaluate XPath query */ + +xmlXPathObjectPtr + pgxml_xpath(text *document, xmlChar *xpath) + { + + xmlDocPtr doctree; + xmlXPathContextPtr ctxt; + xmlXPathObjectPtr res; + + xmlXPathCompExprPtr comppath; + + int32 docsize; + + + docsize = VARSIZE(document) - VARHDRSZ; + + pgxml_parser_init(); + + doctree = xmlParseMemory((char *) VARDATA(document), docsize); + if (doctree == NULL) + { /* not well-formed */ + return NULL; + } + + ctxt = xmlXPathNewContext(doctree); + ctxt->node = xmlDocGetRootElement(doctree); + + + /* compile the path */ + comppath = xmlXPathCompile(xpath); + if (comppath == NULL) + { + xmlCleanupParser(); + xmlFreeDoc(doctree); + elog_error(ERROR,"XPath Syntax Error",1); + + return NULL; + } + + /* Now evaluate the path expression. */ + res = xmlXPathCompiledEval(comppath, ctxt); + xmlXPathFreeCompExpr(comppath); + + if (res == NULL) + { + xmlXPathFreeContext(ctxt); + // xmlCleanupParser(); + xmlFreeDoc(doctree); + + return NULL; + } + /* xmlFreeDoc(doctree); */ + return res; + } + +text +*pgxml_result_to_text(xmlXPathObjectPtr res, + xmlChar *toptag, + xmlChar *septag, + xmlChar *plainsep) +{ + xmlChar *xpresstr; + int32 ressize; + text *xpres; + + if (res == NULL) + { + return NULL; + } + switch (res->type) + { + case XPATH_NODESET: + xpresstr = pgxmlNodeSetToText(res->nodesetval, + toptag, + septag, plainsep); + break; + + case XPATH_STRING: + xpresstr = xmlStrdup(res->stringval); + break; + + default: + elog(NOTICE, "Unsupported XQuery result: %d", res->type); + xpresstr = xmlStrdup("<unsupported/>"); + } + + + /* Now convert this result back to text */ + ressize = strlen(xpresstr); + xpres = (text *) palloc(ressize + VARHDRSZ); + memcpy(VARDATA(xpres), xpresstr, ressize); + VARATT_SIZEP(xpres) = ressize + VARHDRSZ; + + /* Free various storage */ + xmlCleanupParser(); + /* xmlFreeDoc(doctree); -- will die at end of tuple anyway */ + + xmlFree(xpresstr); + + elog_error(ERROR,"XPath error",0); + + + return xpres; +} + +/* xpath_table is a table function. It needs some tidying (as do the + * other functions here! + */ + +PG_FUNCTION_INFO_V1(xpath_table); + +Datum xpath_table(PG_FUNCTION_ARGS) +{ +/* SPI (input tuple) support */ + SPITupleTable *tuptable; + HeapTuple spi_tuple; + TupleDesc spi_tupdesc; + +/* Output tuple (tuplestore) support */ + Tuplestorestate *tupstore = NULL; + TupleDesc ret_tupdesc; + HeapTuple ret_tuple; + + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + AttInMetadata *attinmeta; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + +/* Function parameters */ + char *pkeyfield = GET_STR(PG_GETARG_TEXT_P(0)); + char *xmlfield = GET_STR(PG_GETARG_TEXT_P(1)); + char *relname = GET_STR(PG_GETARG_TEXT_P(2)); + char *xpathset = GET_STR(PG_GETARG_TEXT_P(3)); + char *condition = GET_STR(PG_GETARG_TEXT_P(4)); + + char **values; + xmlChar **xpaths; + xmlChar *pos; + xmlChar *pathsep= "|"; + + int numpaths; + int ret; + int proc; + int i; + int j; + int rownr; /* For issuing multiple rows from one original document */ + int had_values; /* To determine end of nodeset results */ + + StringInfo querysql; + +/* We only have a valid tuple description in table function mode */ + if (rsinfo->expectedDesc == NULL) { + ereport(ERROR,(errcode(ERRCODE_SYNTAX_ERROR), + errmsg("xpath_table must be called as a table function"))); + } + +/* The tuplestore must exist in a higher context than + * this function call (per_query_ctx is used) */ + + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + +/* Create the tuplestore - SortMem is the max in-memory size before it is + * shipped to a disk heap file. Just like ... SortMem! + */ + + tupstore = tuplestore_begin_heap(true, false, SortMem); + + MemoryContextSwitchTo(oldcontext); + + /* get the requested return tuple description */ + ret_tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); + + /* At the moment we assume that the returned attributes make sense + * for the XPath specififed (i.e. we trust the caller). + * It's not fatal if they get it wrong - the input function for the + * column type will raise an error if the path result can't be converted + * into the correct binary representation. + */ + + attinmeta = TupleDescGetAttInMetadata(ret_tupdesc); + + /* We want to materialise because it means that we don't have to + * carry libxml2 parser state between invocations of this function + */ + + /* check to see if caller supports us returning a tuplestore */ + if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("xpath_table requires Materialize mode, but it is not " + "allowed in this context"))); + + // Set return mode and allocate value space. + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setDesc = ret_tupdesc; + + values = (char **) palloc(ret_tupdesc->natts * sizeof(char *)); + + xpaths = (xmlChar **) palloc(ret_tupdesc->natts * sizeof(xmlChar *)); + + /* Split XPaths. xpathset is a writable CString. */ + + /* Note that we stop splitting once we've done all needed for tupdesc */ + + numpaths=0; + pos = xpathset; + do { + xpaths[numpaths] = pos; + pos = strstr(pos,pathsep); + if (pos != NULL) { + *pos = '\0'; + pos++; + } + numpaths++; + } while ((pos != NULL) && (numpaths < (ret_tupdesc->natts - 1) )); + + /* Now build query */ + + querysql = makeStringInfo(); + + /* Build initial sql statement */ + appendStringInfo(querysql, "SELECT %s, %s FROM %s WHERE %s", + pkeyfield, + xmlfield, + relname, + condition + ); + + + if ((ret = SPI_connect()) < 0) { + elog(ERROR, "xpath_table: SPI_connect returned %d", ret); + } + + if ((ret = SPI_exec(querysql->data,0)) != SPI_OK_SELECT) { + elog(ERROR,"xpath_table: SPI execution failed for query %s",querysql->data); + } + + proc= SPI_processed; + /* elog(DEBUG1,"xpath_table: SPI returned %d rows",proc); */ + tuptable = SPI_tuptable; + spi_tupdesc = tuptable->tupdesc; + +/* Switch out of SPI context */ + MemoryContextSwitchTo(oldcontext); + + +/* Check that SPI returned correct result. If you put a comma into one of + * the function parameters, this will catch it when the SPI query returns + * e.g. 3 columns. + */ + + if (spi_tupdesc->natts != 2) { + ereport(ERROR,(errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Expression returning multiple columns is not valid in parameter list"), + errdetail("Expected two columns in SPI result, got %d",spi_tupdesc->natts))); + } + +/* Setup the parser. Beware that this must happen in the same context as the + * cleanup - which means that any error from here on must do cleanup to + * ensure that the entity table doesn't get freed by being out of context. + */ + pgxml_parser_init(); + + /* For each row i.e. document returned from SPI */ + for (i=0; i < proc; i++) { + char *pkey; + char *xmldoc; + + xmlDocPtr doctree; + xmlXPathContextPtr ctxt; + xmlXPathObjectPtr res; + xmlChar *resstr; + + + xmlXPathCompExprPtr comppath; + + /* Extract the row data as C Strings */ + + spi_tuple = tuptable->vals[i]; + pkey = SPI_getvalue(spi_tuple, spi_tupdesc,1); + xmldoc = SPI_getvalue(spi_tuple, spi_tupdesc,2); + + + /* Clear the values array, so that not-well-formed documents + * return NULL in all columns. + */ + + /* Note that this also means that spare columns will be NULL. */ + for (j=0; j < ret_tupdesc->natts; j++) { + values[j]= NULL; + } + + /* Insert primary key */ + values[0]=pkey; + + /* Parse the document */ + doctree = xmlParseMemory(xmldoc, strlen(xmldoc)); + + if (doctree == NULL) + { /* not well-formed, so output all-NULL tuple */ + + ret_tuple = BuildTupleFromCStrings(attinmeta, values); + oldcontext = MemoryContextSwitchTo(per_query_ctx); + tuplestore_puttuple(tupstore, ret_tuple); + MemoryContextSwitchTo(oldcontext); + heap_freetuple(ret_tuple); + } + else + { + /* New loop here - we have to deal with nodeset results */ + rownr=0; + + do { + /* Now evaluate the set of xpaths. */ + had_values=0; + for (j=0; j < numpaths; j++) { + + ctxt = xmlXPathNewContext(doctree); + ctxt->node = xmlDocGetRootElement(doctree); + xmlSetGenericErrorFunc(ctxt, pgxml_errorHandler); + + /* compile the path */ + comppath = xmlXPathCompile(xpaths[j]); + if (comppath == NULL) + { + xmlCleanupParser(); + xmlFreeDoc(doctree); + + elog_error(ERROR,"XPath Syntax Error",1); + + PG_RETURN_NULL(); /* Keep compiler happy */ + } + + /* Now evaluate the path expression. */ + res = xmlXPathCompiledEval(comppath, ctxt); + xmlXPathFreeCompExpr(comppath); + + if (res != NULL) + { + switch (res->type) + { + case XPATH_NODESET: + /* We see if this nodeset has enough nodes */ + if ((res->nodesetval != NULL) && (rownr < res->nodesetval->nodeNr)) { + resstr = + xmlXPathCastNodeToString(res->nodesetval->nodeTab[rownr]); + had_values=1; + } else { + resstr = NULL; + } + + break; + + case XPATH_STRING: + resstr = xmlStrdup(res->stringval); + break; + + default: + elog(NOTICE, "Unsupported XQuery result: %d", res->type); + resstr = xmlStrdup("<unsupported/>"); + } + + + // Insert this into the appropriate column in the result tuple. + values[j+1] = resstr; + } + xmlXPathFreeContext(ctxt); + } + // Now add the tuple to the output, if there is one. + if (had_values) { + ret_tuple = BuildTupleFromCStrings(attinmeta, values); + oldcontext = MemoryContextSwitchTo(per_query_ctx); + tuplestore_puttuple(tupstore, ret_tuple); + MemoryContextSwitchTo(oldcontext); + heap_freetuple(ret_tuple); + } + + rownr++; + + } while (had_values); + + } + + xmlFreeDoc(doctree); + + pfree(pkey); + pfree(xmldoc); + } + + xmlCleanupParser(); +/* Needed to flag completeness in 7.3.1. 7.4 defines it as a no-op. */ + tuplestore_donestoring(tupstore); + + SPI_finish(); + + rsinfo->setResult=tupstore; + + /* + * SFRM_Materialize mode expects us to return a NULL Datum. The actual + * tuples are in our tuplestore and passed back through + * rsinfo->setResult. rsinfo->setDesc is set to the tuple description + * that we actually used to build our tuples with, so the caller can + * verify we did what it was expecting. + */ + return (Datum) 0; + +} |